From c82c3673245ca11d455b067c97fadda4a8406cb9 Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 27 Apr 2024 04:14:59 +0200 Subject: Respect the target-dir config and show tests' output --- src/cmd.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/cmd.rs (limited to 'src/cmd.rs') diff --git a/src/cmd.rs b/src/cmd.rs new file mode 100644 index 0000000..28f21c5 --- /dev/null +++ b/src/cmd.rs @@ -0,0 +1,70 @@ +use anyhow::{Context, Result}; +use std::{io::Read, path::Path, process::Command}; + +pub fn run_cmd(mut cmd: Command, description: &str, output: &mut Vec) -> Result { + let (mut reader, writer) = os_pipe::pipe() + .with_context(|| format!("Failed to create a pipe to run the command `{description}``"))?; + + let writer_clone = writer.try_clone().with_context(|| { + format!("Failed to clone the pipe writer for the command `{description}`") + })?; + + let mut handle = cmd + .stdout(writer_clone) + .stderr(writer) + .spawn() + .with_context(|| format!("Failed to run the command `{description}`"))?; + + // Prevent pipe deadlock. + drop(cmd); + + reader + .read_to_end(output) + .with_context(|| format!("Failed to read the output of the command `{description}`"))?; + + output.push(b'\n'); + + handle + .wait() + .with_context(|| format!("Failed to wait on the command `{description}` to exit")) + .map(|status| status.success()) +} + +pub struct CargoCmd<'a> { + pub subcommand: &'a str, + pub args: &'a [&'a str], + pub exercise_name: &'a str, + pub description: &'a str, + pub hide_warnings: bool, + pub target_dir: &'a Path, + pub output: &'a mut Vec, + pub dev: bool, +} + +impl<'a> CargoCmd<'a> { + pub fn run(&mut self) -> Result { + let mut cmd = Command::new("cargo"); + cmd.arg(self.subcommand); + + // A hack to make `cargo run` work when developing Rustlings. + if self.dev { + cmd.arg("--manifest-path") + .arg("dev/Cargo.toml") + .arg("--target-dir") + .arg(self.target_dir); + } + + cmd.arg("--color") + .arg("always") + .arg("-q") + .arg("--bin") + .arg(self.exercise_name) + .args(self.args); + + if self.hide_warnings { + cmd.env("RUSTFLAGS", "-A warnings"); + } + + run_cmd(cmd, self.description, self.output) + } +} -- cgit v1.2.3 From 32415e1e6cca9e0fb9a3019ed8e75956c7f7f92e Mon Sep 17 00:00:00 2001 From: mo8it Date: Wed, 1 May 2024 17:55:49 +0200 Subject: Document cmd --- src/cmd.rs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/cmd.rs') diff --git a/src/cmd.rs b/src/cmd.rs index 28f21c5..e4bc112 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -1,6 +1,8 @@ use anyhow::{Context, Result}; use std::{io::Read, path::Path, process::Command}; +// Run a command with a description for a possible error and append the merged stdout and stderr. +// The boolean in the returned `Result` is true if the command's exit status is success. pub fn run_cmd(mut cmd: Command, description: &str, output: &mut Vec) -> Result { let (mut reader, writer) = os_pipe::pipe() .with_context(|| format!("Failed to create a pipe to run the command `{description}``"))?; @@ -35,13 +37,18 @@ pub struct CargoCmd<'a> { pub args: &'a [&'a str], pub exercise_name: &'a str, pub description: &'a str, + // RUSTFLAGS="-A warnings" pub hide_warnings: bool, + // Added as `--target-dir` if `Self::dev` is true. pub target_dir: &'a Path, + // The output buffer to append the merged stdout and stderr. pub output: &'a mut Vec, + // true while developing Rustlings. pub dev: bool, } impl<'a> CargoCmd<'a> { + // Run `cargo SUBCOMMAND --bin EXERCISE_NAME … ARGS`. pub fn run(&mut self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg(self.subcommand); -- cgit v1.2.3 From d425dbe203c17166e2e0b5692695448f0cb85513 Mon Sep 17 00:00:00 2001 From: mo8it Date: Wed, 1 May 2024 18:08:18 +0200 Subject: Test run_cmd --- src/cmd.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/cmd.rs') diff --git a/src/cmd.rs b/src/cmd.rs index e4bc112..9762cf8 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -75,3 +75,19 @@ impl<'a> CargoCmd<'a> { run_cmd(cmd, self.description, self.output) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_run_cmd() { + let mut cmd = Command::new("echo"); + cmd.arg("Hello"); + + let mut output = Vec::with_capacity(8); + run_cmd(cmd, "echo …", &mut output).unwrap(); + + assert_eq!(output, b"Hello\n\n"); + } +} -- cgit v1.2.3 From d48e86b1540dcf649412c088cc50161f3e356e26 Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 13 May 2024 21:40:40 +0200 Subject: Use public comments for public items --- src/app_state.rs | 10 +++++----- src/cargo_toml.rs | 14 +++++++------- src/cmd.rs | 14 +++++++------- src/embedded.rs | 10 +++++----- src/exercise.rs | 2 +- 5 files changed, 25 insertions(+), 25 deletions(-) (limited to 'src/cmd.rs') diff --git a/src/app_state.rs b/src/app_state.rs index b10ebb5..c7c090f 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -330,8 +330,8 @@ impl AppState { } } - // Official exercises: Dump the solution file form the binary and return its path. - // Third-party exercises: Check if a solution file exists and return its path in that case. + /// Official exercises: Dump the solution file form the binary and return its path. + /// Third-party exercises: Check if a solution file exists and return its path in that case. pub fn current_solution_path(&self) -> Result> { if DEBUG_PROFILE { return Ok(None); @@ -358,9 +358,9 @@ impl AppState { } } - // Mark the current exercise as done and move on to the next pending exercise if one exists. - // If all exercises are marked as done, run all of them to make sure that they are actually - // done. If an exercise which is marked as done fails, mark it as pending and continue on it. + /// Mark the current exercise as done and move on to the next pending exercise if one exists. + /// If all exercises are marked as done, run all of them to make sure that they are actually + /// done. If an exercise which is marked as done fails, mark it as pending and continue on it. pub fn done_current_exercise(&mut self, writer: &mut StdoutLock) -> Result { let exercise = &mut self.exercises[self.current_exercise_ind]; if !exercise.done { diff --git a/src/cargo_toml.rs b/src/cargo_toml.rs index 106e6a7..b7951f6 100644 --- a/src/cargo_toml.rs +++ b/src/cargo_toml.rs @@ -2,10 +2,10 @@ use anyhow::{Context, Result}; use crate::info_file::ExerciseInfo; -// Return the start and end index of the content of the list `bin = […]`. -// bin = [xxxxxxxxxxxxxxxxx] -// |start_ind | -// |end_ind +/// Return the start and end index of the content of the list `bin = […]`. +/// bin = [xxxxxxxxxxxxxxxxx] +/// |start_ind | +/// |end_ind pub fn bins_start_end_ind(cargo_toml: &str) -> Result<(usize, usize)> { let start_ind = cargo_toml .find("bin = [") @@ -20,8 +20,8 @@ pub fn bins_start_end_ind(cargo_toml: &str) -> Result<(usize, usize)> { Ok((start_ind, end_ind)) } -// Generate and append the content of the `bin` list in `Cargo.toml`. -// The `exercise_path_prefix` is the prefix of the `path` field of every list entry. +/// Generate and append the content of the `bin` list in `Cargo.toml`. +/// The `exercise_path_prefix` is the prefix of the `path` field of every list entry. pub fn append_bins( buf: &mut Vec, exercise_infos: &[ExerciseInfo], @@ -43,7 +43,7 @@ pub fn append_bins( } } -// Update the `bin` list and leave everything else unchanged. +/// Update the `bin` list and leave everything else unchanged. pub fn updated_cargo_toml( exercise_infos: &[ExerciseInfo], current_cargo_toml: &str, diff --git a/src/cmd.rs b/src/cmd.rs index 9762cf8..b914ed8 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -1,8 +1,8 @@ use anyhow::{Context, Result}; use std::{io::Read, path::Path, process::Command}; -// Run a command with a description for a possible error and append the merged stdout and stderr. -// The boolean in the returned `Result` is true if the command's exit status is success. +/// Run a command with a description for a possible error and append the merged stdout and stderr. +/// The boolean in the returned `Result` is true if the command's exit status is success. pub fn run_cmd(mut cmd: Command, description: &str, output: &mut Vec) -> Result { let (mut reader, writer) = os_pipe::pipe() .with_context(|| format!("Failed to create a pipe to run the command `{description}``"))?; @@ -37,18 +37,18 @@ pub struct CargoCmd<'a> { pub args: &'a [&'a str], pub exercise_name: &'a str, pub description: &'a str, - // RUSTFLAGS="-A warnings" + /// RUSTFLAGS="-A warnings" pub hide_warnings: bool, - // Added as `--target-dir` if `Self::dev` is true. + /// Added as `--target-dir` if `Self::dev` is true. pub target_dir: &'a Path, - // The output buffer to append the merged stdout and stderr. + /// The output buffer to append the merged stdout and stderr. pub output: &'a mut Vec, - // true while developing Rustlings. + /// true while developing Rustlings. pub dev: bool, } impl<'a> CargoCmd<'a> { - // Run `cargo SUBCOMMAND --bin EXERCISE_NAME … ARGS`. + /// Run `cargo SUBCOMMAND --bin EXERCISE_NAME … ARGS`. pub fn run(&mut self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg(self.subcommand); diff --git a/src/embedded.rs b/src/embedded.rs index bc1a5cc..6f87068 100644 --- a/src/embedded.rs +++ b/src/embedded.rs @@ -6,7 +6,7 @@ use std::{ use crate::info_file::ExerciseInfo; -// Contains all embedded files. +/// Contains all embedded files. pub static EMBEDDED_FILES: EmbeddedFiles = rustlings_macros::include_files!(); #[derive(Clone, Copy)] @@ -73,16 +73,16 @@ impl ExerciseDir { } } -// All embedded files. +/// All embedded files. pub struct EmbeddedFiles { - // `info.toml` + /// The content of the `info.toml` file. pub info_file: &'static str, exercise_files: &'static [ExerciseFiles], exercise_dirs: &'static [ExerciseDir], } impl EmbeddedFiles { - // Dump all the embedded files of the `exercises/` direcotry. + /// Dump all the embedded files of the `exercises/` direcotry. pub fn init_exercises_dir(&self, exercise_infos: &[ExerciseInfo]) -> Result<()> { create_dir("exercises").context("Failed to create the directory `exercises`")?; @@ -110,7 +110,7 @@ impl EmbeddedFiles { WriteStrategy::Overwrite.write(path, exercise_files.exercise) } - // Write the solution file to disk and return its path. + /// Write the solution file to disk and return its path. pub fn write_solution_to_disk( &self, exercise_ind: usize, diff --git a/src/exercise.rs b/src/exercise.rs index 494fc42..a63c9aa 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -14,7 +14,7 @@ use crate::{ DEBUG_PROFILE, }; -// The initial capacity of the output buffer. +/// The initial capacity of the output buffer. pub const OUTPUT_CAPACITY: usize = 1 << 14; pub struct Exercise { -- cgit v1.2.3 From 611f9d8722593430d82187aebee9db5cc6952da1 Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 1 Jun 2024 21:48:15 +0200 Subject: Check that all solutions run successfully --- src/app_state.rs | 41 ++++++++++-------- src/cmd.rs | 4 +- src/dev/check.rs | 59 +++++++++++++++++-------- src/exercise.rs | 124 +++++++++++++++++++++++++++++++++++------------------ src/info_file.rs | 19 +++++++- src/run.rs | 4 +- src/watch/state.rs | 4 +- 7 files changed, 169 insertions(+), 86 deletions(-) (limited to 'src/cmd.rs') diff --git a/src/app_state.rs b/src/app_state.rs index c7c090f..e9a5b10 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -11,7 +11,7 @@ use std::{ use crate::{ clear_terminal, embedded::EMBEDDED_FILES, - exercise::{Exercise, OUTPUT_CAPACITY}, + exercise::{Exercise, RunnableExercise, OUTPUT_CAPACITY}, info_file::ExerciseInfo, DEBUG_PROFILE, }; @@ -40,6 +40,25 @@ struct CargoMetadata { target_directory: PathBuf, } +pub fn parse_target_dir() -> Result { + // Get the target directory from Cargo. + let metadata_output = Command::new("cargo") + .arg("metadata") + .arg("-q") + .arg("--format-version") + .arg("1") + .arg("--no-deps") + .stdin(Stdio::null()) + .stderr(Stdio::inherit()) + .output() + .context(CARGO_METADATA_ERR)? + .stdout; + + serde_json::de::from_slice::(&metadata_output) + .context("Failed to read the field `target_directory` from the `cargo metadata` output") + .map(|metadata| metadata.target_directory) +} + pub struct AppState { current_exercise_ind: usize, exercises: Vec, @@ -104,23 +123,7 @@ impl AppState { exercise_infos: Vec, final_message: String, ) -> Result<(Self, StateFileStatus)> { - // Get the target directory from Cargo. - let metadata_output = Command::new("cargo") - .arg("metadata") - .arg("-q") - .arg("--format-version") - .arg("1") - .arg("--no-deps") - .stdin(Stdio::null()) - .stderr(Stdio::inherit()) - .output() - .context(CARGO_METADATA_ERR)? - .stdout; - let target_dir = serde_json::de::from_slice::(&metadata_output) - .context( - "Failed to read the field `target_directory` from the `cargo metadata` output", - )? - .target_directory; + let target_dir = parse_target_dir()?; let exercises = exercise_infos .into_iter() @@ -381,7 +384,7 @@ impl AppState { write!(writer, "Running {exercise} ... ")?; writer.flush()?; - let success = exercise.run(&mut output, &self.target_dir)?; + let success = exercise.run_exercise(&mut output, &self.target_dir)?; if !success { writeln!(writer, "{}\n", "FAILED".red())?; diff --git a/src/cmd.rs b/src/cmd.rs index b914ed8..6092f53 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -35,7 +35,7 @@ pub fn run_cmd(mut cmd: Command, description: &str, output: &mut Vec) -> Res pub struct CargoCmd<'a> { pub subcommand: &'a str, pub args: &'a [&'a str], - pub exercise_name: &'a str, + pub bin_name: &'a str, pub description: &'a str, /// RUSTFLAGS="-A warnings" pub hide_warnings: bool, @@ -65,7 +65,7 @@ impl<'a> CargoCmd<'a> { .arg("always") .arg("-q") .arg("--bin") - .arg(self.exercise_name) + .arg(self.bin_name) .args(self.args); if self.hide_warnings { diff --git a/src/dev/check.rs b/src/dev/check.rs index 59352e9..6a3597c 100644 --- a/src/dev/check.rs +++ b/src/dev/check.rs @@ -2,12 +2,14 @@ use anyhow::{anyhow, bail, Context, Error, Result}; use std::{ cmp::Ordering, fs::{self, read_dir, OpenOptions}, - io::Read, + io::{self, Read, Write}, path::{Path, PathBuf}, }; use crate::{ + app_state::parse_target_dir, cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY}, + exercise::{RunnableExercise, OUTPUT_CAPACITY}, info_file::{ExerciseInfo, InfoFile}, CURRENT_FORMAT_VERSION, DEBUG_PROFILE, }; @@ -17,6 +19,29 @@ fn forbidden_char(input: &str) -> Option { input.chars().find(|c| !c.is_alphanumeric() && *c != '_') } +// Check that the Cargo.toml file is up-to-date. +fn check_cargo_toml( + exercise_infos: &[ExerciseInfo], + current_cargo_toml: &str, + exercise_path_prefix: &[u8], +) -> Result<()> { + let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?; + + let old_bins = ¤t_cargo_toml.as_bytes()[bins_start_ind..bins_end_ind]; + let mut new_bins = Vec::with_capacity(BINS_BUFFER_CAPACITY); + append_bins(&mut new_bins, exercise_infos, exercise_path_prefix); + + if old_bins != new_bins { + if DEBUG_PROFILE { + bail!("The file `dev/Cargo.toml` is outdated. Please run `cargo run -- dev update` to update it"); + } + + bail!("The file `Cargo.toml` is outdated. Please run `rustlings dev update` to update it"); + } + + Ok(()) +} + // Check the info of all exercises and return their paths in a set. fn check_info_file_exercises(info_file: &InfoFile) -> Result> { let mut names = hashbrown::HashSet::with_capacity(info_file.exercises.len()); @@ -136,24 +161,20 @@ fn check_exercises(info_file: &InfoFile) -> Result<()> { Ok(()) } -// Check that the Cargo.toml file is up-to-date. -fn check_cargo_toml( - exercise_infos: &[ExerciseInfo], - current_cargo_toml: &str, - exercise_path_prefix: &[u8], -) -> Result<()> { - let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?; +fn check_solutions(info_file: &InfoFile) -> Result<()> { + let target_dir = parse_target_dir()?; + let mut output = Vec::with_capacity(OUTPUT_CAPACITY); - let old_bins = ¤t_cargo_toml.as_bytes()[bins_start_ind..bins_end_ind]; - let mut new_bins = Vec::with_capacity(BINS_BUFFER_CAPACITY); - append_bins(&mut new_bins, exercise_infos, exercise_path_prefix); - - if old_bins != new_bins { - if DEBUG_PROFILE { - bail!("The file `dev/Cargo.toml` is outdated. Please run `cargo run -- dev update` to update it"); + for exercise_info in &info_file.exercises { + let success = exercise_info.run_solution(&mut output, &target_dir)?; + if !success { + io::stderr().write_all(&output)?; + + bail!( + "Failed to run the solution of the exercise {}", + exercise_info.name, + ); } - - bail!("The file `Cargo.toml` is outdated. Please run `rustlings dev update` to update it"); } Ok(()) @@ -161,7 +182,6 @@ fn check_cargo_toml( pub fn check() -> Result<()> { let info_file = InfoFile::parse()?; - check_exercises(&info_file)?; // A hack to make `cargo run -- dev check` work when developing Rustlings. if DEBUG_PROFILE { @@ -176,6 +196,9 @@ pub fn check() -> Result<()> { check_cargo_toml(&info_file.exercises, ¤t_cargo_toml, b"")?; } + check_exercises(&info_file)?; + check_solutions(&info_file)?; + println!("\nEverything looks fine!"); Ok(()) diff --git a/src/exercise.rs b/src/exercise.rs index 4bc37cd..b6adc14 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -17,6 +17,35 @@ use crate::{ /// The initial capacity of the output buffer. pub const OUTPUT_CAPACITY: usize = 1 << 14; +// Run an exercise binary and append its output to the `output` buffer. +// Compilation must be done before calling this method. +fn run_bin(bin_name: &str, output: &mut Vec, target_dir: &Path) -> Result { + writeln!(output, "{}", "Output".underlined())?; + + // 7 = "/debug/".len() + let mut bin_path = PathBuf::with_capacity(target_dir.as_os_str().len() + 7 + bin_name.len()); + bin_path.push(target_dir); + bin_path.push("debug"); + bin_path.push(bin_name); + + let success = run_cmd(Command::new(&bin_path), &bin_path.to_string_lossy(), output)?; + + if !success { + // This output is important to show the user that something went wrong. + // Otherwise, calling something like `exit(1)` in an exercise without further output + // leaves the user confused about why the exercise isn't done yet. + writeln!( + output, + "{}", + "The exercise didn't run successfully (nonzero exit code)" + .bold() + .red(), + )?; + } + + Ok(success) +} + /// See `info_file::ExerciseInfo` pub struct Exercise { pub dir: Option<&'static str>, @@ -30,39 +59,25 @@ pub struct Exercise { } impl Exercise { - // Run the exercise's binary and append its output to the `output` buffer. - // Compilation should be done before calling this method. - fn run_bin(&self, output: &mut Vec, target_dir: &Path) -> Result { - writeln!(output, "{}", "Output".underlined())?; - - // 7 = "/debug/".len() - let mut bin_path = - PathBuf::with_capacity(target_dir.as_os_str().len() + 7 + self.name.len()); - bin_path.push(target_dir); - bin_path.push("debug"); - bin_path.push(self.name); - - let success = run_cmd(Command::new(&bin_path), &bin_path.to_string_lossy(), output)?; - - if !success { - // This output is important to show the user that something went wrong. - // Otherwise, calling something like `exit(1)` in an exercise without further output - // leaves the user confused about why the exercise isn't done yet. - writeln!( - output, - "{}", - "The exercise didn't run successfully (nonzero exit code)" - .bold() - .red(), - )?; - } + pub fn terminal_link(&self) -> StyledContent> { + style(TerminalFileLink(self.path)).underlined().blue() + } +} - Ok(success) +impl Display for Exercise { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.path.fmt(f) } +} - /// Compile, check and run the exercise. - /// The output is written to the `output` buffer after clearing it. - pub fn run(&self, output: &mut Vec, target_dir: &Path) -> Result { +pub trait RunnableExercise { + fn name(&self) -> &str; + fn strict_clippy(&self) -> bool; + fn test(&self) -> bool; + + // Compile, check and run the exercise or its solution (depending on `bin_name´). + // The output is written to the `output` buffer after clearing it. + fn run(&self, bin_name: &str, output: &mut Vec, target_dir: &Path) -> Result { output.clear(); // Developing the official Rustlings. @@ -71,7 +86,7 @@ impl Exercise { let build_success = CargoCmd { subcommand: "build", args: &[], - exercise_name: self.name, + bin_name, description: "cargo build …", hide_warnings: false, target_dir, @@ -87,7 +102,7 @@ impl Exercise { output.clear(); // `--profile test` is required to also check code with `[cfg(test)]`. - let clippy_args: &[&str] = if self.strict_clippy { + let clippy_args: &[&str] = if self.strict_clippy() { &["--profile", "test", "--", "-D", "warnings"] } else { &["--profile", "test"] @@ -95,7 +110,7 @@ impl Exercise { let clippy_success = CargoCmd { subcommand: "clippy", args: clippy_args, - exercise_name: self.name, + bin_name, description: "cargo clippy …", hide_warnings: false, target_dir, @@ -107,14 +122,14 @@ impl Exercise { return Ok(false); } - if !self.test { - return self.run_bin(output, target_dir); + if !self.test() { + return run_bin(bin_name, output, target_dir); } let test_success = CargoCmd { subcommand: "test", args: &["--", "--color", "always", "--show-output"], - exercise_name: self.name, + bin_name, description: "cargo test …", // Hide warnings because they are shown by Clippy. hide_warnings: true, @@ -124,18 +139,43 @@ impl Exercise { } .run()?; - let run_success = self.run_bin(output, target_dir)?; + let run_success = run_bin(bin_name, output, target_dir)?; Ok(test_success && run_success) } - pub fn terminal_link(&self) -> StyledContent> { - style(TerminalFileLink(self.path)).underlined().blue() + /// Compile, check and run the exercise. + /// The output is written to the `output` buffer after clearing it. + #[inline] + fn run_exercise(&self, output: &mut Vec, target_dir: &Path) -> Result { + self.run(self.name(), output, target_dir) + } + + /// Compile, check and run the exercise's solution. + /// The output is written to the `output` buffer after clearing it. + fn run_solution(&self, output: &mut Vec, target_dir: &Path) -> Result { + let name = self.name(); + let mut bin_name = String::with_capacity(name.len()); + bin_name.push_str(name); + bin_name.push_str("_sol"); + + self.run(&bin_name, output, target_dir) } } -impl Display for Exercise { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.path.fmt(f) +impl RunnableExercise for Exercise { + #[inline] + fn name(&self) -> &str { + self.name + } + + #[inline] + fn strict_clippy(&self) -> bool { + self.strict_clippy + } + + #[inline] + fn test(&self) -> bool { + self.test } } diff --git a/src/info_file.rs b/src/info_file.rs index 5ea487f..f226f73 100644 --- a/src/info_file.rs +++ b/src/info_file.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Context, Error, Result}; use serde::Deserialize; use std::{fs, io::ErrorKind}; -use crate::embedded::EMBEDDED_FILES; +use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise}; /// Deserialized from the `info.toml` file. #[derive(Deserialize)] @@ -75,6 +75,23 @@ impl ExerciseInfo { } } +impl RunnableExercise for ExerciseInfo { + #[inline] + fn name(&self) -> &str { + &self.name + } + + #[inline] + fn strict_clippy(&self) -> bool { + self.strict_clippy + } + + #[inline] + fn test(&self) -> bool { + self.test + } +} + /// The deserialized `info.toml` file. #[derive(Deserialize)] pub struct InfoFile { diff --git a/src/run.rs b/src/run.rs index 36899b9..899d0a9 100644 --- a/src/run.rs +++ b/src/run.rs @@ -4,14 +4,14 @@ use std::io::{self, Write}; use crate::{ app_state::{AppState, ExercisesProgress}, - exercise::OUTPUT_CAPACITY, + exercise::{RunnableExercise, OUTPUT_CAPACITY}, terminal_link::TerminalFileLink, }; pub fn run(app_state: &mut AppState) -> Result<()> { let exercise = app_state.current_exercise(); let mut output = Vec::with_capacity(OUTPUT_CAPACITY); - let success = exercise.run(&mut output, app_state.target_dir())?; + let success = exercise.run_exercise(&mut output, app_state.target_dir())?; let mut stdout = io::stdout().lock(); stdout.write_all(&output)?; diff --git a/src/watch/state.rs b/src/watch/state.rs index 14c3f01..dd43c56 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -8,7 +8,7 @@ use std::io::{self, StdoutLock, Write}; use crate::{ app_state::{AppState, ExercisesProgress}, clear_terminal, - exercise::OUTPUT_CAPACITY, + exercise::{RunnableExercise, OUTPUT_CAPACITY}, progress_bar::progress_bar, terminal_link::TerminalFileLink, }; @@ -54,7 +54,7 @@ impl<'a> WatchState<'a> { let success = self .app_state .current_exercise() - .run(&mut self.output, self.app_state.target_dir())?; + .run_exercise(&mut self.output, self.app_state.target_dir())?; if success { self.done_status = if let Some(solution_path) = self.app_state.current_solution_path()? { -- cgit v1.2.3