summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormo8it <mo8it@proton.me>2024-04-04 21:06:11 +0200
committermo8it <mo8it@proton.me>2024-04-04 21:06:11 +0200
commit34375b2ebfbdb0b6504a56c82635c8c9d3d6ce59 (patch)
treecad0bc7aa09a386adcd2e3521960eba632e4b75a /src
parent9ea744a7104f441ef505db0a96e852f93d8c0bf4 (diff)
Clean up as a preparation for the TUI
Diffstat (limited to 'src')
-rw-r--r--src/main.rs44
-rw-r--r--src/run.rs38
-rw-r--r--src/verify.rs227
3 files changed, 65 insertions, 244 deletions
diff --git a/src/main.rs b/src/main.rs
index c8c6584..20ec290 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,10 +7,9 @@ use clap::{Parser, Subcommand};
use console::Emoji;
use notify_debouncer_mini::notify::RecursiveMode;
use notify_debouncer_mini::{new_debouncer, DebouncedEventKind};
-use shlex::Shlex;
use std::io::{BufRead, Write};
use std::path::Path;
-use std::process::{exit, Command};
+use std::process::exit;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, RecvTimeoutError};
use std::sync::{Arc, Mutex};
@@ -31,9 +30,6 @@ mod verify;
#[derive(Parser)]
#[command(version)]
struct Args {
- /// Show outputs from the test exercises
- #[arg(long)]
- nocapture: bool,
#[command(subcommand)]
command: Option<Subcommands>,
}
@@ -45,11 +41,7 @@ enum Subcommands {
/// Verify all exercises according to the recommended order
Verify,
/// Rerun `verify` when files were edited
- Watch {
- /// Show hints on success
- #[arg(long)]
- success_hints: bool,
- },
+ Watch,
/// Run/Test a single exercise
Run {
/// The name of the exercise
@@ -117,7 +109,6 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini
exit(1);
}
- let verbose = args.nocapture;
let command = args.command.unwrap_or_else(|| {
println!("{DEFAULT_OUT}\n");
exit(0);
@@ -203,7 +194,7 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini
Subcommands::Run { name } => {
let exercise = find_exercise(&name, &exercises)?;
- run(exercise, verbose).unwrap_or_else(|_| exit(1));
+ run(exercise).unwrap_or_else(|_| exit(1));
}
Subcommands::Reset { name } => {
@@ -219,12 +210,12 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini
println!("{}", exercise.hint);
}
- Subcommands::Verify => match verify(&exercises, (0, exercises.len()), verbose, false)? {
+ Subcommands::Verify => match verify(&exercises, (0, exercises.len()))? {
VerifyState::AllExercisesDone => println!("All exercises done!"),
VerifyState::Failed(exercise) => bail!("Exercise {exercise} failed"),
},
- Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) {
+ Subcommands::Watch => match watch(&exercises) {
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.");
@@ -277,17 +268,6 @@ fn spawn_watch_shell(
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}");
}
@@ -319,7 +299,7 @@ enum WatchStatus {
Unfinished,
}
-fn watch(exercises: &[Exercise], verbose: bool, success_hints: bool) -> Result<WatchStatus> {
+fn watch(exercises: &[Exercise]) -> Result<WatchStatus> {
/* Clears the terminal with an ANSI escape code.
Works in UNIX and newer Windows terminals. */
fn clear_screen() {
@@ -336,11 +316,10 @@ fn watch(exercises: &[Exercise], verbose: bool, success_hints: bool) -> Result<W
clear_screen();
- let failed_exercise_hint =
- match verify(exercises, (0, exercises.len()), verbose, success_hints)? {
- VerifyState::AllExercisesDone => return Ok(WatchStatus::Finished),
- VerifyState::Failed(exercise) => Arc::new(Mutex::new(Some(exercise.hint.clone()))),
- };
+ let failed_exercise_hint = match verify(exercises, (0, exercises.len()))? {
+ VerifyState::AllExercisesDone => return Ok(WatchStatus::Finished),
+ VerifyState::Failed(exercise) => Arc::new(Mutex::new(Some(exercise.hint.clone()))),
+ };
spawn_watch_shell(Arc::clone(&failed_exercise_hint), Arc::clone(&should_quit));
@@ -364,8 +343,6 @@ fn watch(exercises: &[Exercise], verbose: bool, success_hints: bool) -> Result<W
match verify(
pending_exercises.iter().copied(),
(num_done, exercises.len()),
- verbose,
- success_hints,
)? {
VerifyState::AllExercisesDone => return Ok(WatchStatus::Finished),
VerifyState::Failed(exercise) => {
@@ -429,7 +406,6 @@ 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
Watch mode automatically re-evaluates the current exercise
diff --git a/src/run.rs b/src/run.rs
index 3f93f14..0a09ecc 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -1,39 +1,27 @@
-use anyhow::{bail, Result};
+use anyhow::Result;
use std::io::{stdout, Write};
-use std::time::Duration;
-use crate::exercise::{Exercise, Mode};
-use crate::verify::test;
-use indicatif::ProgressBar;
+use crate::exercise::Exercise;
// 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)
-pub fn run(exercise: &Exercise, verbose: bool) -> Result<()> {
- match exercise.mode {
- Mode::Test => test(exercise, verbose),
- Mode::Compile | Mode::Clippy => compile_and_run(exercise),
- }
-}
-
-// Compile and run an exercise.
-// This is strictly for non-test binaries, so output is displayed
-fn compile_and_run(exercise: &Exercise) -> Result<()> {
- let progress_bar = ProgressBar::new_spinner();
- progress_bar.set_message(format!("Running {exercise}..."));
- progress_bar.enable_steady_tick(Duration::from_millis(100));
-
+pub fn run(exercise: &Exercise) -> Result<()> {
let output = exercise.run()?;
- progress_bar.finish_and_clear();
- stdout().write_all(&output.stdout)?;
- if !output.status.success() {
- stdout().write_all(&output.stderr)?;
+ {
+ let mut stdout = stdout().lock();
+ stdout.write_all(&output.stdout)?;
+ stdout.write_all(&output.stderr)?;
+ stdout.flush()?;
+ }
+
+ if output.status.success() {
+ success!("Successfully ran {}", exercise);
+ } else {
warn!("Ran {} with errors", exercise);
- bail!("TODO");
}
- success!("Successfully ran {}", exercise);
Ok(())
}
diff --git a/src/verify.rs b/src/verify.rs
index ef966f6..5b05394 100644
--- a/src/verify.rs
+++ b/src/verify.rs
@@ -1,12 +1,6 @@
-use anyhow::{bail, Result};
+use anyhow::Result;
use console::style;
-use indicatif::{ProgressBar, ProgressStyle};
-use std::{
- env,
- io::{stdout, Write},
- process::Output,
- time::Duration,
-};
+use std::io::{stdout, Write};
use crate::exercise::{Exercise, Mode, State};
@@ -23,201 +17,64 @@ pub enum VerifyState<'a> {
pub fn verify<'a>(
pending_exercises: impl IntoIterator<Item = &'a Exercise>,
progress: (usize, usize),
- verbose: bool,
- success_hints: bool,
) -> Result<VerifyState<'a>> {
- let (num_done, total) = progress;
- let bar = ProgressBar::new(total as u64);
- 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!")
- .progress_chars("#>-"),
+ let (mut num_done, total) = progress;
+ println!(
+ "Progress: {num_done}/{total} ({:.1}%)\n",
+ num_done as f32 / total as f32 * 100.0,
);
- bar.set_position(num_done as u64);
- bar.set_message(format!("({percentage:.1} %)"));
for exercise in pending_exercises {
- let compile_result = match exercise.mode {
- Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose, success_hints)?,
- Mode::Compile => compile_and_run_interactively(exercise, success_hints)?,
- Mode::Clippy => compile_only(exercise, success_hints)?,
- };
- if !compile_result {
- return Ok(VerifyState::Failed(exercise));
- }
- percentage += 100.0 / total as f32;
- bar.inc(1);
- bar.set_message(format!("({percentage:.1} %)"));
- }
-
- bar.finish();
- println!("You completed all exercises!");
-
- Ok(VerifyState::AllExercisesDone)
-}
-
-#[derive(PartialEq, Eq)]
-enum RunMode {
- Interactive,
- NonInteractive,
-}
-
-// Compile and run the resulting test harness of the given 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
-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.enable_steady_tick(Duration::from_millis(100));
-
- let _ = exercise.run()?;
- progress_bar.finish_and_clear();
+ let output = exercise.run()?;
- prompt_for_completion(exercise, None, success_hints)
-}
-
-// Compile the given Exercise and run the resulting binary in an interactive mode
-fn compile_and_run_interactively(exercise: &Exercise, success_hints: bool) -> Result<bool> {
- let progress_bar = ProgressBar::new_spinner();
- progress_bar.set_message(format!("Running {exercise}..."));
- progress_bar.enable_steady_tick(Duration::from_millis(100));
-
- let output = exercise.run()?;
- progress_bar.finish_and_clear();
-
- if !output.status.success() {
- warn!("Ran {} with errors", exercise);
{
let mut stdout = stdout().lock();
stdout.write_all(&output.stdout)?;
stdout.write_all(&output.stderr)?;
stdout.flush()?;
}
- bail!("TODO");
- }
-
- prompt_for_completion(exercise, Some(output), success_hints)
-}
-// Compile the given Exercise as a test harness and display
-// the output if verbose is set to true
-fn compile_and_test(
- exercise: &Exercise,
- run_mode: RunMode,
- verbose: bool,
- success_hints: bool,
-) -> Result<bool> {
- let progress_bar = ProgressBar::new_spinner();
- progress_bar.set_message(format!("Testing {exercise}..."));
- progress_bar.enable_steady_tick(Duration::from_millis(100));
-
- let output = exercise.run()?;
- progress_bar.finish_and_clear();
-
- if !output.status.success() {
- warn!(
- "Testing of {} failed! Please try again. Here's the output:",
- exercise
- );
- {
- let mut stdout = stdout().lock();
- stdout.write_all(&output.stdout)?;
- stdout.write_all(&output.stderr)?;
- stdout.flush()?;
+ if !output.status.success() {
+ return Ok(VerifyState::Failed(exercise));
}
- bail!("TODO");
- }
-
- if verbose {
- stdout().write_all(&output.stdout)?;
- }
-
- if run_mode == RunMode::Interactive {
- prompt_for_completion(exercise, None, success_hints)
- } else {
- Ok(true)
- }
-}
-
-fn prompt_for_completion(
- exercise: &Exercise,
- prompt_output: Option<Output>,
- success_hints: bool,
-) -> Result<bool> {
- let context = match exercise.state()? {
- State::Done => return Ok(true),
- 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),
- }
-
- let no_emoji = env::var("NO_EMOJI").is_ok();
- let clippy_success_msg = if no_emoji {
- "The code is compiling, and Clippy is happy!"
- } else {
- "The code is compiling, and šŸ“Ž Clippy šŸ“Ž is happy!"
- };
-
- let success_msg = match exercise.mode {
- Mode::Compile => "The code is compiling!",
- Mode::Test => "The code is compiling, and the tests pass!",
- Mode::Clippy => clippy_success_msg,
- };
-
- if no_emoji {
- println!("\n~*~ {success_msg} ~*~\n");
- } else {
- println!("\nšŸŽ‰ šŸŽ‰ {success_msg} šŸŽ‰ šŸŽ‰\n");
- }
-
- if let Some(output) = prompt_output {
- let separator = separator();
- println!("Output:\n{separator}");
- stdout().write_all(&output.stdout).unwrap();
- println!("\n{separator}\n");
- }
- if success_hints {
- println!(
- "Hints:\n{separator}\n{}\n{separator}\n",
- exercise.hint,
- separator = separator(),
- );
- }
+ println!();
+ match exercise.mode {
+ Mode::Compile => success!("Successfully ran {}!", exercise),
+ Mode::Test => success!("Successfully tested {}!", exercise),
+ Mode::Clippy => success!("Successfully checked {}!", exercise),
+ }
- println!("You can keep working on this exercise,");
- println!(
- "or jump into the next one by removing the {} comment:",
- style("`I AM NOT DONE`").bold()
- );
- println!();
- for context_line in context {
- let formatted_line = if context_line.important {
- format!("{}", style(context_line.line).bold())
- } else {
- context_line.line
- };
+ if let State::Pending(context) = exercise.state()? {
+ println!(
+ "\nYou can keep working on this exercise,
+or jump into the next one by removing the {} comment:\n",
+ style("`I AM NOT DONE`").bold()
+ );
+
+ for context_line in context {
+ let formatted_line = if context_line.important {
+ format!("{}", style(context_line.line).bold())
+ } else {
+ context_line.line
+ };
+
+ println!(
+ "{:>2} {} {}",
+ style(context_line.number).blue().bold(),
+ style("|").blue(),
+ formatted_line,
+ );
+ }
+ return Ok(VerifyState::Failed(exercise));
+ }
+ num_done += 1;
println!(
- "{:>2} {} {}",
- style(context_line.number).blue().bold(),
- style("|").blue(),
- formatted_line,
+ "Progress: {num_done}/{total} ({:.1}%)\n",
+ num_done as f32 / total as f32 * 100.0,
);
}
- Ok(false)
-}
-
-fn separator() -> console::StyledObject<&'static str> {
- style("====================").bold()
+ Ok(VerifyState::AllExercisesDone)
}