diff --git a/src/exercise.rs b/src/exercise.rs index 136e9439..e841aed2 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Display, Formatter}; use std::fs::{self, remove_file, File}; use std::io::{self, BufRead, BufReader}; use std::path::PathBuf; -use std::process::{self, Command}; +use std::process::{self, exit, Command}; use std::{array, env, mem}; use winnow::ascii::{space0, Caseless}; use winnow::combinator::opt; @@ -15,7 +15,8 @@ const RUSTC_NO_DEBUG_ARGS: &[&str] = &["-C", "strip=debuginfo"]; const CONTEXT: usize = 2; const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/22_clippy/Cargo.toml"; -fn not_done(input: &str) -> bool { +// Checks if the line contains the "I AM NOT DONE" comment. +fn contains_not_done_comment(input: &str) -> bool { ( space0::<_, ()>, "//", @@ -219,12 +220,15 @@ path = "{}.rs""#, pub fn state(&self) -> State { let source_file = File::open(&self.path).unwrap_or_else(|e| { - panic!( - "We were unable to open the exercise file {}! {e}", - self.path.display() - ) + println!( + "Failed to open the exercise file {}: {e}", + self.path.display(), + ); + exit(1); }); 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') { @@ -236,70 +240,72 @@ path = "{}.rs""#, Ok(n) }; - let mut matched_line_ind: usize = 0; + let mut current_line_number: usize = 1; let mut prev_lines: [_; CONTEXT] = array::from_fn(|_| String::with_capacity(256)); let mut line = String::with_capacity(256); loop { - match read_line(&mut line) { - Ok(0) => break, - Ok(_) => { - if not_done(&line) { - let mut context = Vec::with_capacity(2 * CONTEXT + 1); - for (ind, prev_line) in prev_lines - .into_iter() - .rev() - .take(matched_line_ind) - .enumerate() - { - context.push(ContextLine { - line: prev_line, - // TODO - number: matched_line_ind - CONTEXT + ind + 1, - important: false, - }); - } + let n = read_line(&mut line).unwrap_or_else(|e| { + println!( + "Failed to read the exercise file {}: {e}", + self.path.display(), + ); + exit(1); + }); - context.push(ContextLine { - line, - number: matched_line_ind + 1, - important: true, - }); - - for ind in 0..CONTEXT { - let mut next_line = String::with_capacity(256); - let Ok(n) = read_line(&mut next_line) else { - break; - }; - - if n == 0 { - break; - } - - context.push(ContextLine { - line: next_line, - number: matched_line_ind + ind + 2, - important: false, - }); - } - - return State::Pending(context); - } - - matched_line_ind += 1; - for prev_line in &mut prev_lines { - mem::swap(&mut line, prev_line); - } - line.clear(); - } - Err(e) => panic!( - "We were unable to read the exercise file {}! {e}", - self.path.display() - ), + // Reached the end of the file and didn't find the comment. + if n == 0 { + return State::Done; } - } - State::Done + if contains_not_done_comment(&line) { + let mut context = Vec::with_capacity(2 * CONTEXT + 1); + 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, + }); + } + + context.push(ContextLine { + line, + number: current_line_number, + important: true, + }); + + for ind in 0..CONTEXT { + let mut next_line = String::with_capacity(256); + let Ok(n) = read_line(&mut next_line) else { + break; + }; + + if n == 0 { + break; + } + + context.push(ContextLine { + line: next_line, + number: current_line_number + 1 + ind, + important: false, + }); + } + + return State::Pending(context); + } + + current_line_number += 1; + // Recycle the buffers. + for prev_line in &mut prev_lines { + mem::swap(&mut line, prev_line); + } + line.clear(); + } } // Check that the exercise looks to be solved using self.state() @@ -428,17 +434,17 @@ mod test { #[test] fn test_not_done() { - assert!(not_done("// I AM NOT DONE")); - assert!(not_done("/// I AM NOT DONE")); - assert!(not_done("// I AM NOT DONE")); - assert!(not_done("/// I AM NOT DONE")); - assert!(not_done("// I AM NOT DONE ")); - assert!(not_done("// I AM NOT DONE!")); - assert!(not_done("// I am not done")); - assert!(not_done("// 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!(!not_done("I AM NOT DONE")); - assert!(!not_done("// NOT DONE")); - assert!(!not_done("DONE")); + assert!(!contains_not_done_comment("I AM NOT DONE")); + assert!(!contains_not_done_comment("// NOT DONE")); + assert!(!contains_not_done_comment("DONE")); } }