Implement resetting

This commit is contained in:
mo8it 2024-04-07 22:43:59 +02:00
parent db43efe3ec
commit 99c9ab467b
4 changed files with 53 additions and 33 deletions

View file

@ -10,7 +10,7 @@ use winnow::ascii::{space0, Caseless};
use winnow::combinator::opt; use winnow::combinator::opt;
use winnow::Parser; use winnow::Parser;
use crate::embedded::EMBEDDED_FILES; use crate::embedded::{WriteStrategy, EMBEDDED_FILES};
// The number of context lines above and below a highlighted line. // The number of context lines above and below a highlighted line.
const CONTEXT: usize = 2; const CONTEXT: usize = 2;
@ -220,6 +220,12 @@ impl Exercise {
pub fn looks_done(&self) -> Result<bool> { pub fn looks_done(&self) -> Result<bool> {
self.state().map(|state| state == State::Done) self.state().map(|state| state == State::Done)
} }
pub fn reset(&self) -> Result<()> {
EMBEDDED_FILES
.write_exercise_to_disk(&self.path, WriteStrategy::Overwrite)
.with_context(|| format!("Failed to reset the exercise {self}"))
}
} }
impl Display for Exercise { impl Display for Exercise {

View file

@ -48,6 +48,12 @@ pub fn list(state_file: &mut StateFile, exercises: &[Exercise]) -> Result<()> {
KeyCode::Up | KeyCode::Char('k') => ui_state.select_previous(), KeyCode::Up | KeyCode::Char('k') => ui_state.select_previous(),
KeyCode::Home | KeyCode::Char('g') => ui_state.select_first(), KeyCode::Home | KeyCode::Char('g') => ui_state.select_first(),
KeyCode::End | KeyCode::Char('G') => ui_state.select_last(), KeyCode::End | KeyCode::Char('G') => ui_state.select_last(),
KeyCode::Char('r') => {
let selected = ui_state.selected();
exercises[selected].reset()?;
state_file.reset(selected)?;
ui_state.table = ui_state.table.rows(UiState::rows(state_file, exercises));
}
KeyCode::Char('c') => { KeyCode::Char('c') => {
state_file.set_next_exercise_ind(ui_state.selected())?; state_file.set_next_exercise_ind(ui_state.selected())?;
ui_state.table = ui_state.table.rows(UiState::rows(state_file, exercises)); ui_state.table = ui_state.table.rows(UiState::rows(state_file, exercises));

View file

@ -16,7 +16,6 @@ mod verify;
mod watch; mod watch;
use crate::consts::WELCOME; use crate::consts::WELCOME;
use crate::embedded::{WriteStrategy, EMBEDDED_FILES};
use crate::exercise::{Exercise, ExerciseList}; use crate::exercise::{Exercise, ExerciseList};
use crate::run::run; use crate::run::run;
use crate::verify::verify; use crate::verify::verify;
@ -56,6 +55,26 @@ enum Subcommands {
List, List,
} }
fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> Result<(usize, &'a Exercise)> {
if name == "next" {
for (ind, exercise) in exercises.iter().enumerate() {
if !exercise.looks_done()? {
return Ok((ind, exercise));
}
}
println!("🎉 Congratulations! You have done all the exercises!");
println!("🔚 There are no more exercises to do next!");
exit(0);
}
exercises
.iter()
.enumerate()
.find(|(_, exercise)| exercise.name == name)
.with_context(|| format!("No exercise found for '{name}'!"))
}
fn main() -> Result<()> { fn main() -> Result<()> {
let args = Args::parse(); let args = Args::parse();
@ -86,30 +105,29 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini
exit(1); exit(1);
} }
let mut state = StateFile::read_or_default(&exercises); let mut state_file = StateFile::read_or_default(&exercises);
match args.command { match args.command {
None | Some(Subcommands::Watch) => { None | Some(Subcommands::Watch) => {
watch::watch(&state, &exercises)?; watch::watch(&state_file, &exercises)?;
} }
// `Init` is handled above. // `Init` is handled above.
Some(Subcommands::Init) => (), Some(Subcommands::Init) => (),
Some(Subcommands::List) => { Some(Subcommands::List) => {
list::list(&mut state, &exercises)?; list::list(&mut state_file, &exercises)?;
} }
Some(Subcommands::Run { name }) => { Some(Subcommands::Run { name }) => {
let exercise = find_exercise(&name, &exercises)?; let (_, exercise) = find_exercise(&name, &exercises)?;
run(exercise).unwrap_or_else(|_| exit(1)); run(exercise).unwrap_or_else(|_| exit(1));
} }
Some(Subcommands::Reset { name }) => { Some(Subcommands::Reset { name }) => {
let exercise = find_exercise(&name, &exercises)?; let (ind, exercise) = find_exercise(&name, &exercises)?;
EMBEDDED_FILES exercise.reset()?;
.write_exercise_to_disk(&exercise.path, WriteStrategy::Overwrite) state_file.reset(ind)?;
.with_context(|| format!("Failed to reset the exercise {exercise}"))?;
println!("The file {} has been reset!", exercise.path.display()); println!("The file {} has been reset!", exercise.path.display());
} }
Some(Subcommands::Hint { name }) => { Some(Subcommands::Hint { name }) => {
let exercise = find_exercise(&name, &exercises)?; let (_, exercise) = find_exercise(&name, &exercises)?;
println!("{}", exercise.hint); println!("{}", exercise.hint);
} }
Some(Subcommands::Verify) => match verify(&exercises, 0)? { Some(Subcommands::Verify) => match verify(&exercises, 0)? {
@ -120,22 +138,3 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini
Ok(()) Ok(())
} }
fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> Result<&'a Exercise> {
if name == "next" {
for exercise in exercises {
if !exercise.looks_done()? {
return Ok(exercise);
}
}
println!("🎉 Congratulations! You have done all the exercises!");
println!("🔚 There are no more exercises to do next!");
exit(0);
}
exercises
.iter()
.find(|e| e.name == name)
.with_context(|| format!("No exercise found for '{name}'!"))
}

View file

@ -10,9 +10,11 @@ pub struct StateFile {
progress: Vec<bool>, progress: Vec<bool>,
} }
const BAD_INDEX_ERR: &str = "The next exercise index is higher than the number of exercises";
impl StateFile { impl StateFile {
fn read(exercises: &[Exercise]) -> Option<Self> { fn read(exercises: &[Exercise]) -> Option<Self> {
let file_content = fs::read(".rustlings.json").ok()?; let file_content = fs::read(".rustlings-state.json").ok()?;
let slf: Self = serde_json::de::from_slice(&file_content).ok()?; let slf: Self = serde_json::de::from_slice(&file_content).ok()?;
@ -34,6 +36,8 @@ impl StateFile {
// TODO: Capacity // TODO: Capacity
let mut buf = Vec::with_capacity(1024); let mut buf = Vec::with_capacity(1024);
serde_json::ser::to_writer(&mut buf, self).context("Failed to serialize the state")?; serde_json::ser::to_writer(&mut buf, self).context("Failed to serialize the state")?;
fs::write(".rustlings-state.json", buf)
.context("Failed to write the state file `.rustlings-state.json`")?;
Ok(()) Ok(())
} }
@ -45,9 +49,8 @@ impl StateFile {
pub fn set_next_exercise_ind(&mut self, ind: usize) -> Result<()> { pub fn set_next_exercise_ind(&mut self, ind: usize) -> Result<()> {
if ind >= self.progress.len() { if ind >= self.progress.len() {
bail!("The next exercise index is higher than the number of exercises"); bail!(BAD_INDEX_ERR);
} }
self.next_exercise_ind = ind; self.next_exercise_ind = ind;
self.write() self.write()
} }
@ -56,4 +59,10 @@ impl StateFile {
pub fn progress(&self) -> &[bool] { pub fn progress(&self) -> &[bool] {
&self.progress &self.progress
} }
pub fn reset(&mut self, ind: usize) -> Result<()> {
let done = self.progress.get_mut(ind).context(BAD_INDEX_ERR)?;
*done = false;
self.write()
}
} }