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")
.long("list")
.help("Lists available recipes"))
.arg(Arg::with_name("quiet")
.short("q")
.long("quiet")
.help("Suppress all output"))
.arg(Arg::with_name("dry-run")
.long("dry-run")
.help("Print recipe text without executing"))
@ -69,6 +73,11 @@ pub fn app() {
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 working_directory_option = matches.value_of("working-directory");
@ -164,10 +173,13 @@ pub fn app() {
dry_run: matches.is_present("dry-run"),
evaluate: matches.is_present("evaluate"),
overrides: overrides,
quiet: matches.is_present("quiet"),
};
if let Err(run_error) = justfile.run(&arguments, &options) {
warn!("{}", run_error);
if !options.quiet {
warn!("{}", run_error);
}
match run_error {
RunError::Code{code, .. } | RunError::BacktickCode{code, ..} => process::exit(code),
_ => 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>,
scope: &Map<&'a str, String>,
exports: &Set<&'a str>,
quiet: bool,
) -> Result<String, RunError<'a>> {
let mut cmd = process::Command::new("sh");
try!(export_env(&mut cmd, scope, exports));
cmd.arg("-cu")
.arg(raw)
.stderr(process::Stdio::inherit());
.arg(raw);
cmd.stderr(if quiet {
process::Stdio::null()
} else {
process::Stdio::inherit()
});
match cmd.output() {
Ok(output) => {
@ -216,7 +222,7 @@ impl<'a> Recipe<'a> {
arguments: &[&'a str],
scope: &Map<&'a str, String>,
exports: &Set<&'a str>,
dry_run: bool,
options: &RunOptions,
) -> Result<(), RunError<'a>> {
let argument_map = arguments .iter().enumerate()
.map(|(i, argument)| (self.parameters[i], *argument)).collect();
@ -227,6 +233,7 @@ impl<'a> Recipe<'a> {
exports: exports,
assignments: &Map::new(),
overrides: &Map::new(),
quiet: options.quiet,
};
if self.shebang {
@ -235,7 +242,7 @@ impl<'a> Recipe<'a> {
evaluated_lines.push(try!(evaluator.evaluate_line(&line, &argument_map)));
}
if dry_run {
if options.dry_run {
for line in evaluated_lines {
warn!("{}", line);
}
@ -305,14 +312,14 @@ impl<'a> Recipe<'a> {
for line in &self.lines {
let evaluated = &try!(evaluator.evaluate_line(&line, &argument_map));
let mut command = evaluated.as_str();
let quiet = command.starts_with('@');
if quiet {
let quiet_command = command.starts_with('@');
if quiet_command {
command = &command[1..];
}
if dry_run || !quiet {
if options.dry_run || !(quiet_command || options.quiet) {
warn!("{}", command);
}
if dry_run {
if options.dry_run {
continue;
}
@ -320,6 +327,11 @@ impl<'a> Recipe<'a> {
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!(match cmd.status() {
@ -543,13 +555,15 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
fn evaluate_assignments<'a>(
assignments: &Map<&'a str, Expression<'a>>,
overrides: &Map<&str, &str>,
quiet: bool,
) -> Result<Map<&'a str, String>, RunError<'a>> {
let mut evaluator = Evaluator {
evaluated: Map::new(),
scope: &Map::new(),
exports: &Set::new(),
assignments: assignments,
evaluated: Map::new(),
exports: &Set::new(),
overrides: overrides,
quiet: quiet,
scope: &Map::new(),
};
for name in assignments.keys() {
@ -560,11 +574,12 @@ fn evaluate_assignments<'a>(
}
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>>,
evaluated: Map<&'a str, String>,
exports: &'b Set<&'a str>,
overrides: &'b Map<&'b str, &'b str>,
quiet: bool,
scope: &'b Map<&'a str, String>,
}
impl<'a, 'b> Evaluator<'a, 'b> {
@ -630,7 +645,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
}
Expression::String{ref cooked, ..} => cooked.clone(),
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} => {
try!(self.evaluate_expression(lhs, arguments))
@ -837,6 +852,7 @@ struct RunOptions<'a> {
dry_run: bool,
evaluate: bool,
overrides: Map<&'a str, &'a str>,
quiet: bool,
}
impl<'a, 'b> Justfile<'a> where 'a: 'b {
@ -865,7 +881,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
fn run(
&'a self,
arguments: &[&'a str],
options: &RunOptions<'a>,
options: &RunOptions<'a>,
) -> Result<(), RunError<'a>> {
let unknown_overrides = options.overrides.keys().cloned()
.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});
}
let scope = try!(evaluate_assignments(&self.assignments, &options.overrides));
let scope = try!(evaluate_assignments(&self.assignments, &options.overrides, options.quiet));
if options.evaluate {
for (name, value) in scope {
println!("{} = \"{}\"", name, value);
@ -899,7 +915,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
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(());
}
} else {
@ -917,7 +933,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
return Err(RunError::UnknownRecipes{recipes: missing});
}
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(())
}
@ -928,14 +944,14 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
arguments: &[&'a str],
scope: &Map<&'c str, String>,
ran: &mut Set<&'a str>,
dry_run: bool,
options: &RunOptions<'a>,
) -> Result<(), RunError> {
for dependency_name in &recipe.dependencies {
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);
Ok(())
}