diff options
| author | marisa <mokou@posteo.de> | 2019-11-11 17:21:06 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-11-11 17:21:06 +0100 |
| commit | ec2d4bd3ee665f2a4c79dd42c41078223074d4c1 (patch) | |
| tree | 6106b922559491112240f6465f965cf811caf5b8 /src/exercise.rs | |
| parent | ce9fa6ebbfdc3e7585d488d9409797285708316f (diff) | |
| parent | 9a9007abae86c3b1b1c09778a6544ced54ea4453 (diff) | |
Merge branch 'master' into refactor-hints
Diffstat (limited to 'src/exercise.rs')
| -rw-r--r-- | src/exercise.rs | 113 |
1 files changed, 110 insertions, 3 deletions
diff --git a/src/exercise.rs b/src/exercise.rs index 0e8a199..be59284 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -1,16 +1,20 @@ +use regex::Regex; use serde::Deserialize; use std::fmt::{self, Display, Formatter}; -use std::fs::remove_file; +use std::fs::{remove_file, File}; +use std::io::Read; use std::path::PathBuf; use std::process::{self, Command, Output}; const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"]; +const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE"; +const CONTEXT: usize = 2; fn temp_file() -> String { format!("./temp_{}", process::id()) } -#[derive(Deserialize)] +#[derive(Deserialize, Copy, Clone)] #[serde(rename_all = "lowercase")] pub enum Mode { Compile, @@ -30,6 +34,19 @@ pub struct Exercise { pub hint: String, } +#[derive(PartialEq, Debug)] +pub enum State { + Done, + Pending(Vec<ContextLine>), +} + +#[derive(PartialEq, Debug)] +pub struct ContextLine { + pub line: String, + pub number: usize, + pub important: bool, +} + impl Exercise { pub fn compile(&self) -> Output { match self.mode { @@ -54,6 +71,48 @@ impl Exercise { pub fn clean(&self) { let _ignored = remove_file(&temp_file()); } + + pub fn state(&self) -> State { + let mut source_file = + File::open(&self.path).expect("We were unable to open the exercise file!"); + + let source = { + let mut s = String::new(); + source_file + .read_to_string(&mut s) + .expect("We were unable to read the exercise file!"); + s + }; + + let re = Regex::new(I_AM_DONE_REGEX).unwrap(); + + if !re.is_match(&source) { + return State::Done; + } + + let matched_line_index = source + .lines() + .enumerate() + .filter_map(|(i, line)| if re.is_match(line) { Some(i) } else { None }) + .next() + .expect("This should not happen at all"); + + let min_line = ((matched_line_index as i32) - (CONTEXT as i32)).max(0) as usize; + let max_line = matched_line_index + CONTEXT; + + let context = source + .lines() + .enumerate() + .filter(|&(i, _)| i >= min_line && i <= max_line) + .map(|(i, line)| ContextLine { + line: line.to_string(), + number: i + 1, + important: i == matched_line_index, + }) + .collect(); + + State::Pending(context) + } } impl Display for Exercise { @@ -65,7 +124,6 @@ impl Display for Exercise { #[cfg(test)] mod test { use super::*; - use std::fs::File; use std::path::Path; #[test] @@ -80,4 +138,53 @@ mod test { exercise.clean(); assert!(!Path::new(&temp_file()).exists()); } + + #[test] + fn test_pending_state() { + let exercise = Exercise { + path: PathBuf::from("tests/fixture/state/pending_exercise.rs"), + mode: Mode::Compile, + }; + + 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, State::Pending(expected)); + } + + #[test] + fn test_finished_exercise() { + let exercise = Exercise { + path: PathBuf::from("tests/fixture/state/finished_exercise.rs"), + mode: Mode::Compile, + }; + + assert_eq!(exercise.state(), State::Done); + } } |
