diff options
| author | apogeeoak <59737221+apogeeoak@users.noreply.github.com> | 2022-02-04 19:27:42 -0500 |
|---|---|---|
| committer | apogeeoak <59737221+apogeeoak@users.noreply.github.com> | 2022-02-04 19:27:42 -0500 |
| commit | c1f35e46dffc63e1c8660b8e1fc91e9a9fca3e39 (patch) | |
| tree | 6d25f2fcd6e0de9d09f588ae33f7017c34cd31c9 /src | |
| parent | f78c48020830d7900dd8d81f355606581670446d (diff) | |
| parent | cd2b5e8e3b616e769d2c17df45f813772aa81530 (diff) | |
Merge branch 'main' into text
Diffstat (limited to 'src')
| -rw-r--r-- | src/exercise.rs | 9 | ||||
| -rw-r--r-- | src/main.rs | 97 | ||||
| -rw-r--r-- | src/verify.rs | 16 |
3 files changed, 73 insertions, 49 deletions
diff --git a/src/exercise.rs b/src/exercise.rs index d934cfd..6e49a9a 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -133,7 +133,7 @@ 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 clipy exercises, build + // 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. @@ -154,7 +154,7 @@ path = "{}.rs""#, Command::new("cargo") .args(&["clippy", "--manifest-path", CLIPPY_CARGO_TOML_PATH]) .args(RUSTC_COLOR_ARGS) - .args(&["--", "-D", "warnings"]) + .args(&["--", "-D", "warnings","-D","clippy::float_cmp"]) .output() } } @@ -162,7 +162,7 @@ path = "{}.rs""#, if cmd.status.success() { Ok(CompiledExercise { - exercise: &self, + exercise: self, _handle: FileHandle, }) } else { @@ -217,8 +217,7 @@ path = "{}.rs""#, let matched_line_index = source .lines() .enumerate() - .filter_map(|(i, line)| if re.is_match(line) { Some(i) } else { None }) - .next() + .find_map(|(i, line)| if re.is_match(line) { Some(i) } else { None }) .expect("This should not happen at all"); let min_line = ((matched_line_index as i32) - (CONTEXT as i32)).max(0) as usize; diff --git a/src/main.rs b/src/main.rs index 64161e5..453b8c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,8 @@ use std::fs; use std::io::{self, prelude::*}; use std::path::Path; use std::process::{Command, Stdio}; -use std::sync::mpsc::channel; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::{channel, RecvTimeoutError}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; @@ -23,7 +24,7 @@ mod run; mod verify; // In sync with crate version -const VERSION: &str = "4.4.0"; +const VERSION: &str = "4.6.0"; #[derive(FromArgs, PartialEq, Debug)] /// Rustlings is a collection of small exercises to get you used to writing and reading Rust code @@ -154,9 +155,7 @@ fn main() { "Pending" }; let solve_cond = { - (e.looks_done() && subargs.solved) - || (!e.looks_done() && subargs.unsolved) - || (!subargs.solved && !subargs.unsolved) + (e.looks_done() && subargs.solved) || (!e.looks_done() && subargs.unsolved) || (!subargs.solved && !subargs.unsolved) }; if solve_cond && (filter_cond || subargs.filter.is_none()) { let line = if subargs.paths { @@ -194,7 +193,7 @@ fn main() { Subcommands::Run(subargs) => { let exercise = find_exercise(&subargs.name, &exercises); - run(&exercise, verbose).unwrap_or_else(|_| std::process::exit(1)); + run(exercise, verbose).unwrap_or_else(|_| std::process::exit(1)); } Subcommands::Hint(subargs) => { @@ -207,38 +206,50 @@ fn main() { verify(&exercises, verbose).unwrap_or_else(|_| std::process::exit(1)); } - Subcommands::Watch(_subargs) => { - if let Err(e) = watch(&exercises, verbose) { - println!( - "Error: Could not watch your progress. Error message was {:?}.", - e - ); + Subcommands::Watch(_subargs) => match watch(&exercises, verbose) { + 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."); std::process::exit(1); } - println!( - "{emoji} All exercises completed! {emoji}", - emoji = Emoji("🎉", "★") - ); - println!("\n{}\n", FENISH_LINE); - } + Ok(WatchStatus::Finished) => { + println!("{emoji} All exercises completed! {emoji}", emoji = Emoji("🎉", "★")); + println!("\n{}\n", FENISH_LINE); + } + 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"); + } + }, } } -fn spawn_watch_shell(failed_exercise_hint: &Arc<Mutex<Option<String>>>) { +fn spawn_watch_shell(failed_exercise_hint: &Arc<Mutex<Option<String>>>, should_quit: Arc<AtomicBool>) { let failed_exercise_hint = Arc::clone(failed_exercise_hint); - println!("Type 'hint' or open the corresponding README.md file to get help or type 'clear' to clear the screen."); + println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here."); thread::spawn(move || loop { let mut input = String::new(); match io::stdin().read_line(&mut input) { Ok(_) => { let input = input.trim(); - if input.eq("hint") { + if input == "hint" { if let Some(hint) = &*failed_exercise_hint.lock().unwrap() { println!("{}", hint); } - } else if input.eq("clear") { + } 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!(" help - displays this help message"); + println!(); + println!("Watch mode automatically re-evaluates the current exercise"); + println!("when you edit a file's contents.") } else { println!("unknown command: {}", input); } @@ -249,16 +260,26 @@ fn spawn_watch_shell(failed_exercise_hint: &Arc<Mutex<Option<String>>>) { } fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise { - exercises - .iter() - .find(|e| e.name == name) - .unwrap_or_else(|| { + if name.eq("next") { + exercises.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!"); + std::process::exit(1) + }) + } else { + exercises.iter().find(|e| e.name == name).unwrap_or_else(|| { println!("No exercise found for '{}'!", name); std::process::exit(1) }) + } +} + +enum WatchStatus { + Finished, + Unfinished, } -fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<()> { +fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<WatchStatus> { /* Clears the terminal with an ANSI escape code. Works in UNIX and newer Windows terminals. */ fn clear_screen() { @@ -266,6 +287,7 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<()> { } let (tx, rx) = channel(); + let should_quit = Arc::new(AtomicBool::new(false)); let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?; watcher.watch(Path::new("./exercises"), RecursiveMode::Recursive)?; @@ -274,12 +296,12 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<()> { let to_owned_hint = |t: &Exercise| t.hint.to_owned(); let failed_exercise_hint = match verify(exercises.iter(), verbose) { - Ok(_) => return Ok(()), + Ok(_) => return Ok(WatchStatus::Finished), Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))), }; - spawn_watch_shell(&failed_exercise_hint); + spawn_watch_shell(&failed_exercise_hint, Arc::clone(&should_quit)); loop { - match rx.recv() { + match rx.recv_timeout(Duration::from_secs(1)) { Ok(event) => match event { DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => { if b.extension() == Some(OsStr::new("rs")) && b.exists() { @@ -288,14 +310,10 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<()> { .iter() .skip_while(|e| !filepath.ends_with(&e.path)) // .filter(|e| filepath.ends_with(&e.path)) - .chain( - exercises - .iter() - .filter(|e| !e.looks_done() && !filepath.ends_with(&e.path)), - ); + .chain(exercises.iter().filter(|e| !e.looks_done() && !filepath.ends_with(&e.path))); clear_screen(); match verify(pending_exercises, verbose) { - Ok(_) => return Ok(()), + Ok(_) => return Ok(WatchStatus::Finished), Err(exercise) => { let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap(); *failed_exercise_hint = Some(to_owned_hint(exercise)); @@ -305,8 +323,15 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<()> { } _ => {} }, + Err(RecvTimeoutError::Timeout) => { + // the timeout expired, just check the `should_quit` variable below then loop again + } Err(e) => println!("watch error: {:?}", e), } + // Check if we need to exit + if should_quit.load(Ordering::SeqCst) { + return Ok(WatchStatus::Unfinished); + } } } diff --git a/src/verify.rs b/src/verify.rs index b98598a..fd59fa5 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -14,9 +14,9 @@ pub fn verify<'a>( ) -> Result<(), &'a Exercise> { for exercise in start_at { let compile_result = match exercise.mode { - Mode::Test => compile_and_test(&exercise, RunMode::Interactive, verbose), - Mode::Compile => compile_and_run_interactively(&exercise), - Mode::Clippy => compile_only(&exercise), + Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose), + Mode::Compile => compile_and_run_interactively(exercise), + Mode::Clippy => compile_only(exercise), }; if !compile_result.unwrap_or(false) { return Err(exercise); @@ -42,11 +42,11 @@ fn compile_only(exercise: &Exercise) -> Result<bool, ()> { progress_bar.set_message(format!("Compiling {}...", exercise).as_str()); progress_bar.enable_steady_tick(100); - let _ = compile(&exercise, &progress_bar)?; + let _ = compile(exercise, &progress_bar)?; progress_bar.finish_and_clear(); success!("Successfully compiled {}!", exercise); - Ok(prompt_for_completion(&exercise, None)) + Ok(prompt_for_completion(exercise, None)) } // Compile the given Exercise and run the resulting binary in an interactive mode @@ -55,7 +55,7 @@ fn compile_and_run_interactively(exercise: &Exercise) -> Result<bool, ()> { progress_bar.set_message(format!("Compiling {}...", exercise).as_str()); progress_bar.enable_steady_tick(100); - let compilation = compile(&exercise, &progress_bar)?; + let compilation = compile(exercise, &progress_bar)?; progress_bar.set_message(format!("Running {}...", exercise).as_str()); let result = compilation.run(); @@ -73,7 +73,7 @@ fn compile_and_run_interactively(exercise: &Exercise) -> Result<bool, ()> { success!("Successfully ran {}!", exercise); - Ok(prompt_for_completion(&exercise, Some(output.stdout))) + Ok(prompt_for_completion(exercise, Some(output.stdout))) } // Compile the given Exercise as a test harness and display @@ -94,7 +94,7 @@ fn compile_and_test(exercise: &Exercise, run_mode: RunMode, verbose: bool) -> Re } success!("Successfully tested {}", &exercise); if let RunMode::Interactive = run_mode { - Ok(prompt_for_completion(&exercise, None)) + Ok(prompt_for_completion(exercise, None)) } else { Ok(true) } |
