Auto merge of #271 - jrvidal:refactor, r=fmoko
refactor: exercise evaluation After working a bit on #270, I realized that it'd be useful to first perform a minor refactor of exercise evaluation. * Now we have standard methods to compile + execute that return `Result`s. * Success/failure messages are standardized.
This commit is contained in:
commit
7e8530b21f
|
@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter};
|
||||||
use std::fs::{remove_file, File};
|
use std::fs::{remove_file, File};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{self, Command, Output};
|
use std::process::{self, Command};
|
||||||
|
|
||||||
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
|
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 I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE";
|
||||||
|
@ -47,9 +47,34 @@ pub struct ContextLine {
|
||||||
pub important: bool,
|
pub important: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct CompiledExercise<'a> {
|
||||||
|
exercise: &'a Exercise,
|
||||||
|
_handle: FileHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CompiledExercise<'a> {
|
||||||
|
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
||||||
|
self.exercise.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ExerciseOutput {
|
||||||
|
pub stdout: String,
|
||||||
|
pub stderr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FileHandle;
|
||||||
|
|
||||||
|
impl Drop for FileHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
clean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Exercise {
|
impl Exercise {
|
||||||
pub fn compile(&self) -> Output {
|
pub fn compile(&self) -> Result<CompiledExercise, ExerciseOutput> {
|
||||||
match self.mode {
|
let cmd = match self.mode {
|
||||||
Mode::Compile => Command::new("rustc")
|
Mode::Compile => Command::new("rustc")
|
||||||
.args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
|
.args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
|
||||||
.args(RUSTC_COLOR_ARGS)
|
.args(RUSTC_COLOR_ARGS)
|
||||||
|
@ -59,17 +84,37 @@ impl Exercise {
|
||||||
.args(RUSTC_COLOR_ARGS)
|
.args(RUSTC_COLOR_ARGS)
|
||||||
.output(),
|
.output(),
|
||||||
}
|
}
|
||||||
.expect("Failed to run 'compile' command.")
|
.expect("Failed to run 'compile' command.");
|
||||||
|
|
||||||
|
if cmd.status.success() {
|
||||||
|
Ok(CompiledExercise {
|
||||||
|
exercise: &self,
|
||||||
|
_handle: FileHandle,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
clean();
|
||||||
|
Err(ExerciseOutput {
|
||||||
|
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
||||||
|
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self) -> Output {
|
fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
||||||
Command::new(&temp_file())
|
let cmd = Command::new(&temp_file())
|
||||||
.output()
|
.output()
|
||||||
.expect("Failed to run 'run' command")
|
.expect("Failed to run 'run' command");
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clean(&self) {
|
let output = ExerciseOutput {
|
||||||
let _ignored = remove_file(&temp_file());
|
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
||||||
|
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if cmd.status.success() {
|
||||||
|
Ok(output)
|
||||||
|
} else {
|
||||||
|
Err(output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> State {
|
pub fn state(&self) -> State {
|
||||||
|
@ -121,6 +166,10 @@ impl Display for Exercise {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clean() {
|
||||||
|
let _ignored = remove_file(&temp_file());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -131,11 +180,12 @@ mod test {
|
||||||
File::create(&temp_file()).unwrap();
|
File::create(&temp_file()).unwrap();
|
||||||
let exercise = Exercise {
|
let exercise = Exercise {
|
||||||
name: String::from("example"),
|
name: String::from("example"),
|
||||||
path: PathBuf::from("example.rs"),
|
path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
|
||||||
mode: Mode::Test,
|
mode: Mode::Compile,
|
||||||
hint: String::from(""),
|
hint: String::from(""),
|
||||||
};
|
};
|
||||||
exercise.clean();
|
let compiled = exercise.compile().unwrap();
|
||||||
|
drop(compiled);
|
||||||
assert!(!Path::new(&temp_file()).exists());
|
assert!(!Path::new(&temp_file()).exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,9 @@ use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod ui;
|
||||||
|
|
||||||
mod exercise;
|
mod exercise;
|
||||||
mod run;
|
mod run;
|
||||||
mod verify;
|
mod verify;
|
||||||
|
|
54
src/run.rs
54
src/run.rs
|
@ -1,6 +1,5 @@
|
||||||
use crate::exercise::{Exercise, Mode};
|
use crate::exercise::{Exercise, Mode};
|
||||||
use crate::verify::test;
|
use crate::verify::test;
|
||||||
use console::{style, Emoji};
|
|
||||||
use indicatif::ProgressBar;
|
use indicatif::ProgressBar;
|
||||||
|
|
||||||
pub fn run(exercise: &Exercise) -> Result<(), ()> {
|
pub fn run(exercise: &Exercise) -> Result<(), ()> {
|
||||||
|
@ -11,42 +10,41 @@ pub fn run(exercise: &Exercise) -> Result<(), ()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
|
fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
|
||||||
let progress_bar = ProgressBar::new_spinner();
|
let progress_bar = ProgressBar::new_spinner();
|
||||||
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
||||||
progress_bar.enable_steady_tick(100);
|
progress_bar.enable_steady_tick(100);
|
||||||
|
|
||||||
let compilecmd = exercise.compile();
|
let compilation_result = exercise.compile();
|
||||||
|
let compilation = match compilation_result {
|
||||||
|
Ok(compilation) => compilation,
|
||||||
|
Err(output) => {
|
||||||
|
progress_bar.finish_and_clear();
|
||||||
|
warn!(
|
||||||
|
"Compilation of {} failed!, Compiler error message:\n",
|
||||||
|
exercise
|
||||||
|
);
|
||||||
|
println!("{}", output.stderr);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
progress_bar.set_message(format!("Running {}...", exercise).as_str());
|
progress_bar.set_message(format!("Running {}...", exercise).as_str());
|
||||||
if compilecmd.status.success() {
|
let result = compilation.run();
|
||||||
let runcmd = exercise.run();
|
progress_bar.finish_and_clear();
|
||||||
progress_bar.finish_and_clear();
|
|
||||||
|
|
||||||
if runcmd.status.success() {
|
match result {
|
||||||
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
|
Ok(output) => {
|
||||||
let formatstr = format!("{} Successfully ran {}", Emoji("✅", "✓"), exercise);
|
println!("{}", output.stdout);
|
||||||
println!("{}", style(formatstr).green());
|
success!("Successfully ran {}", exercise);
|
||||||
exercise.clean();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
}
|
||||||
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
|
Err(output) => {
|
||||||
println!("{}", String::from_utf8_lossy(&runcmd.stderr));
|
println!("{}", output.stdout);
|
||||||
|
println!("{}", output.stderr);
|
||||||
|
|
||||||
let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), exercise);
|
warn!("Ran {} with errors", exercise);
|
||||||
println!("{}", style(formatstr).red());
|
|
||||||
exercise.clean();
|
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
progress_bar.finish_and_clear();
|
|
||||||
let formatstr = format!(
|
|
||||||
"{} Compilation of {} failed! Compiler error message:\n",
|
|
||||||
Emoji("⚠️ ", "!"),
|
|
||||||
exercise
|
|
||||||
);
|
|
||||||
println!("{}", style(formatstr).red());
|
|
||||||
println!("{}", String::from_utf8_lossy(&compilecmd.stderr));
|
|
||||||
exercise.clean();
|
|
||||||
Err(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
23
src/ui.rs
Normal file
23
src/ui.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
macro_rules! warn {
|
||||||
|
($fmt:literal, $ex:expr) => {{
|
||||||
|
use console::{style, Emoji};
|
||||||
|
let formatstr = format!($fmt, $ex);
|
||||||
|
println!(
|
||||||
|
"{} {}",
|
||||||
|
style(Emoji("⚠️ ", "!")).red(),
|
||||||
|
style(formatstr).red()
|
||||||
|
);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! success {
|
||||||
|
($fmt:literal, $ex:expr) => {{
|
||||||
|
use console::{style, Emoji};
|
||||||
|
let formatstr = format!($fmt, $ex);
|
||||||
|
println!(
|
||||||
|
"{} {}",
|
||||||
|
style(Emoji("✅", "✓")).green(),
|
||||||
|
style(formatstr).green()
|
||||||
|
);
|
||||||
|
}};
|
||||||
|
}
|
106
src/verify.rs
106
src/verify.rs
|
@ -1,11 +1,11 @@
|
||||||
use crate::exercise::{Exercise, Mode, State};
|
use crate::exercise::{Exercise, Mode, State};
|
||||||
use console::{style, Emoji};
|
use console::style;
|
||||||
use indicatif::ProgressBar;
|
use indicatif::ProgressBar;
|
||||||
|
|
||||||
pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<(), &'a Exercise> {
|
pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<(), &'a Exercise> {
|
||||||
for exercise in start_at {
|
for exercise in start_at {
|
||||||
let compile_result = match exercise.mode {
|
let compile_result = match exercise.mode {
|
||||||
Mode::Test => compile_and_test_interactively(&exercise),
|
Mode::Test => compile_and_test(&exercise, RunMode::Interactive),
|
||||||
Mode::Compile => compile_only(&exercise),
|
Mode::Compile => compile_only(&exercise),
|
||||||
};
|
};
|
||||||
if !compile_result.unwrap_or(false) {
|
if !compile_result.unwrap_or(false) {
|
||||||
|
@ -15,8 +15,13 @@ pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<()
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RunMode {
|
||||||
|
Interactive,
|
||||||
|
NonInteractive,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn test(exercise: &Exercise) -> Result<(), ()> {
|
pub fn test(exercise: &Exercise) -> Result<(), ()> {
|
||||||
compile_and_test(exercise, true)?;
|
compile_and_test(exercise, RunMode::NonInteractive)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,69 +29,64 @@ fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
|
||||||
let progress_bar = ProgressBar::new_spinner();
|
let progress_bar = ProgressBar::new_spinner();
|
||||||
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
||||||
progress_bar.enable_steady_tick(100);
|
progress_bar.enable_steady_tick(100);
|
||||||
let compile_output = exercise.compile();
|
let compilation_result = exercise.compile();
|
||||||
progress_bar.finish_and_clear();
|
progress_bar.finish_and_clear();
|
||||||
if compile_output.status.success() {
|
|
||||||
let formatstr = format!("{} Successfully compiled {}!", Emoji("✅", "✓"), exercise);
|
match compilation_result {
|
||||||
println!("{}", style(formatstr).green());
|
Ok(_) => {
|
||||||
exercise.clean();
|
success!("Successfully compiled {}!", exercise);
|
||||||
Ok(prompt_for_completion(&exercise))
|
Ok(prompt_for_completion(&exercise))
|
||||||
} else {
|
}
|
||||||
let formatstr = format!(
|
Err(output) => {
|
||||||
"{} Compilation of {} failed! Compiler error message:\n",
|
warn!(
|
||||||
Emoji("⚠️ ", "!"),
|
"Compilation of {} failed! Compiler error message:\n",
|
||||||
exercise
|
exercise
|
||||||
);
|
);
|
||||||
println!("{}", style(formatstr).red());
|
println!("{}", output.stderr);
|
||||||
println!("{}", String::from_utf8_lossy(&compile_output.stderr));
|
Err(())
|
||||||
exercise.clean();
|
}
|
||||||
Err(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_and_test_interactively(exercise: &Exercise) -> Result<bool, ()> {
|
fn compile_and_test(exercise: &Exercise, run_mode: RunMode) -> Result<bool, ()> {
|
||||||
compile_and_test(exercise, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile_and_test(exercise: &Exercise, skip_prompt: bool) -> Result<bool, ()> {
|
|
||||||
let progress_bar = ProgressBar::new_spinner();
|
let progress_bar = ProgressBar::new_spinner();
|
||||||
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
|
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
|
||||||
progress_bar.enable_steady_tick(100);
|
progress_bar.enable_steady_tick(100);
|
||||||
|
|
||||||
let compile_output = exercise.compile();
|
let compilation_result = exercise.compile();
|
||||||
if compile_output.status.success() {
|
|
||||||
progress_bar.set_message(format!("Running {}...", exercise).as_str());
|
|
||||||
|
|
||||||
let runcmd = exercise.run();
|
let compilation = match compilation_result {
|
||||||
progress_bar.finish_and_clear();
|
Ok(compilation) => compilation,
|
||||||
|
Err(output) => {
|
||||||
if runcmd.status.success() {
|
progress_bar.finish_and_clear();
|
||||||
let formatstr = format!("{} Successfully tested {}!", Emoji("✅", "✓"), exercise);
|
warn!(
|
||||||
println!("{}", style(formatstr).green());
|
"Compiling of {} failed! Please try again. Here's the output:",
|
||||||
exercise.clean();
|
|
||||||
Ok(skip_prompt || prompt_for_completion(exercise))
|
|
||||||
} else {
|
|
||||||
let formatstr = format!(
|
|
||||||
"{} Testing of {} failed! Please try again. Here's the output:",
|
|
||||||
Emoji("⚠️ ", "!"),
|
|
||||||
exercise
|
exercise
|
||||||
);
|
);
|
||||||
println!("{}", style(formatstr).red());
|
println!("{}", output.stderr);
|
||||||
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
|
return Err(());
|
||||||
exercise.clean();
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = compilation.run();
|
||||||
|
progress_bar.finish_and_clear();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
if let RunMode::Interactive = run_mode {
|
||||||
|
Ok(prompt_for_completion(&exercise))
|
||||||
|
} else {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(output) => {
|
||||||
|
warn!(
|
||||||
|
"Testing of {} failed! Please try again. Here's the output:",
|
||||||
|
exercise
|
||||||
|
);
|
||||||
|
println!("{}", output.stdout);
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
progress_bar.finish_and_clear();
|
|
||||||
let formatstr = format!(
|
|
||||||
"{} Compiling of {} failed! Please try again. Here's the output:",
|
|
||||||
Emoji("⚠️ ", "!"),
|
|
||||||
exercise
|
|
||||||
);
|
|
||||||
println!("{}", style(formatstr).red());
|
|
||||||
println!("{}", String::from_utf8_lossy(&compile_output.stderr));
|
|
||||||
exercise.clean();
|
|
||||||
Err(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue