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

View File

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

View File

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

View File

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

View File

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