Repository: casey/just
Pull request #771 (2021-03-25 23:46:41)

Suppress all output to stderr when `--quiet`
Suppress all warnings and error messages when `--quiet` is passed.


Patch:
diff --git a/src/config.rs b/src/config.rs
index 654e4f70..684fe1b6 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -483,14 +483,14 @@ impl Config {
     }
 
     if let Completions { shell } = self.subcommand {
-      return Subcommand::completions(&shell);
+      return Subcommand::completions(self.verbosity, &shell);
     }
 
     let search =
       Search::find(&self.search_config, &self.invocation_directory).eprint(self.color)?;
 
     if self.subcommand == Edit {
-      return Self::edit(&search);
+      return self.edit(&search);
     }
 
     let src = fs::read_to_string(&search.justfile)
@@ -502,11 +502,13 @@ impl Config {
 
     let justfile = Compiler::compile(&src).eprint(self.color)?;
 
-    for warning in &justfile.warnings {
-      if self.color.stderr().active() {
-        eprintln!("{:#}", warning);
-      } else {
-        eprintln!("{}", warning);
+    if self.verbosity.loud() {
+      for warning in &justfile.warnings {
+        if self.color.stderr().active() {
+          eprintln!("{:#}", warning);
+        } else {
+          eprintln!("{}", warning);
+        }
       }
     }
 
@@ -520,7 +522,7 @@ impl Config {
         arguments,
         overrides,
       } => self.run(justfile, &search, overrides, arguments)?,
-      Show { ref name } => Self::show(&name, justfile)?,
+      Show { ref name } => self.show(&name, justfile)?,
       Summary => self.summary(justfile),
       Variables => Self::variables(justfile),
       Completions { .. } | Edit | Init => unreachable!(),
@@ -544,7 +546,9 @@ impl Config {
       .collect::<Vec<&Recipe<Dependency>>>();
 
     if recipes.is_empty() {
-      eprintln!("Justfile contains no choosable recipes.");
+      if self.verbosity.loud() {
+        eprintln!("Justfile contains no choosable recipes.");
+      }
       return Err(EXIT_FAILURE);
     }
 
@@ -565,11 +569,13 @@ impl Config {
     let mut child = match result {
       Ok(child) => child,
       Err(error) => {
-        eprintln!(
-          "Chooser `{}` invocation failed: {}",
-          chooser.to_string_lossy(),
-          error
-        );
+        if self.verbosity.loud() {
+          eprintln!(
+            "Chooser `{}` invocation failed: {}",
+            chooser.to_string_lossy(),
+            error
+          );
+        }
         return Err(EXIT_FAILURE);
       },
     };
@@ -581,11 +587,13 @@ impl Config {
         .expect("Child was created with piped stdio")
         .write_all(format!("{}\n", recipe.name).as_bytes())
       {
-        eprintln!(
-          "Failed to write to chooser `{}`: {}",
-          chooser.to_string_lossy(),
-          error
-        );
+        if self.verbosity.loud() {
+          eprintln!(
+            "Failed to write to chooser `{}`: {}",
+            chooser.to_string_lossy(),
+            error
+          );
+        }
         return Err(EXIT_FAILURE);
       }
     }
@@ -593,21 +601,25 @@ impl Config {
     let output = match child.wait_with_output() {
       Ok(output) => output,
       Err(error) => {
-        eprintln!(
-          "Failed to read output from chooser `{}`: {}",
-          chooser.to_string_lossy(),
-          error
-        );
+        if self.verbosity.loud() {
+          eprintln!(
+            "Failed to read output from chooser `{}`: {}",
+            chooser.to_string_lossy(),
+            error
+          );
+        }
         return Err(EXIT_FAILURE);
       },
     };
 
     if !output.status.success() {
-      eprintln!(
-        "Chooser `{}` returned error: {}",
-        chooser.to_string_lossy(),
-        output.status
-      );
+      if self.verbosity.loud() {
+        eprintln!(
+          "Chooser `{}` returned error: {}",
+          chooser.to_string_lossy(),
+          output.status
+        );
+      }
       return Err(output.status.code().unwrap_or(EXIT_FAILURE));
     }
 
@@ -626,7 +638,7 @@ impl Config {
     println!("{}", justfile);
   }
 
-  pub(crate) fn edit(search: &Search) -> Result<(), i32> {
+  pub(crate) fn edit(&self, search: &Search) -> Result<(), i32> {
     let editor = env::var_os("VISUAL")
       .or_else(|| env::var_os("EDITOR"))
       .unwrap_or_else(|| "vim".into());
@@ -641,15 +653,19 @@ impl Config {
         if status.success() {
           Ok(())
         } else {
-          eprintln!("Editor `{}` failed: {}", editor.to_string_lossy(), status);
+          if self.verbosity.loud() {
+            eprintln!("Editor `{}` failed: {}", editor.to_string_lossy(), status);
+          }
           Err(status.code().unwrap_or(EXIT_FAILURE))
         },
       Err(error) => {
-        eprintln!(
-          "Editor `{}` invocation failed: {}",
-          editor.to_string_lossy(),
-          error
-        );
+        if self.verbosity.loud() {
+          eprintln!(
+            "Editor `{}` invocation failed: {}",
+            editor.to_string_lossy(),
+            error
+          );
+        }
         Err(EXIT_FAILURE)
       },
     }
@@ -660,17 +676,23 @@ impl Config {
       Search::init(&self.search_config, &self.invocation_directory).eprint(self.color)?;
 
     if search.justfile.exists() {
-      eprintln!("Justfile `{}` already exists", search.justfile.display());
+      if self.verbosity.loud() {
+        eprintln!("Justfile `{}` already exists", search.justfile.display());
+      }
       Err(EXIT_FAILURE)
     } else if let Err(err) = fs::write(&search.justfile, INIT_JUSTFILE) {
-      eprintln!(
-        "Failed to write justfile to `{}`: {}",
-        search.justfile.display(),
-        err
-      );
+      if self.verbosity.loud() {
+        eprintln!(
+          "Failed to write justfile to `{}`: {}",
+          search.justfile.display(),
+          err
+        );
+      }
       Err(EXIT_FAILURE)
     } else {
-      eprintln!("Wrote justfile to `{}`", search.justfile.display());
+      if self.verbosity.loud() {
+        eprintln!("Wrote justfile to `{}`", search.justfile.display());
+      }
       Ok(())
     }
   }
@@ -766,7 +788,7 @@ impl Config {
     overrides: &BTreeMap<String, String>,
     arguments: &[String],
   ) -> Result<(), i32> {
-    if let Err(error) = InterruptHandler::install() {
+    if let Err(error) = InterruptHandler::install(self.verbosity) {
       warn!("Failed to set CTRL-C handler: {}", error)
     }
 
@@ -779,7 +801,7 @@ impl Config {
     }
   }
 
-  fn show(name: &str, justfile: Justfile) -> Result<(), i32> {
+  fn show(&self, name: &str, justfile: Justfile) -> Result<(), i32> {
     if let Some(alias) = justfile.get_alias(name) {
       let recipe = justfile.get_recipe(alias.target.name.lexeme()).unwrap();
       println!("{}", alias);
@@ -789,9 +811,11 @@ impl Config {
       println!("{}", recipe);
       Ok(())
     } else {
-      eprintln!("Justfile does not contain recipe `{}`.", name);
-      if let Some(suggestion) = justfile.suggest(name) {
-        eprintln!("{}", suggestion);
+      if self.verbosity.loud() {
+        eprintln!("Justfile does not contain recipe `{}`.", name);
+        if let Some(suggestion) = justfile.suggest(name) {
+          eprintln!("{}", suggestion);
+        }
       }
       Err(EXIT_FAILURE)
     }
@@ -799,7 +823,9 @@ impl Config {
 
   fn summary(&self, justfile: Justfile) {
     if justfile.count() == 0 {
-      eprintln!("Justfile contains no recipes.");
+      if self.verbosity.loud() {
+        eprintln!("Justfile contains no recipes.");
+      }
     } else {
       let summary = justfile
         .public_recipes(self.unsorted)
diff --git a/src/interrupt_handler.rs b/src/interrupt_handler.rs
index 65e40a83..a0d161ea 100644
--- a/src/interrupt_handler.rs
+++ b/src/interrupt_handler.rs
@@ -3,10 +3,13 @@ use crate::common::*;
 pub(crate) struct InterruptHandler {
   blocks:      u32,
   interrupted: bool,
+  verbosity:   Verbosity,
 }
 
 impl InterruptHandler {
-  pub(crate) fn install() -> Result<(), ctrlc::Error> {
+  pub(crate) fn install(verbosity: Verbosity) -> Result<(), ctrlc::Error> {
+    let mut instance = Self::instance();
+    instance.verbosity = verbosity;
     ctrlc::set_handler(|| Self::instance().interrupt())
   }
 
@@ -30,6 +33,7 @@ impl InterruptHandler {
     Self {
       blocks:      0,
       interrupted: false,
+      verbosity:   Verbosity::default(),
     }
   }
 
@@ -53,9 +57,11 @@ impl InterruptHandler {
 
   pub(crate) fn unblock(&mut self) {
     if self.blocks == 0 {
-      eprintln!("{}", RuntimeError::Internal {
-        message: "attempted to unblock interrupt handler, but handler was not blocked".to_owned(),
-      });
+      if self.verbosity.loud() {
+        eprintln!("{}", RuntimeError::Internal {
+          message: "attempted to unblock interrupt handler, but handler was not blocked".to_owned(),
+        });
+      }
       std::process::exit(EXIT_FAILURE);
     }
 
diff --git a/src/recipe.rs b/src/recipe.rs
index 48767cb2..625e8ba3 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -96,7 +96,7 @@ impl<'src, D> Recipe<'src, D> {
         evaluated_lines.push(evaluator.evaluate_line(line, false)?);
       }
 
-      if config.dry_run || self.quiet {
+      if config.verbosity.loud() && (config.dry_run || self.quiet) {
         for line in &evaluated_lines {
           eprintln!("{}", line);
         }
diff --git a/src/subcommand.rs b/src/subcommand.rs
index 287e1de3..53e7399f 100644
--- a/src/subcommand.rs
+++ b/src/subcommand.rs
@@ -203,18 +203,25 @@ const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[(
 )];
 
 impl Subcommand {
-  pub(crate) fn completions(shell: &str) -> Result<(), i32> {
+  pub(crate) fn completions(verbosity: Verbosity, shell: &str) -> Result<(), i32> {
     use clap::Shell;
 
-    fn replace(haystack: &mut String, needle: &str, replacement: &str) -> Result<(), i32> {
+    fn replace(
+      verbosity: Verbosity,
+      haystack: &mut String,
+      needle: &str,
+      replacement: &str,
+    ) -> Result<(), i32> {
       if let Some(index) = haystack.find(needle) {
         haystack.replace_range(index..index + needle.len(), replacement);
         Ok(())
       } else {
-        eprintln!("Failed to find text:");
-        eprintln!("{}", needle);
-        eprintln!("…in completion script:");
-        eprintln!("{}", haystack);
+        if verbosity.loud() {
+          eprintln!("Failed to find text:");
+          eprintln!("{}", needle);
+          eprintln!("…in completion script:");
+          eprintln!("{}", haystack);
+        }
         Err(EXIT_FAILURE)
       }
     }
@@ -232,19 +239,19 @@ impl Subcommand {
     match shell {
       Shell::Bash =>
         for (needle, replacement) in BASH_COMPLETION_REPLACEMENTS {
-          replace(&mut script, needle, replacement)?;
+          replace(verbosity, &mut script, needle, replacement)?;
         },
       Shell::Fish => {
         script.insert_str(0, FISH_RECIPE_COMPLETIONS);
       },
       Shell::PowerShell =>
         for (needle, replacement) in POWERSHELL_COMPLETION_REPLACEMENTS {
-          replace(&mut script, needle, replacement)?;
+          replace(verbosity, &mut script, needle, replacement)?;
         },
 
       Shell::Zsh =>
         for (needle, replacement) in ZSH_COMPLETION_REPLACEMENTS {
-          replace(&mut script, needle, replacement)?;
+          replace(verbosity, &mut script, needle, replacement)?;
         },
       Shell::Elvish => {},
     }
diff --git a/src/verbosity.rs b/src/verbosity.rs
index c4fadff7..51b9b9c9 100644
--- a/src/verbosity.rs
+++ b/src/verbosity.rs
@@ -21,6 +21,10 @@ impl Verbosity {
     matches!(self, Quiet)
   }
 
+  pub(crate) fn loud(self) -> bool {
+    !self.quiet()
+  }
+
   pub(crate) fn loquacious(self) -> bool {
     match self {
       Quiet | Taciturn => false,
@@ -35,3 +39,9 @@ impl Verbosity {
     }
   }
 }
+
+impl Default for Verbosity {
+  fn default() -> Self {
+    Self::Taciturn
+  }
+}


Test patch:
diff --git a/tests/lib.rs b/tests/lib.rs
index ea2abbf9..dbdf1498 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -15,6 +15,7 @@ mod init;
 mod interrupts;
 mod invocation_directory;
 mod misc;
+mod quiet;
 mod readme;
 mod search;
 mod shell;
diff --git a/tests/misc.rs b/tests/misc.rs
index 36e2e085..989fdf29 100644
--- a/tests/misc.rs
+++ b/tests/misc.rs
@@ -666,71 +666,6 @@ test! {
   status:   EXIT_FAILURE,
 }
 
-test! {
-  name:     quiet_flag_no_stdout,
-  justfile: r#"
-default:
-  @echo hello
-"#,
-  args:     ("--quiet"),
-  stdout:   "",
-}
-
-test! {
-  name:     quiet_flag_no_stderr,
-  justfile: r#"
-default:
-  @echo hello 1>&2
-"#,
-  args:     ("--quiet"),
-  stdout:   "",
-}
-
-test! {
-  name:     quiet_flag_no_command_echoing,
-  justfile: r#"
-default:
-  exit
-"#,
-  args:     ("--quiet"),
-  stdout:   "",
-}
-
-test! {
-  name:     quiet_flag_no_error_messages,
-  justfile: r#"
-default:
-  exit 100
-"#,
-  args:     ("--quiet"),
-  stdout:   "",
-  status:   100,
-}
-
-test! {
-  name:     quiet_flag_no_assignment_backtick_stderr,
-  justfile: r#"
-a := `echo hello 1>&2`
-default:
-  exit 100
-"#,
-  args:     ("--quiet"),
-  stdout:   "",
-  status:   100,
-}
-
-test! {
-  name:     quiet_flag_no_interpolation_backtick_stderr,
-  justfile: r#"
-default:
-  echo `echo hello 1>&2`
-  exit 100
-"#,
-  args:     ("--quiet"),
-  stdout:   "",
-  status:   100,
-}
-
 test! {
   name:     argument_single,
   justfile: "
diff --git a/tests/quiet.rs b/tests/quiet.rs
new file mode 100644
index 00000000..1f3058e8
--- /dev/null
+++ b/tests/quiet.rs
@@ -0,0 +1,147 @@
+use crate::common::*;
+
+test! {
+  name:     no_stdout,
+  justfile: r#"
+default:
+  @echo hello
+"#,
+  args:     ("--quiet"),
+  stdout:   "",
+}
+
+test! {
+  name:     stderr,
+  justfile: r#"
+default:
+  @echo hello 1>&2
+"#,
+  args:     ("--quiet"),
+  stdout:   "",
+}
+
+test! {
+  name:     command_echoing,
+  justfile: r#"
+default:
+  exit
+"#,
+  args:     ("--quiet"),
+  stdout:   "",
+}
+
+test! {
+  name:     error_messages,
+  justfile: r#"
+default:
+  exit 100
+"#,
+  args:     ("--quiet"),
+  stdout:   "",
+  status:   100,
+}
+
+test! {
+  name:     assignment_backtick_stderr,
+  justfile: r#"
+a := `echo hello 1>&2`
+default:
+  exit 100
+"#,
+  args:     ("--quiet"),
+  stdout:   "",
+  status:   100,
+}
+
+test! {
+  name:     interpolation_backtick_stderr,
+  justfile: r#"
+default:
+  echo `echo hello 1>&2`
+  exit 100
+"#,
+  args:     ("--quiet"),
+  stdout:   "",
+  status:   100,
+}
+
+test! {
+  name: warning,
+  justfile: "
+    foo = 'bar'
+
+    baz:
+  ",
+  args: ("--quiet"),
+}
+
+test! {
+  name: choose_none,
+  justfile: "",
+  args: ("--choose", "--quiet"),
+  status: EXIT_FAILURE,
+}
+
+test! {
+  name: choose_invocation,
+  justfile: "foo:",
+  args: ("--choose", "--quiet", "--shell", "asdfasdfasfdasdfasdfadsf"),
+  status: EXIT_FAILURE,
+  shell: false,
+}
+
+test! {
+  name: choose_status,
+  justfile: "foo:",
+  args: ("--choose", "--quiet", "--chooser", "/usr/bin/env false"),
+  status: EXIT_FAILURE,
+}
+
+test! {
+  name: edit_invocation,
+  justfile: "foo:",
+  args: ("--edit", "--quiet"),
+  env: {
+    "VISUAL": "adsfasdfasdfadsfadfsaf",
+  },
+  status: EXIT_FAILURE,
+}
+
+test! {
+  name: edit_status,
+  justfile: "foo:",
+  args: ("--edit", "--quiet"),
+  env: {
+    "VISUAL": "false",
+  },
+  status: EXIT_FAILURE,
+}
+
+test! {
+  name: init_exists,
+  justfile: "foo:",
+  args: ("--init", "--quiet"),
+  status: EXIT_FAILURE,
+}
+
+test! {
+  name: show_missing,
+  justfile: "foo:",
+  args: ("--show", "bar", "--quiet"),
+  status: EXIT_FAILURE,
+}
+
+test! {
+  name: summary_none,
+  justfile: "",
+  args: ("--summary", "--quiet"),
+}
+
+test! {
+  name: quiet_shebang,
+  justfile: "
+    @foo:
+      #!/bin/sh
+  ",
+  args: ("--quiet"),
+}