mirror of
https://github.com/rust-lang/rustlings
synced 2024-11-05 00:00:12 +00:00
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::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{self, Command, Output};
|
||||
use std::process::{self, Command};
|
||||
|
||||
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
|
||||
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 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 {
|
||||
pub fn compile(&self) -> Output {
|
||||
match self.mode {
|
||||
pub fn compile(&self) -> Result<CompiledExercise, ExerciseOutput> {
|
||||
let cmd = match self.mode {
|
||||
Mode::Compile => Command::new("rustc")
|
||||
.args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
|
||||
.args(RUSTC_COLOR_ARGS)
|
||||
@ -59,17 +84,37 @@ impl Exercise {
|
||||
.args(RUSTC_COLOR_ARGS)
|
||||
.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 {
|
||||
Command::new(&temp_file())
|
||||
fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
||||
let cmd = Command::new(&temp_file())
|
||||
.output()
|
||||
.expect("Failed to run 'run' command")
|
||||
}
|
||||
.expect("Failed to run 'run' command");
|
||||
|
||||
pub fn clean(&self) {
|
||||
let _ignored = remove_file(&temp_file());
|
||||
let output = ExerciseOutput {
|
||||
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 {
|
||||
@ -121,6 +166,10 @@ impl Display for Exercise {
|
||||
}
|
||||
}
|
||||
|
||||
fn clean() {
|
||||
let _ignored = remove_file(&temp_file());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@ -131,11 +180,12 @@ mod test {
|
||||
File::create(&temp_file()).unwrap();
|
||||
let exercise = Exercise {
|
||||
name: String::from("example"),
|
||||
path: PathBuf::from("example.rs"),
|
||||
mode: Mode::Test,
|
||||
path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
|
||||
mode: Mode::Compile,
|
||||
hint: String::from(""),
|
||||
};
|
||||
exercise.clean();
|
||||
let compiled = exercise.compile().unwrap();
|
||||
drop(compiled);
|
||||
assert!(!Path::new(&temp_file()).exists());
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,9 @@ use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod ui;
|
||||
|
||||
mod exercise;
|
||||
mod run;
|
||||
mod verify;
|
||||
|
58
src/run.rs
58
src/run.rs
@ -1,6 +1,5 @@
|
||||
use crate::exercise::{Exercise, Mode};
|
||||
use crate::verify::test;
|
||||
use console::{style, Emoji};
|
||||
use indicatif::ProgressBar;
|
||||
|
||||
pub fn run(exercise: &Exercise) -> Result<(), ()> {
|
||||
@ -11,42 +10,41 @@ pub fn run(exercise: &Exercise) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
|
||||
fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
|
||||
let progress_bar = ProgressBar::new_spinner();
|
||||
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
||||
progress_bar.enable_steady_tick(100);
|
||||
|
||||
let compilecmd = exercise.compile();
|
||||
progress_bar.set_message(format!("Running {}...", exercise).as_str());
|
||||
if compilecmd.status.success() {
|
||||
let runcmd = exercise.run();
|
||||
let compilation_result = exercise.compile();
|
||||
let compilation = match compilation_result {
|
||||
Ok(compilation) => compilation,
|
||||
Err(output) => {
|
||||
progress_bar.finish_and_clear();
|
||||
|
||||
if runcmd.status.success() {
|
||||
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
|
||||
let formatstr = format!("{} Successfully ran {}", Emoji("✅", "✓"), exercise);
|
||||
println!("{}", style(formatstr).green());
|
||||
exercise.clean();
|
||||
Ok(())
|
||||
} else {
|
||||
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
|
||||
println!("{}", String::from_utf8_lossy(&runcmd.stderr));
|
||||
|
||||
let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), exercise);
|
||||
println!("{}", style(formatstr).red());
|
||||
exercise.clean();
|
||||
Err(())
|
||||
}
|
||||
} else {
|
||||
progress_bar.finish_and_clear();
|
||||
let formatstr = format!(
|
||||
"{} Compilation of {} failed! Compiler error message:\n",
|
||||
Emoji("⚠️ ", "!"),
|
||||
warn!(
|
||||
"Compilation of {} failed!, Compiler error message:\n",
|
||||
exercise
|
||||
);
|
||||
println!("{}", style(formatstr).red());
|
||||
println!("{}", String::from_utf8_lossy(&compilecmd.stderr));
|
||||
exercise.clean();
|
||||
println!("{}", output.stderr);
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
progress_bar.set_message(format!("Running {}...", exercise).as_str());
|
||||
let result = compilation.run();
|
||||
progress_bar.finish_and_clear();
|
||||
|
||||
match result {
|
||||
Ok(output) => {
|
||||
println!("{}", output.stdout);
|
||||
success!("Successfully ran {}", exercise);
|
||||
Ok(())
|
||||
}
|
||||
Err(output) => {
|
||||
println!("{}", output.stdout);
|
||||
println!("{}", output.stderr);
|
||||
|
||||
warn!("Ran {} with errors", exercise);
|
||||
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()
|
||||
);
|
||||
}};
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
use crate::exercise::{Exercise, Mode, State};
|
||||
use console::{style, Emoji};
|
||||
use console::style;
|
||||
use indicatif::ProgressBar;
|
||||
|
||||
pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<(), &'a Exercise> {
|
||||
for exercise in start_at {
|
||||
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),
|
||||
};
|
||||
if !compile_result.unwrap_or(false) {
|
||||
@ -15,8 +15,13 @@ pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum RunMode {
|
||||
Interactive,
|
||||
NonInteractive,
|
||||
}
|
||||
|
||||
pub fn test(exercise: &Exercise) -> Result<(), ()> {
|
||||
compile_and_test(exercise, true)?;
|
||||
compile_and_test(exercise, RunMode::NonInteractive)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -24,69 +29,64 @@ 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);
|
||||
let compile_output = exercise.compile();
|
||||
let compilation_result = exercise.compile();
|
||||
progress_bar.finish_and_clear();
|
||||
if compile_output.status.success() {
|
||||
let formatstr = format!("{} Successfully compiled {}!", Emoji("✅", "✓"), exercise);
|
||||
println!("{}", style(formatstr).green());
|
||||
exercise.clean();
|
||||
|
||||
match compilation_result {
|
||||
Ok(_) => {
|
||||
success!("Successfully compiled {}!", exercise);
|
||||
Ok(prompt_for_completion(&exercise))
|
||||
} else {
|
||||
let formatstr = format!(
|
||||
"{} Compilation of {} failed! Compiler error message:\n",
|
||||
Emoji("⚠️ ", "!"),
|
||||
}
|
||||
Err(output) => {
|
||||
warn!(
|
||||
"Compilation of {} failed! Compiler error message:\n",
|
||||
exercise
|
||||
);
|
||||
println!("{}", style(formatstr).red());
|
||||
println!("{}", String::from_utf8_lossy(&compile_output.stderr));
|
||||
exercise.clean();
|
||||
println!("{}", output.stderr);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_and_test_interactively(exercise: &Exercise) -> Result<bool, ()> {
|
||||
compile_and_test(exercise, false)
|
||||
}
|
||||
|
||||
fn compile_and_test(exercise: &Exercise, skip_prompt: bool) -> Result<bool, ()> {
|
||||
fn compile_and_test(exercise: &Exercise, run_mode: RunMode) -> Result<bool, ()> {
|
||||
let progress_bar = ProgressBar::new_spinner();
|
||||
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
|
||||
progress_bar.enable_steady_tick(100);
|
||||
|
||||
let compile_output = exercise.compile();
|
||||
if compile_output.status.success() {
|
||||
progress_bar.set_message(format!("Running {}...", exercise).as_str());
|
||||
let compilation_result = exercise.compile();
|
||||
|
||||
let runcmd = exercise.run();
|
||||
let compilation = match compilation_result {
|
||||
Ok(compilation) => compilation,
|
||||
Err(output) => {
|
||||
progress_bar.finish_and_clear();
|
||||
|
||||
if runcmd.status.success() {
|
||||
let formatstr = format!("{} Successfully tested {}!", Emoji("✅", "✓"), exercise);
|
||||
println!("{}", style(formatstr).green());
|
||||
exercise.clean();
|
||||
Ok(skip_prompt || prompt_for_completion(exercise))
|
||||
} else {
|
||||
let formatstr = format!(
|
||||
"{} Testing of {} failed! Please try again. Here's the output:",
|
||||
Emoji("⚠️ ", "!"),
|
||||
warn!(
|
||||
"Compiling of {} failed! Please try again. Here's the output:",
|
||||
exercise
|
||||
);
|
||||
println!("{}", style(formatstr).red());
|
||||
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
|
||||
exercise.clean();
|
||||
println!("{}", output.stderr);
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
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(())
|
||||
}
|
||||
} 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 New Issue
Block a user