Suppress all output to stderr when --quiet (#771)

Suppress all warnings and error messages when `--quiet` is passed.
This commit is contained in:
Casey Rodarmor 2021-03-25 16:51:29 -07:00 committed by GitHub
parent d398417de3
commit 86c2e52dc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 260 additions and 128 deletions

View File

@ -483,14 +483,14 @@ impl Config {
} }
if let Completions { shell } = self.subcommand { if let Completions { shell } = self.subcommand {
return Subcommand::completions(&shell); return Subcommand::completions(self.verbosity, &shell);
} }
let search = let search =
Search::find(&self.search_config, &self.invocation_directory).eprint(self.color)?; Search::find(&self.search_config, &self.invocation_directory).eprint(self.color)?;
if self.subcommand == Edit { if self.subcommand == Edit {
return Self::edit(&search); return self.edit(&search);
} }
let src = fs::read_to_string(&search.justfile) let src = fs::read_to_string(&search.justfile)
@ -502,6 +502,7 @@ impl Config {
let justfile = Compiler::compile(&src).eprint(self.color)?; let justfile = Compiler::compile(&src).eprint(self.color)?;
if self.verbosity.loud() {
for warning in &justfile.warnings { for warning in &justfile.warnings {
if self.color.stderr().active() { if self.color.stderr().active() {
eprintln!("{:#}", warning); eprintln!("{:#}", warning);
@ -509,6 +510,7 @@ impl Config {
eprintln!("{}", warning); eprintln!("{}", warning);
} }
} }
}
match &self.subcommand { match &self.subcommand {
Choose { overrides, chooser } => Choose { overrides, chooser } =>
@ -520,7 +522,7 @@ impl Config {
arguments, arguments,
overrides, overrides,
} => self.run(justfile, &search, overrides, arguments)?, } => self.run(justfile, &search, overrides, arguments)?,
Show { ref name } => Self::show(&name, justfile)?, Show { ref name } => self.show(&name, justfile)?,
Summary => self.summary(justfile), Summary => self.summary(justfile),
Variables => Self::variables(justfile), Variables => Self::variables(justfile),
Completions { .. } | Edit | Init => unreachable!(), Completions { .. } | Edit | Init => unreachable!(),
@ -544,7 +546,9 @@ impl Config {
.collect::<Vec<&Recipe<Dependency>>>(); .collect::<Vec<&Recipe<Dependency>>>();
if recipes.is_empty() { if recipes.is_empty() {
if self.verbosity.loud() {
eprintln!("Justfile contains no choosable recipes."); eprintln!("Justfile contains no choosable recipes.");
}
return Err(EXIT_FAILURE); return Err(EXIT_FAILURE);
} }
@ -565,11 +569,13 @@ impl Config {
let mut child = match result { let mut child = match result {
Ok(child) => child, Ok(child) => child,
Err(error) => { Err(error) => {
if self.verbosity.loud() {
eprintln!( eprintln!(
"Chooser `{}` invocation failed: {}", "Chooser `{}` invocation failed: {}",
chooser.to_string_lossy(), chooser.to_string_lossy(),
error error
); );
}
return Err(EXIT_FAILURE); return Err(EXIT_FAILURE);
}, },
}; };
@ -581,11 +587,13 @@ impl Config {
.expect("Child was created with piped stdio") .expect("Child was created with piped stdio")
.write_all(format!("{}\n", recipe.name).as_bytes()) .write_all(format!("{}\n", recipe.name).as_bytes())
{ {
if self.verbosity.loud() {
eprintln!( eprintln!(
"Failed to write to chooser `{}`: {}", "Failed to write to chooser `{}`: {}",
chooser.to_string_lossy(), chooser.to_string_lossy(),
error error
); );
}
return Err(EXIT_FAILURE); return Err(EXIT_FAILURE);
} }
} }
@ -593,21 +601,25 @@ impl Config {
let output = match child.wait_with_output() { let output = match child.wait_with_output() {
Ok(output) => output, Ok(output) => output,
Err(error) => { Err(error) => {
if self.verbosity.loud() {
eprintln!( eprintln!(
"Failed to read output from chooser `{}`: {}", "Failed to read output from chooser `{}`: {}",
chooser.to_string_lossy(), chooser.to_string_lossy(),
error error
); );
}
return Err(EXIT_FAILURE); return Err(EXIT_FAILURE);
}, },
}; };
if !output.status.success() { if !output.status.success() {
if self.verbosity.loud() {
eprintln!( eprintln!(
"Chooser `{}` returned error: {}", "Chooser `{}` returned error: {}",
chooser.to_string_lossy(), chooser.to_string_lossy(),
output.status output.status
); );
}
return Err(output.status.code().unwrap_or(EXIT_FAILURE)); return Err(output.status.code().unwrap_or(EXIT_FAILURE));
} }
@ -626,7 +638,7 @@ impl Config {
println!("{}", justfile); 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") let editor = env::var_os("VISUAL")
.or_else(|| env::var_os("EDITOR")) .or_else(|| env::var_os("EDITOR"))
.unwrap_or_else(|| "vim".into()); .unwrap_or_else(|| "vim".into());
@ -641,15 +653,19 @@ impl Config {
if status.success() { if status.success() {
Ok(()) Ok(())
} else { } else {
if self.verbosity.loud() {
eprintln!("Editor `{}` failed: {}", editor.to_string_lossy(), status); eprintln!("Editor `{}` failed: {}", editor.to_string_lossy(), status);
}
Err(status.code().unwrap_or(EXIT_FAILURE)) Err(status.code().unwrap_or(EXIT_FAILURE))
}, },
Err(error) => { Err(error) => {
if self.verbosity.loud() {
eprintln!( eprintln!(
"Editor `{}` invocation failed: {}", "Editor `{}` invocation failed: {}",
editor.to_string_lossy(), editor.to_string_lossy(),
error error
); );
}
Err(EXIT_FAILURE) Err(EXIT_FAILURE)
}, },
} }
@ -660,17 +676,23 @@ impl Config {
Search::init(&self.search_config, &self.invocation_directory).eprint(self.color)?; Search::init(&self.search_config, &self.invocation_directory).eprint(self.color)?;
if search.justfile.exists() { if search.justfile.exists() {
if self.verbosity.loud() {
eprintln!("Justfile `{}` already exists", search.justfile.display()); eprintln!("Justfile `{}` already exists", search.justfile.display());
}
Err(EXIT_FAILURE) Err(EXIT_FAILURE)
} else if let Err(err) = fs::write(&search.justfile, INIT_JUSTFILE) { } else if let Err(err) = fs::write(&search.justfile, INIT_JUSTFILE) {
if self.verbosity.loud() {
eprintln!( eprintln!(
"Failed to write justfile to `{}`: {}", "Failed to write justfile to `{}`: {}",
search.justfile.display(), search.justfile.display(),
err err
); );
}
Err(EXIT_FAILURE) Err(EXIT_FAILURE)
} else { } else {
if self.verbosity.loud() {
eprintln!("Wrote justfile to `{}`", search.justfile.display()); eprintln!("Wrote justfile to `{}`", search.justfile.display());
}
Ok(()) Ok(())
} }
} }
@ -766,7 +788,7 @@ impl Config {
overrides: &BTreeMap<String, String>, overrides: &BTreeMap<String, String>,
arguments: &[String], arguments: &[String],
) -> Result<(), i32> { ) -> Result<(), i32> {
if let Err(error) = InterruptHandler::install() { if let Err(error) = InterruptHandler::install(self.verbosity) {
warn!("Failed to set CTRL-C handler: {}", error) 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) { if let Some(alias) = justfile.get_alias(name) {
let recipe = justfile.get_recipe(alias.target.name.lexeme()).unwrap(); let recipe = justfile.get_recipe(alias.target.name.lexeme()).unwrap();
println!("{}", alias); println!("{}", alias);
@ -789,17 +811,21 @@ impl Config {
println!("{}", recipe); println!("{}", recipe);
Ok(()) Ok(())
} else { } else {
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(name) {
eprintln!("{}", suggestion); eprintln!("{}", suggestion);
} }
}
Err(EXIT_FAILURE) Err(EXIT_FAILURE)
} }
} }
fn summary(&self, justfile: Justfile) { fn summary(&self, justfile: Justfile) {
if justfile.count() == 0 { if justfile.count() == 0 {
if self.verbosity.loud() {
eprintln!("Justfile contains no recipes."); eprintln!("Justfile contains no recipes.");
}
} else { } else {
let summary = justfile let summary = justfile
.public_recipes(self.unsorted) .public_recipes(self.unsorted)

View File

@ -3,10 +3,13 @@ use crate::common::*;
pub(crate) struct InterruptHandler { pub(crate) struct InterruptHandler {
blocks: u32, blocks: u32,
interrupted: bool, interrupted: bool,
verbosity: Verbosity,
} }
impl InterruptHandler { 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()) ctrlc::set_handler(|| Self::instance().interrupt())
} }
@ -30,6 +33,7 @@ impl InterruptHandler {
Self { Self {
blocks: 0, blocks: 0,
interrupted: false, interrupted: false,
verbosity: Verbosity::default(),
} }
} }
@ -53,9 +57,11 @@ impl InterruptHandler {
pub(crate) fn unblock(&mut self) { pub(crate) fn unblock(&mut self) {
if self.blocks == 0 { if self.blocks == 0 {
if self.verbosity.loud() {
eprintln!("{}", RuntimeError::Internal { eprintln!("{}", RuntimeError::Internal {
message: "attempted to unblock interrupt handler, but handler was not blocked".to_owned(), message: "attempted to unblock interrupt handler, but handler was not blocked".to_owned(),
}); });
}
std::process::exit(EXIT_FAILURE); std::process::exit(EXIT_FAILURE);
} }

View File

@ -96,7 +96,7 @@ impl<'src, D> Recipe<'src, D> {
evaluated_lines.push(evaluator.evaluate_line(line, false)?); 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 { for line in &evaluated_lines {
eprintln!("{}", line); eprintln!("{}", line);
} }

View File

@ -203,18 +203,25 @@ const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[(
)]; )];
impl Subcommand { impl Subcommand {
pub(crate) fn completions(shell: &str) -> Result<(), i32> { pub(crate) fn completions(verbosity: Verbosity, shell: &str) -> Result<(), i32> {
use clap::Shell; 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) { if let Some(index) = haystack.find(needle) {
haystack.replace_range(index..index + needle.len(), replacement); haystack.replace_range(index..index + needle.len(), replacement);
Ok(()) Ok(())
} else { } else {
if verbosity.loud() {
eprintln!("Failed to find text:"); eprintln!("Failed to find text:");
eprintln!("{}", needle); eprintln!("{}", needle);
eprintln!("…in completion script:"); eprintln!("…in completion script:");
eprintln!("{}", haystack); eprintln!("{}", haystack);
}
Err(EXIT_FAILURE) Err(EXIT_FAILURE)
} }
} }
@ -232,19 +239,19 @@ impl Subcommand {
match shell { match shell {
Shell::Bash => Shell::Bash =>
for (needle, replacement) in BASH_COMPLETION_REPLACEMENTS { for (needle, replacement) in BASH_COMPLETION_REPLACEMENTS {
replace(&mut script, needle, replacement)?; replace(verbosity, &mut script, needle, replacement)?;
}, },
Shell::Fish => { Shell::Fish => {
script.insert_str(0, FISH_RECIPE_COMPLETIONS); script.insert_str(0, FISH_RECIPE_COMPLETIONS);
}, },
Shell::PowerShell => Shell::PowerShell =>
for (needle, replacement) in POWERSHELL_COMPLETION_REPLACEMENTS { for (needle, replacement) in POWERSHELL_COMPLETION_REPLACEMENTS {
replace(&mut script, needle, replacement)?; replace(verbosity, &mut script, needle, replacement)?;
}, },
Shell::Zsh => Shell::Zsh =>
for (needle, replacement) in ZSH_COMPLETION_REPLACEMENTS { for (needle, replacement) in ZSH_COMPLETION_REPLACEMENTS {
replace(&mut script, needle, replacement)?; replace(verbosity, &mut script, needle, replacement)?;
}, },
Shell::Elvish => {}, Shell::Elvish => {},
} }

View File

@ -21,6 +21,10 @@ impl Verbosity {
matches!(self, Quiet) matches!(self, Quiet)
} }
pub(crate) fn loud(self) -> bool {
!self.quiet()
}
pub(crate) fn loquacious(self) -> bool { pub(crate) fn loquacious(self) -> bool {
match self { match self {
Quiet | Taciturn => false, Quiet | Taciturn => false,
@ -35,3 +39,9 @@ impl Verbosity {
} }
} }
} }
impl Default for Verbosity {
fn default() -> Self {
Self::Taciturn
}
}

View File

@ -15,6 +15,7 @@ mod init;
mod interrupts; mod interrupts;
mod invocation_directory; mod invocation_directory;
mod misc; mod misc;
mod quiet;
mod readme; mod readme;
mod search; mod search;
mod shell; mod shell;

View File

@ -666,71 +666,6 @@ test! {
status: EXIT_FAILURE, 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! { test! {
name: argument_single, name: argument_single,
justfile: " justfile: "

147
tests/quiet.rs Normal file
View File

@ -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"),
}