Repository: casey/just
Pull request #806 (2021-04-25 23:54:54)

Change `--eval` to print value only


Patch:
diff --git a/src/config.rs b/src/config.rs
index 38286783..b133952f 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::<Vec<String>>(),
+        });
+      }
+
       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 68d48902..76ab066b 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<String>,
   },
   #[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<String, String>,
     arguments:  Vec<String>,
   },
   #[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<String, String>,
   },
 }
diff --git a/src/justfile.rs b/src/justfile.rs
index 749243a1..849b63f4 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<Suggestion> {
+  pub(crate) fn suggest_recipe(&self, input: &str) -> Option<Suggestion> {
     let mut suggestions = self
       .recipes
       .keys()
@@ -54,6 +54,26 @@ impl<'src> Justfile<'src> {
       .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>(
     &'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 cea2fbc9..df2ad956 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<Suggestion<'src>>,
+  },
   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 379c681f..36b32731 100644
--- a/src/subcommand.rs
+++ b/src/subcommand.rs
@@ -13,7 +13,7 @@ pub(crate) enum Subcommand {
   Edit,
   Evaluate {
     overrides: BTreeMap<String, String>,
-    variables: Vec<String>,
+    variable:  Option<String>,
   },
   Init,
   List,


Test patch:
diff --git a/tests/evaluate.rs b/tests/evaluate.rs
index 277ae8c4..412314b1 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,
 }