Compare commits

...

21 commits

Author SHA1 Message Date
mo8it f995b4c041 Merge branch 'main' into watch 2024-03-27 14:41:26 +01:00
Mo b8a5886db4
Merge pull request #1914 from mo8it/toml
Reading the `info.toml` file
2024-03-27 14:30:59 +01:00
mo8it b9d2756ce8 Merge branch 'main' into toml 2024-03-27 14:30:10 +01:00
Mo 07dec76f7c
Merge pull request #1916 from mo8it/command
Pipe the output of command to null instead of capturing and ignoring it
2024-03-27 14:24:16 +01:00
Mo deeefcf16c
Merge pull request #1913 from mo8it/which
Use `which` instead of running `rustc --version`
2024-03-27 14:21:11 +01:00
mo8it 8e0f7e56f7 Merge branch 'main' into which 2024-03-27 14:18:20 +01:00
Mo 87ca05b4bb
Merge pull request #1915 from mo8it/home
Remove the home dependency since it is not used
2024-03-27 12:58:30 +01:00
Mo d69a8a7045
Merge pull request #1921 from mo8it/style
Style
2024-03-27 12:57:26 +01:00
mo8it 87001a68c0 The string doesn't have to be a raw string 2024-03-26 17:50:29 +01:00
mo8it a610fc1bc2 Remove unneeded closure 2024-03-26 17:50:10 +01:00
mo8it e89028581c Use == instead of eq 2024-03-26 17:49:55 +01:00
mo8it 980ffa2a2b Use == on simple enums 2024-03-26 17:49:48 +01:00
mo8it 1f2029ae55 Add missing semicolon 2024-03-26 17:49:25 +01:00
mo8it ed0fcf8e3d Formatting 2024-03-26 17:49:05 +01:00
mo8it f36efae25d Only use arg instead of args AND arg 2024-03-26 17:48:06 +01:00
mo8it 853d0593d0 Derive Eq when PartialEq is derived 2024-03-26 17:47:33 +01:00
mo8it d911586788 Pipe the output to null instead of capturing and ignoring it 2024-03-25 17:21:54 +01:00
mo8it dca3ea355e Remove the home dependency since it is not used 2024-03-25 14:10:51 +01:00
mo8it e4520602f5 Use the NotFound variant of the IO error 2024-03-25 02:41:45 +01:00
mo8it 83cd91ccca Replace toml with toml_edit 2024-03-25 02:35:51 +01:00
mo8it 51b4c240ed Use which instead of running rustc --version 2024-03-25 00:30:01 +01:00
6 changed files with 110 additions and 78 deletions

73
Cargo.lock generated
View file

@ -195,6 +195,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "either"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "0.3.6" version = "0.3.6"
@ -207,6 +213,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.23" version = "0.2.23"
@ -354,6 +370,12 @@ version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.21" version = "0.4.21"
@ -521,6 +543,19 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustix"
version = "0.38.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
dependencies = [
"bitflags 2.4.2",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "rustlings" name = "rustlings"
version = "5.6.1" version = "5.6.1"
@ -529,7 +564,6 @@ dependencies = [
"clap", "clap",
"console", "console",
"glob", "glob",
"home",
"indicatif", "indicatif",
"notify-debouncer-mini", "notify-debouncer-mini",
"predicates", "predicates",
@ -537,7 +571,8 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"shlex", "shlex",
"toml", "toml_edit",
"which",
] ]
[[package]] [[package]]
@ -624,18 +659,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]]
name = "toml"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.5" version = "0.6.5"
@ -647,9 +670,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.6" version = "0.22.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -701,6 +724,18 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "which"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7"
dependencies = [
"either",
"home",
"rustix",
"winsafe",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -872,3 +907,9 @@ checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"

View file

@ -12,14 +12,14 @@ edition = "2021"
clap = { version = "4.5.2", features = ["derive"] } clap = { version = "4.5.2", features = ["derive"] }
console = "0.15.8" console = "0.15.8"
glob = "0.3.0" glob = "0.3.0"
home = "0.5.9"
indicatif = "0.17.8" indicatif = "0.17.8"
notify-debouncer-mini = "0.4.1" notify-debouncer-mini = "0.4.1"
regex = "1.10.3" regex = "1.10.3"
serde_json = "1.0.114" serde_json = "1.0.114"
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
shlex = "1.3.0" shlex = "1.3.0"
toml = "0.8.10" toml_edit = { version = "0.22.9", default-features = false, features = ["parse", "serde"] }
which = "6.0.1"
[[bin]] [[bin]]
name = "rustlings" name = "rustlings"

View file

@ -5,7 +5,7 @@ use std::fmt::{self, Display, Formatter};
use std::fs::{self, remove_file, File}; use std::fs::{self, remove_file, File};
use std::io::Read; use std::io::Read;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{self, Command}; use std::process::{self, Command, Stdio};
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"]; const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
const RUSTC_EDITION_ARGS: &[&str] = &["--edition", "2021"]; const RUSTC_EDITION_ARGS: &[&str] = &["--edition", "2021"];
@ -58,7 +58,7 @@ pub struct Exercise {
// An enum to track of the state of an Exercise. // An enum to track of the state of an Exercise.
// An Exercise can be either Done or Pending // An Exercise can be either Done or Pending
#[derive(PartialEq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub enum State { pub enum State {
// The state of the exercise once it's been completed // The state of the exercise once it's been completed
Done, Done,
@ -67,7 +67,7 @@ pub enum State {
} }
// The context information of a pending exercise // The context information of a pending exercise
#[derive(PartialEq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub struct ContextLine { pub struct ContextLine {
// The source code that is still pending completion // The source code that is still pending completion
pub line: String, pub line: String,
@ -148,7 +148,10 @@ path = "{}.rs""#,
.args(RUSTC_COLOR_ARGS) .args(RUSTC_COLOR_ARGS)
.args(RUSTC_EDITION_ARGS) .args(RUSTC_EDITION_ARGS)
.args(RUSTC_NO_DEBUG_ARGS) .args(RUSTC_NO_DEBUG_ARGS)
.output() .stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("Failed to compile!"); .expect("Failed to compile!");
// Due to an issue with Clippy, a cargo clean is required to catch all lints. // Due to an issue with Clippy, a cargo clean is required to catch all lints.
// See https://github.com/rust-lang/rust-clippy/issues/2604 // See https://github.com/rust-lang/rust-clippy/issues/2604
@ -157,7 +160,10 @@ path = "{}.rs""#,
Command::new("cargo") Command::new("cargo")
.args(["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH]) .args(["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
.args(RUSTC_COLOR_ARGS) .args(RUSTC_COLOR_ARGS)
.output() .stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("Failed to run 'cargo clean'"); .expect("Failed to run 'cargo clean'");
Command::new("cargo") Command::new("cargo")
.args(["clippy", "--manifest-path", CLIPPY_CARGO_TOML_PATH]) .args(["clippy", "--manifest-path", CLIPPY_CARGO_TOML_PATH])

View file

@ -11,7 +11,7 @@ use std::ffi::OsStr;
use std::fs; use std::fs;
use std::io::{self, prelude::*}; use std::io::{self, prelude::*};
use std::path::Path; use std::path::Path;
use std::process::{Command, Stdio}; use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, RecvTimeoutError}; use std::sync::mpsc::{channel, RecvTimeoutError};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -92,24 +92,25 @@ fn main() {
println!("\n{WELCOME}\n"); println!("\n{WELCOME}\n");
} }
if !Path::new("info.toml").exists() { if which::which("rustc").is_err() {
println!(
"{} must be run from the rustlings directory",
std::env::current_exe().unwrap().to_str().unwrap()
);
println!("Try `cd rustlings/`!");
std::process::exit(1);
}
if !rustc_exists() {
println!("We cannot find `rustc`."); println!("We cannot find `rustc`.");
println!("Try running `rustc --version` to diagnose your problem."); println!("Try running `rustc --version` to diagnose your problem.");
println!("For instructions on how to install Rust, check the README."); println!("For instructions on how to install Rust, check the README.");
std::process::exit(1); std::process::exit(1);
} }
let toml_str = &fs::read_to_string("info.toml").unwrap(); let info_file = fs::read_to_string("info.toml").unwrap_or_else(|e| {
let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises; match e.kind() {
io::ErrorKind::NotFound => println!(
"The program must be run from the rustlings directory\nTry `cd rustlings/`!",
),
_ => println!("Failed to read the info.toml file: {e}"),
}
std::process::exit(1);
});
let exercises = toml_edit::de::from_str::<ExerciseList>(&info_file)
.unwrap()
.exercises;
let verbose = args.nocapture; let verbose = args.nocapture;
let command = args.command.unwrap_or_else(|| { let command = args.command.unwrap_or_else(|| {
@ -218,16 +219,13 @@ fn main() {
println!("Failed to write rust-project.json to disk for rust-analyzer"); println!("Failed to write rust-project.json to disk for rust-analyzer");
} else { } else {
println!("Successfully generated rust-project.json"); println!("Successfully generated rust-project.json");
println!("rust-analyzer will now parse exercises, restart your language server or editor") println!("rust-analyzer will now parse exercises, restart your language server or editor");
} }
} }
Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) { Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) {
Err(e) => { Err(e) => {
println!( println!("Error: Could not watch your progress. Error message was {e:?}.");
"Error: Could not watch your progress. Error message was {:?}.",
e
);
println!("Most likely you've run out of disk space or your 'inotify limit' has been reached."); println!("Most likely you've run out of disk space or your 'inotify limit' has been reached.");
std::process::exit(1); std::process::exit(1);
} }
@ -295,7 +293,7 @@ fn spawn_watch_shell(
} }
fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise { fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise {
if name.eq("next") { if name == "next" {
exercises exercises
.iter() .iter()
.find(|e| !e.looks_done()) .find(|e| !e.looks_done())
@ -341,7 +339,6 @@ fn watch(
clear_screen(); clear_screen();
let to_owned_hint = |t: &Exercise| t.hint.to_owned();
let failed_exercise_hint = match verify( let failed_exercise_hint = match verify(
exercises.iter(), exercises.iter(),
(0, exercises.len()), (0, exercises.len()),
@ -349,7 +346,7 @@ fn watch(
success_hints, success_hints,
) { ) {
Ok(_) => return Ok(WatchStatus::Finished), Ok(_) => return Ok(WatchStatus::Finished),
Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))), Err(exercise) => Arc::new(Mutex::new(Some(exercise.hint.clone()))),
}; };
spawn_watch_shell(Arc::clone(&failed_exercise_hint), Arc::clone(&should_quit)); spawn_watch_shell(Arc::clone(&failed_exercise_hint), Arc::clone(&should_quit));
loop { loop {
@ -386,7 +383,7 @@ fn watch(
Err(exercise) => { Err(exercise) => {
let mut failed_exercise_hint = let mut failed_exercise_hint =
failed_exercise_hint.lock().unwrap(); failed_exercise_hint.lock().unwrap();
*failed_exercise_hint = Some(to_owned_hint(exercise)); *failed_exercise_hint = Some(exercise.hint.clone());
} }
} }
} }
@ -406,19 +403,7 @@ fn watch(
} }
} }
fn rustc_exists() -> bool { const DEFAULT_OUT: &str = "Thanks for installing Rustlings!
Command::new("rustc")
.args(["--version"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdin(Stdio::null())
.spawn()
.and_then(|mut child| child.wait())
.map(|status| status.success())
.unwrap_or(false)
}
const DEFAULT_OUT: &str = r#"Thanks for installing Rustlings!
Is this your first time? Don't worry, Rustlings was made for beginners! We are Is this your first time? Don't worry, Rustlings was made for beginners! We are
going to teach you a lot of things about Rust, but before we can get going to teach you a lot of things about Rust, but before we can get
@ -444,7 +429,7 @@ started, here's a couple of notes about how Rustlings operates:
autocompletion, run the command `rustlings lsp`. autocompletion, run the command `rustlings lsp`.
Got all that? Great! To get started, run `rustlings watch` in order to get the first Got all that? Great! To get started, run `rustlings watch` in order to get the first
exercise. Make sure to have your editor open!"#; exercise. Make sure to have your editor open!";
const FENISH_LINE: &str = "+----------------------------------------------------+ const FENISH_LINE: &str = "+----------------------------------------------------+
| You made it to the Fe-nish line! | | You made it to the Fe-nish line! |

View file

@ -21,7 +21,8 @@ pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
// Resets the exercise by stashing the changes. // Resets the exercise by stashing the changes.
pub fn reset(exercise: &Exercise) -> Result<(), ()> { pub fn reset(exercise: &Exercise) -> Result<(), ()> {
let command = Command::new("git") let command = Command::new("git")
.args(["stash", "--"]) .arg("stash")
.arg("--")
.arg(&exercise.path) .arg(&exercise.path)
.spawn(); .spawn();

View file

@ -24,7 +24,7 @@ pub fn verify<'a>(
.progress_chars("#>-"), .progress_chars("#>-"),
); );
bar.set_position(num_done as u64); bar.set_position(num_done as u64);
bar.set_message(format!("({:.1} %)", percentage)); bar.set_message(format!("({percentage:.1} %)"));
for exercise in exercises { for exercise in exercises {
let compile_result = match exercise.mode { let compile_result = match exercise.mode {
@ -37,7 +37,7 @@ pub fn verify<'a>(
} }
percentage += 100.0 / total as f32; percentage += 100.0 / total as f32;
bar.inc(1); bar.inc(1);
bar.set_message(format!("({:.1} %)", percentage)); bar.set_message(format!("({percentage:.1} %)"));
if bar.position() == total as u64 { if bar.position() == total as u64 {
println!( println!(
"Progress: You completed {} / {} exercises ({:.1} %).", "Progress: You completed {} / {} exercises ({:.1} %).",
@ -51,6 +51,7 @@ pub fn verify<'a>(
Ok(()) Ok(())
} }
#[derive(PartialEq, Eq)]
enum RunMode { enum RunMode {
Interactive, Interactive,
NonInteractive, NonInteractive,
@ -124,7 +125,7 @@ fn compile_and_test(
if verbose { if verbose {
println!("{}", output.stdout); println!("{}", output.stdout);
} }
if let RunMode::Interactive = run_mode { if run_mode == RunMode::Interactive {
Ok(prompt_for_completion(exercise, None, success_hints)) Ok(prompt_for_completion(exercise, None, success_hints))
} else { } else {
Ok(true) Ok(true)
@ -191,27 +192,25 @@ fn prompt_for_completion(
Mode::Test => "The code is compiling, and the tests pass!", Mode::Test => "The code is compiling, and the tests pass!",
Mode::Clippy => clippy_success_msg, Mode::Clippy => clippy_success_msg,
}; };
println!();
if no_emoji { if no_emoji {
println!("~*~ {success_msg} ~*~") println!("\n~*~ {success_msg} ~*~\n");
} else { } else {
println!("🎉 🎉 {success_msg} 🎉 🎉") println!("\n🎉 🎉 {success_msg} 🎉 🎉\n");
} }
println!();
if let Some(output) = prompt_output { if let Some(output) = prompt_output {
println!("Output:"); println!(
println!("{}", separator()); "Output:\n{separator}\n{output}\n{separator}\n",
println!("{output}"); separator = separator(),
println!("{}", separator()); );
println!();
} }
if success_hints { if success_hints {
println!("Hints:"); println!(
println!("{}", separator()); "Hints:\n{separator}\n{}\n{separator}\n",
println!("{}", exercise.hint); exercise.hint,
println!("{}", separator()); separator = separator(),
println!(); );
} }
println!("You can keep working on this exercise,"); println!("You can keep working on this exercise,");
@ -231,7 +230,7 @@ fn prompt_for_completion(
"{:>2} {} {}", "{:>2} {} {}",
style(context_line.number).blue().bold(), style(context_line.number).blue().bold(),
style("|").blue(), style("|").blue(),
formatted_line formatted_line,
); );
} }