Handle the case when all exercises are done

This commit is contained in:
mo8it 2024-04-12 18:57:04 +02:00
parent a534de0312
commit d5a6dee1b3
4 changed files with 84 additions and 43 deletions

View file

@ -1,8 +1,16 @@
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use crossterm::{
style::Stylize,
terminal::{Clear, ClearType},
ExecutableCommand,
};
use serde::{Deserialize, Serialize}; 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"; const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises";
@ -143,7 +151,7 @@ impl AppState {
Ok(()) Ok(())
} }
fn next_exercise_ind(&self) -> Option<usize> { fn next_pending_exercise_ind(&self) -> Option<usize> {
let current_ind = self.state_file.current_exercise_ind; let current_ind = self.state_file.current_exercise_ind;
if current_ind == self.state_file.progress.len() - 1 { if current_ind == self.state_file.progress.len() - 1 {
@ -167,14 +175,41 @@ impl AppState {
} }
} }
pub fn done_current_exercise(&mut self) -> Result<ExercisesProgress> { pub fn done_current_exercise(&mut self, writer: &mut StdoutLock) -> Result<ExercisesProgress> {
let done = &mut self.state_file.progress[self.state_file.current_exercise_ind]; let done = &mut self.state_file.progress[self.state_file.current_exercise_ind];
if !*done { if !*done {
*done = true; *done = true;
self.n_done += 1; 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); return Ok(ExercisesProgress::AllDone);
}; };
@ -183,3 +218,10 @@ impl AppState {
Ok(ExercisesProgress::Pending) 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.
";

View file

@ -1,6 +1,6 @@
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use crossterm::style::Stylize; use crossterm::style::Stylize;
use std::io::{stdout, Write}; use std::io::{self, Write};
use crate::app_state::{AppState, ExercisesProgress}; 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 exercise = app_state.current_exercise();
let output = exercise.run()?; let output = exercise.run()?;
{ let mut stdout = io::stdout().lock();
let mut stdout = stdout().lock(); stdout.write_all(&output.stdout)?;
stdout.write_all(&output.stdout)?; stdout.write_all(b"\n")?;
stdout.write_all(&output.stderr)?; stdout.write_all(&output.stderr)?;
stdout.flush()?; stdout.flush()?;
}
if !output.status.success() { if !output.status.success() {
bail!("Ran {exercise} with errors"); bail!("Ran {exercise} with errors");
} }
println!( stdout.write_fmt(format_args!(
"{}{}", "{}{}",
"✓ Successfully ran ".green(), "✓ Successfully ran ".green(),
exercise.path.to_string_lossy().green(), exercise.path.to_string_lossy().green(),
); ))?;
match app_state.done_current_exercise()? { match app_state.done_current_exercise(&mut stdout)? {
ExercisesProgress::AllDone => println!( ExercisesProgress::AllDone => (),
"🎉 Congratulations! You have done all the exercises!
🔚 There are no more exercises to do next!"
),
ExercisesProgress::Pending => println!("Next exercise: {}", app_state.current_exercise()), ExercisesProgress::Pending => println!("Next exercise: {}", app_state.current_exercise()),
} }

View file

@ -15,7 +15,7 @@ mod debounce_event;
mod state; mod state;
mod terminal_event; mod terminal_event;
use crate::app_state::AppState; use crate::app_state::{AppState, ExercisesProgress};
use self::{ use self::{
debounce_event::DebounceEventHandler, debounce_event::DebounceEventHandler,
@ -32,6 +32,7 @@ enum WatchEvent {
} }
/// Returned by the watch mode to indicate what to do afterwards. /// Returned by the watch mode to indicate what to do afterwards.
#[must_use]
pub enum WatchExit { pub enum WatchExit {
/// Exit the program. /// Exit the program.
Shutdown, Shutdown,
@ -60,16 +61,20 @@ pub fn watch(app_state: &mut AppState) -> Result<WatchExit> {
while let Ok(event) = rx.recv() { while let Ok(event) = rx.recv() {
match event { match event {
WatchEvent::Input(InputEvent::Next) => { WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise()? {
watch_state.next_exercise()?; ExercisesProgress::AllDone => break,
} ExercisesProgress::Pending => watch_state.run_current_exercise()?,
},
WatchEvent::Input(InputEvent::Hint) => { WatchEvent::Input(InputEvent::Hint) => {
watch_state.show_hint()?; watch_state.show_hint()?;
} }
WatchEvent::Input(InputEvent::List) => { WatchEvent::Input(InputEvent::List) => {
return Ok(WatchExit::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)) => { WatchEvent::Input(InputEvent::Unrecognized(cmd)) => {
watch_state.handle_invalid_cmd(&cmd)?; watch_state.handle_invalid_cmd(&cmd)?;
} }
@ -88,8 +93,6 @@ pub fn watch(app_state: &mut AppState) -> Result<WatchExit> {
} }
} }
watch_state.into_writer().write_all(QUIT_MSG)?;
Ok(WatchExit::Shutdown) Ok(WatchExit::Shutdown)
} }

View file

@ -4,7 +4,10 @@ use crossterm::{
terminal::{size, Clear, ClearType}, terminal::{size, Clear, ClearType},
ExecutableCommand, ExecutableCommand,
}; };
use std::io::{self, StdoutLock, Write}; use std::{
io::{self, StdoutLock, Write},
process::Output,
};
use crate::{ use crate::{
app_state::{AppState, ExercisesProgress}, app_state::{AppState, ExercisesProgress},
@ -49,6 +52,9 @@ impl<'a> WatchState<'a> {
self.stderr = None; self.stderr = None;
self.show_done = true; self.show_done = true;
} else { } else {
self.app_state
.set_pending(self.app_state.current_exercise_ind())?;
self.stderr = Some(output.stderr); self.stderr = Some(output.stderr);
self.show_done = false; self.show_done = false;
} }
@ -61,18 +67,15 @@ impl<'a> WatchState<'a> {
self.run_current_exercise() self.run_current_exercise()
} }
pub fn next_exercise(&mut self) -> Result<()> { pub fn next_exercise(&mut self) -> Result<ExercisesProgress> {
if !self.show_done { if !self.show_done {
self.writer self.writer
.write_all(b"The current exercise isn't done yet\n")?; .write_all(b"The current exercise isn't done yet\n")?;
self.show_prompt()?; self.show_prompt()?;
return Ok(()); return Ok(ExercisesProgress::Pending);
} }
match self.app_state.done_current_exercise()? { self.app_state.done_current_exercise(&mut self.writer)
ExercisesProgress::AllDone => todo!(),
ExercisesProgress::Pending => self.run_current_exercise(),
}
} }
fn show_prompt(&mut self) -> io::Result<()> { fn show_prompt(&mut self) -> io::Result<()> {
@ -93,7 +96,7 @@ impl<'a> WatchState<'a> {
} }
pub fn render(&mut self) -> Result<()> { 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.write_all(b"\n")?;
self.writer.execute(Clear(ClearType::All))?; self.writer.execute(Clear(ClearType::All))?;
@ -111,11 +114,11 @@ impl<'a> WatchState<'a> {
self.writer.write_all(b"\n")?; self.writer.write_all(b"\n")?;
if self.show_hint { if self.show_hint {
self.writer self.writer.write_fmt(format_args!(
.write_fmt(format_args!("{}\n", "Hint".bold().cyan().underlined()))?; "{}\n{}\n\n",
self.writer "Hint".bold().cyan().underlined(),
.write_all(self.app_state.current_exercise().hint.as_bytes())?; self.app_state.current_exercise().hint,
self.writer.write_all(b"\n\n")?; ))?;
} }
if self.show_done { 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, self.app_state.exercises().len() as u16,
line_width, line_width,
)?; )?;
self.writer.write_all(progress_bar.as_bytes())?;
self.writer.write_all(b"Current exercise: ")?;
self.writer.write_fmt(format_args!( self.writer.write_fmt(format_args!(
"{}\n", "{progress_bar}Current exercise: {}\n",
self.app_state self.app_state
.current_exercise() .current_exercise()
.path .path