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.
This commit is contained in:
parent
fa6d5dd002
commit
2938ab1561
@ -197,13 +197,13 @@ b = `echo $exported_variable`
|
|||||||
recipe:
|
recipe:
|
||||||
echo {{b}}
|
echo {{b}}
|
||||||
"#;
|
"#;
|
||||||
let configuration = Configuration {
|
let config = Config {
|
||||||
quiet: true,
|
quiet: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
match parse(text)
|
match parse(text)
|
||||||
.run(&no_cwd_err(), &["recipe"], &configuration)
|
.run(&no_cwd_err(), &["recipe"], &config)
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
{
|
{
|
||||||
RuntimeError::Backtick {
|
RuntimeError::Backtick {
|
||||||
|
@ -38,15 +38,15 @@ pub(crate) use crate::{
|
|||||||
pub(crate) use crate::{
|
pub(crate) use crate::{
|
||||||
alias::Alias, alias_resolver::AliasResolver, assignment_evaluator::AssignmentEvaluator,
|
alias::Alias, alias_resolver::AliasResolver, assignment_evaluator::AssignmentEvaluator,
|
||||||
assignment_resolver::AssignmentResolver, color::Color, compilation_error::CompilationError,
|
assignment_resolver::AssignmentResolver, color::Color, compilation_error::CompilationError,
|
||||||
compilation_error_kind::CompilationErrorKind, configuration::Configuration,
|
compilation_error_kind::CompilationErrorKind, config::Config, expression::Expression,
|
||||||
expression::Expression, fragment::Fragment, function::Function,
|
fragment::Fragment, function::Function, function_context::FunctionContext, functions::Functions,
|
||||||
function_context::FunctionContext, functions::Functions, interrupt_guard::InterruptGuard,
|
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, justfile::Justfile,
|
||||||
interrupt_handler::InterruptHandler, justfile::Justfile, lexer::Lexer, output_error::OutputError,
|
lexer::Lexer, output_error::OutputError, parameter::Parameter, parser::Parser,
|
||||||
parameter::Parameter, parser::Parser, platform::Platform, position::Position, recipe::Recipe,
|
platform::Platform, position::Position, recipe::Recipe, recipe_context::RecipeContext,
|
||||||
recipe_context::RecipeContext, recipe_resolver::RecipeResolver, runtime_error::RuntimeError,
|
recipe_resolver::RecipeResolver, runtime_error::RuntimeError, search_error::SearchError,
|
||||||
search_error::SearchError, shebang::Shebang, state::State, string_literal::StringLiteral,
|
shebang::Shebang, state::State, string_literal::StringLiteral, token::Token,
|
||||||
token::Token, token_kind::TokenKind, use_color::UseColor, variables::Variables,
|
token_kind::TokenKind, use_color::UseColor, variables::Variables, verbosity::Verbosity,
|
||||||
verbosity::Verbosity, warning::Warning,
|
warning::Warning,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) type CompilationResult<'a, T> = Result<T, CompilationError<'a>>;
|
pub(crate) type CompilationResult<'a, T> = Result<T, CompilationError<'a>>;
|
||||||
|
260
src/config.rs
Normal file
260
src/config.rs
Normal file
@ -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 <JUSTFILE> 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 <VARIABLE> to <VALUE>"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("SHELL")
|
||||||
|
.long("shell")
|
||||||
|
.takes_value(true)
|
||||||
|
.default_value(DEFAULT_SHELL)
|
||||||
|
.help("Invoke <SHELL> to run recipes"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("SHOW")
|
||||||
|
.short("s")
|
||||||
|
.long("show")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("RECIPE")
|
||||||
|
.help("Show information about <RECIPE>"),
|
||||||
|
)
|
||||||
|
.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 <WORKING-DIRECTORY> 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::<Vec<&str>>();
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,7 +9,7 @@ pub(crate) struct Justfile<'a> {
|
|||||||
pub(crate) warnings: Vec<Warning<'a>>,
|
pub(crate) warnings: Vec<Warning<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Justfile<'a> where {
|
impl<'a> Justfile<'a> {
|
||||||
pub(crate) fn first(&self) -> Option<&Recipe> {
|
pub(crate) fn first(&self) -> Option<&Recipe> {
|
||||||
let mut first: Option<&Recipe> = None;
|
let mut first: Option<&Recipe> = None;
|
||||||
for recipe in self.recipes.values() {
|
for recipe in self.recipes.values() {
|
||||||
@ -47,9 +47,9 @@ impl<'a> Justfile<'a> where {
|
|||||||
&'a self,
|
&'a self,
|
||||||
invocation_directory: &'a Result<PathBuf, String>,
|
invocation_directory: &'a Result<PathBuf, String>,
|
||||||
arguments: &[&'a str],
|
arguments: &[&'a str],
|
||||||
configuration: &'a Configuration<'a>,
|
config: &'a Config<'a>,
|
||||||
) -> RunResult<'a, ()> {
|
) -> RunResult<'a, ()> {
|
||||||
let unknown_overrides = configuration
|
let unknown_overrides = config
|
||||||
.overrides
|
.overrides
|
||||||
.keys()
|
.keys()
|
||||||
.cloned()
|
.cloned()
|
||||||
@ -68,13 +68,13 @@ impl<'a> Justfile<'a> where {
|
|||||||
&self.assignments,
|
&self.assignments,
|
||||||
invocation_directory,
|
invocation_directory,
|
||||||
&dotenv,
|
&dotenv,
|
||||||
&configuration.overrides,
|
&config.overrides,
|
||||||
configuration.quiet,
|
config.quiet,
|
||||||
configuration.shell,
|
config.shell,
|
||||||
configuration.dry_run,
|
config.dry_run,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if configuration.evaluate {
|
if config.evaluate {
|
||||||
let mut width = 0;
|
let mut width = 0;
|
||||||
for name in scope.keys() {
|
for name in scope.keys() {
|
||||||
width = cmp::max(name.len(), width);
|
width = cmp::max(name.len(), width);
|
||||||
@ -129,7 +129,7 @@ impl<'a> Justfile<'a> where {
|
|||||||
|
|
||||||
let context = RecipeContext {
|
let context = RecipeContext {
|
||||||
invocation_directory,
|
invocation_directory,
|
||||||
configuration,
|
config,
|
||||||
scope,
|
scope,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -432,11 +432,11 @@ a return code:
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_overrides() {
|
fn unknown_overrides() {
|
||||||
let mut configuration: Configuration = Default::default();
|
let mut config: Config = Default::default();
|
||||||
configuration.overrides.insert("foo", "bar");
|
config.overrides.insert("foo", "bar");
|
||||||
configuration.overrides.insert("baz", "bob");
|
config.overrides.insert("baz", "bob");
|
||||||
match parse("a:\n echo {{`f() { return 100; }; f`}}")
|
match parse("a:\n echo {{`f() { return 100; }; f`}}")
|
||||||
.run(&no_cwd_err(), &["a"], &configuration)
|
.run(&no_cwd_err(), &["a"], &config)
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
{
|
{
|
||||||
UnknownOverrides { overrides } => {
|
UnknownOverrides { overrides } => {
|
||||||
@ -458,13 +458,13 @@ wut:
|
|||||||
echo $foo $bar $baz
|
echo $foo $bar $baz
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let configuration = Configuration {
|
let config = Config {
|
||||||
quiet: true,
|
quiet: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
match parse(text)
|
match parse(text)
|
||||||
.run(&no_cwd_err(), &["wut"], &configuration)
|
.run(&no_cwd_err(), &["wut"], &config)
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
{
|
{
|
||||||
Code {
|
Code {
|
||||||
|
@ -20,7 +20,7 @@ mod command_ext;
|
|||||||
mod common;
|
mod common;
|
||||||
mod compilation_error;
|
mod compilation_error;
|
||||||
mod compilation_error_kind;
|
mod compilation_error_kind;
|
||||||
mod configuration;
|
mod config;
|
||||||
mod expression;
|
mod expression;
|
||||||
mod fragment;
|
mod fragment;
|
||||||
mod function;
|
mod function;
|
||||||
|
@ -64,10 +64,10 @@ impl<'a> Recipe<'a> {
|
|||||||
dotenv: &BTreeMap<String, String>,
|
dotenv: &BTreeMap<String, String>,
|
||||||
exports: &BTreeSet<&'a str>,
|
exports: &BTreeSet<&'a str>,
|
||||||
) -> RunResult<'a, ()> {
|
) -> RunResult<'a, ()> {
|
||||||
let configuration = &context.configuration;
|
let config = &context.config;
|
||||||
|
|
||||||
if configuration.verbosity.loquacious() {
|
if config.verbosity.loquacious() {
|
||||||
let color = configuration.color.stderr().banner();
|
let color = config.color.stderr().banner();
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}===> Running recipe `{}`...{}",
|
"{}===> Running recipe `{}`...{}",
|
||||||
color.prefix(),
|
color.prefix(),
|
||||||
@ -80,13 +80,13 @@ impl<'a> Recipe<'a> {
|
|||||||
|
|
||||||
let mut evaluator = AssignmentEvaluator {
|
let mut evaluator = AssignmentEvaluator {
|
||||||
assignments: &empty(),
|
assignments: &empty(),
|
||||||
dry_run: configuration.dry_run,
|
dry_run: config.dry_run,
|
||||||
evaluated: empty(),
|
evaluated: empty(),
|
||||||
invocation_directory: context.invocation_directory,
|
invocation_directory: context.invocation_directory,
|
||||||
overrides: &empty(),
|
overrides: &empty(),
|
||||||
quiet: configuration.quiet,
|
quiet: config.quiet,
|
||||||
scope: &context.scope,
|
scope: &context.scope,
|
||||||
shell: configuration.shell,
|
shell: config.shell,
|
||||||
dotenv,
|
dotenv,
|
||||||
exports,
|
exports,
|
||||||
};
|
};
|
||||||
@ -120,13 +120,13 @@ impl<'a> Recipe<'a> {
|
|||||||
evaluated_lines.push(evaluator.evaluate_line(line, &argument_map)?);
|
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 {
|
for line in &evaluated_lines {
|
||||||
eprintln!("{}", line);
|
eprintln!("{}", line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.dry_run {
|
if config.dry_run {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,8 +159,8 @@ impl<'a> Recipe<'a> {
|
|||||||
text += "\n";
|
text += "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.verbosity.grandiloquent() {
|
if config.verbosity.grandiloquent() {
|
||||||
eprintln!("{}", configuration.color.doc().stderr().paint(&text));
|
eprintln!("{}", config.color.doc().stderr().paint(&text));
|
||||||
}
|
}
|
||||||
|
|
||||||
f.write_all(text.as_bytes())
|
f.write_all(text.as_bytes())
|
||||||
@ -255,27 +255,27 @@ impl<'a> Recipe<'a> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.dry_run
|
if config.dry_run
|
||||||
|| configuration.verbosity.loquacious()
|
|| config.verbosity.loquacious()
|
||||||
|| !((quiet_command ^ self.quiet) || configuration.quiet)
|
|| !((quiet_command ^ self.quiet) || config.quiet)
|
||||||
{
|
{
|
||||||
let color = if configuration.highlight {
|
let color = if config.highlight {
|
||||||
configuration.color.command()
|
config.color.command()
|
||||||
} else {
|
} else {
|
||||||
configuration.color
|
config.color
|
||||||
};
|
};
|
||||||
eprintln!("{}", color.stderr().paint(command));
|
eprintln!("{}", color.stderr().paint(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.dry_run {
|
if config.dry_run {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cmd = Command::new(configuration.shell);
|
let mut cmd = Command::new(config.shell);
|
||||||
|
|
||||||
cmd.arg("-cu").arg(command);
|
cmd.arg("-cu").arg(command);
|
||||||
|
|
||||||
if configuration.quiet {
|
if config.quiet {
|
||||||
cmd.stderr(Stdio::null());
|
cmd.stderr(Stdio::null());
|
||||||
cmd.stdout(Stdio::null());
|
cmd.stdout(Stdio::null());
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,6 @@ use crate::common::*;
|
|||||||
|
|
||||||
pub(crate) struct RecipeContext<'a> {
|
pub(crate) struct RecipeContext<'a> {
|
||||||
pub(crate) invocation_directory: &'a Result<PathBuf, String>,
|
pub(crate) invocation_directory: &'a Result<PathBuf, String>,
|
||||||
pub(crate) configuration: &'a Configuration<'a>,
|
pub(crate) config: &'a Config<'a>,
|
||||||
pub(crate) scope: BTreeMap<&'a str, String>,
|
pub(crate) scope: BTreeMap<&'a str, String>,
|
||||||
}
|
}
|
||||||
|
244
src/run.rs
244
src/run.rs
@ -1,9 +1,6 @@
|
|||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
|
||||||
use crate::configuration::DEFAULT_SHELL;
|
use crate::{interrupt_handler::InterruptHandler, misc::maybe_s};
|
||||||
use crate::interrupt_handler::InterruptHandler;
|
|
||||||
use crate::misc::maybe_s;
|
|
||||||
use clap::{App, AppSettings, Arg, ArgGroup};
|
|
||||||
use std::{convert, ffi};
|
use std::{convert, ffi};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
@ -36,216 +33,14 @@ pub fn run() {
|
|||||||
let invocation_directory =
|
let invocation_directory =
|
||||||
env::current_dir().map_err(|e| format!("Error getting current directory: {}", e));
|
env::current_dir().map_err(|e| format!("Error getting current directory: {}", e));
|
||||||
|
|
||||||
let app = App::new(env!("CARGO_PKG_NAME"))
|
let app = Config::app();
|
||||||
.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 <JUSTFILE> 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 <VARIABLE> to <VALUE>"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("SHELL")
|
|
||||||
.long("shell")
|
|
||||||
.takes_value(true)
|
|
||||||
.default_value(DEFAULT_SHELL)
|
|
||||||
.help("Invoke <SHELL> to run recipes"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("SHOW")
|
|
||||||
.short("s")
|
|
||||||
.long("show")
|
|
||||||
.takes_value(true)
|
|
||||||
.value_name("RECIPE")
|
|
||||||
.help("Show information about <RECIPE>"),
|
|
||||||
)
|
|
||||||
.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 <WORKING-DIRECTORY> 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 matches = app.get_matches();
|
let matches = app.get_matches();
|
||||||
|
|
||||||
let color = match matches.value_of("COLOR").expect("`--color` had no value") {
|
let config = Config::from_matches(&matches);
|
||||||
"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::<Vec<&str>>();
|
|
||||||
|
|
||||||
let justfile = matches.value_of("JUSTFILE").map(Path::new);
|
let justfile = matches.value_of("JUSTFILE").map(Path::new);
|
||||||
|
|
||||||
let mut working_directory = matches.value_of("WORKING-DIRECTORY").map(PathBuf::from);
|
let mut working_directory = matches.value_of("WORKING-DIRECTORY").map(PathBuf::from);
|
||||||
|
|
||||||
if let (Some(justfile), None) = (justfile, working_directory.as_ref()) {
|
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| {
|
let justfile = Parser::parse(&text).unwrap_or_else(|error| {
|
||||||
if color.stderr().active() {
|
if config.color.stderr().active() {
|
||||||
die!("{:#}", error);
|
die!("{:#}", error);
|
||||||
} else {
|
} else {
|
||||||
die!("{}", error);
|
die!("{}", error);
|
||||||
@ -319,7 +114,7 @@ pub fn run() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for warning in &justfile.warnings {
|
for warning in &justfile.warnings {
|
||||||
if color.stderr().active() {
|
if config.color.stderr().active() {
|
||||||
eprintln!("{:#}", warning);
|
eprintln!("{:#}", warning);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("{}", warning);
|
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 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:");
|
println!("Available recipes:");
|
||||||
|
|
||||||
for (name, recipe) in &justfile.recipes {
|
for (name, recipe) in &justfile.recipes {
|
||||||
@ -402,7 +197,7 @@ pub fn run() {
|
|||||||
{
|
{
|
||||||
print!(" {}", name);
|
print!(" {}", name);
|
||||||
for parameter in &recipe.parameters {
|
for parameter in &recipe.parameters {
|
||||||
if color.stdout().active() {
|
if config.color.stdout().active() {
|
||||||
print!(" {:#}", parameter);
|
print!(" {:#}", parameter);
|
||||||
} else {
|
} else {
|
||||||
print!(" {}", parameter);
|
print!(" {}", parameter);
|
||||||
@ -454,8 +249,8 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let arguments = if !rest.is_empty() {
|
let arguments = if !config.arguments.is_empty() {
|
||||||
rest
|
config.arguments.clone()
|
||||||
} else if let Some(recipe) = justfile.first() {
|
} else if let Some(recipe) = justfile.first() {
|
||||||
let min_arguments = recipe.min_arguments();
|
let min_arguments = recipe.min_arguments();
|
||||||
if min_arguments > 0 {
|
if min_arguments > 0 {
|
||||||
@ -471,26 +266,13 @@ pub fn run() {
|
|||||||
die!("Justfile contains no recipes.");
|
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() {
|
if let Err(error) = InterruptHandler::install() {
|
||||||
warn!("Failed to set CTRL-C handler: {}", error)
|
warn!("Failed to set CTRL-C handler: {}", error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(run_error) = justfile.run(&invocation_directory, &arguments, &configuration) {
|
if let Err(run_error) = justfile.run(&invocation_directory, &arguments, &config) {
|
||||||
if !configuration.quiet {
|
if !config.quiet {
|
||||||
if color.stderr().active() {
|
if config.color.stderr().active() {
|
||||||
eprintln!("{:#}", run_error);
|
eprintln!("{:#}", run_error);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("{}", run_error);
|
eprintln!("{}", run_error);
|
||||||
|
Loading…
Reference in New Issue
Block a user