Suggest alternatives to uknown recipes (#91)
Kind of silly, but why not. Will only suggest an alternative if edit distance is less than 3. This could probably increase if the names are longer.
This commit is contained in:
parent
26910a9fdc
commit
3d8d901968
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -6,6 +6,7 @@ dependencies = [
|
||||
"atty 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"brev 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"edit-distance 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -65,6 +66,11 @@ dependencies = [
|
||||
"vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "edit-distance"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.0.1"
|
||||
@ -212,6 +218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||
"checksum brev 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "79571b60a8aa293f43b46370d8ba96fed28a5bee1303ea0e015d175ed0c63b40"
|
||||
"checksum clap 2.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27dac76762fb56019b04aed3ccb43a770a18f80f9c2eb62ee1a18d9fb4ea2430"
|
||||
"checksum edit-distance 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cd50a61206c09132fdf9cbaccc64a82cfccb6be528453903e03d4eb4ec80a61d"
|
||||
"checksum either 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8aa2c82b7e1abd89a8a59fd89c4a51576ea76a894edf5d5b28944dd46edfed8d"
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
"checksum itertools 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ef81b0a15a9e1808cfd3ebe6a87277d29ee88b34ac1197cc7547f1dd6d9f5424"
|
||||
|
@ -11,6 +11,7 @@ ansi_term = "^0.9.0"
|
||||
atty = "^0.2.1"
|
||||
brev = "^0.1.6"
|
||||
clap = "^2.0.0"
|
||||
edit-distance = "^1.0.0"
|
||||
itertools = "^0.5.5"
|
||||
lazy_static = "^0.2.1"
|
||||
regex = "^0.1.77"
|
||||
|
@ -211,7 +211,13 @@ pub fn app() {
|
||||
println!("{}", recipe);
|
||||
process::exit(0);
|
||||
}
|
||||
None => die!("justfile contains no recipe \"{}\"", name)
|
||||
None => {
|
||||
warn!("Justfile does not contain recipe `{}`.", name);
|
||||
if let Some(suggestion) = justfile.suggest(name) {
|
||||
warn!("Did you mean `{}`?", suggestion);
|
||||
}
|
||||
process::exit(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -914,7 +914,7 @@ fn unknown_recipe() {
|
||||
"hello:",
|
||||
255,
|
||||
"",
|
||||
"error: Justfile does not contain recipe `foo`\n",
|
||||
"error: Justfile does not contain recipe `foo`.\n",
|
||||
);
|
||||
}
|
||||
|
||||
@ -925,7 +925,7 @@ fn unknown_recipes() {
|
||||
"hello:",
|
||||
255,
|
||||
"",
|
||||
"error: Justfile does not contain recipes `foo` or `bar`\n",
|
||||
"error: Justfile does not contain recipes `foo` or `bar`.\n",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1093,3 +1093,51 @@ a Z="\t z":
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_suggestion() {
|
||||
integration_test(
|
||||
&["--show", "hell"],
|
||||
r#"
|
||||
hello a b='B ' c='C':
|
||||
echo {{a}} {{b}} {{c}}
|
||||
|
||||
a Z="\t z":
|
||||
"#,
|
||||
255,
|
||||
"",
|
||||
"Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_no_suggestion() {
|
||||
integration_test(
|
||||
&["--show", "hell"],
|
||||
r#"
|
||||
helloooooo a b='B ' c='C':
|
||||
echo {{a}} {{b}} {{c}}
|
||||
|
||||
a Z="\t z":
|
||||
"#,
|
||||
255,
|
||||
"",
|
||||
"Justfile does not contain recipe `hell`.\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_suggestion() {
|
||||
integration_test(
|
||||
&["hell"],
|
||||
r#"
|
||||
hello a b='B ' c='C':
|
||||
echo {{a}} {{b}} {{c}}
|
||||
|
||||
a Z="\t z":
|
||||
"#,
|
||||
255,
|
||||
"",
|
||||
"error: Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n",
|
||||
);
|
||||
}
|
||||
|
30
src/lib.rs
30
src/lib.rs
@ -15,6 +15,7 @@ extern crate tempdir;
|
||||
extern crate itertools;
|
||||
extern crate ansi_term;
|
||||
extern crate unicode_width;
|
||||
extern crate edit_distance;
|
||||
|
||||
use std::io::prelude::*;
|
||||
|
||||
@ -1064,6 +1065,19 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
||||
self.recipes.keys().cloned().collect()
|
||||
}
|
||||
|
||||
fn suggest(&self, name: &str) -> Option<&'a str> {
|
||||
let mut suggestions = self.recipes.keys()
|
||||
.map(|suggestion| (edit_distance::edit_distance(suggestion, name), suggestion))
|
||||
.collect::<Vec<_>>();
|
||||
suggestions.sort();
|
||||
if let Some(&(distance, suggestion)) = suggestions.first() {
|
||||
if distance < 3 {
|
||||
return Some(suggestion)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn run(
|
||||
&'a self,
|
||||
arguments: &[&'a str],
|
||||
@ -1117,7 +1131,12 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
||||
}
|
||||
}
|
||||
if !missing.is_empty() {
|
||||
return Err(RunError::UnknownRecipes{recipes: missing});
|
||||
let suggestion = if missing.len() == 1 {
|
||||
self.suggest(missing.first().unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return Err(RunError::UnknownRecipes{recipes: missing, suggestion: suggestion});
|
||||
}
|
||||
for recipe in arguments.iter().map(|name| &self.recipes[name]) {
|
||||
self.run_recipe(recipe, &[], &scope, &mut ran, options)?;
|
||||
@ -1178,7 +1197,7 @@ enum RunError<'a> {
|
||||
Signal{recipe: &'a str, signal: i32},
|
||||
TmpdirIoError{recipe: &'a str, io_error: io::Error},
|
||||
UnknownFailure{recipe: &'a str},
|
||||
UnknownRecipes{recipes: Vec<&'a str>},
|
||||
UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>},
|
||||
UnknownOverrides{overrides: Vec<&'a str>},
|
||||
BacktickCode{token: Token<'a>, code: i32},
|
||||
BacktickIoError{token: Token<'a>, io_error: io::Error},
|
||||
@ -1196,9 +1215,12 @@ impl<'a> Display for RunError<'a> {
|
||||
let mut error_token = None;
|
||||
|
||||
match *self {
|
||||
RunError::UnknownRecipes{ref recipes} => {
|
||||
write!(f, "Justfile does not contain recipe{} {}",
|
||||
RunError::UnknownRecipes{ref recipes, ref suggestion} => {
|
||||
write!(f, "Justfile does not contain recipe{} {}.",
|
||||
maybe_s(recipes.len()), Or(&ticks(&recipes)))?;
|
||||
if let Some(suggestion) = *suggestion {
|
||||
write!(f, "\nDid you mean `{}`?", suggestion)?;
|
||||
}
|
||||
},
|
||||
RunError::UnknownOverrides{ref overrides} => {
|
||||
write!(f, "Variable{} {} overridden on the command line but not present in justfile",
|
||||
|
@ -711,7 +711,10 @@ fn range() {
|
||||
#[test]
|
||||
fn unknown_recipes() {
|
||||
match parse_success("a:\nb:\nc:").run(&["a", "x", "y", "z"], &Default::default()).unwrap_err() {
|
||||
RunError::UnknownRecipes{recipes} => assert_eq!(recipes, &["x", "y", "z"]),
|
||||
RunError::UnknownRecipes{recipes, suggestion} => {
|
||||
assert_eq!(recipes, &["x", "y", "z"]);
|
||||
assert_eq!(suggestion, None);
|
||||
}
|
||||
other => panic!("expected an unknown recipe error, but got: {}", other),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user