Merge pull request #1633 from mo8it/clap

Port to Clap
This commit is contained in:
liv 2023-09-04 13:35:33 +02:00 committed by GitHub
commit 0b7975b195
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 214 additions and 138 deletions

133
Cargo.lock generated
View file

@ -4,19 +4,61 @@ version = 3
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "anstream"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.2" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
[[package]]
name = "anstyle-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "anstyle-wincon"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "argh" name = "argh"
version = "0.1.12" version = "0.1.12"
@ -77,9 +119,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.6.0" version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
dependencies = [ dependencies = [
"memchr", "memchr",
"regex-automata", "regex-automata",
@ -98,6 +140,52 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.7" version = "0.15.7"
@ -209,6 +297,12 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]] [[package]]
name = "home" name = "home"
version = "0.5.5" version = "0.5.5"
@ -330,9 +424,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]] [[package]]
name = "mio" name = "mio"
@ -493,9 +587,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.9.4" version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -505,9 +599,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.3.7" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -526,6 +620,7 @@ version = "5.5.1"
dependencies = [ dependencies = [
"argh", "argh",
"assert_cmd", "assert_cmd",
"clap",
"console", "console",
"glob", "glob",
"home", "home",
@ -603,10 +698,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "syn" name = "strsim"
version = "2.0.29" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -665,6 +766,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "wait-timeout" name = "wait-timeout"
version = "0.2.0" version = "0.2.0"

View file

@ -10,15 +10,16 @@ edition = "2021"
[dependencies] [dependencies]
argh = "0.1" argh = "0.1"
indicatif = "0.17" indicatif = "0.17.6"
console = "0.15" console = "0.15"
notify = "4.0" notify = "4.0"
toml = "0.7" toml = "0.7.6"
regex = "1.9" regex = "1.5"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.105" serde_json = "1.0.81"
home = "0.5.5" home = "0.5.3"
glob = "0.3.1" glob = "0.3.0"
clap = { version = "4.4.0", features = ["derive"] }
[[bin]] [[bin]]
name = "rustlings" name = "rustlings"
@ -27,4 +28,4 @@ path = "src/main.rs"
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0.12" assert_cmd = "2.0.12"
predicates = "3.0.3" predicates = "3.0.3"
glob = "0.3.1" glob = "0.3.0"

View file

@ -2,7 +2,7 @@ use crate::exercise::{Exercise, ExerciseList};
use crate::project::RustAnalyzerProject; use crate::project::RustAnalyzerProject;
use crate::run::{reset, run}; use crate::run::{reset, run};
use crate::verify::verify; use crate::verify::verify;
use argh::FromArgs; use clap::{Parser, Subcommand};
use console::Emoji; use console::Emoji;
use notify::DebouncedEvent; use notify::DebouncedEvent;
use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use notify::{RecommendedWatcher, RecursiveMode, Watcher};
@ -25,111 +25,69 @@ mod project;
mod run; mod run;
mod verify; mod verify;
// In sync with crate version
const VERSION: &str = "5.5.1";
#[derive(FromArgs, PartialEq, Debug)]
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code /// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
#[derive(Parser)]
#[command(version)]
struct Args { struct Args {
/// show outputs from the test exercises /// Show outputs from the test exercises
#[argh(switch)] #[arg(long)]
nocapture: bool, nocapture: bool,
/// show the executable version #[command(subcommand)]
#[argh(switch, short = 'v')] command: Option<Subcommands>,
version: bool,
#[argh(subcommand)]
nested: Option<Subcommands>,
} }
#[derive(FromArgs, PartialEq, Debug)] #[derive(Subcommand)]
#[argh(subcommand)]
enum Subcommands { enum Subcommands {
Verify(VerifyArgs), /// Verify all exercises according to the recommended order
Watch(WatchArgs), Verify,
Run(RunArgs), /// Rerun `verify` when files were edited
Reset(ResetArgs), Watch {
Hint(HintArgs), /// Show hints on success
List(ListArgs), #[arg(long)]
Lsp(LspArgs),
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "verify")]
/// Verifies all exercises according to the recommended order
struct VerifyArgs {}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "watch")]
/// Reruns `verify` when files were edited
struct WatchArgs {
/// show hints on success
#[argh(switch)]
success_hints: bool, success_hints: bool,
} },
/// Run/Test a single exercise
#[derive(FromArgs, PartialEq, Debug)] Run {
#[argh(subcommand, name = "run")] /// The name of the exercise
/// Runs/Tests a single exercise
struct RunArgs {
#[argh(positional)]
/// the name of the exercise
name: String, name: String,
} },
/// Reset a single exercise using "git stash -- <filename>"
#[derive(FromArgs, PartialEq, Debug)] Reset {
#[argh(subcommand, name = "reset")] /// The name of the exercise
/// Resets a single exercise using "git stash -- <filename>"
struct ResetArgs {
#[argh(positional)]
/// the name of the exercise
name: String, name: String,
} },
/// Return a hint for the given exercise
#[derive(FromArgs, PartialEq, Debug)] Hint {
#[argh(subcommand, name = "hint")] /// The name of the exercise
/// Returns a hint for the given exercise
struct HintArgs {
#[argh(positional)]
/// the name of the exercise
name: String, name: String,
} },
/// List the exercises available in Rustlings
#[derive(FromArgs, PartialEq, Debug)] List {
#[argh(subcommand, name = "lsp")] /// Show only the paths of the exercises
/// Enable rust-analyzer for exercises #[arg(short, long)]
struct LspArgs {}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "list")]
/// Lists the exercises available in Rustlings
struct ListArgs {
#[argh(switch, short = 'p')]
/// show only the paths of the exercises
paths: bool, paths: bool,
#[argh(switch, short = 'n')] /// Show only the names of the exercises
/// show only the names of the exercises #[arg(short, long)]
names: bool, names: bool,
#[argh(option, short = 'f')] /// Provide a string to match exercise names.
/// provide a string to match exercise names /// Comma separated patterns are accepted
/// comma separated patterns are acceptable #[arg(short, long)]
filter: Option<String>, filter: Option<String>,
#[argh(switch, short = 'u')] /// Display only exercises not yet solved
/// display only exercises not yet solved #[arg(short, long)]
unsolved: bool, unsolved: bool,
#[argh(switch, short = 's')] /// Display only exercises that have been solved
/// display only exercises that have been solved #[arg(short, long)]
solved: bool, solved: bool,
},
/// Enable rust-analyzer for exercises
Lsp,
} }
fn main() { fn main() {
let args: Args = argh::from_env(); let args = Args::parse();
if args.version { if args.command.is_none() {
println!("v{VERSION}");
std::process::exit(0);
}
if args.nested.is_none() {
println!("\n{WELCOME}\n"); println!("\n{WELCOME}\n");
} }
@ -153,17 +111,24 @@ fn main() {
let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises; let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;
let verbose = args.nocapture; let verbose = args.nocapture;
let command = args.nested.unwrap_or_else(|| { let command = args.command.unwrap_or_else(|| {
println!("{DEFAULT_OUT}\n"); println!("{DEFAULT_OUT}\n");
std::process::exit(0); std::process::exit(0);
}); });
match command { match command {
Subcommands::List(subargs) => { Subcommands::List {
if !subargs.paths && !subargs.names { paths,
names,
filter,
unsolved,
solved,
} => {
if !paths && !names {
println!("{:<17}\t{:<46}\t{:<7}", "Name", "Path", "Status"); println!("{:<17}\t{:<46}\t{:<7}", "Name", "Path", "Status");
} }
let mut exercises_done: u16 = 0; let mut exercises_done: u16 = 0;
let filters = subargs.filter.clone().unwrap_or_default().to_lowercase(); let filters = filter.clone().unwrap_or_default().to_lowercase();
exercises.iter().for_each(|e| { exercises.iter().for_each(|e| {
let fname = format!("{}", e.path.display()); let fname = format!("{}", e.path.display());
let filter_cond = filters let filter_cond = filters
@ -177,14 +142,14 @@ fn main() {
"Pending" "Pending"
}; };
let solve_cond = { let solve_cond = {
(e.looks_done() && subargs.solved) (e.looks_done() && solved)
|| (!e.looks_done() && subargs.unsolved) || (!e.looks_done() && unsolved)
|| (!subargs.solved && !subargs.unsolved) || (!solved && !unsolved)
}; };
if solve_cond && (filter_cond || subargs.filter.is_none()) { if solve_cond && (filter_cond || filter.is_none()) {
let line = if subargs.paths { let line = if paths {
format!("{fname}\n") format!("{fname}\n")
} else if subargs.names { } else if names {
format!("{}\n", e.name) format!("{}\n", e.name)
} else { } else {
format!("{:<17}\t{fname:<46}\t{status:<7}\n", e.name) format!("{:<17}\t{fname:<46}\t{status:<7}\n", e.name)
@ -214,30 +179,30 @@ fn main() {
std::process::exit(0); std::process::exit(0);
} }
Subcommands::Run(subargs) => { Subcommands::Run { name } => {
let exercise = find_exercise(&subargs.name, &exercises); let exercise = find_exercise(&name, &exercises);
run(exercise, verbose).unwrap_or_else(|_| std::process::exit(1)); run(exercise, verbose).unwrap_or_else(|_| std::process::exit(1));
} }
Subcommands::Reset(subargs) => { Subcommands::Reset { name } => {
let exercise = find_exercise(&subargs.name, &exercises); let exercise = find_exercise(&name, &exercises);
reset(exercise).unwrap_or_else(|_| std::process::exit(1)); reset(exercise).unwrap_or_else(|_| std::process::exit(1));
} }
Subcommands::Hint(subargs) => { Subcommands::Hint { name } => {
let exercise = find_exercise(&subargs.name, &exercises); let exercise = find_exercise(&name, &exercises);
println!("{}", exercise.hint); println!("{}", exercise.hint);
} }
Subcommands::Verify(_subargs) => { Subcommands::Verify => {
verify(&exercises, (0, exercises.len()), verbose, false) verify(&exercises, (0, exercises.len()), verbose, false)
.unwrap_or_else(|_| std::process::exit(1)); .unwrap_or_else(|_| std::process::exit(1));
} }
Subcommands::Lsp(_subargs) => { Subcommands::Lsp => {
let mut project = RustAnalyzerProject::new(); let mut project = RustAnalyzerProject::new();
project project
.get_sysroot_src() .get_sysroot_src()
@ -256,7 +221,7 @@ fn main() {
} }
} }
Subcommands::Watch(_subargs) => match watch(&exercises, verbose, _subargs.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 {:?}.", "Error: Could not watch your progress. Error message was {:?}.",

View file

@ -97,7 +97,10 @@ fn run_single_test_no_filename() {
.arg("run") .arg("run")
.current_dir("tests/fixture/") .current_dir("tests/fixture/")
.assert() .assert()
.code(1); .code(2)
.stderr(predicates::str::contains(
"required arguments were not provided",
));
} }
#[test] #[test]
@ -125,9 +128,9 @@ fn reset_no_exercise() {
.unwrap() .unwrap()
.arg("reset") .arg("reset")
.assert() .assert()
.code(1) .code(2)
.stderr(predicates::str::contains( .stderr(predicates::str::contains(
"positional arguments not provided", "required arguments were not provided",
)); ));
} }