This commit is contained in:
TimLai666 2024-06-10 18:23:38 +08:00 committed by GitHub
parent 235b3bdf50
commit 44fc93af91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 182 additions and 204 deletions

View File

@ -15,7 +15,7 @@ const RUSTC_NO_DEBUG_ARGS: &[&str] = &["-C", "strip=debuginfo"];
const CONTEXT: usize = 2; const CONTEXT: usize = 2;
const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/22_clippy/Cargo.toml"; const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/22_clippy/Cargo.toml";
// Checks if the line contains the "I AM NOT DONE" comment. // 檢查該行是否包含 "I AM NOT DONE" 註釋。
fn contains_not_done_comment(input: &str) -> bool { fn contains_not_done_comment(input: &str) -> bool {
( (
space0::<_, ()>, space0::<_, ()>,
@ -28,7 +28,7 @@ fn contains_not_done_comment(input: &str) -> bool {
.is_ok() .is_ok()
} }
// Get a temporary file name that is hopefully unique // 獲取一個臨時文件名,這個文件名應該是唯一的
#[inline] #[inline]
fn temp_file() -> String { fn temp_file() -> String {
let thread_id: String = format!("{:?}", std::thread::current().id()) let thread_id: String = format!("{:?}", std::thread::current().id())
@ -39,15 +39,15 @@ fn temp_file() -> String {
format!("./temp_{}_{thread_id}", process::id()) format!("./temp_{}_{thread_id}", process::id())
} }
// The mode of the exercise. // 練習的模式。
#[derive(Deserialize, Copy, Clone, Debug)] #[derive(Deserialize, Copy, Clone, Debug)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum Mode { pub enum Mode {
// Indicates that the exercise should be compiled as a binary // 表示練習應該編譯為二進制文件
Compile, Compile,
// Indicates that the exercise should be compiled as a test harness // 表示練習應該編譯為測試框架
Test, Test,
// Indicates that the exercise should be linted with clippy // 表示練習應該使用 clippy 進行檢查
Clippy, Clippy,
} }
@ -56,60 +56,60 @@ pub struct ExerciseList {
pub exercises: Vec<Exercise>, pub exercises: Vec<Exercise>,
} }
// A representation of a rustlings exercise. // Rustlings 練習的表示。
// This is deserialized from the accompanying info.toml file // 這是從伴隨的 info.toml 文件中反序列化而來的
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Exercise { pub struct Exercise {
// Name of the exercise // 練習的名稱
pub name: String, pub name: String,
// The path to the file containing the exercise's source code // 包含練習源代碼的文件的路徑
pub path: PathBuf, pub path: PathBuf,
// The mode of the exercise (Test, Compile, or Clippy) // 練習的模式Test、Compile 或 Clippy
pub mode: Mode, pub mode: Mode,
// The hint text associated with the exercise // 與練習相關的提示文字
pub hint: String, pub hint: String,
} }
// An enum to track of the state of an Exercise. // 用於跟踪練習狀態的枚舉。
// An Exercise can be either Done or Pending // 練習可以是 Done 或 Pending 狀態
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub enum State { pub enum State {
// The state of the exercise once it's been completed // 表示練習已完成的狀態
Done, Done,
// The state of the exercise while it's not completed yet // 表示練習尚未完成的狀態
Pending(Vec<ContextLine>), Pending(Vec<ContextLine>),
} }
// The context information of a pending exercise // 未完成練習的上下文信息
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub struct ContextLine { pub struct ContextLine {
// The source code that is still pending completion // 尚未完成的源代碼
pub line: String, pub line: String,
// The line number of the source code still pending completion // 尚未完成的源代碼行號
pub number: usize, pub number: usize,
// Whether or not this is important // 是否重要
pub important: bool, pub important: bool,
} }
// The result of compiling an exercise // 編譯練習的結果
pub struct CompiledExercise<'a> { pub struct CompiledExercise<'a> {
exercise: &'a Exercise, exercise: &'a Exercise,
_handle: FileHandle, _handle: FileHandle,
} }
impl<'a> CompiledExercise<'a> { impl<'a> CompiledExercise<'a> {
// Run the compiled exercise // 運行已編譯的練習
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> { pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
self.exercise.run() self.exercise.run()
} }
} }
// A representation of an already executed binary // 已執行二進制文件的表示
#[derive(Debug)] #[derive(Debug)]
pub struct ExerciseOutput { pub struct ExerciseOutput {
// The textual contents of the standard output of the binary // 二進制文件標準輸出的文本內容
pub stdout: String, pub stdout: String,
// The textual contents of the standard error of the binary // 二進制文件標準錯誤的文本內容
pub stderr: String, pub stderr: String,
} }
@ -153,10 +153,8 @@ path = "{}.rs""#,
"Failed to write 📎 Clippy 📎 Cargo.toml file." "Failed to write 📎 Clippy 📎 Cargo.toml file."
}; };
fs::write(CLIPPY_CARGO_TOML_PATH, cargo_toml).expect(cargo_toml_error_msg); fs::write(CLIPPY_CARGO_TOML_PATH, cargo_toml).expect(cargo_toml_error_msg);
// To support the ability to run the clippy exercises, build // 為了支持運行 clippy 練習,除了運行 clippy還要構建可執行文件。
// an executable, in addition to running clippy. With a // 如果編譯失敗,這將靜默失敗。但我們期望 clippy 在稍後編譯時反映相同的失敗。
// compilation failure, this would silently fail. But we expect
// clippy to reflect the same failure while compiling later.
Command::new("rustc") Command::new("rustc")
.args([self.path.to_str().unwrap(), "-o", &temp_file()]) .args([self.path.to_str().unwrap(), "-o", &temp_file()])
.args(RUSTC_COLOR_ARGS) .args(RUSTC_COLOR_ARGS)
@ -167,9 +165,9 @@ path = "{}.rs""#,
.stderr(Stdio::null()) .stderr(Stdio::null())
.status() .status()
.expect("Failed to compile!"); .expect("Failed to compile!");
// Due to an issue with Clippy, a cargo clean is required to catch all lints. // 由於 Clippy 的一個問題,需要進行 cargo clean 以捕獲所有 lint。
// See https://github.com/rust-lang/rust-clippy/issues/2604 // 參見 https://github.com/rust-lang/rust-clippy/issues/2604
// This is already fixed on Clippy's master branch. See this issue to track merging into Cargo: // 這已在 Clippy 的主分支中修復。請參見此問題以跟踪合併到 Cargo 中:
// https://github.com/rust-lang/rust-clippy/issues/3837 // https://github.com/rust-lang/rust-clippy/issues/3837
Command::new("cargo") Command::new("cargo")
.args(["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH]) .args(["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
@ -227,14 +225,14 @@ path = "{}.rs""#,
pub fn state(&self) -> State { pub fn state(&self) -> State {
let source_file = File::open(&self.path).unwrap_or_else(|e| { let source_file = File::open(&self.path).unwrap_or_else(|e| {
println!( println!(
"Failed to open the exercise file {}: {e}", "無法打開練習文件 {}: {e}",
self.path.display(), self.path.display(),
); );
exit(1); exit(1);
}); });
let mut source_reader = BufReader::new(source_file); let mut source_reader = BufReader::new(source_file);
// Read the next line into `buf` without the newline at the end. // 將下一行讀入 `buf`,但末尾沒有換行符。
let mut read_line = |buf: &mut String| -> io::Result<_> { let mut read_line = |buf: &mut String| -> io::Result<_> {
let n = source_reader.read_line(buf)?; let n = source_reader.read_line(buf)?;
if buf.ends_with('\n') { if buf.ends_with('\n') {
@ -247,27 +245,27 @@ path = "{}.rs""#,
}; };
let mut current_line_number: usize = 1; let mut current_line_number: usize = 1;
// Keep the last `CONTEXT` lines while iterating over the file lines. // 在遍歷文件行時保留最後的 `CONTEXT` 行。
let mut prev_lines: [_; CONTEXT] = array::from_fn(|_| String::with_capacity(256)); let mut prev_lines: [_; CONTEXT] = array::from_fn(|_| String::with_capacity(256));
let mut line = String::with_capacity(256); let mut line = String::with_capacity(256);
loop { loop {
let n = read_line(&mut line).unwrap_or_else(|e| { let n = read_line(&mut line).unwrap_or_else(|e| {
println!( println!(
"Failed to read the exercise file {}: {e}", "讀取練習文件 {} 失敗: {e}",
self.path.display(), self.path.display(),
); );
exit(1); exit(1);
}); });
// Reached the end of the file and didn't find the comment. // 到達文件末尾但未找到註釋。
if n == 0 { if n == 0 {
return State::Done; return State::Done;
} }
if contains_not_done_comment(&line) { if contains_not_done_comment(&line) {
let mut context = Vec::with_capacity(2 * CONTEXT + 1); let mut context = Vec::with_capacity(2 * CONTEXT + 1);
// Previous lines. // 之前的行。
for (ind, prev_line) in prev_lines for (ind, prev_line) in prev_lines
.into_iter() .into_iter()
.take(current_line_number - 1) .take(current_line_number - 1)
@ -281,22 +279,22 @@ path = "{}.rs""#,
}); });
} }
// Current line. // 當前行。
context.push(ContextLine { context.push(ContextLine {
line, line,
number: current_line_number, number: current_line_number,
important: true, important: true,
}); });
// Next lines. // 後續行。
for ind in 0..CONTEXT { for ind in 0..CONTEXT {
let mut next_line = String::with_capacity(256); let mut next_line = String::with_capacity(256);
let Ok(n) = read_line(&mut next_line) else { let Ok(n) = read_line(&mut next_line) else {
// If an error occurs, just ignore the next lines. // 如果發生錯誤,只需忽略後續行。
break; break;
}; };
// Reached the end of the file. // 到達文件末尾。
if n == 0 { if n == 0 {
break; break;
} }
@ -312,22 +310,22 @@ path = "{}.rs""#,
} }
current_line_number += 1; current_line_number += 1;
// Add the current line as a previous line and shift the older lines by one. // 將當前行添加為前一行,並將較舊的行向後移動一行。
for prev_line in &mut prev_lines { for prev_line in &mut prev_lines {
mem::swap(&mut line, prev_line); mem::swap(&mut line, prev_line);
} }
// The current line now contains the oldest previous line. // 當前行現在包含最舊的前一行。
// Recycle it for reading the next line. // 將其回收以讀取下一行。
line.clear(); line.clear();
} }
} }
// Check that the exercise looks to be solved using self.state() // 使用 self.state() 檢查練習看起來是否已解決
// This is not the best way to check since // 這不是最好的檢查方法,因為
// the user can just remove the "I AM NOT DONE" string from the file // 用戶可以僅從文件中刪除 "I AM NOT DONE" 字符串
// without actually having solved anything. // 而實際上並沒有解決任何問題。
// The only other way to truly check this would to compile and run // 唯一真正檢查的方法是編譯並運行
// the exercise; which would be both costly and counterintuitive // 練習;這既昂貴又違反直覺
pub fn looks_done(&self) -> bool { pub fn looks_done(&self) -> bool {
self.state() == State::Done self.state() == State::Done
} }
@ -366,12 +364,12 @@ mod test {
#[test] #[test]
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn test_no_pdb_file() { fn test_no_pdb_file() {
[Mode::Compile, Mode::Test] // Clippy doesn't like to test [Mode::Compile, Mode::Test] // Clippy 不喜歡測試
.iter() .iter()
.for_each(|mode| { .for_each(|mode| {
let exercise = Exercise { let exercise = Exercise {
name: String::from("example"), name: String::from("example"),
// We want a file that does actually compile // 我們需要一個確實可以編譯的文件
path: PathBuf::from("tests/fixture/state/pending_exercise.rs"), path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
mode: *mode, mode: *mode,
hint: String::from(""), hint: String::from(""),

View File

@ -27,11 +27,11 @@ mod project;
mod run; mod run;
mod verify; mod verify;
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code /// Rustlings 是一組小練習,用來讓您習慣於編寫和閱讀 Rust 代碼
#[derive(Parser)] #[derive(Parser)]
#[command(version)] #[command(version)]
struct Args { struct Args {
/// Show outputs from the test exercises /// 顯示測試練習的輸出
#[arg(long)] #[arg(long)]
nocapture: bool, nocapture: bool,
#[command(subcommand)] #[command(subcommand)]
@ -40,49 +40,49 @@ struct Args {
#[derive(Subcommand)] #[derive(Subcommand)]
enum Subcommands { enum Subcommands {
/// Verify all exercises according to the recommended order /// 按推薦順序驗證所有練習
Verify, Verify,
/// Rerun `verify` when files were edited /// 在文件編輯後重新運行 `verify`
Watch { Watch {
/// Show hints on success /// 成功時顯示提示
#[arg(long)] #[arg(long)]
success_hints: bool, success_hints: bool,
}, },
/// Run/Test a single exercise /// 運行/測試單個練習
Run { Run {
/// The name of the exercise /// 練習的名稱
name: String, name: String,
}, },
/// Reset a single exercise using "git stash -- <filename>" /// 使用 "git stash -- <filename>" 重置單個練習
Reset { Reset {
/// The name of the exercise /// 練習的名稱
name: String, name: String,
}, },
/// Return a hint for the given exercise /// 返回指定練習的提示
Hint { Hint {
/// The name of the exercise /// 練習的名稱
name: String, name: String,
}, },
/// List the exercises available in Rustlings /// 列出 Rustlings 中可用的練習
List { List {
/// Show only the paths of the exercises /// 僅顯示練習的路徑
#[arg(short, long)] #[arg(short, long)]
paths: bool, paths: bool,
/// Show only the names of the exercises /// 僅顯示練習的名稱
#[arg(short, long)] #[arg(short, long)]
names: bool, names: bool,
/// Provide a string to match exercise names. /// 提供一個字符串來匹配練習名稱。
/// Comma separated patterns are accepted /// 接受逗號分隔的模式
#[arg(short, long)] #[arg(short, long)]
filter: Option<String>, filter: Option<String>,
/// Display only exercises not yet solved /// 僅顯示尚未解決的練習
#[arg(short, long)] #[arg(short, long)]
unsolved: bool, unsolved: bool,
/// Display only exercises that have been solved /// 僅顯示已經解決的練習
#[arg(short, long)] #[arg(short, long)]
solved: bool, solved: bool,
}, },
/// Enable rust-analyzer for exercises /// 啟用 rust-analyzer 用於練習
Lsp, Lsp,
} }
@ -94,18 +94,18 @@ fn main() -> Result<()> {
} }
if which::which("rustc").is_err() { if which::which("rustc").is_err() {
println!("We cannot find `rustc`."); println!("我們無法找到 `rustc`。");
println!("Try running `rustc --version` to diagnose your problem."); println!("嘗試運行 `rustc --version` 來診斷您的問題。");
println!("For instructions on how to install Rust, check the README."); println!("有關如何安裝 Rust 的說明,請查看 README。");
std::process::exit(1); std::process::exit(1);
} }
let info_file = fs::read_to_string("info.toml").unwrap_or_else(|e| { let info_file = fs::read_to_string("info.toml").unwrap_or_else(|e| {
match e.kind() { match e.kind() {
io::ErrorKind::NotFound => println!( io::ErrorKind::NotFound => println!(
"The program must be run from the rustlings directory\nTry `cd rustlings/`!", "程序必須在 rustlings 目錄中運行\n嘗試 `cd rustlings/`!",
), ),
_ => println!("Failed to read the info.toml file: {e}"), _ => println!("讀取 info.toml 文件失敗: {e}"),
} }
std::process::exit(1); std::process::exit(1);
}); });
@ -128,7 +128,7 @@ fn main() -> Result<()> {
solved, solved,
} => { } => {
if !paths && !names { if !paths && !names {
println!("{:<17}\t{:<46}\t{:<7}", "Name", "Path", "Status"); println!("{:<17}\t{:<46}\t{:<7}", "名稱", "路徑", "狀態");
} }
let mut exercises_done: u16 = 0; let mut exercises_done: u16 = 0;
let lowercase_filter = filter let lowercase_filter = filter
@ -155,9 +155,9 @@ fn main() -> Result<()> {
let looks_done = exercise.looks_done(); let looks_done = exercise.looks_done();
let status = if looks_done { let status = if looks_done {
exercises_done += 1; exercises_done += 1;
"Done" "已完成"
} else { } else {
"Pending" "未完成"
}; };
let solve_cond = let solve_cond =
(looks_done && solved) || (!looks_done && unsolved) || (!solved && !unsolved); (looks_done && solved) || (!looks_done && unsolved) || (!solved && !unsolved);
@ -169,9 +169,8 @@ fn main() -> Result<()> {
} else { } else {
format!("{:<17}\t{fname:<46}\t{status:<7}\n", exercise.name) format!("{:<17}\t{fname:<46}\t{status:<7}\n", exercise.name)
}; };
// Somehow using println! leads to the binary panicking // 不知為何,使用 println! 在其輸出被管道時會導致二進制文件恐慌
// when its output is piped. // 因此,我們處理了一個 Broken Pipe 錯誤並仍然以 0 退出
// So, we're handling a Broken Pipe error and exiting with 0 anyway
let stdout = std::io::stdout(); let stdout = std::io::stdout();
{ {
let mut handle = stdout.lock(); let mut handle = stdout.lock();
@ -187,7 +186,7 @@ fn main() -> Result<()> {
let percentage_progress = exercises_done as f32 / exercises.len() as f32 * 100.0; let percentage_progress = exercises_done as f32 / exercises.len() as f32 * 100.0;
println!( println!(
"Progress: You completed {} / {} exercises ({:.1} %).", "進度: 您完成了 {} / {} 個練習 ({:.1} %)。",
exercises_done, exercises_done,
exercises.len(), exercises.len(),
percentage_progress percentage_progress
@ -220,29 +219,29 @@ fn main() -> Result<()> {
Subcommands::Lsp => { Subcommands::Lsp => {
if let Err(e) = write_project_json(exercises) { if let Err(e) = write_project_json(exercises) {
println!("Failed to write rust-project.json to disk for rust-analyzer: {e}"); println!("無法將 rust-project.json 寫入磁碟以用於 rust-analyzer: {e}");
} else { } else {
println!("Successfully generated rust-project.json"); println!("成功生成 rust-project.json");
println!("rust-analyzer will now parse exercises, restart your language server or editor"); println!("rust-analyzer 現在將解析練習,重啟您的語言服務器或編輯器");
} }
} }
Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) { Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) {
Err(e) => { Err(e) => {
println!("Error: Could not watch your progress. Error message was {e:?}."); println!("錯誤: 無法監視您的進度。錯誤訊息為 {e:?}");
println!("Most likely you've run out of disk space or your 'inotify limit' has been reached."); println!("最有可能是您的磁碟空間已滿或您的 'inotify 限制' 已達到。");
std::process::exit(1); std::process::exit(1);
} }
Ok(WatchStatus::Finished) => { Ok(WatchStatus::Finished) => {
println!( println!(
"{emoji} All exercises completed! {emoji}", "{emoji} 所有練習都完成了! {emoji}",
emoji = Emoji("🎉", "") emoji = Emoji("🎉", "")
); );
println!("\n{FENISH_LINE}\n"); println!("\n{FENISH_LINE}\n");
} }
Ok(WatchStatus::Unfinished) => { Ok(WatchStatus::Unfinished) => {
println!("We hope you're enjoying learning about Rust!"); println!("我們希望您享受學習 Rust 的過程!");
println!("If you want to continue working on the exercises at a later point, you can simply run `rustlings watch` again"); println!("如果您想在稍後繼續完成這些練習,只需再次運行 `rustlings watch`");
} }
}, },
} }
@ -254,18 +253,18 @@ fn spawn_watch_shell(
failed_exercise_hint: Arc<Mutex<Option<String>>>, failed_exercise_hint: Arc<Mutex<Option<String>>>,
should_quit: Arc<AtomicBool>, should_quit: Arc<AtomicBool>,
) { ) {
println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here."); println!("歡迎來到 watch 模式!您可以輸入 'help' 來獲取此處可用命令的概覽。");
thread::spawn(move || { thread::spawn(move || {
let mut input = String::with_capacity(32); let mut input = String::with_capacity(32);
let mut stdin = io::stdin().lock(); let mut stdin = io::stdin().lock();
loop { loop {
// Recycle input buffer. // 回收輸入緩衝區。
input.clear(); input.clear();
if let Err(e) = stdin.read_line(&mut input) { if let Err(e) = stdin.read_line(&mut input) {
println!("error reading command: {e}"); println!("讀取命令錯誤: {e}");
} }
let input = input.trim(); let input = input.trim();
@ -277,22 +276,22 @@ fn spawn_watch_shell(
println!("\x1B[2J\x1B[1;1H"); println!("\x1B[2J\x1B[1;1H");
} else if input == "quit" { } else if input == "quit" {
should_quit.store(true, Ordering::SeqCst); should_quit.store(true, Ordering::SeqCst);
println!("Bye!"); println!("再見!");
} else if input == "help" { } else if input == "help" {
println!("{WATCH_MODE_HELP_MESSAGE}"); println!("{WATCH_MODE_HELP_MESSAGE}");
} else if let Some(cmd) = input.strip_prefix('!') { } else if let Some(cmd) = input.strip_prefix('!') {
let mut parts = Shlex::new(cmd); let mut parts = Shlex::new(cmd);
let Some(program) = parts.next() else { let Some(program) = parts.next() else {
println!("no command provided"); println!("未提供命令");
continue; continue;
}; };
if let Err(e) = Command::new(program).args(parts).status() { if let Err(e) = Command::new(program).args(parts).status() {
println!("failed to execute command `{cmd}`: {e}"); println!("執行命令 `{cmd}` 失敗: {e}");
} }
} else { } else {
println!("unknown command: {input}\n{WATCH_MODE_HELP_MESSAGE}"); println!("未知命令: {input}\n{WATCH_MODE_HELP_MESSAGE}");
} }
} }
}); });
@ -304,8 +303,8 @@ fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise {
.iter() .iter()
.find(|e| !e.looks_done()) .find(|e| !e.looks_done())
.unwrap_or_else(|| { .unwrap_or_else(|| {
println!("🎉 Congratulations! You have done all the exercises!"); println!("🎉 恭喜!您已完成所有練習!");
println!("🔚 There are no more exercises to do next!"); println!("🔚 沒有更多的練習可以做了!");
std::process::exit(1) std::process::exit(1)
}) })
} else { } else {
@ -313,7 +312,7 @@ fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise {
.iter() .iter()
.find(|e| e.name == name) .find(|e| e.name == name)
.unwrap_or_else(|| { .unwrap_or_else(|| {
println!("No exercise found for '{name}'!"); println!("找不到名為 '{name}' 的練習!");
std::process::exit(1) std::process::exit(1)
}) })
} }
@ -329,8 +328,8 @@ fn watch(
verbose: bool, verbose: bool,
success_hints: bool, success_hints: bool,
) -> notify::Result<WatchStatus> { ) -> notify::Result<WatchStatus> {
/* Clears the terminal with an ANSI escape code. /* 使用 ANSI 轉義碼清除終端。
Works in UNIX and newer Windows terminals. */ UNIX Windows */
fn clear_screen() { fn clear_screen() {
println!("\x1Bc"); println!("\x1Bc");
} }
@ -395,50 +394,35 @@ fn watch(
} }
} }
} }
Err(e) => println!("watch error: {e:?}"), Err(e) => println!("監視錯誤: {e:?}"),
}, },
Err(RecvTimeoutError::Timeout) => { Err(RecvTimeoutError::Timeout) => {
// the timeout expired, just check the `should_quit` variable below then loop again // 超時了,只需檢查下面的 `should_quit` 變量,然後再次循環
} }
Err(e) => println!("watch error: {e:?}"), Err(e) => println!("監視錯誤: {e:?}"),
} }
// Check if we need to exit // 檢查是否需要退出
if should_quit.load(Ordering::SeqCst) { if should_quit.load(Ordering::SeqCst) {
return Ok(WatchStatus::Unfinished); return Ok(WatchStatus::Unfinished);
} }
} }
} }
const DEFAULT_OUT: &str = "Thanks for installing Rustlings! const DEFAULT_OUT: &str = "感謝您安裝 Rustlings
Is this your first time? Don't worry, Rustlings was made for beginners! We are 使Rustlings Rust Rustlings
going to teach you a lot of things about Rust, but before we can get
started, here's a couple of notes about how Rustlings operates:
1. The central concept behind Rustlings is that you solve exercises. These 1. Rustlings
exercises usually have some sort of syntax error in them, which will cause Rustlings
them to fail compilation or testing. Sometimes there's a logic error instead 2. watch Rustlings Rustlings
of a syntax error. No matter what error, it's your job to find it and fix it! 3. 'hint' watch `rustlings hint exercise_name`
You'll know when you fixed it because then, the exercise will compile and 4. GitHub (https://github.com/rust-lang/rustlings/issues/new) 我們會查看每個問題,有時其他學習者也會這樣做,所以您可以互相幫助!
Rustlings will be able to move on to the next exercise. 5. 使 `rust-analyzer` `rustlings lsp`
2. If you run Rustlings in watch mode (which we recommend), it'll automatically
start with the first exercise. Don't get confused by an error message popping
up as soon as you run Rustlings! This is part of the exercise that you're
supposed to solve, so open the exercise file in an editor and start your
detective work!
3. If you're stuck on an exercise, there is a helpful hint you can view by typing
'hint' (in watch mode), or running `rustlings hint exercise_name`.
4. If an exercise doesn't make sense to you, feel free to open an issue on GitHub!
(https://github.com/rust-lang/rustlings/issues/new). We look at every issue,
and sometimes, other learners do too so you can help each other out!
5. If you want to use `rust-analyzer` with exercises, which provides features like
autocompletion, run the command `rustlings lsp`.
Got all that? Great! To get started, run `rustlings watch` in order to get the first `rustlings watch` ";
exercise. Make sure to have your editor open!";
const FENISH_LINE: &str = "+----------------------------------------------------+ const FENISH_LINE: &str = "+----------------------------------------------------+
| You made it to the Fe-nish line! | | Fe-nish |
+-------------------------- ------------------------+ +-------------------------- ------------------------+
\\/\x1b[31m \\/\x1b[31m
@ -457,11 +441,11 @@ const FENISH_LINE: &str = "+----------------------------------------------------
\x1b[0m \x1b[0m
We hope you enjoyed learning about the various aspects of Rust! Rust
If you noticed any issues, please don't hesitate to report them to our repo.
You can also contribute your own exercises to help the greater community!
Before reporting an issue or contributing, please read our guidelines:
https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md"; https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md";
const WELCOME: &str = r" welcome to... const WELCOME: &str = r" welcome to...
@ -472,12 +456,11 @@ const WELCOME: &str = r" welcome to...
|_| \__,_|___/\__|_|_|_| |_|\__, |___/ |_| \__,_|___/\__|_|_|_| |_|\__, |___/
|___/"; |___/";
const WATCH_MODE_HELP_MESSAGE: &str = "Commands available to you in watch mode: const WATCH_MODE_HELP_MESSAGE: &str = "在 watch 模式下可用的命令:
hint - prints the current exercise's hint hint -
clear - clears the screen clear -
quit - quits watch mode quit - 退 watch
!<cmd> - executes a command, like `!rustc --explain E0381` !<cmd> - `!rustc --explain E0381`
help - displays this help message help -
Watch mode automatically re-evaluates the current exercise watch ";
when you edit a file's contents.";

View File

@ -6,8 +6,7 @@ use std::process::{Command, Stdio};
use crate::exercise::Exercise; use crate::exercise::Exercise;
/// Contains the structure of resulting rust-project.json file /// 包含生成 rust-project.json 文件所需的結構和構建數據的函數
/// and functions to build the data required to create the file
#[derive(Serialize)] #[derive(Serialize)]
struct RustAnalyzerProject { struct RustAnalyzerProject {
sysroot_src: PathBuf, sysroot_src: PathBuf,
@ -18,10 +17,10 @@ struct RustAnalyzerProject {
struct Crate { struct Crate {
root_module: PathBuf, root_module: PathBuf,
edition: &'static str, edition: &'static str,
// Not used, but required in the JSON file. // 未使用,但在 JSON 文件中是必需的。
deps: Vec<()>, deps: Vec<()>,
// Only `test` is used for all crates. // 僅使用 `test` 進行所有 crates。
// Therefore, an array is used instead of a `Vec`. // 因此,使用數組而不是 `Vec`。
cfg: [&'static str; 1], cfg: [&'static str; 1],
} }
@ -33,7 +32,7 @@ impl RustAnalyzerProject {
root_module: exercise.path, root_module: exercise.path,
edition: "2021", edition: "2021",
deps: Vec::new(), deps: Vec::new(),
// This allows rust_analyzer to work inside `#[test]` blocks // 這允許 rust_analyzer 在 `#[test]` 區塊內工作
cfg: ["test"], cfg: ["test"],
}) })
.collect(); .collect();
@ -50,13 +49,13 @@ impl RustAnalyzerProject {
.arg("sysroot") .arg("sysroot")
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
.output() .output()
.context("Failed to get the sysroot from `rustc`. Do you have `rustc` installed?")? .context("無法從 `rustc` 獲取 sysroot。您是否安裝了 `rustc`")?
.stdout; .stdout;
let toolchain = let toolchain =
String::from_utf8(toolchain).context("The toolchain path is invalid UTF8")?; String::from_utf8(toolchain).context("工具鏈路徑是無效的 UTF8")?;
let toolchain = toolchain.trim_end(); let toolchain = toolchain.trim_end();
println!("Determined toolchain: {toolchain}\n"); println!("確定的工具鏈: {toolchain}\n");
let mut sysroot_src = PathBuf::with_capacity(256); let mut sysroot_src = PathBuf::with_capacity(256);
sysroot_src.extend([toolchain, "lib", "rustlib", "src", "rust", "library"]); sysroot_src.extend([toolchain, "lib", "rustlib", "src", "rust", "library"]);
@ -68,13 +67,13 @@ impl RustAnalyzerProject {
} }
} }
/// Write `rust-project.json` to disk. /// 將 `rust-project.json` 寫入磁碟。
pub fn write_project_json(exercises: Vec<Exercise>) -> Result<()> { pub fn write_project_json(exercises: Vec<Exercise>) -> Result<()> {
let content = RustAnalyzerProject::build(exercises)?; let content = RustAnalyzerProject::build(exercises)?;
// Using the capacity 2^14 since the file length in bytes is higher than 2^13. // 使用容量 2^14因為文件長度以字節為單位高於 2^13。
// The final length is not known exactly because it depends on the user's sysroot path, // 最終長度不確定,因為它取決於用戶的 sysroot 路徑、
// the current number of exercises etc. // 當前的練習數量等。
let mut buf = Vec::with_capacity(1 << 14); let mut buf = Vec::with_capacity(1 << 14);
serde_json::to_writer(&mut buf, &content)?; serde_json::to_writer(&mut buf, &content)?;
std::fs::write("rust-project.json", buf)?; std::fs::write("rust-project.json", buf)?;

View File

@ -5,10 +5,9 @@ use crate::exercise::{Exercise, Mode};
use crate::verify::test; use crate::verify::test;
use indicatif::ProgressBar; use indicatif::ProgressBar;
// Invoke the rust compiler on the path of the given exercise, // 在給定練習的路徑上調用 Rust 編譯器,並運行隨後的二進制文件。
// and run the ensuing binary. // verbose 參數有助於確定是否顯示
// The verbose argument helps determine whether or not to show // 測試框架的輸出(如果練習模式是測試)
// the output from the test harnesses (if the mode of the exercise is test)
pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> { pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
match exercise.mode { match exercise.mode {
Mode::Test => test(exercise, verbose)?, Mode::Test => test(exercise, verbose)?,
@ -18,7 +17,7 @@ pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
Ok(()) Ok(())
} }
// Resets the exercise by stashing the changes. // 通過存儲更改來重置練習。
pub fn reset(exercise: &Exercise) -> Result<(), ()> { pub fn reset(exercise: &Exercise) -> Result<(), ()> {
let command = Command::new("git") let command = Command::new("git")
.arg("stash") .arg("stash")
@ -32,12 +31,12 @@ pub fn reset(exercise: &Exercise) -> Result<(), ()> {
} }
} }
// Invoke the rust compiler on the path of the given exercise // 在給定練習的路徑上調用 Rust 編譯器
// and run the ensuing binary. // 並運行隨後的二進制文件。
// This is strictly for non-test binaries, so output is displayed // 這僅適用於非測試二進制文件,因此顯示輸出
fn compile_and_run(exercise: &Exercise) -> Result<(), ()> { fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
let progress_bar = ProgressBar::new_spinner(); let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Compiling {exercise}...")); progress_bar.set_message(format!("正在編譯 {exercise}..."));
progress_bar.enable_steady_tick(Duration::from_millis(100)); progress_bar.enable_steady_tick(Duration::from_millis(100));
let compilation_result = exercise.compile(); let compilation_result = exercise.compile();
@ -46,7 +45,7 @@ fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
Err(output) => { Err(output) => {
progress_bar.finish_and_clear(); progress_bar.finish_and_clear();
warn!( warn!(
"Compilation of {} failed!, Compiler error message:\n", "編譯 {} 失敗!編譯器錯誤訊息:\n",
exercise exercise
); );
println!("{}", output.stderr); println!("{}", output.stderr);
@ -54,21 +53,21 @@ fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
} }
}; };
progress_bar.set_message(format!("Running {exercise}...")); progress_bar.set_message(format!("正在運行 {exercise}..."));
let result = compilation.run(); let result = compilation.run();
progress_bar.finish_and_clear(); progress_bar.finish_and_clear();
match result { match result {
Ok(output) => { Ok(output) => {
println!("{}", output.stdout); println!("{}", output.stdout);
success!("Successfully ran {}", exercise); success!("成功運行 {}", exercise);
Ok(()) Ok(())
} }
Err(output) => { Err(output) => {
println!("{}", output.stdout); println!("{}", output.stdout);
println!("{}", output.stderr); println!("{}", output.stderr);
warn!("Ran {} with errors", exercise); warn!("運行 {} 時出現錯誤", exercise);
Err(()) Err(())
} }
} }

View File

@ -3,11 +3,10 @@ use console::style;
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use std::{env, time::Duration}; use std::{env, time::Duration};
// Verify that the provided container of Exercise objects // 驗證提供的 Exercise 對象容器是否可以編譯和運行而不出現任何錯誤。
// can be compiled and run without any failures. // 任何此類錯誤都將報告給最終用戶。
// Any such failures will be reported to the end user. // 如果要驗證的 Exercise 是測試,則 verbose 布爾值
// If the Exercise being verified is a test, the verbose boolean // 決定是否顯示測試框架的輸出。
// determines whether or not the test harness outputs are displayed.
pub fn verify<'a>( pub fn verify<'a>(
exercises: impl IntoIterator<Item = &'a Exercise>, exercises: impl IntoIterator<Item = &'a Exercise>,
progress: (usize, usize), progress: (usize, usize),
@ -19,8 +18,8 @@ pub fn verify<'a>(
let mut percentage = num_done as f32 / total as f32 * 100.0; let mut percentage = num_done as f32 / total as f32 * 100.0;
bar.set_style( bar.set_style(
ProgressStyle::default_bar() ProgressStyle::default_bar()
.template("Progress: [{bar:60.green/red}] {pos}/{len} {msg}") .template("進度: [{bar:60.green/red}] {pos}/{len} {msg}")
.expect("Progressbar template should be valid!") .expect("進度條模板應該是有效的!")
.progress_chars("#>-"), .progress_chars("#>-"),
); );
bar.set_position(num_done as u64); bar.set_position(num_done as u64);
@ -40,7 +39,7 @@ pub fn verify<'a>(
bar.set_message(format!("({percentage:.1} %)")); bar.set_message(format!("({percentage:.1} %)"));
if bar.position() == total as u64 { if bar.position() == total as u64 {
println!( println!(
"Progress: You completed {} / {} exercises ({:.1} %).", "進度: 您完成了 {} / {} 個練習 ({:.1} %)。",
bar.position(), bar.position(),
total, total,
percentage percentage
@ -57,16 +56,16 @@ enum RunMode {
NonInteractive, NonInteractive,
} }
// Compile and run the resulting test harness of the given Exercise // 編譯並運行給定 Exercise 的測試框架
pub fn test(exercise: &Exercise, verbose: bool) -> Result<(), ()> { pub fn test(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
compile_and_test(exercise, RunMode::NonInteractive, verbose, false)?; compile_and_test(exercise, RunMode::NonInteractive, verbose, false)?;
Ok(()) Ok(())
} }
// Invoke the rust compiler without running the resulting binary // 調用 rust 編譯器但不運行生成的二進制文件
fn compile_only(exercise: &Exercise, success_hints: bool) -> Result<bool, ()> { fn compile_only(exercise: &Exercise, success_hints: bool) -> Result<bool, ()> {
let progress_bar = ProgressBar::new_spinner(); let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Compiling {exercise}...")); progress_bar.set_message(format!("正在編譯 {exercise}..."));
progress_bar.enable_steady_tick(Duration::from_millis(100)); progress_bar.enable_steady_tick(Duration::from_millis(100));
let _ = compile(exercise, &progress_bar)?; let _ = compile(exercise, &progress_bar)?;
@ -75,22 +74,22 @@ fn compile_only(exercise: &Exercise, success_hints: bool) -> Result<bool, ()> {
Ok(prompt_for_completion(exercise, None, success_hints)) Ok(prompt_for_completion(exercise, None, success_hints))
} }
// Compile the given Exercise and run the resulting binary in an interactive mode // 以交互模式編譯給定的 Exercise 並運行生成的二進制文件
fn compile_and_run_interactively(exercise: &Exercise, success_hints: bool) -> Result<bool, ()> { fn compile_and_run_interactively(exercise: &Exercise, success_hints: bool) -> Result<bool, ()> {
let progress_bar = ProgressBar::new_spinner(); let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Compiling {exercise}...")); progress_bar.set_message(format!("正在編譯 {exercise}..."));
progress_bar.enable_steady_tick(Duration::from_millis(100)); progress_bar.enable_steady_tick(Duration::from_millis(100));
let compilation = compile(exercise, &progress_bar)?; let compilation = compile(exercise, &progress_bar)?;
progress_bar.set_message(format!("Running {exercise}...")); progress_bar.set_message(format!("正在運行 {exercise}..."));
let result = compilation.run(); let result = compilation.run();
progress_bar.finish_and_clear(); progress_bar.finish_and_clear();
let output = match result { let output = match result {
Ok(output) => output, Ok(output) => output,
Err(output) => { Err(output) => {
warn!("Ran {} with errors", exercise); warn!("運行 {} 時出現錯誤", exercise);
println!("{}", output.stdout); println!("{}", output.stdout);
println!("{}", output.stderr); println!("{}", output.stderr);
return Err(()); return Err(());
@ -104,8 +103,8 @@ fn compile_and_run_interactively(exercise: &Exercise, success_hints: bool) -> Re
)) ))
} }
// Compile the given Exercise as a test harness and display // 將給定的 Exercise 編譯為測試框架並顯示
// the output if verbose is set to true // 如果 verbose 設置為 true 則輸出
fn compile_and_test( fn compile_and_test(
exercise: &Exercise, exercise: &Exercise,
run_mode: RunMode, run_mode: RunMode,
@ -113,7 +112,7 @@ fn compile_and_test(
success_hints: bool, success_hints: bool,
) -> Result<bool, ()> { ) -> Result<bool, ()> {
let progress_bar = ProgressBar::new_spinner(); let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Testing {exercise}...")); progress_bar.set_message(format!("正在測試 {exercise}..."));
progress_bar.enable_steady_tick(Duration::from_millis(100)); progress_bar.enable_steady_tick(Duration::from_millis(100));
let compilation = compile(exercise, &progress_bar)?; let compilation = compile(exercise, &progress_bar)?;
@ -133,7 +132,7 @@ fn compile_and_test(
} }
Err(output) => { Err(output) => {
warn!( warn!(
"Testing of {} failed! Please try again. Here's the output:", "測試 {} 失敗!請再試一次。以下是輸出:",
exercise exercise
); );
println!("{}", output.stdout); println!("{}", output.stdout);
@ -142,8 +141,8 @@ fn compile_and_test(
} }
} }
// Compile the given Exercise and return an object with information // 編譯給定的 Exercise 並返回一個包含
// about the state of the compilation // 編譯狀態信息的對象
fn compile<'a>( fn compile<'a>(
exercise: &'a Exercise, exercise: &'a Exercise,
progress_bar: &ProgressBar, progress_bar: &ProgressBar,
@ -155,7 +154,7 @@ fn compile<'a>(
Err(output) => { Err(output) => {
progress_bar.finish_and_clear(); progress_bar.finish_and_clear();
warn!( warn!(
"Compiling of {} failed! Please try again. Here's the output:", "編譯 {} 失敗!請再試一次。以下是輸出:",
exercise exercise
); );
println!("{}", output.stderr); println!("{}", output.stderr);
@ -174,22 +173,22 @@ fn prompt_for_completion(
State::Pending(context) => context, State::Pending(context) => context,
}; };
match exercise.mode { match exercise.mode {
Mode::Compile => success!("Successfully ran {}!", exercise), Mode::Compile => success!("成功運行 {}", exercise),
Mode::Test => success!("Successfully tested {}!", exercise), Mode::Test => success!("成功測試 {}", exercise),
Mode::Clippy => success!("Successfully compiled {}!", exercise), Mode::Clippy => success!("成功編譯 {}", exercise),
} }
let no_emoji = env::var("NO_EMOJI").is_ok(); let no_emoji = env::var("NO_EMOJI").is_ok();
let clippy_success_msg = if no_emoji { let clippy_success_msg = if no_emoji {
"The code is compiling, and Clippy is happy!" "代碼正在編譯Clippy 很滿意!"
} else { } else {
"The code is compiling, and 📎 Clippy 📎 is happy!" "代碼正在編譯,📎 Clippy 📎 很滿意!"
}; };
let success_msg = match exercise.mode { let success_msg = match exercise.mode {
Mode::Compile => "The code is compiling!", Mode::Compile => "代碼正在編譯!",
Mode::Test => "The code is compiling, and the tests pass!", Mode::Test => "代碼正在編譯,並且測試通過!",
Mode::Clippy => clippy_success_msg, Mode::Clippy => clippy_success_msg,
}; };
@ -201,21 +200,21 @@ fn prompt_for_completion(
if let Some(output) = prompt_output { if let Some(output) = prompt_output {
println!( println!(
"Output:\n{separator}\n{output}\n{separator}\n", "輸出:\n{separator}\n{output}\n{separator}\n",
separator = separator(), separator = separator(),
); );
} }
if success_hints { if success_hints {
println!( println!(
"Hints:\n{separator}\n{}\n{separator}\n", "提示:\n{separator}\n{}\n{separator}\n",
exercise.hint, exercise.hint,
separator = separator(), separator = separator(),
); );
} }
println!("You can keep working on this exercise,"); println!("您可以繼續進行此練習,");
println!( println!(
"or jump into the next one by removing the {} comment:", "或通過刪除 {} 註釋來進入下一個練習:",
style("`I AM NOT DONE`").bold() style("`I AM NOT DONE`").bold()
); );
println!(); println!();