mirror of
https://github.com/rust-lang/rustlings
synced 2024-11-15 08:57:09 +00:00
Handle the case when all exercises are done
This commit is contained in:
parent
a534de0312
commit
d5a6dee1b3
4 changed files with 84 additions and 43 deletions
|
@ -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.
|
||||||
|
|
||||||
|
";
|
||||||
|
|
24
src/run.rs
24
src/run.rs
|
@ -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()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
src/watch.rs
17
src/watch.rs
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue