1685 lines
44 KiB
Rust
1685 lines
44 KiB
Rust
use {
|
|
super::*,
|
|
clap::{
|
|
builder::{styling::AnsiColor, FalseyValueParser, PossibleValuesParser, Styles},
|
|
parser::ValuesRef,
|
|
value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command,
|
|
},
|
|
};
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub(crate) struct Config {
|
|
pub(crate) check: bool,
|
|
pub(crate) color: Color,
|
|
pub(crate) command_color: Option<ansi_term::Color>,
|
|
pub(crate) dotenv_filename: Option<String>,
|
|
pub(crate) dotenv_path: Option<PathBuf>,
|
|
pub(crate) dry_run: bool,
|
|
pub(crate) dump_format: DumpFormat,
|
|
pub(crate) highlight: bool,
|
|
pub(crate) invocation_directory: PathBuf,
|
|
pub(crate) list_heading: String,
|
|
pub(crate) list_prefix: String,
|
|
pub(crate) list_submodules: bool,
|
|
pub(crate) load_dotenv: bool,
|
|
pub(crate) no_aliases: bool,
|
|
pub(crate) no_dependencies: bool,
|
|
pub(crate) search_config: SearchConfig,
|
|
pub(crate) shell: Option<String>,
|
|
pub(crate) shell_args: Option<Vec<String>>,
|
|
pub(crate) shell_command: bool,
|
|
pub(crate) subcommand: Subcommand,
|
|
pub(crate) timestamp: bool,
|
|
pub(crate) timestamp_format: String,
|
|
pub(crate) unsorted: bool,
|
|
pub(crate) unstable: bool,
|
|
pub(crate) verbosity: Verbosity,
|
|
pub(crate) yes: bool,
|
|
}
|
|
|
|
mod cmd {
|
|
pub(crate) const CHANGELOG: &str = "CHANGELOG";
|
|
pub(crate) const CHOOSE: &str = "CHOOSE";
|
|
pub(crate) const COMMAND: &str = "COMMAND";
|
|
pub(crate) const COMPLETIONS: &str = "COMPLETIONS";
|
|
pub(crate) const DUMP: &str = "DUMP";
|
|
pub(crate) const EDIT: &str = "EDIT";
|
|
pub(crate) const EVALUATE: &str = "EVALUATE";
|
|
pub(crate) const FORMAT: &str = "FORMAT";
|
|
pub(crate) const GROUPS: &str = "GROUPS";
|
|
pub(crate) const INIT: &str = "INIT";
|
|
pub(crate) const LIST: &str = "LIST";
|
|
pub(crate) const MAN: &str = "MAN";
|
|
pub(crate) const SHOW: &str = "SHOW";
|
|
pub(crate) const SUMMARY: &str = "SUMMARY";
|
|
pub(crate) const VARIABLES: &str = "VARIABLES";
|
|
|
|
pub(crate) const ALL: &[&str] = &[
|
|
CHANGELOG,
|
|
CHOOSE,
|
|
COMMAND,
|
|
COMPLETIONS,
|
|
DUMP,
|
|
EDIT,
|
|
EVALUATE,
|
|
FORMAT,
|
|
INIT,
|
|
LIST,
|
|
MAN,
|
|
SHOW,
|
|
SUMMARY,
|
|
VARIABLES,
|
|
];
|
|
|
|
pub(crate) const ARGLESS: &[&str] =
|
|
&[CHANGELOG, DUMP, EDIT, FORMAT, INIT, MAN, SUMMARY, VARIABLES];
|
|
}
|
|
|
|
mod arg {
|
|
pub(crate) const ARGUMENTS: &str = "ARGUMENTS";
|
|
pub(crate) const CHECK: &str = "CHECK";
|
|
pub(crate) const CHOOSER: &str = "CHOOSER";
|
|
pub(crate) const CLEAR_SHELL_ARGS: &str = "CLEAR-SHELL-ARGS";
|
|
pub(crate) const COLOR: &str = "COLOR";
|
|
pub(crate) const COMMAND_COLOR: &str = "COMMAND-COLOR";
|
|
pub(crate) const DOTENV_FILENAME: &str = "DOTENV-FILENAME";
|
|
pub(crate) const DOTENV_PATH: &str = "DOTENV-PATH";
|
|
pub(crate) const DRY_RUN: &str = "DRY-RUN";
|
|
pub(crate) const DUMP_FORMAT: &str = "DUMP-FORMAT";
|
|
pub(crate) const GLOBAL_JUSTFILE: &str = "GLOBAL-JUSTFILE";
|
|
pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT";
|
|
pub(crate) const JUSTFILE: &str = "JUSTFILE";
|
|
pub(crate) const LIST_HEADING: &str = "LIST-HEADING";
|
|
pub(crate) const LIST_PREFIX: &str = "LIST-PREFIX";
|
|
pub(crate) const LIST_SUBMODULES: &str = "LIST-SUBMODULES";
|
|
pub(crate) const NO_ALIASES: &str = "NO-ALIASES";
|
|
pub(crate) const NO_DEPS: &str = "NO-DEPS";
|
|
pub(crate) const NO_DOTENV: &str = "NO-DOTENV";
|
|
pub(crate) const NO_HIGHLIGHT: &str = "NO-HIGHLIGHT";
|
|
pub(crate) const QUIET: &str = "QUIET";
|
|
pub(crate) const SET: &str = "SET";
|
|
pub(crate) const SHELL: &str = "SHELL";
|
|
pub(crate) const SHELL_ARG: &str = "SHELL-ARG";
|
|
pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND";
|
|
pub(crate) const TIMESTAMP: &str = "TIMESTAMP";
|
|
pub(crate) const TIMESTAMP_FORMAT: &str = "TIMESTAMP-FORMAT";
|
|
pub(crate) const UNSORTED: &str = "UNSORTED";
|
|
pub(crate) const UNSTABLE: &str = "UNSTABLE";
|
|
pub(crate) const VERBOSE: &str = "VERBOSE";
|
|
pub(crate) const WORKING_DIRECTORY: &str = "WORKING-DIRECTORY";
|
|
pub(crate) const YES: &str = "YES";
|
|
|
|
pub(crate) const COLOR_ALWAYS: &str = "always";
|
|
pub(crate) const COLOR_AUTO: &str = "auto";
|
|
pub(crate) const COLOR_NEVER: &str = "never";
|
|
pub(crate) const COLOR_VALUES: &[&str] = &[COLOR_AUTO, COLOR_ALWAYS, COLOR_NEVER];
|
|
|
|
pub(crate) const COMMAND_COLOR_BLACK: &str = "black";
|
|
pub(crate) const COMMAND_COLOR_BLUE: &str = "blue";
|
|
pub(crate) const COMMAND_COLOR_CYAN: &str = "cyan";
|
|
pub(crate) const COMMAND_COLOR_GREEN: &str = "green";
|
|
pub(crate) const COMMAND_COLOR_PURPLE: &str = "purple";
|
|
pub(crate) const COMMAND_COLOR_RED: &str = "red";
|
|
pub(crate) const COMMAND_COLOR_YELLOW: &str = "yellow";
|
|
pub(crate) const COMMAND_COLOR_VALUES: &[&str] = &[
|
|
COMMAND_COLOR_BLACK,
|
|
COMMAND_COLOR_BLUE,
|
|
COMMAND_COLOR_CYAN,
|
|
COMMAND_COLOR_GREEN,
|
|
COMMAND_COLOR_PURPLE,
|
|
COMMAND_COLOR_RED,
|
|
COMMAND_COLOR_YELLOW,
|
|
];
|
|
|
|
pub(crate) const DUMP_FORMAT_JSON: &str = "json";
|
|
pub(crate) const DUMP_FORMAT_JUST: &str = "just";
|
|
pub(crate) const DUMP_FORMAT_VALUES: &[&str] = &[DUMP_FORMAT_JUST, DUMP_FORMAT_JSON];
|
|
}
|
|
|
|
impl Config {
|
|
pub(crate) fn app() -> Command {
|
|
Command::new(env!("CARGO_PKG_NAME"))
|
|
.bin_name(env!("CARGO_PKG_NAME"))
|
|
.version(env!("CARGO_PKG_VERSION"))
|
|
.author(env!("CARGO_PKG_AUTHORS"))
|
|
.about(concat!(
|
|
env!("CARGO_PKG_DESCRIPTION"),
|
|
" - ",
|
|
env!("CARGO_PKG_HOMEPAGE")
|
|
))
|
|
.trailing_var_arg(true)
|
|
.styles(
|
|
Styles::styled()
|
|
.header(AnsiColor::Yellow.on_default())
|
|
.literal(AnsiColor::Green.on_default())
|
|
.placeholder(AnsiColor::Green.on_default())
|
|
.usage(AnsiColor::Yellow.on_default()),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::CHECK)
|
|
.long("check")
|
|
.action(ArgAction::SetTrue)
|
|
.requires(cmd::FORMAT)
|
|
.help(
|
|
"Run `--fmt` in 'check' mode. Exits with 0 if justfile is formatted correctly. \
|
|
Exits with 1 and prints a diff if formatting is required.",
|
|
),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::CHOOSER)
|
|
.long("chooser")
|
|
.env("JUST_CHOOSER")
|
|
.action(ArgAction::Set)
|
|
.help("Override binary invoked by `--choose`"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::CLEAR_SHELL_ARGS)
|
|
.long("clear-shell-args")
|
|
.action(ArgAction::SetTrue)
|
|
.overrides_with(arg::SHELL_ARG)
|
|
.help("Clear shell arguments"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::COLOR)
|
|
.long("color")
|
|
.env("JUST_COLOR")
|
|
.action(ArgAction::Set)
|
|
.value_parser(PossibleValuesParser::new(arg::COLOR_VALUES))
|
|
.default_value(arg::COLOR_AUTO)
|
|
.help("Print colorful output"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::COMMAND_COLOR)
|
|
.long("command-color")
|
|
.env("JUST_COMMAND_COLOR")
|
|
.action(ArgAction::Set)
|
|
.value_parser(PossibleValuesParser::new(arg::COMMAND_COLOR_VALUES))
|
|
.help("Echo recipe lines in <COMMAND-COLOR>"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::DOTENV_FILENAME)
|
|
.long("dotenv-filename")
|
|
.action(ArgAction::Set)
|
|
.help("Search for environment file named <DOTENV-FILENAME> instead of `.env`")
|
|
.conflicts_with(arg::DOTENV_PATH),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::DOTENV_PATH)
|
|
.short('E')
|
|
.long("dotenv-path")
|
|
.action(ArgAction::Set)
|
|
.value_parser(value_parser!(PathBuf))
|
|
.help("Load <DOTENV-PATH> as environment file instead of searching for one"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::DRY_RUN)
|
|
.short('n')
|
|
.long("dry-run")
|
|
.env("JUST_DRY_RUN")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Print what just would do without doing it")
|
|
.conflicts_with(arg::QUIET),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::DUMP_FORMAT)
|
|
.long("dump-format")
|
|
.env("JUST_DUMP_FORMAT")
|
|
.action(ArgAction::Set)
|
|
.value_parser(PossibleValuesParser::new(arg::DUMP_FORMAT_VALUES))
|
|
.default_value(arg::DUMP_FORMAT_JUST)
|
|
.value_name("FORMAT")
|
|
.help("Dump justfile as <FORMAT>"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::GLOBAL_JUSTFILE)
|
|
.action(ArgAction::SetTrue)
|
|
.long("global-justfile")
|
|
.short('g')
|
|
.conflicts_with(arg::JUSTFILE)
|
|
.conflicts_with(arg::WORKING_DIRECTORY)
|
|
.help("Use global justfile"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::HIGHLIGHT)
|
|
.long("highlight")
|
|
.env("JUST_HIGHLIGHT")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Highlight echoed recipe lines in bold")
|
|
.overrides_with(arg::NO_HIGHLIGHT),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::JUSTFILE)
|
|
.short('f')
|
|
.long("justfile")
|
|
.env("JUST_JUSTFILE")
|
|
.action(ArgAction::Set)
|
|
.value_parser(value_parser!(PathBuf))
|
|
.help("Use <JUSTFILE> as justfile"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::LIST_HEADING)
|
|
.long("list-heading")
|
|
.env("JUST_LIST_HEADING")
|
|
.help("Print <TEXT> before list")
|
|
.value_name("TEXT")
|
|
.action(ArgAction::Set),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::LIST_PREFIX)
|
|
.long("list-prefix")
|
|
.env("JUST_LIST_PREFIX")
|
|
.help("Print <TEXT> before each list item")
|
|
.value_name("TEXT")
|
|
.action(ArgAction::Set),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::LIST_SUBMODULES)
|
|
.long("list-submodules")
|
|
.env("JUST_LIST_SUBMODULES")
|
|
.help("List recipes in submodules")
|
|
.action(ArgAction::SetTrue)
|
|
.env("JUST_LIST_SUBMODULES"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::NO_ALIASES)
|
|
.long("no-aliases")
|
|
.env("JUST_NO_ALIASES")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Don't show aliases in list"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::NO_DEPS)
|
|
.long("no-deps")
|
|
.env("JUST_NO_DEPS")
|
|
.alias("no-dependencies")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Don't run recipe dependencies"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::NO_DOTENV)
|
|
.long("no-dotenv")
|
|
.env("JUST_NO_DOTENV")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Don't load `.env` file"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::NO_HIGHLIGHT)
|
|
.long("no-highlight")
|
|
.env("JUST_NO_HIGHLIGHT")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Don't highlight echoed recipe lines in bold")
|
|
.overrides_with(arg::HIGHLIGHT),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::QUIET)
|
|
.short('q')
|
|
.long("quiet")
|
|
.env("JUST_QUIET")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Suppress all output")
|
|
.conflicts_with(arg::DRY_RUN),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::SET)
|
|
.long("set")
|
|
.action(ArgAction::Append)
|
|
.number_of_values(2)
|
|
.value_names(["VARIABLE", "VALUE"])
|
|
.help("Override <VARIABLE> with <VALUE>"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::SHELL)
|
|
.long("shell")
|
|
.action(ArgAction::Set)
|
|
.help("Invoke <SHELL> to run recipes"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::SHELL_ARG)
|
|
.long("shell-arg")
|
|
.action(ArgAction::Append)
|
|
.allow_hyphen_values(true)
|
|
.overrides_with(arg::CLEAR_SHELL_ARGS)
|
|
.help("Invoke shell with <SHELL-ARG> as an argument"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::SHELL_COMMAND)
|
|
.long("shell-command")
|
|
.requires(cmd::COMMAND)
|
|
.action(ArgAction::SetTrue)
|
|
.help("Invoke <COMMAND> with the shell used to run recipe lines and backticks"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::TIMESTAMP)
|
|
.action(ArgAction::SetTrue)
|
|
.long("timestamp")
|
|
.env("JUST_TIMESTAMP")
|
|
.help("Print recipe command timestamps"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::TIMESTAMP_FORMAT)
|
|
.action(ArgAction::Set)
|
|
.long("timestamp-format")
|
|
.env("JUST_TIMESTAMP_FORMAT")
|
|
.default_value("%H:%M:%S")
|
|
.help("Timestamp format string"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::UNSORTED)
|
|
.long("unsorted")
|
|
.env("JUST_UNSORTED")
|
|
.short('u')
|
|
.action(ArgAction::SetTrue)
|
|
.help("Return list and summary entries in source order"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::UNSTABLE)
|
|
.long("unstable")
|
|
.env("JUST_UNSTABLE")
|
|
.action(ArgAction::SetTrue)
|
|
.value_parser(FalseyValueParser::new())
|
|
.help("Enable unstable features"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::VERBOSE)
|
|
.short('v')
|
|
.long("verbose")
|
|
.env("JUST_VERBOSE")
|
|
.action(ArgAction::Count)
|
|
.help("Use verbose output"),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::WORKING_DIRECTORY)
|
|
.short('d')
|
|
.long("working-directory")
|
|
.env("JUST_WORKING_DIRECTORY")
|
|
.action(ArgAction::Set)
|
|
.value_parser(value_parser!(PathBuf))
|
|
.help("Use <WORKING-DIRECTORY> as working directory. --justfile must also be set")
|
|
.requires(arg::JUSTFILE),
|
|
)
|
|
.arg(
|
|
Arg::new(arg::YES)
|
|
.long("yes")
|
|
.env("JUST_YES")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Automatically confirm all recipes."),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::CHANGELOG)
|
|
.long("changelog")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Print changelog"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::CHOOSE)
|
|
.long("choose")
|
|
.action(ArgAction::SetTrue)
|
|
.help(
|
|
"Select one or more recipes to run using a binary chooser. If `--chooser` is not \
|
|
passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`",
|
|
),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::COMMAND)
|
|
.long("command")
|
|
.short('c')
|
|
.num_args(1..)
|
|
.allow_hyphen_values(true)
|
|
.action(ArgAction::Append)
|
|
.value_parser(value_parser!(std::ffi::OsString))
|
|
.help(
|
|
"Run an arbitrary command with the working directory, `.env`, overrides, and exports \
|
|
set",
|
|
),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::COMPLETIONS)
|
|
.long("completions")
|
|
.action(ArgAction::Set)
|
|
.value_name("SHELL")
|
|
.value_parser(value_parser!(completions::Shell))
|
|
.ignore_case(true)
|
|
.help("Print shell completion script for <SHELL>"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::DUMP)
|
|
.long("dump")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Print justfile"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::EDIT)
|
|
.short('e')
|
|
.long("edit")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::EVALUATE)
|
|
.long("evaluate")
|
|
.alias("eval")
|
|
.action(ArgAction::SetTrue)
|
|
.help(
|
|
"Evaluate and print all variables. If a variable name is given as an argument, only \
|
|
print that variable's value.",
|
|
),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::FORMAT)
|
|
.long("fmt")
|
|
.alias("format")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Format and overwrite justfile"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::GROUPS)
|
|
.long("groups")
|
|
.action(ArgAction::SetTrue)
|
|
.help("List recipe groups"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::INIT)
|
|
.long("init")
|
|
.alias("initialize")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Initialize new justfile in project root"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::LIST)
|
|
.short('l')
|
|
.long("list")
|
|
.num_args(0..)
|
|
.value_name("PATH")
|
|
.action(ArgAction::Set)
|
|
.conflicts_with(arg::ARGUMENTS)
|
|
.help("List available recipes"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::MAN)
|
|
.long("man")
|
|
.action(ArgAction::SetTrue)
|
|
.help("Print man page"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::SHOW)
|
|
.short('s')
|
|
.long("show")
|
|
.num_args(1..)
|
|
.action(ArgAction::Set)
|
|
.value_name("PATH")
|
|
.conflicts_with(arg::ARGUMENTS)
|
|
.help("Show recipe at <PATH>"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::SUMMARY)
|
|
.long("summary")
|
|
.action(ArgAction::SetTrue)
|
|
.help("List names of available recipes"),
|
|
)
|
|
.arg(
|
|
Arg::new(cmd::VARIABLES)
|
|
.long("variables")
|
|
.action(ArgAction::SetTrue)
|
|
.help("List names of variables"),
|
|
)
|
|
.group(ArgGroup::new("SUBCOMMAND").args(cmd::ALL))
|
|
.arg(
|
|
Arg::new(arg::ARGUMENTS)
|
|
.num_args(1..)
|
|
.action(ArgAction::Append)
|
|
.help("Overrides and recipe(s) to run, defaulting to the first recipe in the justfile"),
|
|
)
|
|
}
|
|
|
|
fn color_from_matches(matches: &ArgMatches) -> ConfigResult<Color> {
|
|
let value = matches
|
|
.get_one::<String>(arg::COLOR)
|
|
.ok_or_else(|| ConfigError::Internal {
|
|
message: "`--color` had no value".to_string(),
|
|
})?;
|
|
|
|
match value.as_str() {
|
|
arg::COLOR_AUTO => Ok(Color::auto()),
|
|
arg::COLOR_ALWAYS => Ok(Color::always()),
|
|
arg::COLOR_NEVER => Ok(Color::never()),
|
|
_ => Err(ConfigError::Internal {
|
|
message: format!("Invalid argument `{value}` to --color."),
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn command_color_from_matches(matches: &ArgMatches) -> ConfigResult<Option<ansi_term::Color>> {
|
|
if let Some(value) = matches.get_one::<String>(arg::COMMAND_COLOR) {
|
|
match value.as_str() {
|
|
arg::COMMAND_COLOR_BLACK => Ok(Some(ansi_term::Color::Black)),
|
|
arg::COMMAND_COLOR_BLUE => Ok(Some(ansi_term::Color::Blue)),
|
|
arg::COMMAND_COLOR_CYAN => Ok(Some(ansi_term::Color::Cyan)),
|
|
arg::COMMAND_COLOR_GREEN => Ok(Some(ansi_term::Color::Green)),
|
|
arg::COMMAND_COLOR_PURPLE => Ok(Some(ansi_term::Color::Purple)),
|
|
arg::COMMAND_COLOR_RED => Ok(Some(ansi_term::Color::Red)),
|
|
arg::COMMAND_COLOR_YELLOW => Ok(Some(ansi_term::Color::Yellow)),
|
|
value => Err(ConfigError::Internal {
|
|
message: format!("Invalid argument `{value}` to --command-color."),
|
|
}),
|
|
}
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn dump_format_from_matches(matches: &ArgMatches) -> ConfigResult<DumpFormat> {
|
|
let value =
|
|
matches
|
|
.get_one::<String>(arg::DUMP_FORMAT)
|
|
.ok_or_else(|| ConfigError::Internal {
|
|
message: "`--dump-format` had no value".to_string(),
|
|
})?;
|
|
|
|
match value.as_str() {
|
|
arg::DUMP_FORMAT_JSON => Ok(DumpFormat::Json),
|
|
arg::DUMP_FORMAT_JUST => Ok(DumpFormat::Just),
|
|
_ => Err(ConfigError::Internal {
|
|
message: format!("Invalid argument `{value}` to --dump-format."),
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn parse_module_path(values: ValuesRef<String>) -> ConfigResult<ModulePath> {
|
|
let path = values.clone().map(|s| (*s).as_str()).collect::<Vec<&str>>();
|
|
|
|
let path = if path.len() == 1 && path[0].contains(' ') {
|
|
path[0].split_whitespace().collect::<Vec<&str>>()
|
|
} else {
|
|
path
|
|
};
|
|
|
|
path
|
|
.as_slice()
|
|
.try_into()
|
|
.map_err(|()| ConfigError::ModulePath {
|
|
path: values.cloned().collect(),
|
|
})
|
|
}
|
|
|
|
fn search_config(matches: &ArgMatches, positional: &Positional) -> ConfigResult<SearchConfig> {
|
|
if matches.get_flag(arg::GLOBAL_JUSTFILE) {
|
|
return Ok(SearchConfig::GlobalJustfile);
|
|
}
|
|
|
|
let justfile = matches.get_one::<PathBuf>(arg::JUSTFILE).map(Into::into);
|
|
|
|
let working_directory = matches
|
|
.get_one::<PathBuf>(arg::WORKING_DIRECTORY)
|
|
.map(Into::into);
|
|
|
|
if let Some(search_directory) = positional.search_directory.as_ref().map(PathBuf::from) {
|
|
if justfile.is_some() || working_directory.is_some() {
|
|
return Err(ConfigError::SearchDirConflict);
|
|
}
|
|
Ok(SearchConfig::FromSearchDirectory { search_directory })
|
|
} else {
|
|
match (justfile, working_directory) {
|
|
(None, None) => Ok(SearchConfig::FromInvocationDirectory),
|
|
(Some(justfile), None) => Ok(SearchConfig::WithJustfile { justfile }),
|
|
(Some(justfile), Some(working_directory)) => {
|
|
Ok(SearchConfig::WithJustfileAndWorkingDirectory {
|
|
justfile,
|
|
working_directory,
|
|
})
|
|
}
|
|
(None, Some(_)) => Err(ConfigError::internal(
|
|
"--working-directory set without --justfile",
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn from_matches(matches: &ArgMatches) -> ConfigResult<Self> {
|
|
let mut overrides = BTreeMap::new();
|
|
if let Some(mut values) = matches.get_many::<String>(arg::SET) {
|
|
while let (Some(k), Some(v)) = (values.next(), values.next()) {
|
|
overrides.insert(k.into(), v.into());
|
|
}
|
|
}
|
|
|
|
let positional = Positional::from_values(
|
|
matches
|
|
.get_many::<String>(arg::ARGUMENTS)
|
|
.map(|s| s.map(String::as_str)),
|
|
);
|
|
|
|
for (name, value) in &positional.overrides {
|
|
overrides.insert(name.clone(), value.clone());
|
|
}
|
|
|
|
let search_config = Self::search_config(matches, &positional)?;
|
|
|
|
for subcommand in cmd::ARGLESS {
|
|
if matches.get_flag(subcommand) {
|
|
match (!overrides.is_empty(), !positional.arguments.is_empty()) {
|
|
(false, false) => {}
|
|
(true, false) => {
|
|
return Err(ConfigError::SubcommandOverrides {
|
|
subcommand,
|
|
overrides,
|
|
});
|
|
}
|
|
(false, true) => {
|
|
return Err(ConfigError::SubcommandArguments {
|
|
arguments: positional.arguments,
|
|
subcommand,
|
|
});
|
|
}
|
|
(true, true) => {
|
|
return Err(ConfigError::SubcommandOverridesAndArguments {
|
|
arguments: positional.arguments,
|
|
subcommand,
|
|
overrides,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let subcommand = if matches.get_flag(cmd::CHANGELOG) {
|
|
Subcommand::Changelog
|
|
} else if matches.get_flag(cmd::CHOOSE) {
|
|
Subcommand::Choose {
|
|
chooser: matches.get_one::<String>(arg::CHOOSER).map(Into::into),
|
|
overrides,
|
|
}
|
|
} else if let Some(values) = matches.get_many::<OsString>(cmd::COMMAND) {
|
|
let mut arguments = values.map(Into::into).collect::<Vec<OsString>>();
|
|
Subcommand::Command {
|
|
binary: arguments.remove(0),
|
|
arguments,
|
|
overrides,
|
|
}
|
|
} else if let Some(&shell) = matches.get_one::<completions::Shell>(cmd::COMPLETIONS) {
|
|
Subcommand::Completions { shell }
|
|
} else if matches.get_flag(cmd::DUMP) {
|
|
Subcommand::Dump
|
|
} else if matches.get_flag(cmd::EDIT) {
|
|
Subcommand::Edit
|
|
} else if matches.get_flag(cmd::EVALUATE) {
|
|
if positional.arguments.len() > 1 {
|
|
return Err(ConfigError::SubcommandArguments {
|
|
subcommand: cmd::EVALUATE,
|
|
arguments: positional
|
|
.arguments
|
|
.into_iter()
|
|
.skip(1)
|
|
.collect::<Vec<String>>(),
|
|
});
|
|
}
|
|
|
|
Subcommand::Evaluate {
|
|
variable: positional.arguments.into_iter().next(),
|
|
overrides,
|
|
}
|
|
} else if matches.get_flag(cmd::FORMAT) {
|
|
Subcommand::Format
|
|
} else if matches.get_flag(cmd::GROUPS) {
|
|
Subcommand::Groups
|
|
} else if matches.get_flag(cmd::INIT) {
|
|
Subcommand::Init
|
|
} else if let Some(path) = matches.get_many::<String>(cmd::LIST) {
|
|
Subcommand::List {
|
|
path: Self::parse_module_path(path)?,
|
|
}
|
|
} else if matches.get_flag(cmd::MAN) {
|
|
Subcommand::Man
|
|
} else if let Some(path) = matches.get_many::<String>(cmd::SHOW) {
|
|
Subcommand::Show {
|
|
path: Self::parse_module_path(path)?,
|
|
}
|
|
} else if matches.get_flag(cmd::SUMMARY) {
|
|
Subcommand::Summary
|
|
} else if matches.get_flag(cmd::VARIABLES) {
|
|
Subcommand::Variables
|
|
} else {
|
|
Subcommand::Run {
|
|
arguments: positional.arguments,
|
|
overrides,
|
|
}
|
|
};
|
|
|
|
Ok(Self {
|
|
check: matches.get_flag(arg::CHECK),
|
|
color: Self::color_from_matches(matches)?,
|
|
command_color: Self::command_color_from_matches(matches)?,
|
|
dotenv_filename: matches
|
|
.get_one::<String>(arg::DOTENV_FILENAME)
|
|
.map(Into::into),
|
|
dotenv_path: matches.get_one::<PathBuf>(arg::DOTENV_PATH).map(Into::into),
|
|
dry_run: matches.get_flag(arg::DRY_RUN),
|
|
dump_format: Self::dump_format_from_matches(matches)?,
|
|
highlight: !matches.get_flag(arg::NO_HIGHLIGHT),
|
|
invocation_directory: env::current_dir().context(config_error::CurrentDirContext)?,
|
|
list_heading: matches
|
|
.get_one::<String>(arg::LIST_HEADING)
|
|
.map_or_else(|| "Available recipes:\n".into(), Into::into),
|
|
list_prefix: matches
|
|
.get_one::<String>(arg::LIST_PREFIX)
|
|
.map_or_else(|| " ".into(), Into::into),
|
|
list_submodules: matches.get_flag(arg::LIST_SUBMODULES),
|
|
load_dotenv: !matches.get_flag(arg::NO_DOTENV),
|
|
no_aliases: matches.get_flag(arg::NO_ALIASES),
|
|
no_dependencies: matches.get_flag(arg::NO_DEPS),
|
|
search_config,
|
|
shell: matches.get_one::<String>(arg::SHELL).map(Into::into),
|
|
shell_args: if matches.get_flag(arg::CLEAR_SHELL_ARGS) {
|
|
Some(Vec::new())
|
|
} else {
|
|
matches
|
|
.get_many::<String>(arg::SHELL_ARG)
|
|
.map(|s| s.map(Into::into).collect())
|
|
},
|
|
shell_command: matches.get_flag(arg::SHELL_COMMAND),
|
|
subcommand,
|
|
timestamp: matches.get_flag(arg::TIMESTAMP),
|
|
timestamp_format: matches
|
|
.get_one::<String>(arg::TIMESTAMP_FORMAT)
|
|
.unwrap()
|
|
.into(),
|
|
unsorted: matches.get_flag(arg::UNSORTED),
|
|
unstable: matches.get_flag(arg::UNSTABLE),
|
|
verbosity: if matches.get_flag(arg::QUIET) {
|
|
Verbosity::Quiet
|
|
} else {
|
|
Verbosity::from_flag_occurrences(matches.get_count(arg::VERBOSE))
|
|
},
|
|
yes: matches.get_flag(arg::YES),
|
|
})
|
|
}
|
|
|
|
pub(crate) fn require_unstable(&self, message: &str) -> RunResult<'static> {
|
|
if self.unstable {
|
|
Ok(())
|
|
} else {
|
|
Err(Error::Unstable {
|
|
message: message.to_owned(),
|
|
})
|
|
}
|
|
}
|
|
|
|
pub(crate) fn run(self, loader: &Loader) -> RunResult {
|
|
if let Err(error) = InterruptHandler::install(self.verbosity) {
|
|
warn!("Failed to set CTRL-C handler: {error}");
|
|
}
|
|
|
|
self.subcommand.execute(&self, loader)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use {
|
|
super::*,
|
|
clap::error::{ContextKind, ContextValue},
|
|
pretty_assertions::assert_eq,
|
|
};
|
|
|
|
macro_rules! test {
|
|
{
|
|
name: $name:ident,
|
|
args: [$($arg:expr),*],
|
|
$(color: $color:expr,)?
|
|
$(dry_run: $dry_run:expr,)?
|
|
$(dump_format: $dump_format:expr,)?
|
|
$(highlight: $highlight:expr,)?
|
|
$(no_dependencies: $no_dependencies:expr,)?
|
|
$(search_config: $search_config:expr,)?
|
|
$(shell: $shell:expr,)?
|
|
$(shell_args: $shell_args:expr,)?
|
|
$(subcommand: $subcommand:expr,)?
|
|
$(unsorted: $unsorted:expr,)?
|
|
$(verbosity: $verbosity:expr,)?
|
|
} => {
|
|
#[test]
|
|
fn $name() {
|
|
let arguments = &[
|
|
"just",
|
|
$($arg,)*
|
|
];
|
|
|
|
let want = Config {
|
|
$(color: $color,)?
|
|
$(dry_run: $dry_run,)?
|
|
$(dump_format: $dump_format,)?
|
|
$(highlight: $highlight,)?
|
|
$(no_dependencies: $no_dependencies,)?
|
|
$(search_config: $search_config,)?
|
|
$(shell: $shell,)?
|
|
$(shell_args: $shell_args,)?
|
|
$(subcommand: $subcommand,)?
|
|
$(unsorted: $unsorted,)?
|
|
$(verbosity: $verbosity,)?
|
|
..testing::config(&[])
|
|
};
|
|
|
|
test(arguments, want);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn test(arguments: &[&str], want: Config) {
|
|
let app = Config::app();
|
|
let matches = app
|
|
.try_get_matches_from(arguments)
|
|
.expect("argument parsing failed");
|
|
let have = Config::from_matches(&matches).expect("config parsing failed");
|
|
assert_eq!(have, want);
|
|
}
|
|
|
|
macro_rules! error {
|
|
{
|
|
name: $name:ident,
|
|
args: [$($arg:expr),*],
|
|
} => {
|
|
#[test]
|
|
fn $name() {
|
|
let arguments = &[
|
|
"just",
|
|
$($arg,)*
|
|
];
|
|
|
|
let app = Config::app();
|
|
|
|
app.try_get_matches_from(arguments).expect_err("Expected clap error");
|
|
}
|
|
};
|
|
{
|
|
name: $name:ident,
|
|
args: [$($arg:expr),*],
|
|
error: $error:pat,
|
|
$(check: $check:block,)?
|
|
} => {
|
|
#[test]
|
|
fn $name() {
|
|
let arguments = &[
|
|
"just",
|
|
$($arg,)*
|
|
];
|
|
|
|
let app = Config::app();
|
|
|
|
let matches = app.try_get_matches_from(arguments).expect("Matching fails");
|
|
|
|
match Config::from_matches(&matches).expect_err("config parsing succeeded") {
|
|
$error => { $($check)? }
|
|
other => panic!("Unexpected config error: {other}"),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! error_matches {
|
|
(
|
|
name: $name:ident,
|
|
args: [$($arg:expr),*],
|
|
error: $error:pat,
|
|
$(check: $check:block,)?
|
|
) => {
|
|
#[test]
|
|
fn $name() {
|
|
let arguments = &[
|
|
"just",
|
|
$($arg,)*
|
|
];
|
|
|
|
let app = Config::app();
|
|
|
|
match app.try_get_matches_from(arguments) {
|
|
Err($error) => { $($check)? }
|
|
other => panic!("Unexpected result from get matches: {other:?}")
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! map {
|
|
{} => {
|
|
BTreeMap::new()
|
|
};
|
|
{
|
|
$($key:literal : $value:literal),* $(,)?
|
|
} => {
|
|
{
|
|
let mut map: BTreeMap<String, String> = BTreeMap::new();
|
|
$(
|
|
map.insert($key.to_owned(), $value.to_owned());
|
|
)*
|
|
map
|
|
}
|
|
}
|
|
}
|
|
|
|
test! {
|
|
name: default_config,
|
|
args: [],
|
|
}
|
|
|
|
test! {
|
|
name: color_default,
|
|
args: [],
|
|
color: Color::auto(),
|
|
}
|
|
|
|
test! {
|
|
name: color_never,
|
|
args: ["--color", "never"],
|
|
color: Color::never(),
|
|
}
|
|
|
|
test! {
|
|
name: color_always,
|
|
args: ["--color", "always"],
|
|
color: Color::always(),
|
|
}
|
|
|
|
test! {
|
|
name: color_auto,
|
|
args: ["--color", "auto"],
|
|
color: Color::auto(),
|
|
}
|
|
|
|
error! {
|
|
name: color_bad_value,
|
|
args: ["--color", "foo"],
|
|
}
|
|
|
|
test! {
|
|
name: dry_run_default,
|
|
args: [],
|
|
dry_run: false,
|
|
}
|
|
|
|
test! {
|
|
name: dry_run_long,
|
|
args: ["--dry-run"],
|
|
dry_run: true,
|
|
}
|
|
|
|
test! {
|
|
name: dry_run_short,
|
|
args: ["-n"],
|
|
dry_run: true,
|
|
}
|
|
|
|
error! {
|
|
name: dry_run_quiet,
|
|
args: ["--dry-run", "--quiet"],
|
|
}
|
|
|
|
test! {
|
|
name: highlight_default,
|
|
args: [],
|
|
highlight: true,
|
|
}
|
|
|
|
test! {
|
|
name: highlight_yes,
|
|
args: ["--highlight"],
|
|
highlight: true,
|
|
}
|
|
|
|
test! {
|
|
name: highlight_no,
|
|
args: ["--no-highlight"],
|
|
highlight: false,
|
|
}
|
|
|
|
test! {
|
|
name: highlight_no_yes,
|
|
args: ["--no-highlight", "--highlight"],
|
|
highlight: true,
|
|
}
|
|
|
|
test! {
|
|
name: highlight_no_yes_no,
|
|
args: ["--no-highlight", "--highlight", "--no-highlight"],
|
|
highlight: false,
|
|
}
|
|
|
|
test! {
|
|
name: highlight_yes_no,
|
|
args: ["--highlight", "--no-highlight"],
|
|
highlight: false,
|
|
}
|
|
|
|
test! {
|
|
name: no_deps,
|
|
args: ["--no-deps"],
|
|
no_dependencies: true,
|
|
}
|
|
|
|
test! {
|
|
name: no_dependencies,
|
|
args: ["--no-dependencies"],
|
|
no_dependencies: true,
|
|
}
|
|
|
|
test! {
|
|
name: unsorted_default,
|
|
args: [],
|
|
unsorted: false,
|
|
}
|
|
|
|
test! {
|
|
name: unsorted_long,
|
|
args: ["--unsorted"],
|
|
unsorted: true,
|
|
}
|
|
|
|
test! {
|
|
name: unsorted_short,
|
|
args: ["-u"],
|
|
unsorted: true,
|
|
}
|
|
|
|
test! {
|
|
name: quiet_default,
|
|
args: [],
|
|
verbosity: Verbosity::Taciturn,
|
|
}
|
|
|
|
test! {
|
|
name: quiet_long,
|
|
args: ["--quiet"],
|
|
verbosity: Verbosity::Quiet,
|
|
}
|
|
|
|
test! {
|
|
name: quiet_short,
|
|
args: ["-q"],
|
|
verbosity: Verbosity::Quiet,
|
|
}
|
|
|
|
error! {
|
|
name: dotenv_both_filename_and_path,
|
|
args: ["--dotenv-filename", "foo", "--dotenv-path", "bar"],
|
|
}
|
|
|
|
test! {
|
|
name: set_default,
|
|
args: [],
|
|
subcommand: Subcommand::Run {
|
|
arguments: Vec::new(),
|
|
overrides: map!(),
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: set_one,
|
|
args: ["--set", "foo", "bar"],
|
|
subcommand: Subcommand::Run {
|
|
arguments: Vec::new(),
|
|
overrides: map!{"foo": "bar"},
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: set_empty,
|
|
args: ["--set", "foo", ""],
|
|
subcommand: Subcommand::Run {
|
|
arguments: Vec::new(),
|
|
overrides: map!{"foo": ""},
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: set_two,
|
|
args: ["--set", "foo", "bar", "--set", "bar", "baz"],
|
|
subcommand: Subcommand::Run {
|
|
arguments: Vec::new(),
|
|
overrides: map!{"foo": "bar", "bar": "baz"},
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: set_override,
|
|
args: ["--set", "foo", "bar", "--set", "foo", "baz"],
|
|
subcommand: Subcommand::Run {
|
|
arguments: Vec::new(),
|
|
overrides: map!{"foo": "baz"},
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: set_bad,
|
|
args: ["--set", "foo"],
|
|
}
|
|
|
|
test! {
|
|
name: shell_default,
|
|
args: [],
|
|
shell: None,
|
|
shell_args: None,
|
|
}
|
|
|
|
test! {
|
|
name: shell_set,
|
|
args: ["--shell", "tclsh"],
|
|
shell: Some("tclsh".to_owned()),
|
|
}
|
|
|
|
test! {
|
|
name: shell_args_set,
|
|
args: ["--shell-arg", "hello"],
|
|
shell: None,
|
|
shell_args: Some(vec!["hello".into()]),
|
|
}
|
|
|
|
test! {
|
|
name: verbosity_default,
|
|
args: [],
|
|
verbosity: Verbosity::Taciturn,
|
|
}
|
|
|
|
test! {
|
|
name: verbosity_long,
|
|
args: ["--verbose"],
|
|
verbosity: Verbosity::Loquacious,
|
|
}
|
|
|
|
test! {
|
|
name: verbosity_loquacious,
|
|
args: ["-v"],
|
|
verbosity: Verbosity::Loquacious,
|
|
}
|
|
|
|
test! {
|
|
name: verbosity_grandiloquent,
|
|
args: ["-v", "-v"],
|
|
verbosity: Verbosity::Grandiloquent,
|
|
}
|
|
|
|
test! {
|
|
name: verbosity_great_grandiloquent,
|
|
args: ["-v", "-v", "-v"],
|
|
verbosity: Verbosity::Grandiloquent,
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_default,
|
|
args: [],
|
|
subcommand: Subcommand::Run {
|
|
arguments: Vec::new(),
|
|
overrides: map!{},
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_changelog,
|
|
args: ["--list", "--changelog"],
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_summary,
|
|
args: ["--list", "--summary"],
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_dump,
|
|
args: ["--list", "--dump"],
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_fmt,
|
|
args: ["--list", "--fmt"],
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_init,
|
|
args: ["--list", "--init"],
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_evaluate,
|
|
args: ["--list", "--evaluate"],
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_show,
|
|
args: ["--list", "--show"],
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_completions,
|
|
args: ["--list", "--completions"],
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_variables,
|
|
args: ["--list", "--variables"],
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_conflict_choose,
|
|
args: ["--list", "--choose"],
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_completions,
|
|
args: ["--completions", "bash"],
|
|
subcommand: Subcommand::Completions{ shell: completions::Shell::Bash },
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_completions_uppercase,
|
|
args: ["--completions", "BASH"],
|
|
subcommand: Subcommand::Completions{ shell: completions::Shell::Bash },
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_completions_invalid,
|
|
args: ["--completions", "monstersh"],
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_dump,
|
|
args: ["--dump"],
|
|
subcommand: Subcommand::Dump,
|
|
}
|
|
|
|
test! {
|
|
name: dump_format,
|
|
args: ["--dump-format", "json"],
|
|
dump_format: DumpFormat::Json,
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_edit,
|
|
args: ["--edit"],
|
|
subcommand: Subcommand::Edit,
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_evaluate,
|
|
args: ["--evaluate"],
|
|
subcommand: Subcommand::Evaluate {
|
|
overrides: map!{},
|
|
variable: None,
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_evaluate_overrides,
|
|
args: ["--evaluate", "x=y"],
|
|
subcommand: Subcommand::Evaluate {
|
|
overrides: map!{"x": "y"},
|
|
variable: None,
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_evaluate_overrides_with_argument,
|
|
args: ["--evaluate", "x=y", "foo"],
|
|
subcommand: Subcommand::Evaluate {
|
|
overrides: map!{"x": "y"},
|
|
variable: Some("foo".to_owned()),
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_list_long,
|
|
args: ["--list"],
|
|
subcommand: Subcommand::List{ path: ModulePath { path: Vec::new(), spaced: false } },
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_list_short,
|
|
args: ["-l"],
|
|
subcommand: Subcommand::List{ path: ModulePath { path: Vec::new(), spaced: false } },
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_list_arguments,
|
|
args: ["--list", "bar"],
|
|
subcommand: Subcommand::List{ path: ModulePath { path: vec!["bar".into()], spaced: false } },
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_show_long,
|
|
args: ["--show", "build"],
|
|
subcommand: Subcommand::Show { path: ModulePath { path: vec!["build".into()], spaced: false } },
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_show_short,
|
|
args: ["-s", "build"],
|
|
subcommand: Subcommand::Show { path: ModulePath { path: vec!["build".into()], spaced: false } },
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_show_multiple_args,
|
|
args: ["--show", "foo", "bar"],
|
|
subcommand: Subcommand::Show {
|
|
path: ModulePath {
|
|
path: vec!["foo".into(), "bar".into()],
|
|
spaced: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: subcommand_summary,
|
|
args: ["--summary"],
|
|
subcommand: Subcommand::Summary,
|
|
}
|
|
|
|
test! {
|
|
name: arguments,
|
|
args: ["foo", "bar"],
|
|
subcommand: Subcommand::Run {
|
|
arguments: vec![String::from("foo"), String::from("bar")],
|
|
overrides: map!{},
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: arguments_leading_equals,
|
|
args: ["=foo"],
|
|
subcommand: Subcommand::Run {
|
|
arguments: vec!["=foo".to_owned()],
|
|
overrides: map!{},
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: overrides,
|
|
args: ["foo=bar", "bar=baz"],
|
|
subcommand: Subcommand::Run {
|
|
arguments: Vec::new(),
|
|
overrides: map!{"foo": "bar", "bar": "baz"},
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: overrides_empty,
|
|
args: ["foo=", "bar="],
|
|
subcommand: Subcommand::Run {
|
|
arguments: Vec::new(),
|
|
overrides: map!{"foo": "", "bar": ""},
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: overrides_override_sets,
|
|
args: ["--set", "foo", "0", "--set", "bar", "1", "foo=bar", "bar=baz"],
|
|
subcommand: Subcommand::Run {
|
|
arguments: Vec::new(),
|
|
overrides: map!{"foo": "bar", "bar": "baz"},
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: shell_args_default,
|
|
args: [],
|
|
}
|
|
|
|
test! {
|
|
name: shell_args_set_hyphen,
|
|
args: ["--shell-arg", "--foo"],
|
|
shell_args: Some(vec!["--foo".to_owned()]),
|
|
}
|
|
|
|
test! {
|
|
name: shell_args_set_word,
|
|
args: ["--shell-arg", "foo"],
|
|
shell_args: Some(vec!["foo".to_owned()]),
|
|
}
|
|
|
|
test! {
|
|
name: shell_args_set_multiple,
|
|
args: ["--shell-arg", "foo", "--shell-arg", "bar"],
|
|
shell_args: Some(vec!["foo".to_owned(), "bar".to_owned()]),
|
|
|
|
}
|
|
|
|
test! {
|
|
name: shell_args_clear,
|
|
args: ["--clear-shell-args"],
|
|
shell_args: Some(Vec::new()),
|
|
|
|
}
|
|
|
|
test! {
|
|
name: shell_args_clear_and_set,
|
|
args: ["--clear-shell-args", "--shell-arg", "bar"],
|
|
shell_args: Some(vec!["bar".to_owned()]),
|
|
|
|
}
|
|
|
|
test! {
|
|
name: shell_args_set_and_clear,
|
|
args: ["--shell-arg", "bar", "--clear-shell-args"],
|
|
shell_args: Some(Vec::new()),
|
|
|
|
}
|
|
|
|
test! {
|
|
name: shell_args_set_multiple_and_clear,
|
|
args: ["--shell-arg", "bar", "--shell-arg", "baz", "--clear-shell-args"],
|
|
shell_args: Some(Vec::new()),
|
|
|
|
}
|
|
|
|
test! {
|
|
name: search_config_default,
|
|
args: [],
|
|
search_config: SearchConfig::FromInvocationDirectory,
|
|
}
|
|
|
|
test! {
|
|
name: search_config_from_working_directory_and_justfile,
|
|
args: ["--working-directory", "foo", "--justfile", "bar"],
|
|
search_config: SearchConfig::WithJustfileAndWorkingDirectory {
|
|
justfile: PathBuf::from("bar"),
|
|
working_directory: PathBuf::from("foo"),
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: search_config_justfile_long,
|
|
args: ["--justfile", "foo"],
|
|
search_config: SearchConfig::WithJustfile {
|
|
justfile: PathBuf::from("foo"),
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: search_config_justfile_short,
|
|
args: ["-f", "foo"],
|
|
search_config: SearchConfig::WithJustfile {
|
|
justfile: PathBuf::from("foo"),
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: search_directory_parent,
|
|
args: ["../"],
|
|
search_config: SearchConfig::FromSearchDirectory {
|
|
search_directory: PathBuf::from(".."),
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: search_directory_parent_with_recipe,
|
|
args: ["../build"],
|
|
search_config: SearchConfig::FromSearchDirectory {
|
|
search_directory: PathBuf::from(".."),
|
|
},
|
|
subcommand: Subcommand::Run { arguments: vec!["build".to_owned()], overrides: BTreeMap::new() },
|
|
}
|
|
|
|
test! {
|
|
name: search_directory_child,
|
|
args: ["foo/"],
|
|
search_config: SearchConfig::FromSearchDirectory {
|
|
search_directory: PathBuf::from("foo"),
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: search_directory_deep,
|
|
args: ["foo/bar/"],
|
|
search_config: SearchConfig::FromSearchDirectory {
|
|
search_directory: PathBuf::from("foo/bar"),
|
|
},
|
|
}
|
|
|
|
test! {
|
|
name: search_directory_child_with_recipe,
|
|
args: ["foo/build"],
|
|
search_config: SearchConfig::FromSearchDirectory {
|
|
search_directory: PathBuf::from("foo"),
|
|
},
|
|
subcommand: Subcommand::Run { arguments: vec!["build".to_owned()], overrides: BTreeMap::new() },
|
|
}
|
|
|
|
error! {
|
|
name: search_directory_conflict_justfile,
|
|
args: ["--justfile", "bar", "foo/build"],
|
|
error: ConfigError::SearchDirConflict,
|
|
}
|
|
|
|
error! {
|
|
name: search_directory_conflict_working_directory,
|
|
args: ["--justfile", "bar", "--working-directory", "baz", "foo/build"],
|
|
error: ConfigError::SearchDirConflict,
|
|
}
|
|
|
|
error_matches! {
|
|
name: completions_argument,
|
|
args: ["--completions", "foo"],
|
|
error: error,
|
|
check: {
|
|
assert_eq!(error.kind(), clap::error::ErrorKind::InvalidValue);
|
|
assert_eq!(error.context().collect::<Vec<_>>(), vec![
|
|
(
|
|
ContextKind::InvalidArg,
|
|
&ContextValue::String("--completions <SHELL>".into())),
|
|
(
|
|
ContextKind::InvalidValue,
|
|
&ContextValue::String("foo".into()),
|
|
),
|
|
(
|
|
ContextKind::ValidValue,
|
|
&ContextValue::Strings([
|
|
"bash".into(),
|
|
"elvish".into(),
|
|
"fish".into(),
|
|
"nushell".into(),
|
|
"powershell".into(),
|
|
"zsh".into()].into()
|
|
),
|
|
),
|
|
]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: changelog_arguments,
|
|
args: ["--changelog", "bar"],
|
|
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::CHANGELOG);
|
|
assert_eq!(arguments, &["bar"]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: dump_arguments,
|
|
args: ["--dump", "bar"],
|
|
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::DUMP);
|
|
assert_eq!(arguments, &["bar"]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: edit_arguments,
|
|
args: ["--edit", "bar"],
|
|
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::EDIT);
|
|
assert_eq!(arguments, &["bar"]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: fmt_arguments,
|
|
args: ["--fmt", "bar"],
|
|
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::FORMAT);
|
|
assert_eq!(arguments, &["bar"]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: fmt_alias,
|
|
args: ["--format", "bar"],
|
|
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::FORMAT);
|
|
assert_eq!(arguments, &["bar"]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: init_arguments,
|
|
args: ["--init", "bar"],
|
|
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::INIT);
|
|
assert_eq!(arguments, &["bar"]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: init_alias,
|
|
args: ["--initialize", "bar"],
|
|
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::INIT);
|
|
assert_eq!(arguments, &["bar"]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: summary_arguments,
|
|
args: ["--summary", "bar"],
|
|
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::SUMMARY);
|
|
assert_eq!(arguments, &["bar"]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: subcommand_overrides_and_arguments,
|
|
args: ["--summary", "bar=baz", "bar"],
|
|
error: ConfigError::SubcommandOverridesAndArguments { subcommand, arguments, overrides },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::SUMMARY);
|
|
assert_eq!(overrides, map!{"bar": "baz"});
|
|
assert_eq!(arguments, &["bar"]);
|
|
},
|
|
}
|
|
|
|
error! {
|
|
name: summary_overrides,
|
|
args: ["--summary", "bar=baz"],
|
|
error: ConfigError::SubcommandOverrides { subcommand, overrides },
|
|
check: {
|
|
assert_eq!(subcommand, cmd::SUMMARY);
|
|
assert_eq!(overrides, map!{"bar": "baz"});
|
|
},
|
|
}
|
|
}
|