summaryrefslogtreecommitdiff
path: root/src/app_state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/app_state.rs')
-rw-r--r--src/app_state.rs128
1 files changed, 84 insertions, 44 deletions
diff --git a/src/app_state.rs b/src/app_state.rs
index 98c6384..9a378de 100644
--- a/src/app_state.rs
+++ b/src/app_state.rs
@@ -4,15 +4,14 @@ use crossterm::{
terminal::{Clear, ClearType},
ExecutableCommand,
};
-use std::io::{StdoutLock, Write};
-
-mod state_file;
+use std::{
+ fs::{self, File},
+ io::{Read, StdoutLock, Write},
+};
use crate::{exercise::Exercise, info_file::InfoFile, FENISH_LINE};
-use self::state_file::{write, StateFileDeser};
-
-const STATE_FILE_NAME: &str = ".rustlings-state.json";
+const STATE_FILE_NAME: &str = ".rustlings-state.txt";
const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises";
#[must_use]
@@ -27,11 +26,51 @@ pub struct AppState {
n_done: u16,
welcome_message: String,
final_message: String,
+ file_buf: Vec<u8>,
}
impl AppState {
+ fn update_from_file(&mut self) {
+ self.file_buf.clear();
+ self.n_done = 0;
+
+ if File::open(STATE_FILE_NAME)
+ .and_then(|mut file| file.read_to_end(&mut self.file_buf))
+ .is_ok()
+ {
+ let mut lines = self.file_buf.split(|c| *c == b'\n');
+ let Some(current_exercise_name) = lines.next() else {
+ return;
+ };
+
+ if lines.next().is_none() {
+ return;
+ }
+
+ let mut done_exercises = hashbrown::HashSet::with_capacity(self.exercises.len());
+
+ for done_exerise_name in lines {
+ if done_exerise_name.is_empty() {
+ break;
+ }
+ done_exercises.insert(done_exerise_name);
+ }
+
+ for (ind, exercise) in self.exercises.iter_mut().enumerate() {
+ if done_exercises.contains(exercise.name.as_bytes()) {
+ exercise.done = true;
+ self.n_done += 1;
+ }
+
+ if exercise.name.as_bytes() == current_exercise_name {
+ self.current_exercise_ind = ind;
+ }
+ }
+ }
+ }
+
pub fn new(info_file: InfoFile) -> Self {
- let mut exercises = info_file
+ let exercises = info_file
.exercises
.into_iter()
.map(|mut exercise_info| {
@@ -55,42 +94,18 @@ impl AppState {
})
.collect::<Vec<_>>();
- let (current_exercise_ind, n_done) = StateFileDeser::read().map_or((0, 0), |state_file| {
- let mut state_file_exercises =
- hashbrown::HashMap::with_capacity(state_file.exercises.len());
-
- for (ind, exercise_state) in state_file.exercises.into_iter().enumerate() {
- state_file_exercises.insert(
- exercise_state.name,
- (ind == state_file.current_exercise_ind, exercise_state.done),
- );
- }
-
- let mut current_exercise_ind = 0;
- let mut n_done = 0;
- for (ind, exercise) in exercises.iter_mut().enumerate() {
- if let Some((current, done)) = state_file_exercises.get(exercise.name) {
- if *done {
- exercise.done = true;
- n_done += 1;
- }
-
- if *current {
- current_exercise_ind = ind;
- }
- }
- }
-
- (current_exercise_ind, n_done)
- });
-
- Self {
- current_exercise_ind,
+ let mut slf = Self {
+ current_exercise_ind: 0,
exercises,
- n_done,
+ n_done: 0,
welcome_message: info_file.welcome_message.unwrap_or_default(),
final_message: info_file.final_message.unwrap_or_default(),
- }
+ file_buf: Vec::with_capacity(2048),
+ };
+
+ slf.update_from_file();
+
+ slf
}
#[inline]
@@ -120,7 +135,7 @@ impl AppState {
self.current_exercise_ind = ind;
- write(self)
+ self.write()
}
pub fn set_current_exercise_by_name(&mut self, name: &str) -> Result<()> {
@@ -132,7 +147,7 @@ impl AppState {
.position(|exercise| exercise.name == name)
.with_context(|| format!("No exercise found for '{name}'!"))?;
- write(self)
+ self.write()
}
pub fn set_pending(&mut self, ind: usize) -> Result<()> {
@@ -141,7 +156,7 @@ impl AppState {
if exercise.done {
exercise.done = false;
self.n_done -= 1;
- write(self)?;
+ self.write()?;
}
Ok(())
@@ -193,7 +208,7 @@ impl AppState {
self.exercises[exercise_ind].done = false;
self.n_done -= 1;
- write(self)?;
+ self.write()?;
return Ok(ExercisesProgress::Pending);
}
@@ -213,6 +228,31 @@ impl AppState {
Ok(ExercisesProgress::Pending)
}
+
+ // Write the state file.
+ // The file's format is very simple:
+ // - The first line is the name of the current exercise.
+ // - The second line is an empty line.
+ // - All remaining lines are the names of done exercises.
+ fn write(&mut self) -> Result<()> {
+ self.file_buf.clear();
+
+ self.file_buf
+ .extend_from_slice(self.current_exercise().name.as_bytes());
+ self.file_buf.extend_from_slice(b"\n\n");
+
+ for exercise in &self.exercises {
+ if exercise.done {
+ self.file_buf.extend_from_slice(exercise.name.as_bytes());
+ self.file_buf.extend_from_slice(b"\n");
+ }
+ }
+
+ fs::write(STATE_FILE_NAME, &self.file_buf)
+ .with_context(|| format!("Failed to write the state file {STATE_FILE_NAME}"))?;
+
+ Ok(())
+ }
}
const RERUNNING_ALL_EXERCISES_MSG: &[u8] = b"