From b0380e1f0eec68c4021ee9298358bd585a5bf68e Mon Sep 17 00:00:00 2001 From: Henri Fontana Date: Tue, 18 Jul 2023 03:10:24 -0700 Subject: [PATCH] pt-BR: Some updates and fixes (#994) pt-BR: Some updates and fixes to the Brazilian Portuguese translation. --- po/pt-BR.po | 429 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 351 insertions(+), 78 deletions(-) diff --git a/po/pt-BR.po b/po/pt-BR.po index a279f187..8b7bc274 100644 --- a/po/pt-BR.po +++ b/po/pt-BR.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Comprehensive Rust 🦀\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2023-07-13 16:10-0700\n" +"PO-Revision-Date: 2023-07-17 12:30-0700\n" "Last-Translator: \n" "Language-Team: \n" "Language: pt_BR\n" @@ -141,7 +141,7 @@ msgstr "Conversões Implícitas" #: src/SUMMARY.md:39 msgid "Arrays and for Loops" -msgstr "Vetores e Laços For" +msgstr "Matrizes e Loops for" #: src/SUMMARY.md:41 msgid "Day 1: Afternoon" @@ -185,11 +185,11 @@ msgstr "Gerenciamento de Memória Baseado em Escopo" #: src/SUMMARY.md:52 msgid "Garbage Collection" -msgstr "Garbage Collection (Coletor de lixo)" +msgstr "Gerenciamento Automático de Memória" #: src/SUMMARY.md:53 msgid "Rust Memory Management" -msgstr "Gerenciamento de Memória do Rust" +msgstr "Gerenciamento de Memória no Rust" #: src/SUMMARY.md:54 msgid "Comparison" @@ -201,7 +201,7 @@ msgstr "Ownership" #: src/SUMMARY.md:56 msgid "Move Semantics" -msgstr "Semântica do move (mover)" +msgstr "Semântica do Move (mover)" #: src/SUMMARY.md:57 msgid "Moved Strings in Rust" @@ -257,7 +257,7 @@ msgstr "Structs" #: src/SUMMARY.md:77 msgid "Tuple Structs" -msgstr "Structs como Tuplas" +msgstr "Estruturas de Tuplas (Tuple Structs)" #: src/SUMMARY.md:78 msgid "Field Shorthand Syntax" @@ -301,7 +301,7 @@ msgstr "Desestruturando Matrizes" #: src/SUMMARY.md:89 msgid "Match Guards" -msgstr "Guardas de Correspondência (match)" +msgstr "Guardas de Correspondência (Match Guards)" #: src/SUMMARY.md:91 msgid "Health Statistics" @@ -3413,7 +3413,7 @@ msgid "" "* We create a slice by borrowing `a` and specifying the starting and ending " "indexes in brackets.\n" "\n" -"* If the slice starts at index 0, Rust's range syntax allows us to drop the " +"* If the slice starts at index 0, Rust’s range syntax allows us to drop the " "starting index, meaning that `&a[0..a.len()]` and `&a[..a.len()]` are " "identical.\n" " \n" @@ -3490,10 +3490,10 @@ msgid "" msgstr "" "```rust,editable\n" "fn main() {\n" -" let s1: &str = \"World\";\n" +" let s1: &str = \"Mundo\";\n" " println!(\"s1: {s1}\");\n" "\n" -" let mut s2: String = String::from(\"Hello \");\n" +" let mut s2: String = String::from(\"Olá \");\n" " println!(\"s2: {s2}\");\n" " s2.push_str(s1);\n" " println!(\"s2: {s2}\");\n" @@ -3615,6 +3615,33 @@ msgid "" "}\n" "```" msgstr "" +"```rust,editable\n" +"fn main() {\n" +" imprimir_fizzbuzz_para(20);\n" +"}\n" +"\n" +"fn eh_divisivel(n: u32, divisor: u32) -> bool {\n" +" if divisor == 0 {\n" +" return false;\n" +" }\n" +" n % divisor == 0\n" +"}\n" +"\n" +"fn fizzbuzz(n: u32) -> String {\n" +" let fizz = if eh_divisivel(n, 3) { \"fizz\" } else { \"\" };\n" +" let buzz = if eh_divisivel(n, 5) { \"buzz\" } else { \"\" };\n" +" if fizz.is_empty() && buzz.is_empty() {\n" +" return format!(\"{n}\");\n" +" }\n" +" format!(\"{fizz}{buzz}\")\n" +"}\n" +"\n" +"fn imprimir_fizzbuzz_para(n: u32) {\n" +" for i in 1..=n {\n" +" println!(\"{}\", fizzbuzz(i));\n" +" }\n" +"}\n" +"```" #: src/basic-syntax/functions.md:35 msgid "" @@ -3638,8 +3665,8 @@ msgstr "" "* Algumas funções não têm valor de retorno e retornam o 'tipo unitário', " "`()`. O compilador irá inferir isso se o tipo de retorno `-> ()` for " "omitido.\n" -"* A expressão de intervalo no loop `for` em `fizzbuzz_to()` contém `=n`, o " -"que faz com que inclua o limite superior." +"* A expressão de intervalo no loop `for` em `imprimir_fizzbuzz_para()` " +"contém `=n`, o que faz com que inclua o limite superior." #: src/basic-syntax/rustdoc.md:1 msgid "# Rustdoc" @@ -3672,7 +3699,7 @@ msgstr "" "/// Determine se o primeiro argumento é divisível pelo segundo argumento.\n" "///\n" "/// Se o segundo argumento for zero, o resultado é falso.\n" -"fn is_divisible_by(lhs: u32, rhs: u32) -> bool {\n" +"fn divisivel_por(lhs: u32, rhs: u32) -> bool {\n" " if rhs == 0 {\n" " return false; // Caso excepcional, retorne antes\n" " }\n" @@ -3895,7 +3922,7 @@ msgid "" msgstr "" "* Conversões implícitas entre tipos.\n" "\n" -"* Matrizes (_Arrays_) e laços (loops) `for`." +"* Matrizes (_Arrays_) e _loops_ (laços) `for`." #: src/exercises/day-1/morning.md:11 msgid "A few things to consider while solving the exercises:" @@ -4055,7 +4082,7 @@ msgstr "" #: src/exercises/day-1/for-loops.md:1 msgid "# Arrays and `for` Loops" -msgstr "# Matrizes (Arrays) e Laços (Loops) `for`" +msgstr "# Matrizes (_Arrays_) e _Loops_ (Laços) `for`" #: src/exercises/day-1/for-loops.md:3 msgid "We saw that an array can be declared like this:" @@ -4429,7 +4456,8 @@ msgid "" "Globally-scoped names for values can be given with static variables and " "constant definitions." msgstr "" -"Nomes com escopo global podem ser dados com variáveis estáticas e constantes." +"Nomes com escopo global podem ser obtidos por meio de variáveis estáticas e " +"definições de constantes." #: src/basic-syntax/static-and-const.md:5 msgid "## `const`" @@ -4534,6 +4562,10 @@ msgid "" "Because `static` variables are accessible from any thread, mutable static " "variables require manual, unsafe, synchronization of accesses." msgstr "" +"Nós iremos ver [dados estáticos mutáveis](../unsafe/mutable-static-variables." +"md) no capítulo sobre Rust _inseguro_.\n" +"Variáveis estáticas mutáveis são acessíveis por qualquer _thread_ e por isso " +"necessitam de sincronização de acessos manual e insegura (_unsafe_)." #: src/basic-syntax/static-and-const.md:50 msgid "" @@ -4836,8 +4868,8 @@ msgid "" "If not done with care, this can lead to crashes, bugs, security " "vulnerabilities, and memory leaks." msgstr "" -"Se não for feito com cuidado, isso pode levar a travamentos, bugs, " -"vulnerabilidades de segurança e vazamentos de memória." +"Se isto não for feito com cuidado, travamentos, bugs, vulnerabilidades de " +"segurança e vazamentos de memória podem ocorrer." #: src/memory-management/manual.md:7 msgid "## C Example" @@ -5035,8 +5067,7 @@ msgstr "" #: src/memory-management/rust.md:10 msgid "Rust achieves this by modeling _ownership_ explicitly." -msgstr "" -"O Rust consegue isso modelando a propriedade (_ownership_) explicitamente." +msgstr "O Rust consegue isso modelando o _ownership_ (posse) explicitamente." #: src/memory-management/rust.md:14 msgid "" @@ -5185,7 +5216,7 @@ msgstr "# Semântica do `Move` (Mover)" #: src/ownership/move-semantics.md:3 msgid "An assignment will transfer ownership between variables:" -msgstr "Uma atribuição transferirá a _ownership_ entre variáveis:" +msgstr "Uma atribuição transferirá o _ownership_ entre variáveis:" #: src/ownership/move-semantics.md:5 msgid "" @@ -5215,7 +5246,7 @@ msgid "" "* When `s2` goes out of scope, the string data is freed.\n" "* There is always _exactly_ one variable binding which owns a value." msgstr "" -"* A atribuição de `s1` a `s2` transfere a _ownership_.\n" +"* A atribuição de `s1` a `s2` transfere o _ownership_.\n" "* Os dados foram _movidos_ de `s1` e `s1` não está mais acessível.\n" "* Quando `s1` sai do escopo, nada acontece: ele não tem _ownership_.\n" "* Quando `s2` sai do escopo, os dados da string são liberados.\n" @@ -5564,8 +5595,8 @@ msgid "" "struct Point(i32, i32);\n" "\n" "fn main() {\n" -" let p2 = p1;\n" " let p1 = Point(3, 4);\n" +" let p2 = p1;\n" " println!(\"p1: {p1:?}\");\n" " println!(\"p2: {p2:?}\");\n" "}\n" @@ -6643,7 +6674,7 @@ msgstr "" #: src/structs.md:1 msgid "# Structs" -msgstr "# Estruturas (Structs)" +msgstr "# _Structs_ (Estruturas)" #: src/structs.md:3 msgid "Like C and C++, Rust has support for custom structs:" @@ -6742,7 +6773,7 @@ msgstr "" #: src/structs/tuple-structs.md:1 msgid "# Tuple Structs" -msgstr "# Estruturas Tupla (Tuple Structs)" +msgstr "# Estruturas de Tuplas (_Tuple Structs_)" #: src/structs/tuple-structs.md:3 msgid "If the field names are unimportant, you can use a tuple struct:" @@ -6802,7 +6833,7 @@ msgstr "" "struct Newtons(f64);\n" "\n" "fn calcular_forca_nas_turbinas() -> LibrasDeForca {\n" -" todo!(“Pergunte para um cientista de foguetes da NASA”)\n" +" todo!(\"Pergunte para um cientista de foguetes da NASA\")\n" "}\n" "\n" "fn definir_forca_nas_turbinas(force: Newtons) {\n" @@ -6826,20 +6857,20 @@ msgid "" "`OddNumber(u32)`.\n" "* Demonstrate how to add a `f64` value to a `Newtons` type by accessing the " "single field in the newtype.\n" -" * Rust generally doesn't like inexplicit things, like automatic " +" * Rust generally doesn’t like inexplicit things, like automatic " "unwrapping or for instance using booleans as integers.\n" " * Operator overloading is discussed on Day 3 (generics).\n" "* The example is a subtle reference to the [Mars Climate Orbiter](https://en." "wikipedia.org/wiki/Mars_Climate_Orbiter) failure." msgstr "" -"_Newtypes_ são uma ótima maneira de codificar informações adicionais sobre o " -"valor em um tipo primitivo, por exemplo:\n" -" * O número é medido em alguma unidade: `Newtons` no exemplo acima.\n" +"* _Newtypes_ são uma ótima maneira de codificar informações adicionais sobre " +"o valor em um tipo primitivo, por exemplo:\n" +" * O número é medido em algumas unidades: `Newtons` no exemplo acima.\n" " * O valor passou por alguma validação quando foi criado, então não é " "preciso validá-lo novamente a cada uso: `NumeroTelefone(String)` ou " "`NumeroImpar(u32)`.\n" "* Demonstre como somar um valor `f64` em um valor do tipo `Newtons` " -"acessando o campo único do _newtype_.\n" +"acessando o campo único no _newtype_.\n" " * Geralmente, Rust não gosta de coisas implícitas, como _unwrapping_ " "automático ou, por exemplo, usar booleanos como inteiros.\n" " * Sobrecarga de operadores é discutido no Dia 3 (_generics_).\n" @@ -6992,10 +7023,10 @@ msgstr "" " ```\n" "\n" "* Métodos são definidos no bloco `impl`.\n" -"* Use struct update syntax to define a new structure using `peter`. Note " -"that the variable `peter` will no longer be accessible afterwards.\n" -"* Utilize `{:#?}` para imprimir _structs_ para utilizar a representação " -"`Debug` (de Depuração)." +"* Use a sintaxe de atualização de estruturas para definir uma nova `struct` " +"usando `pedro`. Note que a variável `pedro` não será mais acessível após.\n" +"* Utilize `{:#?}` para imprimir _structs_ utilizando a representação de " +"depuração (`Debug`)." #: src/enums.md:1 msgid "# Enums" @@ -7007,7 +7038,7 @@ msgid "" "different variants:" msgstr "" "A palavra-chave `enum` permite a criação de um tipo que possui algumas\n" -"variações diferentes:" +"variantes diferentes:" #: src/enums.md:6 msgid "" @@ -7215,7 +7246,7 @@ msgid "" "Rust enums are packed tightly, taking constraints due to alignment into " "account:" msgstr "" -"Enums, em Rust, são empacotados firmemente, levando em consideração as " +"Enums, em Rust, são agrupados de maneira compacta, levando em consideração " "restrições devido ao alinhamento:" #: src/enums/sizes.md:5 @@ -8277,7 +8308,7 @@ msgstr "" #: src/exercises/day-2/health-statistics.md:1 msgid "# Health Statistics" -msgstr "# Estatísticas de saúde" +msgstr "# Estatísticas de Saúde" #: src/exercises/day-2/health-statistics.md:3 msgid "" @@ -8312,7 +8343,6 @@ msgstr "" "que estão faltando:" #: src/exercises/day-2/health-statistics.md:13 -#, fuzzy msgid "" "```rust,should_panic\n" "// TODO: remove this when you're done with your implementation.\n" @@ -8422,6 +8452,20 @@ msgstr "" " nome: String,\n" " idade: u32,\n" " peso: f32,\n" +" num_visitas: usize,\n" +" ult_pressao_sang: Option<(u32, u32)>,\n" +"}\n" +"\n" +"pub struct Medicoes {\n" +" altura: f32,\n" +" pressao_sangue: (u32, u32),\n" +"}\n" +"\n" +"pub struct RelatorioSaude<'a> {\n" +" nome_paciente: &'a str,\n" +" num_visitas: u32,\n" +" dif_altura: f32,\n" +" dif_pressao_sangue: Option<(i32, i32)>,\n" "}\n" "\n" "impl Usuario {\n" @@ -8437,7 +8481,11 @@ msgstr "" " unimplemented!()\n" " }\n" "\n" -" pub fn peso(&self) -> f32 {\n" +" pub fn altura(&self) -> f32 {\n" +" unimplemented!()\n" +" }\n" +"\n" +" pub fn visitas_medico(&self) -> u32 {\n" " unimplemented!()\n" " }\n" "\n" @@ -8445,7 +8493,7 @@ msgstr "" " unimplemented!()\n" " }\n" "\n" -" pub fn definir_peso(&mut self, novo_peso: f32) {\n" +" pub fn definir_altura(&mut self, novo_altura: f32) {\n" " unimplemented!()\n" " }\n" "}\n" @@ -8456,13 +8504,13 @@ msgstr "" "}\n" "\n" "#[test]\n" -"fn test_peso() {\n" +"fn test_altura() {\n" " let beto = Usuario::new(String::from(\"Beto\"), 32, 155.2);\n" -" assert_eq!(beto.peso(), 155.2);\n" +" assert_eq!(beto.altura(), 155.2);\n" "}\n" "\n" "#[test]\n" -"fn test_set_age() {\n" +"fn test_definir_idade() {\n" " let mut beto = Usuario::new(String::from(\"Beto\"), 32, 155.2);\n" " assert_eq!(beto.idade(), 32);\n" " beto.definir_idade(33);\n" @@ -8472,7 +8520,7 @@ msgstr "" #: src/exercises/day-2/points-polygons.md:1 msgid "# Polygon Struct" -msgstr "# Estrutura para polígono" +msgstr "# _Struct_ para Polígono" #: src/exercises/day-2/points-polygons.md:3 msgid "" @@ -8482,7 +8530,7 @@ msgid "" "the\n" "tests pass:" msgstr "" -"Vamos criar uma estrutura `Poligono` que contém alguns `Pontos`. Copie o " +"Vamos criar um _struct_ `Poligono` que contém alguns `Pontos`. Copie o " "código abaixo\n" "em e preencha os métodos que faltam para fazer " "os\n" @@ -10701,7 +10749,7 @@ msgid "" "\n" " ```rust,ignore\n" " #[path = \"some/path.rs\"]\n" -" mod some_module { }\n" +" mod some_module;\n" " ```\n" "\n" " This is useful, for example, if you would like to place tests for a module " @@ -11065,7 +11113,6 @@ msgid "" msgstr "" "Rust oferece suporte a tipos genéricos, que permitem algoritmos ou " "estruturas de dados\n" - " (como ordenação ou árvore binária)\n" "abstrair os tipos de dados usados ou armazenados." @@ -11515,7 +11562,6 @@ msgstr "" " '- - - - - - - - - - - - - - - - - - - - - " "- - -'\n" "\n" - "```" #: src/traits/trait-objects.md:72 @@ -17085,7 +17131,7 @@ msgid "" "exist. Once a pin is\n" " moved out of the port struct nobody else can take it.\n" " * Changing the configuration of a pin consumes the old pin instance, so you " -"can't keep use the old\n" +"can’t keep use the old\n" " instance afterwards.\n" " * The type of a value indicates the state that it is in: e.g. in this case, " "the configuration state\n" @@ -18152,9 +18198,9 @@ msgid "" " writeln!(uart, \"main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})\").unwrap();\n" "\n" " loop {\n" -" if let Some(b) = uart.read_byte() {\n" -" uart.write_byte(b);\n" -" match b {\n" +" if let Some(byte) = uart.read_byte() {\n" +" uart.write_byte(byte);\n" +" match byte {\n" " b'\\r' => {\n" " uart.write_byte(b'\\n');\n" " }\n" @@ -21024,8 +21070,7 @@ msgstr "Seu arquivo `src/main.rs` deve se parecer com isto:" #: src/exercises/concurrency/link-checker.md:57 msgid "" "```rust,compile_fail\n" -"use reqwest::blocking::{get, Response};\n" -"use reqwest::Url;\n" +"use reqwest::{blocking::Client, Url};\n" "use scraper::{Html, Selector};\n" "use thiserror::Error;\n" "\n" @@ -21033,34 +21078,57 @@ msgid "" "enum Error {\n" " #[error(\"request error: {0}\")]\n" " ReqwestError(#[from] reqwest::Error),\n" +" #[error(\"bad http response: {0}\")]\n" +" BadResponse(String),\n" "}\n" "\n" -"fn extract_links(response: Response) -> Result, Error> {\n" -" let base_url = response.url().to_owned();\n" -" let document = response.text()?;\n" -" let html = Html::parse_document(&document);\n" -" let selector = Selector::parse(\"a\").unwrap();\n" +"#[derive(Debug)]\n" +"struct CrawlCommand {\n" +" url: Url,\n" +" extract_links: bool,\n" +"}\n" "\n" -" let mut valid_urls = Vec::new();\n" -" for element in html.select(&selector) {\n" -" if let Some(href) = element.value().attr(\"href\") {\n" -" match base_url.join(href) {\n" -" Ok(url) => valid_urls.push(url),\n" -" Err(err) => {\n" -" println!(\"On {base_url}: could not parse {href:?}: " -"{err} (ignored)\",);\n" -" }\n" +"fn visit_page(client: &Client, command: &CrawlCommand) -> Result, " +"Error> {\n" +" println!(\"Checking {:#}\", command.url);\n" +" let response = client.get(command.url.clone()).send()?;\n" +" if !response.status().is_success() {\n" +" return Err(Error::BadResponse(response.status().to_string()));\n" +" }\n" +"\n" +" let mut link_urls = Vec::new();\n" +" if !command.extract_links {\n" +" return Ok(link_urls);\n" +" }\n" +"\n" +" let base_url = response.url().to_owned();\n" +" let body_text = response.text()?;\n" +" let document = Html::parse_document(&body_text);\n" +"\n" +" let selector = Selector::parse(\"a\").unwrap();\n" +" let href_values = document\n" +" .select(&selector)\n" +" .filter_map(|element| element.value().attr(\"href\"));\n" +" for href in href_values {\n" +" match base_url.join(href) {\n" +" Ok(link_url) => {\n" +" link_urls.push(link_url);\n" +" }\n" +" Err(err) => {\n" +" println!(\"On {base_url:#}: ignored unparsable {href:?}: " +"{err}\");\n" " }\n" " }\n" " }\n" -"\n" -" Ok(valid_urls)\n" +" Ok(link_urls)\n" "}\n" "\n" "fn main() {\n" +" let client = Client::new();\n" " let start_url = Url::parse(\"https://www.google.org\").unwrap();\n" -" let response = get(start_url).unwrap();\n" -" match extract_links(response) {\n" +" let crawl_command = CrawlCommand{ url: start_url, extract_links: " +"true };\n" +" match visit_page(&client, &crawl_command) {\n" " Ok(links) => println!(\"Links: {links:#?}\"),\n" " Err(err) => println!(\"Could not extract links: {err:#}\"),\n" " }\n" @@ -21068,11 +21136,11 @@ msgid "" "```" msgstr "" -#: src/exercises/concurrency/link-checker.md:100 +#: src/exercises/concurrency/link-checker.md:120 msgid "Run the code in `src/main.rs` with" msgstr "Execute o código em `src/main.rs` com" -#: src/exercises/concurrency/link-checker.md:102 +#: src/exercises/concurrency/link-checker.md:122 #, fuzzy msgid "" "```shell\n" @@ -21083,12 +21151,12 @@ msgstr "" "$ cargo run\n" "```" -#: src/exercises/concurrency/link-checker.md:106 +#: src/exercises/concurrency/link-checker.md:126 #: src/exercises/concurrency/chat-app.md:140 msgid "## Tasks" msgstr "## Tarefas" -#: src/exercises/concurrency/link-checker.md:108 +#: src/exercises/concurrency/link-checker.md:128 msgid "" "* Use threads to check the links in parallel: send the URLs to be checked to " "a\n" @@ -22847,7 +22915,7 @@ msgstr "# Dia 1 Exercícios matinais" #: src/exercises/day-1/solutions-morning.md:3 msgid "## Arrays and `for` Loops" -msgstr "## Vetores e laços `for`" +msgstr "## Matrizes e Loops `for`" #: src/exercises/day-1/solutions-morning.md:5 msgid "([back to exercise](for-loops.md))" @@ -24668,6 +24736,211 @@ msgid "" "```" msgstr "" +#: src/exercises/concurrency/solutions-morning.md:104 +#, fuzzy +msgid "## Link Checker" +msgstr "# Verificador de links _multi-threads_" + +#: src/exercises/concurrency/solutions-morning.md:106 +#, fuzzy +msgid "([back to exercise](link-checker.md))" +msgstr "([voltar ao exercício](luhn.md))" + +#: src/exercises/concurrency/solutions-morning.md:108 +msgid "" +"```rust,compile_fail\n" +"// Copyright 2022 Google LLC\n" +"//\n" +"// Licensed under the Apache License, Version 2.0 (the \"License\");\n" +"// you may not use this file except in compliance with the License.\n" +"// You may obtain a copy of the License at\n" +"//\n" +"// http://www.apache.org/licenses/LICENSE-2.0\n" +"//\n" +"// Unless required by applicable law or agreed to in writing, software\n" +"// distributed under the License is distributed on an \"AS IS\" BASIS,\n" +"// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +"// See the License for the specific language governing permissions and\n" +"// limitations under the License.\n" +"\n" +"use std::{sync::Arc, sync::Mutex, sync::mpsc, thread};\n" +"\n" +"// ANCHOR: setup\n" +"use reqwest::{blocking::Client, Url};\n" +"use scraper::{Html, Selector};\n" +"use thiserror::Error;\n" +"\n" +"#[derive(Error, Debug)]\n" +"enum Error {\n" +" #[error(\"request error: {0}\")]\n" +" ReqwestError(#[from] reqwest::Error),\n" +" #[error(\"bad http response: {0}\")]\n" +" BadResponse(String),\n" +"}\n" +"// ANCHOR_END: setup\n" +"\n" +"// ANCHOR: visit_page\n" +"#[derive(Debug)]\n" +"struct CrawlCommand {\n" +" url: Url,\n" +" extract_links: bool,\n" +"}\n" +"\n" +"fn visit_page(client: &Client, command: &CrawlCommand) -> Result, " +"Error> {\n" +" println!(\"Checking {:#}\", command.url);\n" +" let response = client.get(command.url.clone()).send()?;\n" +" if !response.status().is_success() {\n" +" return Err(Error::BadResponse(response.status().to_string()));\n" +" }\n" +"\n" +" let mut link_urls = Vec::new();\n" +" if !command.extract_links {\n" +" return Ok(link_urls);\n" +" }\n" +"\n" +" let base_url = response.url().to_owned();\n" +" let body_text = response.text()?;\n" +" let document = Html::parse_document(&body_text);\n" +"\n" +" let selector = Selector::parse(\"a\").unwrap();\n" +" let href_values = document\n" +" .select(&selector)\n" +" .filter_map(|element| element.value().attr(\"href\"));\n" +" for href in href_values {\n" +" match base_url.join(href) {\n" +" Ok(link_url) => {\n" +" link_urls.push(link_url);\n" +" }\n" +" Err(err) => {\n" +" println!(\"On {base_url:#}: ignored unparsable {href:?}: " +"{err}\");\n" +" }\n" +" }\n" +" }\n" +" Ok(link_urls)\n" +"}\n" +"// ANCHOR_END: visit_page\n" +"\n" +"struct CrawlState {\n" +" domain: String,\n" +" visited_pages: std::collections::HashSet,\n" +"}\n" +"\n" +"impl CrawlState {\n" +" fn new(start_url: &Url) -> CrawlState {\n" +" let mut visited_pages = std::collections::HashSet::new();\n" +" visited_pages.insert(start_url.as_str().to_string());\n" +" CrawlState {\n" +" domain: start_url.domain().unwrap().to_string(),\n" +" visited_pages,\n" +" }\n" +" }\n" +"\n" +" /// Determine whether links within the given page should be extracted.\n" +" fn should_extract_links(&self, url: &Url) -> bool {\n" +" let Some(url_domain) = url.domain() else {\n" +" return false;\n" +" };\n" +" url_domain == self.domain\n" +" }\n" +"\n" +" /// Mark the given page as visited, returning true if it had already\n" +" /// been visited.\n" +" fn mark_visited(&mut self, url: &Url) -> bool {\n" +" self.visited_pages.insert(url.as_str().to_string())\n" +" }\n" +"}\n" +"\n" +"type CrawlResult = Result, (Url, Error)>;\n" +"fn spawn_crawler_threads(\n" +" command_receiver: mpsc::Receiver,\n" +" result_sender: mpsc::Sender,\n" +" thread_count: u32,\n" +") {\n" +" let command_receiver = Arc::new(Mutex::new(command_receiver));\n" +"\n" +" for _ in 0..thread_count {\n" +" let result_sender = result_sender.clone();\n" +" let command_receiver = command_receiver.clone();\n" +" thread::spawn(move || {\n" +" let client = Client::new();\n" +" loop {\n" +" let command_result = {\n" +" let receiver_guard = command_receiver.lock().unwrap();\n" +" receiver_guard.recv()\n" +" };\n" +" let Ok(crawl_command) = command_result else {\n" +" // The sender got dropped. No more commands coming in.\n" +" break;\n" +" };\n" +" let crawl_result = match visit_page(&client, &crawl_command) " +"{\n" +" Ok(link_urls) => Ok(link_urls),\n" +" Err(error) => Err((crawl_command.url, error)),\n" +" };\n" +" result_sender.send(crawl_result).unwrap();\n" +" }\n" +" });\n" +" }\n" +"}\n" +"\n" +"fn control_crawl(\n" +" start_url: Url,\n" +" command_sender: mpsc::Sender,\n" +" result_receiver: mpsc::Receiver,\n" +") -> Vec {\n" +" let mut crawl_state = CrawlState::new(&start_url);\n" +" let start_command = CrawlCommand { url: start_url, extract_links: " +"true };\n" +" command_sender.send(start_command).unwrap();\n" +" let mut pending_urls = 1;\n" +"\n" +" let mut bad_urls = Vec::new();\n" +" while pending_urls > 0 {\n" +" let crawl_result = result_receiver.recv().unwrap();\n" +" pending_urls -= 1;\n" +"\n" +" match crawl_result {\n" +" Ok(link_urls) => {\n" +" for url in link_urls {\n" +" if crawl_state.mark_visited(&url) {\n" +" let extract_links = crawl_state." +"should_extract_links(&url);\n" +" let crawl_command = CrawlCommand { url, " +"extract_links };\n" +" command_sender.send(crawl_command).unwrap();\n" +" pending_urls += 1;\n" +" }\n" +" }\n" +" }\n" +" Err((url, error)) => {\n" +" bad_urls.push(url);\n" +" println!(\"Got crawling error: {:#}\", error);\n" +" continue;\n" +" }\n" +" }\n" +" }\n" +" bad_urls\n" +"}\n" +"\n" +"fn check_links(start_url: Url) -> Vec {\n" +" let (result_sender, result_receiver) = mpsc::channel::();\n" +" let (command_sender, command_receiver) = mpsc::channel::" +"();\n" +" spawn_crawler_threads(command_receiver, result_sender, 16);\n" +" control_crawl(start_url, command_sender, result_receiver)\n" +"}\n" +"\n" +"fn main() {\n" +" let start_url = reqwest::Url::parse(\"https://www.google.org\")." +"unwrap();\n" +" let bad_urls = check_links(start_url);\n" +" println!(\"Bad URLs: {:#?}\", bad_urls);\n" +"}\n" +"```" +msgstr "" + #: src/exercises/concurrency/solutions-afternoon.md:1 #, fuzzy msgid "# Concurrency Afternoon Exercise"