Skip to content

Commit d01a71f

Browse files
committed
Extract exercise struct to encapsulate path logic
1 parent 04d1d4c commit d01a71f

File tree

6 files changed

+151
-128
lines changed

6 files changed

+151
-128
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ console = "0.6.2"
1111
syntect = "3.0.2"
1212
notify = "4.0.0"
1313
toml = "0.4.10"
14+
serde = {version = "1.0.10", features = ["derive"]}
1415

1516
[[bin]]
1617
name = "rustlings"

src/exercise.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use std::fmt::{self, Display, Formatter};
2+
use std::fs::{remove_file};
3+
use std::path::{PathBuf};
4+
use std::process::{self, Command, Output};
5+
use serde::Deserialize;
6+
7+
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
8+
9+
fn temp_file() -> String {
10+
format!("./temp_{}", process::id())
11+
}
12+
13+
#[derive(Deserialize)]
14+
#[serde(rename_all = "lowercase")]
15+
pub enum Mode {
16+
Compile,
17+
Test,
18+
}
19+
20+
#[derive(Deserialize)]
21+
pub struct ExerciseList {
22+
pub exercises: Vec<Exercise>,
23+
}
24+
25+
#[derive(Deserialize)]
26+
pub struct Exercise {
27+
pub path: PathBuf,
28+
pub mode: Mode,
29+
}
30+
31+
impl Exercise {
32+
pub fn compile(&self) -> Output {
33+
match self.mode {
34+
Mode::Compile => Command::new("rustc")
35+
.args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
36+
.args(RUSTC_COLOR_ARGS)
37+
.output(),
38+
Mode::Test => Command::new("rustc")
39+
.args(&["--test", self.path.to_str().unwrap(), "-o", &temp_file()])
40+
.args(RUSTC_COLOR_ARGS)
41+
.output(),
42+
}
43+
.expect("Failed to run 'compile' command.")
44+
}
45+
46+
pub fn run(&self) -> Output {
47+
Command::new(&temp_file())
48+
.output()
49+
.expect("Failed to run 'run' command")
50+
}
51+
52+
pub fn clean(&self) {
53+
let _ignored = remove_file(&temp_file());
54+
}
55+
}
56+
57+
impl Display for Exercise {
58+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
59+
write!(f, "{}", self.path.to_str().unwrap())
60+
}
61+
}
62+
63+
#[test]
64+
fn test_clean() {
65+
std::fs::File::create(&temp_file()).unwrap();
66+
let exercise = Exercise {
67+
path: PathBuf::from("example.rs"),
68+
mode: Mode::Test,
69+
};
70+
exercise.clean();
71+
assert!(!std::path::Path::new(&temp_file()).exists());
72+
}

src/main.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use crate::exercise::{Exercise, ExerciseList};
12
use crate::run::run;
23
use crate::verify::verify;
34
use clap::{crate_version, App, Arg, SubCommand};
45
use notify::DebouncedEvent;
56
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
67
use std::ffi::OsStr;
8+
use std::fs;
79
use std::io::BufRead;
810
use std::path::Path;
911
use std::sync::mpsc::channel;
@@ -13,8 +15,8 @@ use syntect::highlighting::{Style, ThemeSet};
1315
use syntect::parsing::SyntaxSet;
1416
use syntect::util::as_24_bit_terminal_escaped;
1517

18+
mod exercise;
1619
mod run;
17-
mod util;
1820
mod verify;
1921

2022
fn main() {
@@ -56,16 +58,33 @@ fn main() {
5658
std::process::exit(1);
5759
}
5860

59-
if let Some(matches) = matches.subcommand_matches("run") {
60-
run(matches.clone()).unwrap_or_else(|_| std::process::exit(1));
61+
let toml_str = &fs::read_to_string("info.toml").unwrap();
62+
let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;
63+
64+
if let Some(ref matches) = matches.subcommand_matches("run") {
65+
let filename = matches.value_of("file").unwrap_or_else(|| {
66+
println!("Please supply a file name!");
67+
std::process::exit(1);
68+
});
69+
70+
let filepath = Path::new(filename).canonicalize().unwrap();
71+
let exercise = exercises
72+
.iter()
73+
.find(|e| filepath.ends_with(&e.path))
74+
.unwrap_or_else(|| {
75+
println!("No exercise found for your file name!");
76+
std::process::exit(1)
77+
});
78+
79+
run(&exercise).unwrap_or_else(|_| std::process::exit(1));
6180
}
6281

6382
if matches.subcommand_matches("verify").is_some() {
64-
verify(None).unwrap_or_else(|_| std::process::exit(1));
83+
verify(&exercises).unwrap_or_else(|_| std::process::exit(1));
6584
}
6685

6786
if matches.subcommand_matches("watch").is_some() {
68-
watch().unwrap();
87+
watch(&exercises).unwrap();
6988
}
7089

7190
if matches.subcommand_name().is_none() {
@@ -81,21 +100,25 @@ fn main() {
81100
println!("\x1b[0m");
82101
}
83102

84-
fn watch() -> notify::Result<()> {
103+
fn watch(exercises: &[Exercise]) -> notify::Result<()> {
85104
let (tx, rx) = channel();
86105

87106
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
88107
watcher.watch(Path::new("./exercises"), RecursiveMode::Recursive)?;
89108

90-
let _ignored = verify(None);
109+
let _ignored = verify(exercises.iter());
91110

92111
loop {
93112
match rx.recv() {
94113
Ok(event) => match event {
95114
DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => {
96115
if b.extension() == Some(OsStr::new("rs")) {
97116
println!("----------**********----------\n");
98-
let _ignored = verify(Some(b.as_path().to_str().unwrap()));
117+
let filepath = b.as_path().canonicalize().unwrap();
118+
let exercise = exercises
119+
.iter()
120+
.skip_while(|e| !filepath.ends_with(&e.path));
121+
let _ignored = verify(exercise);
99122
}
100123
}
101124
_ => {}

src/run.rs

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,52 @@
1-
use crate::util;
1+
use crate::exercise::{Mode, Exercise};
22
use crate::verify::test;
33
use console::{style, Emoji};
44
use indicatif::ProgressBar;
5-
use std::fs;
6-
use toml::Value;
75

8-
pub fn run(matches: clap::ArgMatches) -> Result<(), ()> {
9-
if let Some(filename) = matches.value_of("file") {
10-
let toml: Value = fs::read_to_string("info.toml").unwrap().parse().unwrap();
11-
let tomlvec: &Vec<Value> = toml.get("exercises").unwrap().as_array().unwrap();
12-
let mut exercises = tomlvec.clone();
13-
exercises.retain(|i| i.get("path").unwrap().as_str().unwrap() == filename);
14-
if exercises.is_empty() {
15-
println!("No exercise found for your filename!");
16-
std::process::exit(1);
17-
}
18-
19-
let exercise: &Value = &exercises[0];
20-
match exercise.get("mode").unwrap().as_str().unwrap() {
21-
"test" => test(exercise.get("path").unwrap().as_str().unwrap())?,
22-
"compile" => compile_and_run(exercise.get("path").unwrap().as_str().unwrap())?,
23-
_ => (),
24-
}
25-
Ok(())
26-
} else {
27-
panic!("Please supply a filename!");
6+
pub fn run(exercise: &Exercise) -> Result<(), ()> {
7+
match exercise.mode {
8+
Mode::Test => test(exercise)?,
9+
Mode::Compile => compile_and_run(exercise)?,
2810
}
11+
Ok(())
2912
}
3013

31-
pub fn compile_and_run(filename: &str) -> Result<(), ()> {
14+
pub fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
3215
let progress_bar = ProgressBar::new_spinner();
33-
progress_bar.set_message(format!("Compiling {}...", filename).as_str());
16+
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
3417
progress_bar.enable_steady_tick(100);
3518

36-
let compilecmd = util::compile_cmd(filename);
37-
progress_bar.set_message(format!("Running {}...", filename).as_str());
19+
let compilecmd = exercise.compile();
20+
progress_bar.set_message(format!("Running {}...", exercise).as_str());
3821
if compilecmd.status.success() {
39-
let runcmd = util::run_cmd();
22+
let runcmd = exercise.run();
4023
progress_bar.finish_and_clear();
4124

4225
if runcmd.status.success() {
4326
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
44-
let formatstr = format!("{} Successfully ran {}", Emoji("✅", "✓"), filename);
27+
let formatstr = format!("{} Successfully ran {}", Emoji("✅", "✓"), exercise);
4528
println!("{}", style(formatstr).green());
46-
util::clean();
29+
exercise.clean();
4730
Ok(())
4831
} else {
4932
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
5033
println!("{}", String::from_utf8_lossy(&runcmd.stderr));
5134

52-
let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), filename);
35+
let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), exercise);
5336
println!("{}", style(formatstr).red());
54-
util::clean();
37+
exercise.clean();
5538
Err(())
5639
}
5740
} else {
5841
progress_bar.finish_and_clear();
5942
let formatstr = format!(
6043
"{} Compilation of {} failed! Compiler error message:\n",
6144
Emoji("⚠️ ", "!"),
62-
filename
45+
exercise
6346
);
6447
println!("{}", style(formatstr).red());
6548
println!("{}", String::from_utf8_lossy(&compilecmd.stderr));
66-
util::clean();
49+
exercise.clean();
6750
Err(())
6851
}
6952
}

src/util.rs

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)