Change --eval to print variable value only (#806)

This commit is contained in:
Casey Rodarmor 2021-04-25 17:02:57 -07:00 committed by GitHub
parent b8a65149be
commit 09b370e10d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 137 additions and 58 deletions

View File

@ -375,20 +375,20 @@ impl Config {
(false, false) => {}, (false, false) => {},
(true, false) => { (true, false) => {
return Err(ConfigError::SubcommandOverrides { return Err(ConfigError::SubcommandOverrides {
subcommand: format!("--{}", subcommand.to_lowercase()), subcommand,
overrides, overrides,
}); });
}, },
(false, true) => { (false, true) => {
return Err(ConfigError::SubcommandArguments { return Err(ConfigError::SubcommandArguments {
subcommand: format!("--{}", subcommand.to_lowercase()),
arguments: positional.arguments, arguments: positional.arguments,
subcommand,
}); });
}, },
(true, true) => { (true, true) => {
return Err(ConfigError::SubcommandOverridesAndArguments { return Err(ConfigError::SubcommandOverridesAndArguments {
subcommand: format!("--{}", subcommand.to_lowercase()),
arguments: positional.arguments, arguments: positional.arguments,
subcommand,
overrides, overrides,
}); });
}, },
@ -420,8 +420,19 @@ impl Config {
name: name.to_owned(), name: name.to_owned(),
} }
} else if matches.is_present(cmd::EVALUATE) { } else if matches.is_present(cmd::EVALUATE) {
if positional.arguments.len() > 1 {
return Err(ConfigError::SubcommandArguments {
subcommand: cmd::EVALUATE,
arguments: positional
.arguments
.into_iter()
.skip(1)
.collect::<Vec<String>>(),
});
}
Subcommand::Evaluate { Subcommand::Evaluate {
variables: positional.arguments, variable: positional.arguments.into_iter().next(),
overrides, overrides,
} }
} else if matches.is_present(cmd::VARIABLES) { } else if matches.is_present(cmd::VARIABLES) {
@ -811,7 +822,7 @@ impl Config {
} else { } else {
if self.verbosity.loud() { if self.verbosity.loud() {
eprintln!("Justfile does not contain recipe `{}`.", name); eprintln!("Justfile does not contain recipe `{}`.", name);
if let Some(suggestion) = justfile.suggest(name) { if let Some(suggestion) = justfile.suggest_recipe(name) {
eprintln!("{}", suggestion); eprintln!("{}", suggestion);
} }
} }
@ -1330,7 +1341,7 @@ ARGS:
args: ["--evaluate"], args: ["--evaluate"],
subcommand: Subcommand::Evaluate { subcommand: Subcommand::Evaluate {
overrides: map!{}, overrides: map!{},
variables: vec![], variable: None,
}, },
} }
@ -1339,7 +1350,7 @@ ARGS:
args: ["--evaluate", "x=y"], args: ["--evaluate", "x=y"],
subcommand: Subcommand::Evaluate { subcommand: Subcommand::Evaluate {
overrides: map!{"x": "y"}, overrides: map!{"x": "y"},
variables: vec![], variable: None,
}, },
} }
@ -1348,7 +1359,7 @@ ARGS:
args: ["--evaluate", "x=y", "foo"], args: ["--evaluate", "x=y", "foo"],
subcommand: Subcommand::Evaluate { subcommand: Subcommand::Evaluate {
overrides: map!{"x": "y"}, overrides: map!{"x": "y"},
variables: vec!["foo".to_owned()], variable: Some("foo".to_owned()),
}, },
} }
@ -1577,7 +1588,7 @@ ARGS:
args: ["--completions", "zsh", "foo"], args: ["--completions", "zsh", "foo"],
error: ConfigError::SubcommandArguments { subcommand, arguments }, error: ConfigError::SubcommandArguments { subcommand, arguments },
check: { check: {
assert_eq!(subcommand, "--completions"); assert_eq!(subcommand, cmd::COMPLETIONS);
assert_eq!(arguments, &["foo"]); assert_eq!(arguments, &["foo"]);
}, },
} }
@ -1587,7 +1598,7 @@ ARGS:
args: ["--list", "bar"], args: ["--list", "bar"],
error: ConfigError::SubcommandArguments { subcommand, arguments }, error: ConfigError::SubcommandArguments { subcommand, arguments },
check: { check: {
assert_eq!(subcommand, "--list"); assert_eq!(subcommand, cmd::LIST);
assert_eq!(arguments, &["bar"]); assert_eq!(arguments, &["bar"]);
}, },
} }
@ -1597,7 +1608,7 @@ ARGS:
args: ["--dump", "bar"], args: ["--dump", "bar"],
error: ConfigError::SubcommandArguments { subcommand, arguments }, error: ConfigError::SubcommandArguments { subcommand, arguments },
check: { check: {
assert_eq!(subcommand, "--dump"); assert_eq!(subcommand, cmd::DUMP);
assert_eq!(arguments, &["bar"]); assert_eq!(arguments, &["bar"]);
}, },
} }
@ -1607,7 +1618,7 @@ ARGS:
args: ["--edit", "bar"], args: ["--edit", "bar"],
error: ConfigError::SubcommandArguments { subcommand, arguments }, error: ConfigError::SubcommandArguments { subcommand, arguments },
check: { check: {
assert_eq!(subcommand, "--edit"); assert_eq!(subcommand, cmd::EDIT);
assert_eq!(arguments, &["bar"]); assert_eq!(arguments, &["bar"]);
}, },
} }
@ -1617,7 +1628,7 @@ ARGS:
args: ["--init", "bar"], args: ["--init", "bar"],
error: ConfigError::SubcommandArguments { subcommand, arguments }, error: ConfigError::SubcommandArguments { subcommand, arguments },
check: { check: {
assert_eq!(subcommand, "--init"); assert_eq!(subcommand, cmd::INIT);
assert_eq!(arguments, &["bar"]); assert_eq!(arguments, &["bar"]);
}, },
} }
@ -1627,7 +1638,7 @@ ARGS:
args: ["--show", "foo", "bar"], args: ["--show", "foo", "bar"],
error: ConfigError::SubcommandArguments { subcommand, arguments }, error: ConfigError::SubcommandArguments { subcommand, arguments },
check: { check: {
assert_eq!(subcommand, "--show"); assert_eq!(subcommand, cmd::SHOW);
assert_eq!(arguments, &["bar"]); assert_eq!(arguments, &["bar"]);
}, },
} }
@ -1637,7 +1648,7 @@ ARGS:
args: ["--summary", "bar"], args: ["--summary", "bar"],
error: ConfigError::SubcommandArguments { subcommand, arguments }, error: ConfigError::SubcommandArguments { subcommand, arguments },
check: { check: {
assert_eq!(subcommand, "--summary"); assert_eq!(subcommand, cmd::SUMMARY);
assert_eq!(arguments, &["bar"]); assert_eq!(arguments, &["bar"]);
}, },
} }
@ -1647,7 +1658,7 @@ ARGS:
args: ["--summary", "bar=baz", "bar"], args: ["--summary", "bar=baz", "bar"],
error: ConfigError::SubcommandOverridesAndArguments { subcommand, arguments, overrides }, error: ConfigError::SubcommandOverridesAndArguments { subcommand, arguments, overrides },
check: { check: {
assert_eq!(subcommand, "--summary"); assert_eq!(subcommand, cmd::SUMMARY);
assert_eq!(overrides, map!{"bar": "baz"}); assert_eq!(overrides, map!{"bar": "baz"});
assert_eq!(arguments, &["bar"]); assert_eq!(arguments, &["bar"]);
}, },
@ -1658,7 +1669,7 @@ ARGS:
args: ["--summary", "bar=baz"], args: ["--summary", "bar=baz"],
error: ConfigError::SubcommandOverrides { subcommand, overrides }, error: ConfigError::SubcommandOverrides { subcommand, overrides },
check: { check: {
assert_eq!(subcommand, "--summary"); assert_eq!(subcommand, cmd::SUMMARY);
assert_eq!(overrides, map!{"bar": "baz"}); assert_eq!(overrides, map!{"bar": "baz"});
}, },
} }

View File

@ -16,33 +16,33 @@ pub(crate) enum ConfigError {
))] ))]
SearchDirConflict, SearchDirConflict,
#[snafu(display( #[snafu(display(
"`{}` used with unexpected {}: {}", "`--{}` used with unexpected {}: {}",
subcommand, subcommand.to_lowercase(),
Count("argument", arguments.len()), Count("argument", arguments.len()),
List::and_ticked(arguments) List::and_ticked(arguments)
))] ))]
SubcommandArguments { SubcommandArguments {
subcommand: String, subcommand: &'static str,
arguments: Vec<String>, arguments: Vec<String>,
}, },
#[snafu(display( #[snafu(display(
"`{}` used with unexpected overrides: {}; and arguments: {}", "`--{}` used with unexpected overrides: {}; and arguments: {}",
subcommand, subcommand.to_lowercase(),
List::and_ticked(overrides.iter().map(|(key, value)| format!("{}={}", key, value))), List::and_ticked(overrides.iter().map(|(key, value)| format!("{}={}", key, value))),
List::and_ticked(arguments))) List::and_ticked(arguments)))
] ]
SubcommandOverridesAndArguments { SubcommandOverridesAndArguments {
subcommand: String, subcommand: &'static str,
overrides: BTreeMap<String, String>, overrides: BTreeMap<String, String>,
arguments: Vec<String>, arguments: Vec<String>,
}, },
#[snafu(display( #[snafu(display(
"`{}` used with unexpected overrides: {}", "`--{}` used with unexpected overrides: {}",
subcommand, subcommand.to_lowercase(),
List::and_ticked(overrides.iter().map(|(key, value)| format!("{}={}", key, value))), List::and_ticked(overrides.iter().map(|(key, value)| format!("{}={}", key, value))),
))] ))]
SubcommandOverrides { SubcommandOverrides {
subcommand: String, subcommand: &'static str,
overrides: BTreeMap<String, String>, overrides: BTreeMap<String, String>,
}, },
} }

View File

@ -28,7 +28,7 @@ impl<'src> Justfile<'src> {
self.recipes.len() self.recipes.len()
} }
pub(crate) fn suggest(&self, input: &str) -> Option<Suggestion> { pub(crate) fn suggest_recipe(&self, input: &str) -> Option<Suggestion> {
let mut suggestions = self let mut suggestions = self
.recipes .recipes
.keys() .keys()
@ -54,6 +54,26 @@ impl<'src> Justfile<'src> {
.next() .next()
} }
pub(crate) fn suggest_variable(&self, input: &str) -> Option<Suggestion> {
let mut suggestions = self
.assignments
.keys()
.map(|name| {
(edit_distance(name, input), Suggestion {
name,
target: None,
})
})
.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>( pub(crate) fn run<'run>(
&'run self, &'run self,
config: &'run Config, config: &'run Config,
@ -107,26 +127,24 @@ impl<'src> Justfile<'src> {
)? )?
}; };
if let Subcommand::Evaluate { variables, .. } = &config.subcommand { if let Subcommand::Evaluate { variable, .. } = &config.subcommand {
if let Some(variable) = variable {
if let Some(value) = scope.value(variable) {
print!("{}", value);
} else {
return Err(RuntimeError::EvalUnknownVariable {
suggestion: self.suggest_variable(&variable),
variable: variable.to_owned(),
});
}
} else {
let mut width = 0; let mut width = 0;
for name in scope.names() { for name in scope.names() {
if !variables.is_empty() && !variables.iter().any(|variable| variable == name) {
continue;
}
width = cmp::max(name.len(), width); width = cmp::max(name.len(), width);
} }
for binding in scope.bindings() { for binding in scope.bindings() {
if !variables.is_empty()
&& !variables
.iter()
.any(|variable| variable == binding.name.lexeme())
{
continue;
}
println!( println!(
"{0:1$} := \"{2}\"", "{0:1$} := \"{2}\"",
binding.name.lexeme(), binding.name.lexeme(),
@ -134,6 +152,7 @@ impl<'src> Justfile<'src> {
binding.value binding.value
); );
} }
}
return Ok(()); return Ok(());
} }
@ -186,7 +205,7 @@ impl<'src> Justfile<'src> {
if !missing.is_empty() { if !missing.is_empty() {
let suggestion = if missing.len() == 1 { let suggestion = if missing.len() == 1 {
self.suggest(missing.first().unwrap()) self.suggest_recipe(missing.first().unwrap())
} else { } else {
None None
}; };

View File

@ -25,6 +25,10 @@ pub(crate) enum RuntimeError<'src> {
Dotenv { Dotenv {
dotenv_error: dotenv::Error, dotenv_error: dotenv::Error,
}, },
EvalUnknownVariable {
variable: String,
suggestion: Option<Suggestion<'src>>,
},
FunctionCall { FunctionCall {
function: Name<'src>, function: Name<'src>,
message: String, message: String,
@ -106,6 +110,15 @@ impl<'src> Display for RuntimeError<'src> {
write!(f, "{}", message.prefix())?; write!(f, "{}", message.prefix())?;
match self { match self {
EvalUnknownVariable {
variable,
suggestion,
} => {
write!(f, "Justfile does not contain variable `{}`.", variable,)?;
if let Some(suggestion) = *suggestion {
write!(f, "\n{}", suggestion)?;
}
},
UnknownRecipes { UnknownRecipes {
recipes, recipes,
suggestion, suggestion,

View File

@ -13,7 +13,7 @@ pub(crate) enum Subcommand {
Edit, Edit,
Evaluate { Evaluate {
overrides: BTreeMap<String, String>, overrides: BTreeMap<String, String>,
variables: Vec<String>, variable: Option<String>,
}, },
Init, Init,
List, List,

View File

@ -1,3 +1,5 @@
use crate::common::*;
test! { test! {
name: evaluate, name: evaluate,
justfile: r#" justfile: r#"
@ -29,15 +31,49 @@ test! {
} }
test! { test! {
name: evaluate_arguments, name: evaluate_multiple,
justfile: " justfile: "
a := 'x' a := 'x'
b := 'y' b := 'y'
c := 'z' c := 'z'
", ",
args: ("--evaluate", "a", "c"), args: ("--evaluate", "a", "c"),
stdout: r#" stderr: "error: `--evaluate` used with unexpected argument: `c`\n",
a := "x" status: EXIT_FAILURE,
c := "z" }
"#,
test! {
name: evaluate_single,
justfile: "
a := 'x'
b := 'y'
c := 'z'
",
args: ("--evaluate", "b"),
stdout: "y",
}
test! {
name: evaluate_no_suggestion,
justfile: "
abc := 'x'
",
args: ("--evaluate", "aby"),
stderr: "
error: Justfile does not contain variable `aby`.
Did you mean `abc`?
",
status: EXIT_FAILURE,
}
test! {
name: evaluate_suggestion,
justfile: "
hello := 'x'
",
args: ("--evaluate", "goodbye"),
stderr: "
error: Justfile does not contain variable `goodbye`.
",
status: EXIT_FAILURE,
} }