summaryrefslogtreecommitdiff
path: root/src/exercise.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/exercise.rs')
-rw-r--r--src/exercise.rs206
1 files changed, 1 insertions, 205 deletions
diff --git a/src/exercise.rs b/src/exercise.rs
index ca47009..de435d1 100644
--- a/src/exercise.rs
+++ b/src/exercise.rs
@@ -1,38 +1,14 @@
use anyhow::{Context, Result};
use serde::Deserialize;
use std::{
- array,
fmt::{self, Debug, Display, Formatter},
- fs::{self, File},
- io::{self, BufRead, BufReader},
- mem,
+ fs::{self},
path::PathBuf,
process::{Command, Output},
};
-use winnow::{
- ascii::{space0, Caseless},
- combinator::opt,
- Parser,
-};
use crate::embedded::{WriteStrategy, EMBEDDED_FILES};
-// The number of context lines above and below a highlighted line.
-const CONTEXT: usize = 2;
-
-// Check if the line contains the "I AM NOT DONE" comment.
-fn contains_not_done_comment(input: &str) -> bool {
- (
- space0::<_, ()>,
- "//",
- opt('/'),
- space0,
- Caseless("I AM NOT DONE"),
- )
- .parse_next(&mut &*input)
- .is_ok()
-}
-
// The mode of the exercise.
#[derive(Deserialize, Copy, Clone)]
#[serde(rename_all = "lowercase")]
@@ -78,13 +54,6 @@ pub struct Exercise {
pub hint: String,
}
-// The state of an Exercise.
-#[derive(PartialEq, Eq, Debug)]
-pub enum State {
- Done,
- Pending(Vec<ContextLine>),
-}
-
// The context information of a pending exercise.
#[derive(PartialEq, Eq, Debug)]
pub struct ContextLine {
@@ -129,105 +98,6 @@ impl Exercise {
}
}
- pub fn state(&self) -> Result<State> {
- let source_file = File::open(&self.path)
- .with_context(|| format!("Failed to open the exercise file {}", self.path.display()))?;
- let mut source_reader = BufReader::new(source_file);
-
- // Read the next line into `buf` without the newline at the end.
- let mut read_line = |buf: &mut String| -> io::Result<_> {
- let n = source_reader.read_line(buf)?;
- if buf.ends_with('\n') {
- buf.pop();
- if buf.ends_with('\r') {
- buf.pop();
- }
- }
- Ok(n)
- };
-
- let mut current_line_number: usize = 1;
- // Keep the last `CONTEXT` lines while iterating over the file lines.
- let mut prev_lines: [_; CONTEXT] = array::from_fn(|_| String::with_capacity(256));
- let mut line = String::with_capacity(256);
-
- loop {
- let n = read_line(&mut line).with_context(|| {
- format!("Failed to read the exercise file {}", self.path.display())
- })?;
-
- // Reached the end of the file and didn't find the comment.
- if n == 0 {
- return Ok(State::Done);
- }
-
- if contains_not_done_comment(&line) {
- let mut context = Vec::with_capacity(2 * CONTEXT + 1);
- // Previous lines.
- for (ind, prev_line) in prev_lines
- .into_iter()
- .take(current_line_number - 1)
- .enumerate()
- .rev()
- {
- context.push(ContextLine {
- line: prev_line,
- number: current_line_number - 1 - ind,
- important: false,
- });
- }
-
- // Current line.
- context.push(ContextLine {
- line,
- number: current_line_number,
- important: true,
- });
-
- // Next lines.
- for ind in 0..CONTEXT {
- let mut next_line = String::with_capacity(256);
- let Ok(n) = read_line(&mut next_line) else {
- // If an error occurs, just ignore the next lines.
- break;
- };
-
- // Reached the end of the file.
- if n == 0 {
- break;
- }
-
- context.push(ContextLine {
- line: next_line,
- number: current_line_number + 1 + ind,
- important: false,
- });
- }
-
- return Ok(State::Pending(context));
- }
-
- current_line_number += 1;
- // Add the current line as a previous line and shift the older lines by one.
- for prev_line in &mut prev_lines {
- mem::swap(&mut line, prev_line);
- }
- // The current line now contains the oldest previous line.
- // Recycle it for reading the next line.
- line.clear();
- }
- }
-
- // Check that the exercise looks to be solved using self.state()
- // This is not the best way to check since
- // the user can just remove the "I AM NOT DONE" string from the file
- // without actually having solved anything.
- // The only other way to truly check this would to compile and run
- // the exercise; which would be both costly and counterintuitive
- pub fn looks_done(&self) -> Result<bool> {
- self.state().map(|state| state == State::Done)
- }
-
pub fn reset(&self) -> Result<()> {
EMBEDDED_FILES
.write_exercise_to_disk(&self.path, WriteStrategy::Overwrite)
@@ -240,77 +110,3 @@ impl Display for Exercise {
self.path.fmt(f)
}
}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn test_pending_state() {
- let exercise = Exercise {
- name: "pending_exercise".into(),
- path: PathBuf::from("tests/fixture/state/exercises/pending_exercise.rs"),
- mode: Mode::Compile,
- hint: String::new(),
- };
-
- let state = exercise.state();
- let expected = vec![
- ContextLine {
- line: "// fake_exercise".to_string(),
- number: 1,
- important: false,
- },
- ContextLine {
- line: "".to_string(),
- number: 2,
- important: false,
- },
- ContextLine {
- line: "// I AM NOT DONE".to_string(),
- number: 3,
- important: true,
- },
- ContextLine {
- line: "".to_string(),
- number: 4,
- important: false,
- },
- ContextLine {
- line: "fn main() {".to_string(),
- number: 5,
- important: false,
- },
- ];
-
- assert_eq!(state.unwrap(), State::Pending(expected));
- }
-
- #[test]
- fn test_finished_exercise() {
- let exercise = Exercise {
- name: "finished_exercise".into(),
- path: PathBuf::from("tests/fixture/state/exercises/finished_exercise.rs"),
- mode: Mode::Compile,
- hint: String::new(),
- };
-
- assert_eq!(exercise.state().unwrap(), State::Done);
- }
-
- #[test]
- fn test_not_done() {
- assert!(contains_not_done_comment("// I AM NOT DONE"));
- assert!(contains_not_done_comment("/// I AM NOT DONE"));
- assert!(contains_not_done_comment("// I AM NOT DONE"));
- assert!(contains_not_done_comment("/// I AM NOT DONE"));
- assert!(contains_not_done_comment("// I AM NOT DONE "));
- assert!(contains_not_done_comment("// I AM NOT DONE!"));
- assert!(contains_not_done_comment("// I am not done"));
- assert!(contains_not_done_comment("// i am NOT done"));
-
- assert!(!contains_not_done_comment("I AM NOT DONE"));
- assert!(!contains_not_done_comment("// NOT DONE"));
- assert!(!contains_not_done_comment("DONE"));
- }
-}