From 09b370e10dc0a01f8735505cc53dfc0c1ff26a03 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Apr 2021 17:02:57 -0700 Subject: [PATCH] Change `--eval` to print variable value only (#806) --- src/config.rs | 47 +++++++++++++++++++------------ src/config_error.rs | 18 ++++++------ src/justfile.rs | 67 ++++++++++++++++++++++++++++---------------- src/runtime_error.rs | 13 +++++++++ src/subcommand.rs | 2 +- tests/evaluate.rs | 48 +++++++++++++++++++++++++++---- 6 files changed, 137 insertions(+), 58 deletions(-) diff --git a/src/config.rs b/src/config.rs index 3828678335..b133952f64 100644 --- a/src/config.rs +++ b/src/config.rs @@ -375,20 +375,20 @@ impl Config { (false, false) => {}, (true, false) => { return Err(ConfigError::SubcommandOverrides { - subcommand: format!("--{}", subcommand.to_lowercase()), + subcommand, overrides, }); }, (false, true) => { return Err(ConfigError::SubcommandArguments { - subcommand: format!("--{}", subcommand.to_lowercase()), - arguments: positional.arguments, + arguments: positional.arguments, + subcommand, }); }, (true, true) => { return Err(ConfigError::SubcommandOverridesAndArguments { - subcommand: format!("--{}", subcommand.to_lowercase()), arguments: positional.arguments, + subcommand, overrides, }); }, @@ -420,8 +420,19 @@ impl Config { name: name.to_owned(), } } 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::>(), + }); + } + Subcommand::Evaluate { - variables: positional.arguments, + variable: positional.arguments.into_iter().next(), overrides, } } else if matches.is_present(cmd::VARIABLES) { @@ -811,7 +822,7 @@ impl Config { } else { if self.verbosity.loud() { eprintln!("Justfile does not contain recipe `{}`.", name); - if let Some(suggestion) = justfile.suggest(name) { + if let Some(suggestion) = justfile.suggest_recipe(name) { eprintln!("{}", suggestion); } } @@ -1330,7 +1341,7 @@ ARGS: args: ["--evaluate"], subcommand: Subcommand::Evaluate { overrides: map!{}, - variables: vec![], + variable: None, }, } @@ -1339,7 +1350,7 @@ ARGS: args: ["--evaluate", "x=y"], subcommand: Subcommand::Evaluate { overrides: map!{"x": "y"}, - variables: vec![], + variable: None, }, } @@ -1348,7 +1359,7 @@ ARGS: args: ["--evaluate", "x=y", "foo"], subcommand: Subcommand::Evaluate { overrides: map!{"x": "y"}, - variables: vec!["foo".to_owned()], + variable: Some("foo".to_owned()), }, } @@ -1577,7 +1588,7 @@ ARGS: args: ["--completions", "zsh", "foo"], error: ConfigError::SubcommandArguments { subcommand, arguments }, check: { - assert_eq!(subcommand, "--completions"); + assert_eq!(subcommand, cmd::COMPLETIONS); assert_eq!(arguments, &["foo"]); }, } @@ -1587,7 +1598,7 @@ ARGS: args: ["--list", "bar"], error: ConfigError::SubcommandArguments { subcommand, arguments }, check: { - assert_eq!(subcommand, "--list"); + assert_eq!(subcommand, cmd::LIST); assert_eq!(arguments, &["bar"]); }, } @@ -1597,7 +1608,7 @@ ARGS: args: ["--dump", "bar"], error: ConfigError::SubcommandArguments { subcommand, arguments }, check: { - assert_eq!(subcommand, "--dump"); + assert_eq!(subcommand, cmd::DUMP); assert_eq!(arguments, &["bar"]); }, } @@ -1607,7 +1618,7 @@ ARGS: args: ["--edit", "bar"], error: ConfigError::SubcommandArguments { subcommand, arguments }, check: { - assert_eq!(subcommand, "--edit"); + assert_eq!(subcommand, cmd::EDIT); assert_eq!(arguments, &["bar"]); }, } @@ -1617,7 +1628,7 @@ ARGS: args: ["--init", "bar"], error: ConfigError::SubcommandArguments { subcommand, arguments }, check: { - assert_eq!(subcommand, "--init"); + assert_eq!(subcommand, cmd::INIT); assert_eq!(arguments, &["bar"]); }, } @@ -1627,7 +1638,7 @@ ARGS: args: ["--show", "foo", "bar"], error: ConfigError::SubcommandArguments { subcommand, arguments }, check: { - assert_eq!(subcommand, "--show"); + assert_eq!(subcommand, cmd::SHOW); assert_eq!(arguments, &["bar"]); }, } @@ -1637,7 +1648,7 @@ ARGS: args: ["--summary", "bar"], error: ConfigError::SubcommandArguments { subcommand, arguments }, check: { - assert_eq!(subcommand, "--summary"); + assert_eq!(subcommand, cmd::SUMMARY); assert_eq!(arguments, &["bar"]); }, } @@ -1647,7 +1658,7 @@ ARGS: args: ["--summary", "bar=baz", "bar"], error: ConfigError::SubcommandOverridesAndArguments { subcommand, arguments, overrides }, check: { - assert_eq!(subcommand, "--summary"); + assert_eq!(subcommand, cmd::SUMMARY); assert_eq!(overrides, map!{"bar": "baz"}); assert_eq!(arguments, &["bar"]); }, @@ -1658,7 +1669,7 @@ ARGS: args: ["--summary", "bar=baz"], error: ConfigError::SubcommandOverrides { subcommand, overrides }, check: { - assert_eq!(subcommand, "--summary"); + assert_eq!(subcommand, cmd::SUMMARY); assert_eq!(overrides, map!{"bar": "baz"}); }, } diff --git a/src/config_error.rs b/src/config_error.rs index 68d489020a..76ab066b90 100644 --- a/src/config_error.rs +++ b/src/config_error.rs @@ -16,33 +16,33 @@ pub(crate) enum ConfigError { ))] SearchDirConflict, #[snafu(display( - "`{}` used with unexpected {}: {}", - subcommand, + "`--{}` used with unexpected {}: {}", + subcommand.to_lowercase(), Count("argument", arguments.len()), List::and_ticked(arguments) ))] SubcommandArguments { - subcommand: String, + subcommand: &'static str, arguments: Vec, }, #[snafu(display( - "`{}` used with unexpected overrides: {}; and arguments: {}", - subcommand, + "`--{}` used with unexpected overrides: {}; and arguments: {}", + subcommand.to_lowercase(), List::and_ticked(overrides.iter().map(|(key, value)| format!("{}={}", key, value))), List::and_ticked(arguments))) ] SubcommandOverridesAndArguments { - subcommand: String, + subcommand: &'static str, overrides: BTreeMap, arguments: Vec, }, #[snafu(display( - "`{}` used with unexpected overrides: {}", - subcommand, + "`--{}` used with unexpected overrides: {}", + subcommand.to_lowercase(), List::and_ticked(overrides.iter().map(|(key, value)| format!("{}={}", key, value))), ))] SubcommandOverrides { - subcommand: String, + subcommand: &'static str, overrides: BTreeMap, }, } diff --git a/src/justfile.rs b/src/justfile.rs index 749243a13c..849b63f41d 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -28,7 +28,7 @@ impl<'src> Justfile<'src> { self.recipes.len() } - pub(crate) fn suggest(&self, input: &str) -> Option { + pub(crate) fn suggest_recipe(&self, input: &str) -> Option { let mut suggestions = self .recipes .keys() @@ -54,6 +54,26 @@ impl<'src> Justfile<'src> { .next() } + pub(crate) fn suggest_variable(&self, input: &str) -> Option { + let mut suggestions = self + .assignments + .keys() + .map(|name| { + (edit_distance(name, input), Suggestion { + name, + target: None, + }) + }) + .filter(|(distance, _suggestion)| distance < &3) + .collect::>(); + suggestions.sort_by_key(|(distance, _suggestion)| *distance); + + suggestions + .into_iter() + .map(|(_distance, suggestion)| suggestion) + .next() + } + pub(crate) fn run<'run>( &'run self, config: &'run Config, @@ -107,32 +127,31 @@ impl<'src> Justfile<'src> { )? }; - if let Subcommand::Evaluate { variables, .. } = &config.subcommand { - let mut width = 0; - - for name in scope.names() { - if !variables.is_empty() && !variables.iter().any(|variable| variable == name) { - continue; + 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; - width = cmp::max(name.len(), width); - } - - for binding in scope.bindings() { - if !variables.is_empty() - && !variables - .iter() - .any(|variable| variable == binding.name.lexeme()) - { - continue; + for name in scope.names() { + width = cmp::max(name.len(), width); } - println!( - "{0:1$} := \"{2}\"", - binding.name.lexeme(), - width, - binding.value - ); + for binding in scope.bindings() { + println!( + "{0:1$} := \"{2}\"", + binding.name.lexeme(), + width, + binding.value + ); + } } return Ok(()); @@ -186,7 +205,7 @@ impl<'src> Justfile<'src> { if !missing.is_empty() { let suggestion = if missing.len() == 1 { - self.suggest(missing.first().unwrap()) + self.suggest_recipe(missing.first().unwrap()) } else { None }; diff --git a/src/runtime_error.rs b/src/runtime_error.rs index cea2fbc986..df2ad95600 100644 --- a/src/runtime_error.rs +++ b/src/runtime_error.rs @@ -25,6 +25,10 @@ pub(crate) enum RuntimeError<'src> { Dotenv { dotenv_error: dotenv::Error, }, + EvalUnknownVariable { + variable: String, + suggestion: Option>, + }, FunctionCall { function: Name<'src>, message: String, @@ -106,6 +110,15 @@ impl<'src> Display for RuntimeError<'src> { write!(f, "{}", message.prefix())?; match self { + EvalUnknownVariable { + variable, + suggestion, + } => { + write!(f, "Justfile does not contain variable `{}`.", variable,)?; + if let Some(suggestion) = *suggestion { + write!(f, "\n{}", suggestion)?; + } + }, UnknownRecipes { recipes, suggestion, diff --git a/src/subcommand.rs b/src/subcommand.rs index 379c681f95..36b32731e1 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -13,7 +13,7 @@ pub(crate) enum Subcommand { Edit, Evaluate { overrides: BTreeMap, - variables: Vec, + variable: Option, }, Init, List, diff --git a/tests/evaluate.rs b/tests/evaluate.rs index 277ae8c4a0..412314b1ac 100644 --- a/tests/evaluate.rs +++ b/tests/evaluate.rs @@ -1,3 +1,5 @@ +use crate::common::*; + test! { name: evaluate, justfile: r#" @@ -29,15 +31,49 @@ test! { } test! { - name: evaluate_arguments, + name: evaluate_multiple, justfile: " a := 'x' b := 'y' c := 'z' ", - args: ("--evaluate", "a", "c"), - stdout: r#" - a := "x" - c := "z" - "#, + args: ("--evaluate", "a", "c"), + stderr: "error: `--evaluate` used with unexpected argument: `c`\n", + status: EXIT_FAILURE, +} + +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, }