Suggest aliases for unknown recipes (#624)
This commit is contained in:
parent
875fb41e40
commit
dc7210bca3
@ -61,8 +61,8 @@ pub(crate) use crate::{
|
||||
recipe_resolver::RecipeResolver, runtime_error::RuntimeError, scope::Scope, search::Search,
|
||||
search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting,
|
||||
settings::Settings, shebang::Shebang, show_whitespace::ShowWhitespace,
|
||||
string_literal::StringLiteral, subcommand::Subcommand, table::Table, thunk::Thunk, token::Token,
|
||||
token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
|
||||
string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table,
|
||||
thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
|
||||
unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
|
||||
verbosity::Verbosity, warning::Warning,
|
||||
};
|
||||
|
@ -632,7 +632,7 @@ impl Config {
|
||||
} else {
|
||||
eprintln!("Justfile does not contain recipe `{}`.", name);
|
||||
if let Some(suggestion) = justfile.suggest(name) {
|
||||
eprintln!("Did you mean `{}`?", suggestion);
|
||||
eprintln!("{}", suggestion);
|
||||
}
|
||||
Err(EXIT_FAILURE)
|
||||
}
|
||||
|
@ -28,19 +28,30 @@ impl<'src> Justfile<'src> {
|
||||
self.recipes.len()
|
||||
}
|
||||
|
||||
pub(crate) fn suggest(&self, name: &str) -> Option<&'src str> {
|
||||
pub(crate) fn suggest(&self, input: &str) -> Option<Suggestion> {
|
||||
let mut suggestions = self
|
||||
.recipes
|
||||
.keys()
|
||||
.map(|suggestion| (edit_distance(suggestion, name), suggestion))
|
||||
.collect::<Vec<_>>();
|
||||
suggestions.sort();
|
||||
if let Some(&(distance, suggestion)) = suggestions.first() {
|
||||
if distance < 3 {
|
||||
return Some(suggestion);
|
||||
}
|
||||
}
|
||||
None
|
||||
.map(|name| {
|
||||
(edit_distance(name, input), Suggestion {
|
||||
name,
|
||||
target: None,
|
||||
})
|
||||
})
|
||||
.chain(self.aliases.iter().map(|(name, alias)| {
|
||||
(edit_distance(name, input), Suggestion {
|
||||
name,
|
||||
target: Some(alias.target.name.lexeme()),
|
||||
})
|
||||
}))
|
||||
.filter(|(distance, _suggestion)| distance < &3)
|
||||
.collect::<Vec<(usize, Suggestion)>>();
|
||||
suggestions.sort_by_key(|(distance, _suggestion)| *distance);
|
||||
|
||||
suggestions
|
||||
.into_iter()
|
||||
.map(|(_distance, suggestion)| suggestion)
|
||||
.next()
|
||||
}
|
||||
|
||||
pub(crate) fn run<'run>(
|
||||
@ -301,6 +312,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
run_error! {
|
||||
name: unknown_recipes_show_alias_suggestion,
|
||||
src: "
|
||||
foo:
|
||||
echo foo
|
||||
|
||||
alias z := foo
|
||||
",
|
||||
args: ["zz"],
|
||||
error: UnknownRecipes {
|
||||
recipes,
|
||||
suggestion,
|
||||
},
|
||||
check: {
|
||||
assert_eq!(recipes, &["zz"]);
|
||||
assert_eq!(suggestion, Some(Suggestion {
|
||||
name: "z",
|
||||
target: Some("foo"),
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// This test exists to make sure that shebang recipes run correctly. Although
|
||||
// this script is still executed by a shell its behavior depends on the value of
|
||||
// a variable and continuing even though a command fails, whereas in plain
|
||||
|
@ -23,10 +23,12 @@
|
||||
clippy::result_expect_used,
|
||||
clippy::shadow_unrelated,
|
||||
clippy::string_add,
|
||||
clippy::struct_excessive_bools,
|
||||
clippy::too_many_lines,
|
||||
clippy::unreachable,
|
||||
clippy::use_debug,
|
||||
clippy::wildcard_enum_match_arm
|
||||
clippy::wildcard_enum_match_arm,
|
||||
clippy::wildcard_imports
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
@ -111,6 +113,7 @@ mod shebang;
|
||||
mod show_whitespace;
|
||||
mod string_literal;
|
||||
mod subcommand;
|
||||
mod suggestion;
|
||||
mod table;
|
||||
mod thunk;
|
||||
mod token;
|
||||
|
@ -56,7 +56,7 @@ pub(crate) enum RuntimeError<'src> {
|
||||
},
|
||||
UnknownRecipes {
|
||||
recipes: Vec<&'src str>,
|
||||
suggestion: Option<&'src str>,
|
||||
suggestion: Option<Suggestion<'src>>,
|
||||
},
|
||||
Unknown {
|
||||
recipe: &'src str,
|
||||
@ -117,7 +117,7 @@ impl<'src> Display for RuntimeError<'src> {
|
||||
List::or_ticked(recipes),
|
||||
)?;
|
||||
if let Some(suggestion) = *suggestion {
|
||||
write!(f, "\nDid you mean `{}`?", suggestion)?;
|
||||
write!(f, "\n{}", suggestion)?;
|
||||
}
|
||||
},
|
||||
UnknownOverrides { overrides } => {
|
||||
|
17
src/suggestion.rs
Normal file
17
src/suggestion.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::common::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub(crate) struct Suggestion<'src> {
|
||||
pub(crate) name: &'src str,
|
||||
pub(crate) target: Option<&'src str>,
|
||||
}
|
||||
|
||||
impl<'src> Display for Suggestion<'src> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "Did you mean `{}`", self.name)?;
|
||||
if let Some(target) = self.target {
|
||||
write!(f, ", an alias for `{}`", target)?;
|
||||
}
|
||||
write!(f, "?")
|
||||
}
|
||||
}
|
@ -1224,6 +1224,22 @@ a Z="\t z":
|
||||
status: EXIT_FAILURE,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: show_alias_suggestion,
|
||||
justfile: r#"
|
||||
hello a b='B ' c='C':
|
||||
echo {{a}} {{b}} {{c}}
|
||||
|
||||
alias foo := hello
|
||||
|
||||
a Z="\t z":
|
||||
"#,
|
||||
args: ("--show", "fo"),
|
||||
stdout: "",
|
||||
stderr: "Justfile does not contain recipe `fo`.\nDid you mean `foo`, an alias for `hello`?\n",
|
||||
status: EXIT_FAILURE,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: show_no_suggestion,
|
||||
justfile: r#"
|
||||
@ -1238,6 +1254,22 @@ a Z="\t z":
|
||||
status: EXIT_FAILURE,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: show_no_alias_suggestion,
|
||||
justfile: r#"
|
||||
hello a b='B ' c='C':
|
||||
echo {{a}} {{b}} {{c}}
|
||||
|
||||
alias foo := hello
|
||||
|
||||
a Z="\t z":
|
||||
"#,
|
||||
args: ("--show", "fooooooo"),
|
||||
stdout: "",
|
||||
stderr: "Justfile does not contain recipe `fooooooo`.\n",
|
||||
status: EXIT_FAILURE,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: run_suggestion,
|
||||
justfile: r#"
|
||||
|
Loading…
Reference in New Issue
Block a user