From d5a6dee1b329f68d00bee61c6b6c7a0adbf8bab5 Mon Sep 17 00:00:00 2001 From: mo8it Date: Fri, 12 Apr 2024 18:57:04 +0200 Subject: [PATCH] Handle the case when all exercises are done --- src/app_state.rs | 52 +++++++++++++++++++++++++++++++++++++++++----- src/run.rs | 24 +++++++++------------ src/watch.rs | 17 ++++++++------- src/watch/state.rs | 34 +++++++++++++++--------------- 4 files changed, 84 insertions(+), 43 deletions(-) diff --git a/src/app_state.rs b/src/app_state.rs index 4a0912e4..b1440e8a 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -1,8 +1,16 @@ use anyhow::{bail, Context, Result}; +use crossterm::{ + style::Stylize, + terminal::{Clear, ClearType}, + ExecutableCommand, +}; use serde::{Deserialize, Serialize}; -use std::fs; +use std::{ + fs, + io::{StdoutLock, Write}, +}; -use crate::exercise::Exercise; +use crate::{exercise::Exercise, FENISH_LINE}; const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises"; @@ -143,7 +151,7 @@ impl AppState { Ok(()) } - fn next_exercise_ind(&self) -> Option { + fn next_pending_exercise_ind(&self) -> Option { let current_ind = self.state_file.current_exercise_ind; if current_ind == self.state_file.progress.len() - 1 { @@ -167,14 +175,41 @@ impl AppState { } } - pub fn done_current_exercise(&mut self) -> Result { + pub fn done_current_exercise(&mut self, writer: &mut StdoutLock) -> Result { let done = &mut self.state_file.progress[self.state_file.current_exercise_ind]; if !*done { *done = true; self.n_done += 1; } - let Some(ind) = self.next_exercise_ind() else { + let Some(ind) = self.next_pending_exercise_ind() else { + writer.write_all(RERUNNING_ALL_EXERCISES_MSG)?; + + for (exercise_ind, exercise) in self.exercises().iter().enumerate() { + writer.write_fmt(format_args!("Running {exercise} ... "))?; + writer.flush()?; + + if !exercise.run()?.status.success() { + self.state_file.current_exercise_ind = exercise_ind; + self.current_exercise = exercise; + + // No check if the exercise is done before setting it to pending + // because no pending exercise was found. + self.state_file.progress[exercise_ind] = false; + self.n_done -= 1; + + self.state_file.write()?; + + return Ok(ExercisesProgress::Pending); + } + + writer.write_fmt(format_args!("{}\n", "ok".green()))?; + } + + writer.execute(Clear(ClearType::All))?; + writer.write_all(FENISH_LINE.as_bytes())?; + // TODO: Show final message. + return Ok(ExercisesProgress::AllDone); }; @@ -183,3 +218,10 @@ impl AppState { Ok(ExercisesProgress::Pending) } } + +const RERUNNING_ALL_EXERCISES_MSG: &[u8] = b" +All exercises seem to be done. +Recompiling and running all exercises to make sure that all of them are actually done. +This might take some minutes. + +"; diff --git a/src/run.rs b/src/run.rs index 18da193b..ea790e9a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Result}; use crossterm::style::Stylize; -use std::io::{stdout, Write}; +use std::io::{self, Write}; use crate::app_state::{AppState, ExercisesProgress}; @@ -8,28 +8,24 @@ pub fn run(app_state: &mut AppState) -> Result<()> { let exercise = app_state.current_exercise(); let output = exercise.run()?; - { - let mut stdout = stdout().lock(); - stdout.write_all(&output.stdout)?; - stdout.write_all(&output.stderr)?; - stdout.flush()?; - } + let mut stdout = io::stdout().lock(); + stdout.write_all(&output.stdout)?; + stdout.write_all(b"\n")?; + stdout.write_all(&output.stderr)?; + stdout.flush()?; if !output.status.success() { bail!("Ran {exercise} with errors"); } - println!( + stdout.write_fmt(format_args!( "{}{}", "✓ Successfully ran ".green(), exercise.path.to_string_lossy().green(), - ); + ))?; - match app_state.done_current_exercise()? { - ExercisesProgress::AllDone => println!( - "🎉 Congratulations! You have done all the exercises! -🔚 There are no more exercises to do next!" - ), + match app_state.done_current_exercise(&mut stdout)? { + ExercisesProgress::AllDone => (), ExercisesProgress::Pending => println!("Next exercise: {}", app_state.current_exercise()), } diff --git a/src/watch.rs b/src/watch.rs index 357b5c71..beb69b3d 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -15,7 +15,7 @@ mod debounce_event; mod state; mod terminal_event; -use crate::app_state::AppState; +use crate::app_state::{AppState, ExercisesProgress}; use self::{ debounce_event::DebounceEventHandler, @@ -32,6 +32,7 @@ enum WatchEvent { } /// Returned by the watch mode to indicate what to do afterwards. +#[must_use] pub enum WatchExit { /// Exit the program. Shutdown, @@ -60,16 +61,20 @@ pub fn watch(app_state: &mut AppState) -> Result { while let Ok(event) = rx.recv() { match event { - WatchEvent::Input(InputEvent::Next) => { - watch_state.next_exercise()?; - } + WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise()? { + ExercisesProgress::AllDone => break, + ExercisesProgress::Pending => watch_state.run_current_exercise()?, + }, WatchEvent::Input(InputEvent::Hint) => { watch_state.show_hint()?; } WatchEvent::Input(InputEvent::List) => { return Ok(WatchExit::List); } - WatchEvent::Input(InputEvent::Quit) => break, + WatchEvent::Input(InputEvent::Quit) => { + watch_state.into_writer().write_all(QUIT_MSG)?; + break; + } WatchEvent::Input(InputEvent::Unrecognized(cmd)) => { watch_state.handle_invalid_cmd(&cmd)?; } @@ -88,8 +93,6 @@ pub fn watch(app_state: &mut AppState) -> Result { } } - watch_state.into_writer().write_all(QUIT_MSG)?; - Ok(WatchExit::Shutdown) } diff --git a/src/watch/state.rs b/src/watch/state.rs index 462633d1..70b6ae48 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -4,7 +4,10 @@ use crossterm::{ terminal::{size, Clear, ClearType}, ExecutableCommand, }; -use std::io::{self, StdoutLock, Write}; +use std::{ + io::{self, StdoutLock, Write}, + process::Output, +}; use crate::{ app_state::{AppState, ExercisesProgress}, @@ -49,6 +52,9 @@ impl<'a> WatchState<'a> { self.stderr = None; self.show_done = true; } else { + self.app_state + .set_pending(self.app_state.current_exercise_ind())?; + self.stderr = Some(output.stderr); self.show_done = false; } @@ -61,18 +67,15 @@ impl<'a> WatchState<'a> { self.run_current_exercise() } - pub fn next_exercise(&mut self) -> Result<()> { + pub fn next_exercise(&mut self) -> Result { if !self.show_done { self.writer .write_all(b"The current exercise isn't done yet\n")?; self.show_prompt()?; - return Ok(()); + return Ok(ExercisesProgress::Pending); } - match self.app_state.done_current_exercise()? { - ExercisesProgress::AllDone => todo!(), - ExercisesProgress::Pending => self.run_current_exercise(), - } + self.app_state.done_current_exercise(&mut self.writer) } fn show_prompt(&mut self) -> io::Result<()> { @@ -93,7 +96,7 @@ impl<'a> WatchState<'a> { } pub fn render(&mut self) -> Result<()> { - // Prevent having the first line shifted after clearing because of the prompt. + // Prevent having the first line shifted. self.writer.write_all(b"\n")?; self.writer.execute(Clear(ClearType::All))?; @@ -111,11 +114,11 @@ impl<'a> WatchState<'a> { self.writer.write_all(b"\n")?; if self.show_hint { - self.writer - .write_fmt(format_args!("{}\n", "Hint".bold().cyan().underlined()))?; - self.writer - .write_all(self.app_state.current_exercise().hint.as_bytes())?; - self.writer.write_all(b"\n\n")?; + self.writer.write_fmt(format_args!( + "{}\n{}\n\n", + "Hint".bold().cyan().underlined(), + self.app_state.current_exercise().hint, + ))?; } if self.show_done { @@ -134,11 +137,8 @@ When you are done experimenting, enter `n` or `next` to go to the next exercise self.app_state.exercises().len() as u16, line_width, )?; - self.writer.write_all(progress_bar.as_bytes())?; - - self.writer.write_all(b"Current exercise: ")?; self.writer.write_fmt(format_args!( - "{}\n", + "{progress_bar}Current exercise: {}\n", self.app_state .current_exercise() .path