From 2938ab15619916de0ecd53fb4936ca395c509311 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Mon, 7 Oct 2019 02:06:45 -0700 Subject: [PATCH] Start pulling argument parsing out of run::run() (#483) run::run() is pretty unwieldy. As a first step in improving it, this commit pulls most of the argument parsing into the `config` module. It also renames `Configuration` to `Config`, just to be easier to type. --- src/assignment_evaluator.rs | 4 +- src/common.rs | 18 +-- src/config.rs | 260 ++++++++++++++++++++++++++++++++++++ src/configuration.rs | 29 ---- src/justfile.rs | 30 ++--- src/lib.rs | 2 +- src/recipe.rs | 38 +++--- src/recipe_context.rs | 2 +- src/run.rs | 244 ++------------------------------- 9 files changed, 320 insertions(+), 307 deletions(-) create mode 100644 src/config.rs delete mode 100644 src/configuration.rs diff --git a/src/assignment_evaluator.rs b/src/assignment_evaluator.rs index e038fc8..2fbaa9e 100644 --- a/src/assignment_evaluator.rs +++ b/src/assignment_evaluator.rs @@ -197,13 +197,13 @@ b = `echo $exported_variable` recipe: echo {{b}} "#; - let configuration = Configuration { + let config = Config { quiet: true, ..Default::default() }; match parse(text) - .run(&no_cwd_err(), &["recipe"], &configuration) + .run(&no_cwd_err(), &["recipe"], &config) .unwrap_err() { RuntimeError::Backtick { diff --git a/src/common.rs b/src/common.rs index 842d97c..cf692d2 100644 --- a/src/common.rs +++ b/src/common.rs @@ -38,15 +38,15 @@ pub(crate) use crate::{ pub(crate) use crate::{ alias::Alias, alias_resolver::AliasResolver, assignment_evaluator::AssignmentEvaluator, assignment_resolver::AssignmentResolver, color::Color, compilation_error::CompilationError, - compilation_error_kind::CompilationErrorKind, configuration::Configuration, - expression::Expression, fragment::Fragment, function::Function, - function_context::FunctionContext, functions::Functions, interrupt_guard::InterruptGuard, - interrupt_handler::InterruptHandler, justfile::Justfile, lexer::Lexer, output_error::OutputError, - parameter::Parameter, parser::Parser, platform::Platform, position::Position, recipe::Recipe, - recipe_context::RecipeContext, recipe_resolver::RecipeResolver, runtime_error::RuntimeError, - search_error::SearchError, shebang::Shebang, state::State, string_literal::StringLiteral, - token::Token, token_kind::TokenKind, use_color::UseColor, variables::Variables, - verbosity::Verbosity, warning::Warning, + compilation_error_kind::CompilationErrorKind, config::Config, expression::Expression, + fragment::Fragment, function::Function, function_context::FunctionContext, functions::Functions, + interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, justfile::Justfile, + lexer::Lexer, output_error::OutputError, parameter::Parameter, parser::Parser, + platform::Platform, position::Position, recipe::Recipe, recipe_context::RecipeContext, + recipe_resolver::RecipeResolver, runtime_error::RuntimeError, search_error::SearchError, + shebang::Shebang, state::State, string_literal::StringLiteral, token::Token, + token_kind::TokenKind, use_color::UseColor, variables::Variables, verbosity::Verbosity, + warning::Warning, }; pub(crate) type CompilationResult<'a, T> = Result>; diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..b2e3b15 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,260 @@ +use crate::common::*; + +use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches}; + +pub(crate) const DEFAULT_SHELL: &str = "sh"; + +pub(crate) struct Config<'a> { + pub(crate) dry_run: bool, + pub(crate) evaluate: bool, + pub(crate) highlight: bool, + pub(crate) overrides: BTreeMap<&'a str, &'a str>, + pub(crate) quiet: bool, + pub(crate) shell: &'a str, + pub(crate) color: Color, + pub(crate) verbosity: Verbosity, + pub(crate) arguments: Vec<&'a str>, +} + +impl<'a> Config<'a> { + pub(crate) fn app() -> App<'static, 'static> { + let app = App::new(env!("CARGO_PKG_NAME")) + .help_message("Print help information") + .version_message("Print version information") + .setting(AppSettings::ColoredHelp) + .setting(AppSettings::TrailingVarArg) + .arg( + Arg::with_name("ARGUMENTS") + .multiple(true) + .help("The recipe(s) to run, defaults to the first recipe in the justfile"), + ) + .arg( + Arg::with_name("COLOR") + .long("color") + .takes_value(true) + .possible_values(&["auto", "always", "never"]) + .default_value("auto") + .help("Print colorful output"), + ) + .arg( + Arg::with_name("DRY-RUN") + .long("dry-run") + .help("Print what just would do without doing it") + .conflicts_with("QUIET"), + ) + .arg( + Arg::with_name("DUMP") + .long("dump") + .help("Print entire justfile"), + ) + .arg( + Arg::with_name("EDIT") + .short("e") + .long("edit") + .help("Open justfile with $EDITOR"), + ) + .arg( + Arg::with_name("EVALUATE") + .long("evaluate") + .help("Print evaluated variables"), + ) + .arg( + Arg::with_name("HIGHLIGHT") + .long("highlight") + .help("Highlight echoed recipe lines in bold"), + ) + .arg( + Arg::with_name("JUSTFILE") + .short("f") + .long("justfile") + .takes_value(true) + .help("Use as justfile."), + ) + .arg( + Arg::with_name("LIST") + .short("l") + .long("list") + .help("List available recipes and their arguments"), + ) + .arg( + Arg::with_name("QUIET") + .short("q") + .long("quiet") + .help("Suppress all output") + .conflicts_with("DRY-RUN"), + ) + .arg( + Arg::with_name("SET") + .long("set") + .takes_value(true) + .number_of_values(2) + .value_names(&["VARIABLE", "VALUE"]) + .multiple(true) + .help("Set to "), + ) + .arg( + Arg::with_name("SHELL") + .long("shell") + .takes_value(true) + .default_value(DEFAULT_SHELL) + .help("Invoke to run recipes"), + ) + .arg( + Arg::with_name("SHOW") + .short("s") + .long("show") + .takes_value(true) + .value_name("RECIPE") + .help("Show information about "), + ) + .arg( + Arg::with_name("SUMMARY") + .long("summary") + .help("List names of available recipes"), + ) + .arg( + Arg::with_name("VERBOSE") + .short("v") + .long("verbose") + .multiple(true) + .help("Use verbose output"), + ) + .arg( + Arg::with_name("WORKING-DIRECTORY") + .short("d") + .long("working-directory") + .takes_value(true) + .help("Use as working directory. --justfile must also be set") + .requires("JUSTFILE"), + ) + .group(ArgGroup::with_name("EARLY-EXIT").args(&[ + "DUMP", + "EDIT", + "LIST", + "SHOW", + "SUMMARY", + "ARGUMENTS", + "EVALUATE", + ])); + + if cfg!(feature = "help4help2man") { + app.version(env!("CARGO_PKG_VERSION")).about(concat!( + "- Please see ", + env!("CARGO_PKG_HOMEPAGE"), + " for more information." + )) + } else { + app + .version(concat!("v", env!("CARGO_PKG_VERSION"))) + .author(env!("CARGO_PKG_AUTHORS")) + .about(concat!( + env!("CARGO_PKG_DESCRIPTION"), + " - ", + env!("CARGO_PKG_HOMEPAGE") + )) + } + } + + pub(crate) fn from_matches(matches: &'a ArgMatches<'a>) -> Config<'a> { + let verbosity = Verbosity::from_flag_occurrences(matches.occurrences_of("VERBOSE")); + + let color = match matches.value_of("COLOR").expect("`--color` had no value") { + "auto" => Color::auto(), + "always" => Color::always(), + "never" => Color::never(), + other => die!( + "Invalid argument `{}` to --color. This is a bug in just.", + other + ), + }; + + let set_count = matches.occurrences_of("SET"); + let mut overrides = BTreeMap::new(); + if set_count > 0 { + let mut values = matches.values_of("SET").unwrap(); + for _ in 0..set_count { + overrides.insert(values.next().unwrap(), values.next().unwrap()); + } + } + + fn is_override(arg: &&str) -> bool { + arg.chars().skip(1).any(|c| c == '=') + } + + let raw_arguments: Vec<&str> = matches + .values_of("ARGUMENTS") + .map(Iterator::collect) + .unwrap_or_default(); + + for argument in raw_arguments.iter().cloned().take_while(is_override) { + let i = argument + .char_indices() + .skip(1) + .find(|&(_, c)| c == '=') + .unwrap() + .0; + + let name = &argument[..i]; + let value = &argument[i + 1..]; + + overrides.insert(name, value); + } + + let arguments = raw_arguments + .into_iter() + .skip_while(is_override) + .enumerate() + .flat_map(|(i, argument)| { + if i == 0 { + if let Some(i) = argument.rfind('/') { + if matches.is_present("WORKING-DIRECTORY") { + die!("--working-directory and a path prefixed recipe may not be used together."); + } + + let (dir, recipe) = argument.split_at(i + 1); + + if let Err(error) = env::set_current_dir(dir) { + die!("Error changing directory: {}", error); + } + + if recipe.is_empty() { + return None; + } else { + return Some(recipe); + } + } + } + + Some(argument) + }) + .collect::>(); + + Config { + dry_run: matches.is_present("DRY-RUN"), + evaluate: matches.is_present("EVALUATE"), + highlight: matches.is_present("HIGHLIGHT"), + quiet: matches.is_present("QUIET"), + shell: matches.value_of("SHELL").unwrap(), + verbosity, + color, + overrides, + arguments, + } + } +} + +impl<'a> Default for Config<'a> { + fn default() -> Config<'static> { + Config { + dry_run: false, + evaluate: false, + highlight: false, + overrides: empty(), + arguments: empty(), + quiet: false, + shell: DEFAULT_SHELL, + color: default(), + verbosity: Verbosity::from_flag_occurrences(0), + } + } +} diff --git a/src/configuration.rs b/src/configuration.rs deleted file mode 100644 index 568d44b..0000000 --- a/src/configuration.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::common::*; - -pub(crate) const DEFAULT_SHELL: &str = "sh"; - -pub(crate) struct Configuration<'a> { - pub(crate) dry_run: bool, - pub(crate) evaluate: bool, - pub(crate) highlight: bool, - pub(crate) overrides: BTreeMap<&'a str, &'a str>, - pub(crate) quiet: bool, - pub(crate) shell: &'a str, - pub(crate) color: Color, - pub(crate) verbosity: Verbosity, -} - -impl<'a> Default for Configuration<'a> { - fn default() -> Configuration<'static> { - Configuration { - dry_run: false, - evaluate: false, - highlight: false, - overrides: empty(), - quiet: false, - shell: DEFAULT_SHELL, - color: default(), - verbosity: Verbosity::from_flag_occurrences(0), - } - } -} diff --git a/src/justfile.rs b/src/justfile.rs index 5221bbf..e19f3cd 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -9,7 +9,7 @@ pub(crate) struct Justfile<'a> { pub(crate) warnings: Vec>, } -impl<'a> Justfile<'a> where { +impl<'a> Justfile<'a> { pub(crate) fn first(&self) -> Option<&Recipe> { let mut first: Option<&Recipe> = None; for recipe in self.recipes.values() { @@ -47,9 +47,9 @@ impl<'a> Justfile<'a> where { &'a self, invocation_directory: &'a Result, arguments: &[&'a str], - configuration: &'a Configuration<'a>, + config: &'a Config<'a>, ) -> RunResult<'a, ()> { - let unknown_overrides = configuration + let unknown_overrides = config .overrides .keys() .cloned() @@ -68,13 +68,13 @@ impl<'a> Justfile<'a> where { &self.assignments, invocation_directory, &dotenv, - &configuration.overrides, - configuration.quiet, - configuration.shell, - configuration.dry_run, + &config.overrides, + config.quiet, + config.shell, + config.dry_run, )?; - if configuration.evaluate { + if config.evaluate { let mut width = 0; for name in scope.keys() { width = cmp::max(name.len(), width); @@ -129,7 +129,7 @@ impl<'a> Justfile<'a> where { let context = RecipeContext { invocation_directory, - configuration, + config, scope, }; @@ -432,11 +432,11 @@ a return code: #[test] fn unknown_overrides() { - let mut configuration: Configuration = Default::default(); - configuration.overrides.insert("foo", "bar"); - configuration.overrides.insert("baz", "bob"); + let mut config: Config = Default::default(); + config.overrides.insert("foo", "bar"); + config.overrides.insert("baz", "bob"); match parse("a:\n echo {{`f() { return 100; }; f`}}") - .run(&no_cwd_err(), &["a"], &configuration) + .run(&no_cwd_err(), &["a"], &config) .unwrap_err() { UnknownOverrides { overrides } => { @@ -458,13 +458,13 @@ wut: echo $foo $bar $baz "#; - let configuration = Configuration { + let config = Config { quiet: true, ..Default::default() }; match parse(text) - .run(&no_cwd_err(), &["wut"], &configuration) + .run(&no_cwd_err(), &["wut"], &config) .unwrap_err() { Code { diff --git a/src/lib.rs b/src/lib.rs index ee66e14..d6b1b72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ mod command_ext; mod common; mod compilation_error; mod compilation_error_kind; -mod configuration; +mod config; mod expression; mod fragment; mod function; diff --git a/src/recipe.rs b/src/recipe.rs index 5245f2f..1ed5a9d 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -64,10 +64,10 @@ impl<'a> Recipe<'a> { dotenv: &BTreeMap, exports: &BTreeSet<&'a str>, ) -> RunResult<'a, ()> { - let configuration = &context.configuration; + let config = &context.config; - if configuration.verbosity.loquacious() { - let color = configuration.color.stderr().banner(); + if config.verbosity.loquacious() { + let color = config.color.stderr().banner(); eprintln!( "{}===> Running recipe `{}`...{}", color.prefix(), @@ -80,13 +80,13 @@ impl<'a> Recipe<'a> { let mut evaluator = AssignmentEvaluator { assignments: &empty(), - dry_run: configuration.dry_run, + dry_run: config.dry_run, evaluated: empty(), invocation_directory: context.invocation_directory, overrides: &empty(), - quiet: configuration.quiet, + quiet: config.quiet, scope: &context.scope, - shell: configuration.shell, + shell: config.shell, dotenv, exports, }; @@ -120,13 +120,13 @@ impl<'a> Recipe<'a> { evaluated_lines.push(evaluator.evaluate_line(line, &argument_map)?); } - if configuration.dry_run || self.quiet { + if config.dry_run || self.quiet { for line in &evaluated_lines { eprintln!("{}", line); } } - if configuration.dry_run { + if config.dry_run { return Ok(()); } @@ -159,8 +159,8 @@ impl<'a> Recipe<'a> { text += "\n"; } - if configuration.verbosity.grandiloquent() { - eprintln!("{}", configuration.color.doc().stderr().paint(&text)); + if config.verbosity.grandiloquent() { + eprintln!("{}", config.color.doc().stderr().paint(&text)); } f.write_all(text.as_bytes()) @@ -255,27 +255,27 @@ impl<'a> Recipe<'a> { continue; } - if configuration.dry_run - || configuration.verbosity.loquacious() - || !((quiet_command ^ self.quiet) || configuration.quiet) + if config.dry_run + || config.verbosity.loquacious() + || !((quiet_command ^ self.quiet) || config.quiet) { - let color = if configuration.highlight { - configuration.color.command() + let color = if config.highlight { + config.color.command() } else { - configuration.color + config.color }; eprintln!("{}", color.stderr().paint(command)); } - if configuration.dry_run { + if config.dry_run { continue; } - let mut cmd = Command::new(configuration.shell); + let mut cmd = Command::new(config.shell); cmd.arg("-cu").arg(command); - if configuration.quiet { + if config.quiet { cmd.stderr(Stdio::null()); cmd.stdout(Stdio::null()); } diff --git a/src/recipe_context.rs b/src/recipe_context.rs index 5777513..973e63a 100644 --- a/src/recipe_context.rs +++ b/src/recipe_context.rs @@ -2,6 +2,6 @@ use crate::common::*; pub(crate) struct RecipeContext<'a> { pub(crate) invocation_directory: &'a Result, - pub(crate) configuration: &'a Configuration<'a>, + pub(crate) config: &'a Config<'a>, pub(crate) scope: BTreeMap<&'a str, String>, } diff --git a/src/run.rs b/src/run.rs index a0b3c09..22a7a4a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,9 +1,6 @@ use crate::common::*; -use crate::configuration::DEFAULT_SHELL; -use crate::interrupt_handler::InterruptHandler; -use crate::misc::maybe_s; -use clap::{App, AppSettings, Arg, ArgGroup}; +use crate::{interrupt_handler::InterruptHandler, misc::maybe_s}; use std::{convert, ffi}; use unicode_width::UnicodeWidthStr; @@ -36,216 +33,14 @@ pub fn run() { let invocation_directory = env::current_dir().map_err(|e| format!("Error getting current directory: {}", e)); - let app = App::new(env!("CARGO_PKG_NAME")) - .help_message("Print help information") - .version_message("Print version information") - .setting(AppSettings::ColoredHelp) - .setting(AppSettings::TrailingVarArg) - .arg( - Arg::with_name("ARGUMENTS") - .multiple(true) - .help("The recipe(s) to run, defaults to the first recipe in the justfile"), - ) - .arg( - Arg::with_name("COLOR") - .long("color") - .takes_value(true) - .possible_values(&["auto", "always", "never"]) - .default_value("auto") - .help("Print colorful output"), - ) - .arg( - Arg::with_name("DRY-RUN") - .long("dry-run") - .help("Print what just would do without doing it") - .conflicts_with("QUIET"), - ) - .arg( - Arg::with_name("DUMP") - .long("dump") - .help("Print entire justfile"), - ) - .arg( - Arg::with_name("EDIT") - .short("e") - .long("edit") - .help("Open justfile with $EDITOR"), - ) - .arg( - Arg::with_name("EVALUATE") - .long("evaluate") - .help("Print evaluated variables"), - ) - .arg( - Arg::with_name("HIGHLIGHT") - .long("highlight") - .help("Highlight echoed recipe lines in bold"), - ) - .arg( - Arg::with_name("JUSTFILE") - .short("f") - .long("justfile") - .takes_value(true) - .help("Use as justfile."), - ) - .arg( - Arg::with_name("LIST") - .short("l") - .long("list") - .help("List available recipes and their arguments"), - ) - .arg( - Arg::with_name("QUIET") - .short("q") - .long("quiet") - .help("Suppress all output") - .conflicts_with("DRY-RUN"), - ) - .arg( - Arg::with_name("SET") - .long("set") - .takes_value(true) - .number_of_values(2) - .value_names(&["VARIABLE", "VALUE"]) - .multiple(true) - .help("Set to "), - ) - .arg( - Arg::with_name("SHELL") - .long("shell") - .takes_value(true) - .default_value(DEFAULT_SHELL) - .help("Invoke to run recipes"), - ) - .arg( - Arg::with_name("SHOW") - .short("s") - .long("show") - .takes_value(true) - .value_name("RECIPE") - .help("Show information about "), - ) - .arg( - Arg::with_name("SUMMARY") - .long("summary") - .help("List names of available recipes"), - ) - .arg( - Arg::with_name("VERBOSE") - .short("v") - .long("verbose") - .multiple(true) - .help("Use verbose output"), - ) - .arg( - Arg::with_name("WORKING-DIRECTORY") - .short("d") - .long("working-directory") - .takes_value(true) - .help("Use as working directory. --justfile must also be set") - .requires("JUSTFILE"), - ) - .group(ArgGroup::with_name("EARLY-EXIT").args(&[ - "DUMP", - "EDIT", - "LIST", - "SHOW", - "SUMMARY", - "ARGUMENTS", - "EVALUATE", - ])); - - let app = if cfg!(feature = "help4help2man") { - app.version(env!("CARGO_PKG_VERSION")).about(concat!( - "- Please see ", - env!("CARGO_PKG_HOMEPAGE"), - " for more information." - )) - } else { - app - .version(concat!("v", env!("CARGO_PKG_VERSION"))) - .author(env!("CARGO_PKG_AUTHORS")) - .about(concat!( - env!("CARGO_PKG_DESCRIPTION"), - " - ", - env!("CARGO_PKG_HOMEPAGE") - )) - }; + let app = Config::app(); let matches = app.get_matches(); - let color = match matches.value_of("COLOR").expect("`--color` had no value") { - "auto" => Color::auto(), - "always" => Color::always(), - "never" => Color::never(), - other => die!( - "Invalid argument `{}` to --color. This is a bug in just.", - other - ), - }; - - let set_count = matches.occurrences_of("SET"); - let mut overrides = BTreeMap::new(); - if set_count > 0 { - let mut values = matches.values_of("SET").unwrap(); - for _ in 0..set_count { - overrides.insert(values.next().unwrap(), values.next().unwrap()); - } - } - - fn is_override(arg: &&str) -> bool { - arg.chars().skip(1).any(|c| c == '=') - } - - let raw_arguments: Vec<&str> = matches - .values_of("ARGUMENTS") - .map(Iterator::collect) - .unwrap_or_default(); - - for argument in raw_arguments.iter().cloned().take_while(is_override) { - let i = argument - .char_indices() - .skip(1) - .find(|&(_, c)| c == '=') - .unwrap() - .0; - - let name = &argument[..i]; - let value = &argument[i + 1..]; - - overrides.insert(name, value); - } - - let rest = raw_arguments - .into_iter() - .skip_while(is_override) - .enumerate() - .flat_map(|(i, argument)| { - if i == 0 { - if let Some(i) = argument.rfind('/') { - if matches.is_present("WORKING-DIRECTORY") { - die!("--working-directory and a path prefixed recipe may not be used together."); - } - - let (dir, recipe) = argument.split_at(i + 1); - - if let Err(error) = env::set_current_dir(dir) { - die!("Error changing directory: {}", error); - } - - if recipe.is_empty() { - return None; - } else { - return Some(recipe); - } - } - } - - Some(argument) - }) - .collect::>(); + let config = Config::from_matches(&matches); let justfile = matches.value_of("JUSTFILE").map(Path::new); + let mut working_directory = matches.value_of("WORKING-DIRECTORY").map(PathBuf::from); if let (Some(justfile), None) = (justfile, working_directory.as_ref()) { @@ -311,7 +106,7 @@ pub fn run() { } let justfile = Parser::parse(&text).unwrap_or_else(|error| { - if color.stderr().active() { + if config.color.stderr().active() { die!("{:#}", error); } else { die!("{}", error); @@ -319,7 +114,7 @@ pub fn run() { }); for warning in &justfile.warnings { - if color.stderr().active() { + if config.color.stderr().active() { eprintln!("{:#}", warning); } else { eprintln!("{}", warning); @@ -386,7 +181,7 @@ pub fn run() { let max_line_width = cmp::min(line_widths.values().cloned().max().unwrap_or(0), 30); - let doc_color = color.stdout().doc(); + let doc_color = config.color.stdout().doc(); println!("Available recipes:"); for (name, recipe) in &justfile.recipes { @@ -402,7 +197,7 @@ pub fn run() { { print!(" {}", name); for parameter in &recipe.parameters { - if color.stdout().active() { + if config.color.stdout().active() { print!(" {:#}", parameter); } else { print!(" {}", parameter); @@ -454,8 +249,8 @@ pub fn run() { } } - let arguments = if !rest.is_empty() { - rest + let arguments = if !config.arguments.is_empty() { + config.arguments.clone() } else if let Some(recipe) = justfile.first() { let min_arguments = recipe.min_arguments(); if min_arguments > 0 { @@ -471,26 +266,13 @@ pub fn run() { die!("Justfile contains no recipes."); }; - let verbosity = Verbosity::from_flag_occurrences(matches.occurrences_of("VERBOSE")); - - let configuration = Configuration { - dry_run: matches.is_present("DRY-RUN"), - evaluate: matches.is_present("EVALUATE"), - highlight: matches.is_present("HIGHLIGHT"), - quiet: matches.is_present("QUIET"), - shell: matches.value_of("SHELL").unwrap(), - verbosity, - color, - overrides, - }; - if let Err(error) = InterruptHandler::install() { warn!("Failed to set CTRL-C handler: {}", error) } - if let Err(run_error) = justfile.run(&invocation_directory, &arguments, &configuration) { - if !configuration.quiet { - if color.stderr().active() { + if let Err(run_error) = justfile.run(&invocation_directory, &arguments, &config) { + if !config.quiet { + if config.color.stderr().active() { eprintln!("{:#}", run_error); } else { eprintln!("{}", run_error);