Merge branch 'master' into refactor-hints

This commit is contained in:
marisa
2019-11-11 17:21:06 +01:00
committed by GitHub
61 changed files with 315 additions and 12 deletions

View File

@@ -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);
}
}

View File

@@ -127,11 +127,11 @@ fn watch(exercises: &[Exercise]) -> notify::Result<()> {
DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => {
if b.extension() == Some(OsStr::new("rs")) && b.exists() {
let filepath = b.as_path().canonicalize().unwrap();
let exercise = exercises
let pending_exercises = exercises
.iter()
.skip_while(|e| !filepath.ends_with(&e.path));
clear_screen();
let _ignored = verify(exercise);
let _ignored = verify(pending_exercises);
}
}
_ => {}

View File

@@ -5,7 +5,9 @@ use indicatif::ProgressBar;
pub fn run(exercise: &Exercise) -> Result<(), ()> {
match exercise.mode {
Mode::Test => test(exercise)?,
Mode::Test => {
test(exercise)?;
}
Mode::Compile => compile_and_run(exercise)?,
}
Ok(())

View File

@@ -1,18 +1,21 @@
use crate::exercise::{Exercise, Mode};
use crate::exercise::{ContextLine, Exercise, Mode, State};
use console::{style, Emoji};
use indicatif::ProgressBar;
pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<(), ()> {
for exercise in start_at {
match exercise.mode {
let is_done = match exercise.mode {
Mode::Test => test(&exercise)?,
Mode::Compile => compile_only(&exercise)?,
};
if !is_done {
return Err(());
}
}
Ok(())
}
fn compile_only(exercise: &Exercise) -> Result<(), ()> {
fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
progress_bar.enable_steady_tick(100);
@@ -22,7 +25,12 @@ fn compile_only(exercise: &Exercise) -> Result<(), ()> {
let formatstr = format!("{} Successfully compiled {}!", Emoji("", ""), exercise);
println!("{}", style(formatstr).green());
exercise.clean();
Ok(())
if let State::Pending(context) = exercise.state() {
print_everything_looks_good(exercise.mode, context);
Ok(false)
} else {
Ok(true)
}
} else {
let formatstr = format!(
"{} Compilation of {} failed! Compiler error message:\n",
@@ -36,7 +44,7 @@ fn compile_only(exercise: &Exercise) -> Result<(), ()> {
}
}
pub fn test(exercise: &Exercise) -> Result<(), ()> {
pub fn test(exercise: &Exercise) -> Result<bool, ()> {
let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
progress_bar.enable_steady_tick(100);
@@ -52,7 +60,12 @@ pub fn test(exercise: &Exercise) -> Result<(), ()> {
let formatstr = format!("{} Successfully tested {}!", Emoji("", ""), exercise);
println!("{}", style(formatstr).green());
exercise.clean();
Ok(())
if let State::Pending(context) = exercise.state() {
print_everything_looks_good(exercise.mode, context);
Ok(false)
} else {
Ok(true)
}
} else {
let formatstr = format!(
"{} Testing of {} failed! Please try again. Here's the output:",
@@ -77,3 +90,34 @@ pub fn test(exercise: &Exercise) -> Result<(), ()> {
Err(())
}
}
fn print_everything_looks_good(mode: Mode, context: Vec<ContextLine>) {
let success_msg = match mode {
Mode::Compile => "The code is compiling!",
Mode::Test => "The code is compiling, and the tests pass!",
};
println!("");
println!("🎉 🎉 {} 🎉 🎉", success_msg);
println!("");
println!("You can keep working on this exercise,");
println!(
"or jump into the next one by removing the {} comment:",
style("`I AM NOT DONE`").bold()
);
println!("");
for context_line in context {
let formatted_line = if context_line.important {
format!("{}", style(context_line.line).bold())
} else {
format!("{}", context_line.line)
};
println!(
"{:>2} {} {}",
style(context_line.number).blue().bold(),
style("|").blue(),
formatted_line
);
}
}