From 3dce7e56961a40748f428d10c50540a075839f8d Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 23 Mar 2024 18:51:25 +0100 Subject: Improvements to watch mode --- src/main.rs | 85 +++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 36 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index a06f0c5..7c469d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use clap::{Parser, Subcommand}; use console::Emoji; use notify_debouncer_mini::notify::{self, RecursiveMode}; use notify_debouncer_mini::{new_debouncer, DebouncedEventKind}; +use shlex::Shlex; use std::ffi::OsStr; use std::fs; use std::io::{self, prelude::*}; @@ -25,6 +26,16 @@ mod project; mod run; mod verify; +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 + ! - executes a command, like `!rustc --explain E0381` + help - displays this help message + +Watch mode automatically re-evaluates the current exercise +when you edit a file's contents."; + /// Rustlings is a collection of small exercises to get you used to writing and reading Rust code #[derive(Parser)] #[command(version)] @@ -246,47 +257,49 @@ fn main() { } fn spawn_watch_shell( - failed_exercise_hint: &Arc>>, + failed_exercise_hint: Arc>>, should_quit: Arc, ) { - let failed_exercise_hint = Arc::clone(failed_exercise_hint); println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here."); - thread::spawn(move || loop { + + thread::spawn(move || { let mut input = String::new(); - match io::stdin().read_line(&mut input) { - Ok(_) => { - let input = input.trim(); - if input == "hint" { - if let Some(hint) = &*failed_exercise_hint.lock().unwrap() { - println!("{hint}"); - } - } else if input == "clear" { - println!("\x1B[2J\x1B[1;1H"); - } else if input.eq("quit") { - should_quit.store(true, Ordering::SeqCst); - println!("Bye!"); - } else if input.eq("help") { - println!("Commands available to you in watch mode:"); - println!(" hint - prints the current exercise's hint"); - println!(" clear - clears the screen"); - println!(" quit - quits watch mode"); - println!(" ! - executes a command, like `!rustc --explain E0381`"); - println!(" help - displays this help message"); - println!(); - println!("Watch mode automatically re-evaluates the current exercise"); - println!("when you edit a file's contents.") - } else if let Some(cmd) = input.strip_prefix('!') { - let parts: Vec<&str> = cmd.split_whitespace().collect(); - if parts.is_empty() { - println!("no command provided"); - } else if let Err(e) = Command::new(parts[0]).args(&parts[1..]).status() { - println!("failed to execute command `{}`: {}", cmd, e); - } - } else { - println!("unknown command: {input}"); + 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}"); + } + + let input = input.trim(); + if input == "hint" { + if let Some(hint) = &*failed_exercise_hint.lock().unwrap() { + println!("{hint}"); } + } else if input == "clear" { + println!("\x1B[2J\x1B[1;1H"); + } else if input == "quit" { + should_quit.store(true, Ordering::SeqCst); + println!("Bye!"); + } 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"); + continue; + }; + + if let Err(e) = Command::new(program).args(parts).status() { + println!("failed to execute command `{cmd}`: {e}"); + } + } else { + println!("unknown command: {input}\n{WATCH_MODE_HELP_MESSAGE}"); } - Err(error) => println!("error reading command: {error}"), } }); } @@ -348,7 +361,7 @@ fn watch( Ok(_) => return Ok(WatchStatus::Finished), Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))), }; - spawn_watch_shell(&failed_exercise_hint, Arc::clone(&should_quit)); + spawn_watch_shell(Arc::clone(&failed_exercise_hint), Arc::clone(&should_quit)); loop { match rx.recv_timeout(Duration::from_secs(1)) { Ok(event) => match event { -- cgit v1.2.3 From 0d93266462f56d28501f068a764405a0cd0bf41a Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 23 Mar 2024 18:56:30 +0100 Subject: Initialize the input buffer with some capacity --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 7c469d5..2b6a48c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -263,7 +263,7 @@ fn spawn_watch_shell( println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here."); thread::spawn(move || { - let mut input = String::new(); + let mut input = String::with_capacity(32); let mut stdin = io::stdin().lock(); loop { -- cgit v1.2.3 From 27fa7c3e4a5bb58b21359e9d6246f66b5f20a978 Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 23 Mar 2024 19:00:15 +0100 Subject: Move the const string to the bottom like others --- src/main.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 2b6a48c..d2614df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,16 +26,6 @@ mod project; mod run; mod verify; -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 - ! - executes a command, like `!rustc --explain E0381` - help - displays this help message - -Watch mode automatically re-evaluates the current exercise -when you edit a file's contents."; - /// Rustlings is a collection of small exercises to get you used to writing and reading Rust code #[derive(Parser)] #[command(version)] @@ -490,3 +480,13 @@ 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 + ! - executes a command, like `!rustc --explain E0381` + help - displays this help message + +Watch mode automatically re-evaluates the current exercise +when you edit a file's contents."; -- cgit v1.2.3 From a325df55d1077c8613905bb82709cd8c80341641 Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 23 Mar 2024 21:56:40 +0100 Subject: Cache filters --- src/main.rs | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index a06f0c5..9bf5866 100644 --- a/src/main.rs +++ b/src/main.rs @@ -128,31 +128,45 @@ fn main() { println!("{:<17}\t{:<46}\t{:<7}", "Name", "Path", "Status"); } let mut exercises_done: u16 = 0; - let filters = filter.clone().unwrap_or_default().to_lowercase(); - exercises.iter().for_each(|e| { - let fname = format!("{}", e.path.display()); + let lowercase_filter = filter + .as_ref() + .map(|s| s.to_lowercase()) + .unwrap_or_default(); + let filters = lowercase_filter + .split(',') + .filter_map(|f| { + let f = f.trim(); + if f.is_empty() { + None + } else { + Some(f) + } + }) + .collect::>(); + + for exercise in &exercises { + let fname = format!("{}", exercise.path.display()); let filter_cond = filters - .split(',') - .filter(|f| !f.trim().is_empty()) - .any(|f| e.name.contains(f) || fname.contains(f)); - let status = if e.looks_done() { + .iter() + .any(|f| exercise.name.contains(f) || fname.contains(f)); + let status = if exercise.looks_done() { exercises_done += 1; "Done" } else { "Pending" }; let solve_cond = { - (e.looks_done() && solved) - || (!e.looks_done() && unsolved) + (exercise.looks_done() && solved) + || (!exercise.looks_done() && unsolved) || (!solved && !unsolved) }; if solve_cond && (filter_cond || filter.is_none()) { let line = if paths { format!("{fname}\n") } else if names { - format!("{}\n", e.name) + format!("{}\n", exercise.name) } else { - format!("{:<17}\t{fname:<46}\t{status:<7}\n", e.name) + format!("{:<17}\t{fname:<46}\t{status:<7}\n", exercise.name) }; // Somehow using println! leads to the binary panicking // when its output is piped. @@ -168,7 +182,8 @@ fn main() { }); } } - }); + } + let percentage_progress = exercises_done as f32 / exercises.len() as f32 * 100.0; println!( "Progress: You completed {} / {} exercises ({:.1} %).", -- cgit v1.2.3 From e1375ef4319641749611124ae495346d32e04e2d Mon Sep 17 00:00:00 2001 From: mo8it Date: Sun, 24 Mar 2024 18:47:27 +0100 Subject: Use to_string_lossy --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 9bf5866..067c810 100644 --- a/src/main.rs +++ b/src/main.rs @@ -145,7 +145,7 @@ fn main() { .collect::>(); for exercise in &exercises { - let fname = format!("{}", exercise.path.display()); + let fname = exercise.path.to_string_lossy(); let filter_cond = filters .iter() .any(|f| exercise.name.contains(f) || fname.contains(f)); -- cgit v1.2.3 From f205ee3d4c6f259c82e4f1226acc6a5ae5e70031 Mon Sep 17 00:00:00 2001 From: mo8it Date: Sun, 24 Mar 2024 18:50:46 +0100 Subject: Call looks_done only once --- src/main.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 067c810..f646fdc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,17 +149,15 @@ fn main() { let filter_cond = filters .iter() .any(|f| exercise.name.contains(f) || fname.contains(f)); - let status = if exercise.looks_done() { + let looks_done = exercise.looks_done(); + let status = if looks_done { exercises_done += 1; "Done" } else { "Pending" }; - let solve_cond = { - (exercise.looks_done() && solved) - || (!exercise.looks_done() && unsolved) - || (!solved && !unsolved) - }; + let solve_cond = + (looks_done && solved) || (!looks_done && unsolved) || (!solved && !unsolved); if solve_cond && (filter_cond || filter.is_none()) { let line = if paths { format!("{fname}\n") -- cgit v1.2.3 From 51b4c240ed006a8279bd94e9b7ed5df67086c86e Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 25 Mar 2024 00:30:01 +0100 Subject: Use `which` instead of running `rustc --version` --- Cargo.lock | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 16 ++-------------- 3 files changed, 57 insertions(+), 14 deletions(-) (limited to 'src/main.rs') diff --git a/Cargo.lock b/Cargo.lock index 3950c47..1bfd301 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,6 +195,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -207,6 +213,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "filetime" version = "0.2.23" @@ -354,6 +370,12 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + [[package]] name = "log" version = "0.4.21" @@ -521,6 +543,19 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustlings" version = "5.6.1" @@ -537,6 +572,7 @@ dependencies = [ "serde", "serde_json", "toml", + "which", ] [[package]] @@ -694,6 +730,18 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "which" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + [[package]] name = "winapi" version = "0.3.9" @@ -865,3 +913,9 @@ checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" dependencies = [ "memchr", ] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" diff --git a/Cargo.toml b/Cargo.toml index 218b799..de65fc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ regex = "1.10.3" serde_json = "1.0.114" serde = { version = "1.0.197", features = ["derive"] } toml = "0.8.10" +which = "6.0.1" [[bin]] name = "rustlings" diff --git a/src/main.rs b/src/main.rs index a06f0c5..f932631 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use std::ffi::OsStr; use std::fs; use std::io::{self, prelude::*}; use std::path::Path; -use std::process::{Command, Stdio}; +use std::process::Command; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, RecvTimeoutError}; use std::sync::{Arc, Mutex}; @@ -100,7 +100,7 @@ fn main() { std::process::exit(1); } - if !rustc_exists() { + 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."); @@ -403,18 +403,6 @@ fn watch( } } -fn rustc_exists() -> bool { - Command::new("rustc") - .args(["--version"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .stdin(Stdio::null()) - .spawn() - .and_then(|mut child| child.wait()) - .map(|status| status.success()) - .unwrap_or(false) -} - const DEFAULT_OUT: &str = r#"Thanks for installing Rustlings! Is this your first time? Don't worry, Rustlings was made for beginners! We are -- cgit v1.2.3 From 83cd91ccca22e36ed94e03cc622a88ef45e6da10 Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 25 Mar 2024 02:35:51 +0100 Subject: Replace toml with toml_edit --- Cargo.lock | 18 +++--------------- Cargo.toml | 2 +- src/main.rs | 6 ++++-- 3 files changed, 8 insertions(+), 18 deletions(-) (limited to 'src/main.rs') diff --git a/Cargo.lock b/Cargo.lock index 3950c47..52b2725 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -536,7 +536,7 @@ dependencies = [ "regex", "serde", "serde_json", - "toml", + "toml_edit", ] [[package]] @@ -617,18 +617,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" -[[package]] -name = "toml" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - [[package]] name = "toml_datetime" version = "0.6.5" @@ -640,9 +628,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ "indexmap", "serde", diff --git a/Cargo.toml b/Cargo.toml index 218b799..2861459 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ notify-debouncer-mini = "0.4.1" regex = "1.10.3" serde_json = "1.0.114" serde = { version = "1.0.197", features = ["derive"] } -toml = "0.8.10" +toml_edit = { version = "0.22.9", default-features = false, features = ["parse", "serde"] } [[bin]] name = "rustlings" diff --git a/src/main.rs b/src/main.rs index a06f0c5..8e0029d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,8 +107,10 @@ fn main() { std::process::exit(1); } - let toml_str = &fs::read_to_string("info.toml").unwrap(); - let exercises = toml::from_str::(toml_str).unwrap().exercises; + let info_file = fs::read_to_string("info.toml").unwrap(); + let exercises = toml_edit::de::from_str::(&info_file) + .unwrap() + .exercises; let verbose = args.nocapture; let command = args.command.unwrap_or_else(|| { -- cgit v1.2.3 From e4520602f52935ff310534afc65160bcc5796a97 Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 25 Mar 2024 02:41:45 +0100 Subject: Use the NotFound variant of the IO error --- src/main.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 8e0029d..d6542aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -91,15 +91,6 @@ fn main() { println!("\n{WELCOME}\n"); } - if !Path::new("info.toml").exists() { - println!( - "{} must be run from the rustlings directory", - std::env::current_exe().unwrap().to_str().unwrap() - ); - println!("Try `cd rustlings/`!"); - std::process::exit(1); - } - if !rustc_exists() { println!("We cannot find `rustc`."); println!("Try running `rustc --version` to diagnose your problem."); @@ -107,7 +98,15 @@ fn main() { std::process::exit(1); } - let info_file = fs::read_to_string("info.toml").unwrap(); + 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/`!", + ), + _ => println!("Failed to read the info.toml file: {e}"), + } + std::process::exit(1); + }); let exercises = toml_edit::de::from_str::(&info_file) .unwrap() .exercises; -- cgit v1.2.3 From efa9f5704853acda6874725004b480d720683faf Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 25 Mar 2024 03:46:56 +0100 Subject: Add anyhow --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/main.rs | 5 ++++- 3 files changed, 12 insertions(+), 1 deletion(-) (limited to 'src/main.rs') diff --git a/Cargo.lock b/Cargo.lock index 3950c47..270051e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + [[package]] name = "assert_cmd" version = "2.0.14" @@ -525,6 +531,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" name = "rustlings" version = "5.6.1" dependencies = [ + "anyhow", "assert_cmd", "clap", "console", diff --git a/Cargo.toml b/Cargo.toml index 218b799..d7b5a09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ authors = [ edition = "2021" [dependencies] +anyhow = "1.0.81" clap = { version = "4.5.2", features = ["derive"] } console = "0.15.8" glob = "0.3.0" diff --git a/src/main.rs b/src/main.rs index a06f0c5..4a4f219 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use crate::exercise::{Exercise, ExerciseList}; use crate::project::RustAnalyzerProject; use crate::run::{reset, run}; use crate::verify::verify; +use anyhow::Result; use clap::{Parser, Subcommand}; use console::Emoji; use notify_debouncer_mini::notify::{self, RecursiveMode}; @@ -84,7 +85,7 @@ enum Subcommands { Lsp, } -fn main() { +fn main() -> Result<()> { let args = Args::parse(); if args.command.is_none() { @@ -243,6 +244,8 @@ fn main() { } }, } + + Ok(()) } fn spawn_watch_shell( -- cgit v1.2.3 From 51712cc19f97972f470c4d8791974f8eaba095d1 Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 25 Mar 2024 03:49:10 +0100 Subject: Merge get_sysroot_src into the constructor --- src/main.rs | 5 +--- src/project.rs | 77 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 39 insertions(+), 43 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 4a4f219..4ce0b30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -204,10 +204,7 @@ fn main() -> Result<()> { } Subcommands::Lsp => { - let mut project = RustAnalyzerProject::new(); - project - .get_sysroot_src() - .expect("Couldn't find toolchain path, do you have `rustc` installed?"); + let mut project = RustAnalyzerProject::build()?; project .exercises_to_json() .expect("Couldn't parse rustlings exercises files"); diff --git a/src/project.rs b/src/project.rs index 93f941d..a7414d1 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,3 +1,4 @@ +use anyhow::{bail, Context, Result}; use glob::glob; use serde::{Deserialize, Serialize}; use std::env; @@ -22,11 +23,44 @@ pub struct Crate { } impl RustAnalyzerProject { - pub fn new() -> RustAnalyzerProject { - RustAnalyzerProject { - sysroot_src: String::new(), - crates: Vec::new(), + pub fn build() -> Result { + // check if RUST_SRC_PATH is set + if let Ok(sysroot_src) = env::var("RUST_SRC_PATH") { + return Ok(Self { + sysroot_src, + crates: Vec::new(), + }); } + + let toolchain = Command::new("rustc") + .arg("--print") + .arg("sysroot") + .output() + .context("Failed to get the sysroot from `rustc`. Do you have `rustc` installed?")? + .stdout; + + let toolchain = + String::from_utf8(toolchain).context("The toolchain path is invalid UTF8")?; + let toolchain = toolchain.trim_end(); + + println!("Determined toolchain: {toolchain}\n"); + + let Ok(sysroot_src) = Path::new(toolchain) + .join("lib") + .join("rustlib") + .join("src") + .join("rust") + .join("library") + .into_os_string() + .into_string() + else { + bail!("The sysroot path is invalid UTF8"); + }; + + Ok(Self { + sysroot_src, + crates: Vec::new(), + }) } /// Write rust-project.json to disk @@ -66,39 +100,4 @@ impl RustAnalyzerProject { } Ok(()) } - - /// Use `rustc` to determine the default toolchain - pub fn get_sysroot_src(&mut self) -> Result<(), Box> { - // check if RUST_SRC_PATH is set - if let Ok(path) = env::var("RUST_SRC_PATH") { - self.sysroot_src = path; - return Ok(()); - } - - let toolchain = Command::new("rustc") - .arg("--print") - .arg("sysroot") - .output()? - .stdout; - - let toolchain = String::from_utf8(toolchain)?; - let toolchain = toolchain.trim_end(); - - println!("Determined toolchain: {toolchain}\n"); - - let Ok(path) = Path::new(toolchain) - .join("lib") - .join("rustlib") - .join("src") - .join("rust") - .join("library") - .into_os_string() - .into_string() - else { - return Err("The sysroot path is invalid UTF8".into()); - }; - self.sysroot_src = path; - - Ok(()) - } } -- cgit v1.2.3 From 87e55ccffde51b08be7d90ab53f1bb2462efa85a Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 25 Mar 2024 22:20:00 +0100 Subject: Use the parsed exercises instead of glob --- Cargo.toml | 1 - src/main.rs | 2 +- src/project.rs | 35 +++++++++++++---------------------- 3 files changed, 14 insertions(+), 24 deletions(-) (limited to 'src/main.rs') diff --git a/Cargo.toml b/Cargo.toml index d7b5a09..ef49947 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ edition = "2021" anyhow = "1.0.81" clap = { version = "4.5.2", features = ["derive"] } console = "0.15.8" -glob = "0.3.0" home = "0.5.9" indicatif = "0.17.8" notify-debouncer-mini = "0.4.1" diff --git a/src/main.rs b/src/main.rs index 4ce0b30..803e2f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -206,7 +206,7 @@ fn main() -> Result<()> { Subcommands::Lsp => { let mut project = RustAnalyzerProject::build()?; project - .exercises_to_json() + .exercises_to_json(exercises) .expect("Couldn't parse rustlings exercises files"); if project.crates.is_empty() { diff --git a/src/project.rs b/src/project.rs index 1f42d4e..534aab0 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,11 +1,12 @@ use anyhow::{bail, Context, Result}; -use glob::glob; use serde::{Deserialize, Serialize}; use std::env; use std::error::Error; use std::path::PathBuf; 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 #[derive(Serialize, Deserialize)] @@ -69,30 +70,20 @@ impl RustAnalyzerProject { Ok(()) } - /// If path contains .rs extension, add a crate to `rust-project.json` - fn path_to_json(&mut self, path: PathBuf) -> Result<(), Box> { - if let Some(ext) = path.extension() { - if ext == "rs" { - self.crates.push(Crate { - root_module: path.display().to_string(), - edition: "2021".to_string(), - deps: Vec::new(), - // This allows rust_analyzer to work inside #[test] blocks - cfg: vec!["test".to_string()], - }) - } - } - - Ok(()) - } - /// Parse the exercises folder for .rs files, any matches will create /// a new `crate` in rust-project.json which allows rust-analyzer to /// treat it like a normal binary - pub fn exercises_to_json(&mut self) -> Result<(), Box> { - for path in glob("./exercises/**/*")? { - self.path_to_json(path?)?; - } + pub fn exercises_to_json(&mut self, exercises: Vec) -> Result<(), Box> { + self.crates = exercises + .into_iter() + .map(|exercise| Crate { + root_module: exercise.path.display().to_string(), + edition: "2021".to_string(), + deps: Vec::new(), + // This allows rust_analyzer to work inside #[test] blocks + cfg: vec!["test".to_string()], + }) + .collect(); Ok(()) } } -- cgit v1.2.3 From f5135ae4df96ee018896d667f3dffa187c959193 Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 25 Mar 2024 22:29:33 +0100 Subject: Remove unneeded check if crates is empty --- src/main.rs | 4 +--- src/project.rs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 803e2f8..1f260ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -209,9 +209,7 @@ fn main() -> Result<()> { .exercises_to_json(exercises) .expect("Couldn't parse rustlings exercises files"); - if project.crates.is_empty() { - println!("Failed find any exercises, make sure you're in the `rustlings` folder"); - } else if project.write_to_disk().is_err() { + if project.write_to_disk().is_err() { println!("Failed to write rust-project.json to disk for rust-analyzer"); } else { println!("Successfully generated rust-project.json"); diff --git a/src/project.rs b/src/project.rs index 534aab0..835a951 100644 --- a/src/project.rs +++ b/src/project.rs @@ -12,7 +12,7 @@ use crate::exercise::Exercise; #[derive(Serialize, Deserialize)] pub struct RustAnalyzerProject { sysroot_src: String, - pub crates: Vec, + crates: Vec, } #[derive(Serialize, Deserialize)] -- cgit v1.2.3 From 8ddbf9635d21a4c0306bd31cca5c4077693ca917 Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 25 Mar 2024 23:01:56 +0100 Subject: Add write_project_json --- src/main.rs | 11 +++-------- src/project.rs | 59 +++++++++++++++++++++++++++------------------------------- 2 files changed, 30 insertions(+), 40 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 1f260ab..46aaf1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use crate::exercise::{Exercise, ExerciseList}; -use crate::project::RustAnalyzerProject; +use crate::project::write_project_json; use crate::run::{reset, run}; use crate::verify::verify; use anyhow::Result; @@ -204,13 +204,8 @@ fn main() -> Result<()> { } Subcommands::Lsp => { - let mut project = RustAnalyzerProject::build()?; - project - .exercises_to_json(exercises) - .expect("Couldn't parse rustlings exercises files"); - - if project.write_to_disk().is_err() { - println!("Failed to write rust-project.json to disk for rust-analyzer"); + if let Err(e) = write_project_json(exercises) { + println!("Failed to write rust-project.json to disk for rust-analyzer: {e}"); } else { println!("Successfully generated rust-project.json"); println!("rust-analyzer will now parse exercises, restart your language server or editor") diff --git a/src/project.rs b/src/project.rs index 54cffe1..acf011d 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,7 +1,6 @@ use anyhow::{Context, Result}; use serde::Serialize; use std::env; -use std::error::Error; use std::path::PathBuf; use std::process::{Command, Stdio}; @@ -10,7 +9,7 @@ use crate::exercise::Exercise; /// Contains the structure of resulting rust-project.json file /// and functions to build the data required to create the file #[derive(Serialize)] -pub struct RustAnalyzerProject { +struct RustAnalyzerProject { sysroot_src: PathBuf, crates: Vec, } @@ -25,12 +24,22 @@ struct Crate { } impl RustAnalyzerProject { - pub fn build() -> Result { - // check if RUST_SRC_PATH is set + fn build(exercises: Vec) -> Result { + let crates = exercises + .into_iter() + .map(|exercise| Crate { + root_module: exercise.path, + edition: "2021", + deps: Vec::new(), + // This allows rust_analyzer to work inside #[test] blocks + cfg: ["test"], + }) + .collect(); + if let Some(path) = env::var_os("RUST_SRC_PATH") { return Ok(Self { sysroot_src: PathBuf::from(path), - crates: Vec::new(), + crates, }); } @@ -53,35 +62,21 @@ impl RustAnalyzerProject { Ok(Self { sysroot_src, - crates: Vec::new(), + crates, }) } +} - /// Write rust-project.json to disk - pub fn write_to_disk(&self) -> Result<(), std::io::Error> { - // Using the capacity 2^14 = 16384 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. - let mut buf = Vec::with_capacity(16384); - serde_json::to_writer(&mut buf, &self).expect("Failed to serialize to JSON"); - std::fs::write("rust-project.json", buf)?; - Ok(()) - } +/// Write `rust-project.json` to disk. +pub fn write_project_json(exercises: Vec) -> Result<()> { + let content = RustAnalyzerProject::build(exercises)?; - /// Parse the exercises folder for .rs files, any matches will create - /// a new `crate` in rust-project.json which allows rust-analyzer to - /// treat it like a normal binary - pub fn exercises_to_json(&mut self, exercises: Vec) -> Result<(), Box> { - self.crates = exercises - .into_iter() - .map(|exercise| Crate { - root_module: exercise.path, - edition: "2021", - deps: Vec::new(), - // This allows rust_analyzer to work inside #[test] blocks - cfg: ["test"], - }) - .collect(); - Ok(()) - } + // 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. + let mut buf = Vec::with_capacity(1 << 14); + serde_json::to_writer(&mut buf, &content)?; + std::fs::write("rust-project.json", buf)?; + + Ok(()) } -- cgit v1.2.3 From ed0fcf8e3d05f5420b55370d4ff4ad8e0ded127b Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 26 Mar 2024 17:49:05 +0100 Subject: Formatting --- src/main.rs | 7 ++----- src/verify.rs | 32 +++++++++++++++----------------- 2 files changed, 17 insertions(+), 22 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index a06f0c5..a0b3af2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -223,10 +223,7 @@ fn main() { Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) { Err(e) => { - println!( - "Error: Could not watch your progress. Error message was {:?}.", - 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."); std::process::exit(1); } @@ -280,7 +277,7 @@ fn spawn_watch_shell( if parts.is_empty() { println!("no command provided"); } else if let Err(e) = Command::new(parts[0]).args(&parts[1..]).status() { - println!("failed to execute command `{}`: {}", cmd, e); + println!("failed to execute command `{cmd}`: {e}"); } } else { println!("unknown command: {input}"); diff --git a/src/verify.rs b/src/verify.rs index aee2afa..3123e45 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -24,7 +24,7 @@ pub fn verify<'a>( .progress_chars("#>-"), ); bar.set_position(num_done as u64); - bar.set_message(format!("({:.1} %)", percentage)); + bar.set_message(format!("({percentage:.1} %)")); for exercise in exercises { let compile_result = match exercise.mode { @@ -37,7 +37,7 @@ pub fn verify<'a>( } percentage += 100.0 / total as f32; bar.inc(1); - bar.set_message(format!("({:.1} %)", percentage)); + bar.set_message(format!("({percentage:.1} %)")); if bar.position() == total as u64 { println!( "Progress: You completed {} / {} exercises ({:.1} %).", @@ -191,27 +191,25 @@ fn prompt_for_completion( Mode::Test => "The code is compiling, and the tests pass!", Mode::Clippy => clippy_success_msg, }; - println!(); + if no_emoji { - println!("~*~ {success_msg} ~*~") + println!("\n~*~ {success_msg} ~*~\n"); } else { - println!("šŸŽ‰ šŸŽ‰ {success_msg} šŸŽ‰ šŸŽ‰") + println!("\nšŸŽ‰ šŸŽ‰ {success_msg} šŸŽ‰ šŸŽ‰\n"); } - println!(); if let Some(output) = prompt_output { - println!("Output:"); - println!("{}", separator()); - println!("{output}"); - println!("{}", separator()); - println!(); + println!( + "Output:\n{separator}\n{output}\n{separator}\n", + separator = separator(), + ); } if success_hints { - println!("Hints:"); - println!("{}", separator()); - println!("{}", exercise.hint); - println!("{}", separator()); - println!(); + println!( + "Hints:\n{separator}\n{}\n{separator}\n", + exercise.hint, + separator = separator(), + ); } println!("You can keep working on this exercise,"); @@ -231,7 +229,7 @@ fn prompt_for_completion( "{:>2} {} {}", style(context_line.number).blue().bold(), style("|").blue(), - formatted_line + formatted_line, ); } -- cgit v1.2.3 From 1f2029ae5503024f71203893fe1eab7b90aa80af Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 26 Mar 2024 17:49:25 +0100 Subject: Add missing semicolon --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index a0b3af2..6884a0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -217,7 +217,7 @@ fn main() { println!("Failed to write rust-project.json to disk for rust-analyzer"); } else { println!("Successfully generated rust-project.json"); - println!("rust-analyzer will now parse exercises, restart your language server or editor") + println!("rust-analyzer will now parse exercises, restart your language server or editor"); } } -- cgit v1.2.3 From e89028581cd03c02cb0971a2772fa382667019a3 Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 26 Mar 2024 17:49:55 +0100 Subject: Use == instead of eq --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 6884a0e..559be69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -289,7 +289,7 @@ fn spawn_watch_shell( } fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise { - if name.eq("next") { + if name == "next" { exercises .iter() .find(|e| !e.looks_done()) -- cgit v1.2.3 From a610fc1bc21a04017542208ef70a8010ee00c04c Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 26 Mar 2024 17:50:10 +0100 Subject: Remove unneeded closure --- src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 559be69..eca73fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -335,7 +335,6 @@ fn watch( clear_screen(); - let to_owned_hint = |t: &Exercise| t.hint.to_owned(); let failed_exercise_hint = match verify( exercises.iter(), (0, exercises.len()), @@ -343,7 +342,7 @@ fn watch( success_hints, ) { Ok(_) => return Ok(WatchStatus::Finished), - Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))), + Err(exercise) => Arc::new(Mutex::new(Some(exercise.hint.clone()))), }; spawn_watch_shell(&failed_exercise_hint, Arc::clone(&should_quit)); loop { @@ -380,7 +379,7 @@ fn watch( Err(exercise) => { let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap(); - *failed_exercise_hint = Some(to_owned_hint(exercise)); + *failed_exercise_hint = Some(exercise.hint.clone()); } } } -- cgit v1.2.3 From 87001a68c0cc6b3498a253d0923e9c609355c4ee Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 26 Mar 2024 17:50:29 +0100 Subject: The string doesn't have to be a raw string --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index eca73fa..141549c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -411,7 +411,7 @@ fn rustc_exists() -> bool { .unwrap_or(false) } -const DEFAULT_OUT: &str = r#"Thanks for installing Rustlings! +const DEFAULT_OUT: &str = "Thanks for installing 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 @@ -437,7 +437,7 @@ started, here's a couple of notes about how Rustlings operates: autocompletion, run the command `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!"#; +exercise. Make sure to have your editor open!"; const FENISH_LINE: &str = "+----------------------------------------------------+ | You made it to the Fe-nish line! | -- cgit v1.2.3