Add --quiet/-q flag to supress all output (#17)

This is for avoiding writing to stderr during tests,
although it's a nice option so it feels worth exposing
to users.
This commit is contained in:
Casey Rodarmor 2016-11-05 01:01:43 -07:00 committed by GitHub
parent 599cc80f86
commit e4d35a8270
3 changed files with 148 additions and 23 deletions

View File

@ -30,6 +30,10 @@ pub fn app() {
.short("l") .short("l")
.long("list") .long("list")
.help("Lists available recipes")) .help("Lists available recipes"))
.arg(Arg::with_name("quiet")
.short("q")
.long("quiet")
.help("Suppress all output"))
.arg(Arg::with_name("dry-run") .arg(Arg::with_name("dry-run")
.long("dry-run") .long("dry-run")
.help("Print recipe text without executing")) .help("Print recipe text without executing"))
@ -69,6 +73,11 @@ pub fn app() {
die!("--justfile and --working-directory may only be used together"); die!("--justfile and --working-directory may only be used together");
} }
// --dry-run and --quiet don't make sense together
if matches.is_present("dry-run") && matches.is_present("quiet") {
die!("--dry-run and --quiet may not be used together");
}
let justfile_option = matches.value_of("justfile"); let justfile_option = matches.value_of("justfile");
let working_directory_option = matches.value_of("working-directory"); let working_directory_option = matches.value_of("working-directory");
@ -164,10 +173,13 @@ pub fn app() {
dry_run: matches.is_present("dry-run"), dry_run: matches.is_present("dry-run"),
evaluate: matches.is_present("evaluate"), evaluate: matches.is_present("evaluate"),
overrides: overrides, overrides: overrides,
quiet: matches.is_present("quiet"),
}; };
if let Err(run_error) = justfile.run(&arguments, &options) { if let Err(run_error) = justfile.run(&arguments, &options) {
warn!("{}", run_error); if !options.quiet {
warn!("{}", run_error);
}
match run_error { match run_error {
RunError::Code{code, .. } | RunError::BacktickCode{code, ..} => process::exit(code), RunError::Code{code, .. } | RunError::BacktickCode{code, ..} => process::exit(code),
_ => process::exit(-1), _ => process::exit(-1),

View File

@ -575,3 +575,100 @@ fn line_error_spacing() {
", ",
); );
} }
#[test]
fn quiet_flag_no_stdout() {
integration_test(
&["--quiet"],
r#"
default:
@echo hello
"#,
0,
"",
"",
);
}
#[test]
fn quiet_flag_no_stderr() {
integration_test(
&["--quiet"],
r#"
default:
@echo hello 1>&2
"#,
0,
"",
"",
);
}
#[test]
fn quiet_flag_no_command_echoing() {
integration_test(
&["--quiet"],
r#"
default:
exit
"#,
0,
"",
"",
);
}
#[test]
fn quiet_flag_no_error_messages() {
integration_test(
&["--quiet"],
r#"
default:
exit 100
"#,
100,
"",
"",
);
}
#[test]
fn quiet_flag_no_assignment_backtick_stderr() {
integration_test(
&["--quiet"],
r#"
a = `echo hello 1>&2`
default:
exit 100
"#,
100,
"",
"",
);
}
#[test]
fn quiet_flag_no_interpolation_backtick_stderr() {
integration_test(
&["--quiet"],
r#"
default:
echo `echo hello 1>&2`
exit 100
"#,
100,
"",
"",
);
}
#[test]
fn quiet_flag_or_dry_run_flag() {
integration_test(
&["--quiet", "--dry-run"],
r#""#,
255,
"",
"--dry-run and --quiet may not be used together\n",
);
}

View File

@ -172,14 +172,20 @@ fn run_backtick<'a>(
token: &Token<'a>, token: &Token<'a>,
scope: &Map<&'a str, String>, scope: &Map<&'a str, String>,
exports: &Set<&'a str>, exports: &Set<&'a str>,
quiet: bool,
) -> Result<String, RunError<'a>> { ) -> Result<String, RunError<'a>> {
let mut cmd = process::Command::new("sh"); let mut cmd = process::Command::new("sh");
try!(export_env(&mut cmd, scope, exports)); try!(export_env(&mut cmd, scope, exports));
cmd.arg("-cu") cmd.arg("-cu")
.arg(raw) .arg(raw);
.stderr(process::Stdio::inherit());
cmd.stderr(if quiet {
process::Stdio::null()
} else {
process::Stdio::inherit()
});
match cmd.output() { match cmd.output() {
Ok(output) => { Ok(output) => {
@ -216,7 +222,7 @@ impl<'a> Recipe<'a> {
arguments: &[&'a str], arguments: &[&'a str],
scope: &Map<&'a str, String>, scope: &Map<&'a str, String>,
exports: &Set<&'a str>, exports: &Set<&'a str>,
dry_run: bool, options: &RunOptions,
) -> Result<(), RunError<'a>> { ) -> Result<(), RunError<'a>> {
let argument_map = arguments .iter().enumerate() let argument_map = arguments .iter().enumerate()
.map(|(i, argument)| (self.parameters[i], *argument)).collect(); .map(|(i, argument)| (self.parameters[i], *argument)).collect();
@ -227,6 +233,7 @@ impl<'a> Recipe<'a> {
exports: exports, exports: exports,
assignments: &Map::new(), assignments: &Map::new(),
overrides: &Map::new(), overrides: &Map::new(),
quiet: options.quiet,
}; };
if self.shebang { if self.shebang {
@ -235,7 +242,7 @@ impl<'a> Recipe<'a> {
evaluated_lines.push(try!(evaluator.evaluate_line(&line, &argument_map))); evaluated_lines.push(try!(evaluator.evaluate_line(&line, &argument_map)));
} }
if dry_run { if options.dry_run {
for line in evaluated_lines { for line in evaluated_lines {
warn!("{}", line); warn!("{}", line);
} }
@ -305,14 +312,14 @@ impl<'a> Recipe<'a> {
for line in &self.lines { for line in &self.lines {
let evaluated = &try!(evaluator.evaluate_line(&line, &argument_map)); let evaluated = &try!(evaluator.evaluate_line(&line, &argument_map));
let mut command = evaluated.as_str(); let mut command = evaluated.as_str();
let quiet = command.starts_with('@'); let quiet_command = command.starts_with('@');
if quiet { if quiet_command {
command = &command[1..]; command = &command[1..];
} }
if dry_run || !quiet { if options.dry_run || !(quiet_command || options.quiet) {
warn!("{}", command); warn!("{}", command);
} }
if dry_run { if options.dry_run {
continue; continue;
} }
@ -320,6 +327,11 @@ impl<'a> Recipe<'a> {
cmd.arg("-cu").arg(command); cmd.arg("-cu").arg(command);
if options.quiet {
cmd.stderr(process::Stdio::null());
cmd.stdout(process::Stdio::null());
}
try!(export_env(&mut cmd, scope, exports)); try!(export_env(&mut cmd, scope, exports));
try!(match cmd.status() { try!(match cmd.status() {
@ -543,13 +555,15 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
fn evaluate_assignments<'a>( fn evaluate_assignments<'a>(
assignments: &Map<&'a str, Expression<'a>>, assignments: &Map<&'a str, Expression<'a>>,
overrides: &Map<&str, &str>, overrides: &Map<&str, &str>,
quiet: bool,
) -> Result<Map<&'a str, String>, RunError<'a>> { ) -> Result<Map<&'a str, String>, RunError<'a>> {
let mut evaluator = Evaluator { let mut evaluator = Evaluator {
evaluated: Map::new(),
scope: &Map::new(),
exports: &Set::new(),
assignments: assignments, assignments: assignments,
evaluated: Map::new(),
exports: &Set::new(),
overrides: overrides, overrides: overrides,
quiet: quiet,
scope: &Map::new(),
}; };
for name in assignments.keys() { for name in assignments.keys() {
@ -560,11 +574,12 @@ fn evaluate_assignments<'a>(
} }
struct Evaluator<'a: 'b, 'b> { struct Evaluator<'a: 'b, 'b> {
evaluated: Map<&'a str, String>,
scope: &'b Map<&'a str, String>,
exports: &'b Set<&'a str>,
assignments: &'b Map<&'a str, Expression<'a>>, assignments: &'b Map<&'a str, Expression<'a>>,
evaluated: Map<&'a str, String>,
exports: &'b Set<&'a str>,
overrides: &'b Map<&'b str, &'b str>, overrides: &'b Map<&'b str, &'b str>,
quiet: bool,
scope: &'b Map<&'a str, String>,
} }
impl<'a, 'b> Evaluator<'a, 'b> { impl<'a, 'b> Evaluator<'a, 'b> {
@ -630,7 +645,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
} }
Expression::String{ref cooked, ..} => cooked.clone(), Expression::String{ref cooked, ..} => cooked.clone(),
Expression::Backtick{raw, ref token} => { Expression::Backtick{raw, ref token} => {
try!(run_backtick(raw, token, &self.scope, &self.exports)) try!(run_backtick(raw, token, &self.scope, &self.exports, self.quiet))
} }
Expression::Concatination{ref lhs, ref rhs} => { Expression::Concatination{ref lhs, ref rhs} => {
try!(self.evaluate_expression(lhs, arguments)) try!(self.evaluate_expression(lhs, arguments))
@ -837,6 +852,7 @@ struct RunOptions<'a> {
dry_run: bool, dry_run: bool,
evaluate: bool, evaluate: bool,
overrides: Map<&'a str, &'a str>, overrides: Map<&'a str, &'a str>,
quiet: bool,
} }
impl<'a, 'b> Justfile<'a> where 'a: 'b { impl<'a, 'b> Justfile<'a> where 'a: 'b {
@ -865,7 +881,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
fn run( fn run(
&'a self, &'a self,
arguments: &[&'a str], arguments: &[&'a str],
options: &RunOptions<'a>, options: &RunOptions<'a>,
) -> Result<(), RunError<'a>> { ) -> Result<(), RunError<'a>> {
let unknown_overrides = options.overrides.keys().cloned() let unknown_overrides = options.overrides.keys().cloned()
.filter(|name| !self.assignments.contains_key(name)) .filter(|name| !self.assignments.contains_key(name))
@ -875,7 +891,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
return Err(RunError::UnknownOverrides{overrides: unknown_overrides}); return Err(RunError::UnknownOverrides{overrides: unknown_overrides});
} }
let scope = try!(evaluate_assignments(&self.assignments, &options.overrides)); let scope = try!(evaluate_assignments(&self.assignments, &options.overrides, options.quiet));
if options.evaluate { if options.evaluate {
for (name, value) in scope { for (name, value) in scope {
println!("{} = \"{}\"", name, value); println!("{} = \"{}\"", name, value);
@ -899,7 +915,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
expected: recipe.parameters.len(), expected: recipe.parameters.len(),
}); });
} }
try!(self.run_recipe(recipe, rest, &scope, &mut ran, options.dry_run)); try!(self.run_recipe(recipe, rest, &scope, &mut ran, options));
return Ok(()); return Ok(());
} }
} else { } else {
@ -917,7 +933,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
return Err(RunError::UnknownRecipes{recipes: missing}); return Err(RunError::UnknownRecipes{recipes: missing});
} }
for recipe in arguments.iter().map(|name| &self.recipes[name]) { for recipe in arguments.iter().map(|name| &self.recipes[name]) {
try!(self.run_recipe(recipe, &[], &scope, &mut ran, options.dry_run)); try!(self.run_recipe(recipe, &[], &scope, &mut ran, options));
} }
Ok(()) Ok(())
} }
@ -928,14 +944,14 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
arguments: &[&'a str], arguments: &[&'a str],
scope: &Map<&'c str, String>, scope: &Map<&'c str, String>,
ran: &mut Set<&'a str>, ran: &mut Set<&'a str>,
dry_run: bool, options: &RunOptions<'a>,
) -> Result<(), RunError> { ) -> Result<(), RunError> {
for dependency_name in &recipe.dependencies { for dependency_name in &recipe.dependencies {
if !ran.contains(dependency_name) { if !ran.contains(dependency_name) {
try!(self.run_recipe(&self.recipes[dependency_name], &[], scope, ran, dry_run)); try!(self.run_recipe(&self.recipes[dependency_name], &[], scope, ran, options));
} }
} }
try!(recipe.run(arguments, &scope, &self.exports, dry_run)); try!(recipe.run(arguments, &scope, &self.exports, options));
ran.insert(recipe.name); ran.insert(recipe.name);
Ok(()) Ok(())
} }