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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.0.1"
|
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 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 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 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 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 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"
|
"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"
|
atty = "^0.2.1"
|
||||||
brev = "^0.1.6"
|
brev = "^0.1.6"
|
||||||
clap = "^2.0.0"
|
clap = "^2.0.0"
|
||||||
|
edit-distance = "^1.0.0"
|
||||||
itertools = "^0.5.5"
|
itertools = "^0.5.5"
|
||||||
lazy_static = "^0.2.1"
|
lazy_static = "^0.2.1"
|
||||||
regex = "^0.1.77"
|
regex = "^0.1.77"
|
||||||
|
@ -211,7 +211,13 @@ pub fn app() {
|
|||||||
println!("{}", recipe);
|
println!("{}", recipe);
|
||||||
process::exit(0);
|
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:",
|
"hello:",
|
||||||
255,
|
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:",
|
"hello:",
|
||||||
255,
|
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 itertools;
|
||||||
extern crate ansi_term;
|
extern crate ansi_term;
|
||||||
extern crate unicode_width;
|
extern crate unicode_width;
|
||||||
|
extern crate edit_distance;
|
||||||
|
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
||||||
@ -1064,6 +1065,19 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
self.recipes.keys().cloned().collect()
|
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(
|
fn run(
|
||||||
&'a self,
|
&'a self,
|
||||||
arguments: &[&'a str],
|
arguments: &[&'a str],
|
||||||
@ -1117,7 +1131,12 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !missing.is_empty() {
|
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]) {
|
for recipe in arguments.iter().map(|name| &self.recipes[name]) {
|
||||||
self.run_recipe(recipe, &[], &scope, &mut ran, options)?;
|
self.run_recipe(recipe, &[], &scope, &mut ran, options)?;
|
||||||
@ -1178,7 +1197,7 @@ enum RunError<'a> {
|
|||||||
Signal{recipe: &'a str, signal: i32},
|
Signal{recipe: &'a str, signal: i32},
|
||||||
TmpdirIoError{recipe: &'a str, io_error: io::Error},
|
TmpdirIoError{recipe: &'a str, io_error: io::Error},
|
||||||
UnknownFailure{recipe: &'a str},
|
UnknownFailure{recipe: &'a str},
|
||||||
UnknownRecipes{recipes: Vec<&'a str>},
|
UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>},
|
||||||
UnknownOverrides{overrides: Vec<&'a str>},
|
UnknownOverrides{overrides: Vec<&'a str>},
|
||||||
BacktickCode{token: Token<'a>, code: i32},
|
BacktickCode{token: Token<'a>, code: i32},
|
||||||
BacktickIoError{token: Token<'a>, io_error: io::Error},
|
BacktickIoError{token: Token<'a>, io_error: io::Error},
|
||||||
@ -1196,9 +1215,12 @@ impl<'a> Display for RunError<'a> {
|
|||||||
let mut error_token = None;
|
let mut error_token = None;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
RunError::UnknownRecipes{ref recipes} => {
|
RunError::UnknownRecipes{ref recipes, ref suggestion} => {
|
||||||
write!(f, "Justfile does not contain recipe{} {}",
|
write!(f, "Justfile does not contain recipe{} {}.",
|
||||||
maybe_s(recipes.len()), Or(&ticks(&recipes)))?;
|
maybe_s(recipes.len()), Or(&ticks(&recipes)))?;
|
||||||
|
if let Some(suggestion) = *suggestion {
|
||||||
|
write!(f, "\nDid you mean `{}`?", suggestion)?;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
RunError::UnknownOverrides{ref overrides} => {
|
RunError::UnknownOverrides{ref overrides} => {
|
||||||
write!(f, "Variable{} {} overridden on the command line but not present in justfile",
|
write!(f, "Variable{} {} overridden on the command line but not present in justfile",
|
||||||
|
@ -711,7 +711,10 @@ fn range() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn unknown_recipes() {
|
fn unknown_recipes() {
|
||||||
match parse_success("a:\nb:\nc:").run(&["a", "x", "y", "z"], &Default::default()).unwrap_err() {
|
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),
|
other => panic!("expected an unknown recipe error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user