summaryrefslogtreecommitdiff
path: root/src/exercise.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/exercise.rs')
-rw-r--r--src/exercise.rs113
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);
+ }
}