From 1547af08b5348bc7531332404862d40f25feac3f Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 14 Jun 2024 16:11:22 -0700 Subject: [PATCH] Allow setting more command-line options with environment variables (#2161) --- src/config.rs | 325 ++++++++++++++++++++------------------ src/function.rs | 141 ++++++++--------- src/lib.rs | 9 +- src/platform.rs | 8 +- src/platform_interface.rs | 4 +- src/subcommand.rs | 26 ++- src/summary.rs | 2 +- src/thunk.rs | 14 +- 8 files changed, 267 insertions(+), 262 deletions(-) diff --git a/src/config.rs b/src/config.rs index 34b72f3..0e8f3d4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,10 +7,6 @@ use { }, }; -const CHOOSE_HELP: &str = "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`"; - #[derive(Debug, PartialEq)] pub(crate) struct Config { pub(crate) check: bool, @@ -154,17 +150,20 @@ impl Config { .trailing_var_arg(true) .styles( Styles::styled() - .header(AnsiColor::Yellow.on_default()) - .usage(AnsiColor::Yellow.on_default()) - .literal(AnsiColor::Green.on_default()) - .placeholder(AnsiColor::Green.on_default()) + .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."), + .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) @@ -173,6 +172,13 @@ impl Config { .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") @@ -190,7 +196,21 @@ impl Config { .value_parser(PossibleValuesParser::new(arg::COMMAND_COLOR_VALUES)) .help("Echo recipe lines in "), ) - .arg(Arg::new(arg::YES).long("yes").action(ArgAction::SetTrue).help("Automatically confirm all recipes.")) + .arg( + Arg::new(arg::DOTENV_FILENAME) + .long("dotenv-filename") + .action(ArgAction::Set) + .help("Search for environment file named 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 as environment file instead of searching for one"), + ) .arg( Arg::new(arg::DRY_RUN) .short('n') @@ -203,66 +223,30 @@ impl Config { .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 "), ) + .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::LIST_HEADING) - .long("list-heading") - .help("Print before list") - .value_name("TEXT") - .action(ArgAction::Set), - ) - .arg( - Arg::new(arg::LIST_PREFIX) - .long("list-prefix") - .help("Print before each list item") - .value_name("TEXT") - .action(ArgAction::Set), - ) - .arg( - Arg::new(arg::LIST_SUBMODULES) - .long("list-submodules") - .help("List recipes in submodules") - .action(ArgAction::SetTrue) - .env("JUST_LIST_SUBMODULES"), - ) - .arg( - Arg::new(arg::NO_ALIASES) - .long("no-aliases") - .action(ArgAction::SetTrue) - .help("Don't show aliases in list"), - ) - .arg ( - Arg::new(arg::NO_DEPS) - .long("no-deps") - .alias("no-dependencies") - .action(ArgAction::SetTrue) - .help("Don't run recipe dependencies") - ) - .arg( - Arg::new(arg::NO_DOTENV) - .long("no-dotenv") - .action(ArgAction::SetTrue) - .help("Don't load `.env` file"), - ) - .arg( - Arg::new(arg::NO_HIGHLIGHT) - .long("no-highlight") - .action(ArgAction::SetTrue) - .help("Don't highlight echoed recipe lines in bold") - .overrides_with(arg::HIGHLIGHT), - ) .arg( Arg::new(arg::JUSTFILE) .short('f') @@ -272,6 +256,60 @@ impl Config { .value_parser(value_parser!(PathBuf)) .help("Use as justfile"), ) + .arg( + Arg::new(arg::LIST_HEADING) + .long("list-heading") + .env("JUST_LIST_HEADING") + .help("Print before list") + .value_name("TEXT") + .action(ArgAction::Set), + ) + .arg( + Arg::new(arg::LIST_PREFIX) + .long("list-prefix") + .env("JUST_LIST_PREFIX") + .help("Print 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') @@ -311,15 +349,24 @@ impl Config { .help("Invoke with the shell used to run recipe lines and backticks"), ) .arg( - Arg::new(arg::CLEAR_SHELL_ARGS) - .long("clear-shell-args") + Arg::new(arg::TIMESTAMP) .action(ArgAction::SetTrue) - .overrides_with(arg::SHELL_ARG) - .help("Clear shell arguments"), + .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"), @@ -350,13 +397,28 @@ impl Config { .help("Use 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(CHOOSE_HELP)) + .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") @@ -395,6 +457,7 @@ impl Config { .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 \ @@ -408,6 +471,12 @@ impl Config { .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") @@ -425,12 +494,6 @@ impl Config { .conflicts_with(arg::ARGUMENTS) .help("List available recipes"), ) - .arg( - Arg::new(cmd::GROUPS) - .long("groups") - .action(ArgAction::SetTrue) - .help("List recipe groups") - ) .arg( Arg::new(cmd::MAN) .long("man") @@ -459,21 +522,6 @@ impl Config { .action(ArgAction::SetTrue) .help("List names of variables"), ) - .arg( - Arg::new(arg::DOTENV_FILENAME) - .long("dotenv-filename") - .action(ArgAction::Set) - .help("Search for environment file named 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 as environment file instead of searching for one") - ) .group(ArgGroup::new("SUBCOMMAND").args(cmd::ALL)) .arg( Arg::new(arg::ARGUMENTS) @@ -481,30 +529,6 @@ impl Config { .action(ArgAction::Append) .help("Overrides and recipe(s) to run, defaulting to the first recipe in the justfile"), ) - .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::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") - ) } fn color_from_matches(matches: &ArgMatches) -> ConfigResult { @@ -611,17 +635,6 @@ impl Config { } pub(crate) fn from_matches(matches: &ArgMatches) -> ConfigResult { - let invocation_directory = env::current_dir().context(config_error::CurrentDirContext)?; - - let verbosity = if matches.get_flag(arg::QUIET) { - Verbosity::Quiet - } else { - Verbosity::from_flag_occurrences(matches.get_count(arg::VERBOSE)) - }; - - let color = Self::color_from_matches(matches)?; - let command_color = Self::command_color_from_matches(matches)?; - let mut overrides = BTreeMap::new(); if let Some(mut values) = matches.get_many::(arg::SET) { while let (Some(k), Some(v)) = (values.next(), values.next()) { @@ -684,28 +697,10 @@ impl Config { } } else if let Some(&shell) = matches.get_one::(cmd::COMPLETIONS) { Subcommand::Completions { shell } - } else if matches.get_flag(cmd::EDIT) { - Subcommand::Edit - } else if matches.get_flag(cmd::SUMMARY) { - Subcommand::Summary } else if matches.get_flag(cmd::DUMP) { Subcommand::Dump - } else if matches.get_flag(cmd::FORMAT) { - Subcommand::Format - } else if matches.get_flag(cmd::INIT) { - Subcommand::Init - } else if let Some(path) = matches.get_many::(cmd::LIST) { - Subcommand::List { - path: Self::parse_module_path(path)?, - } - } else if matches.get_flag(cmd::GROUPS) { - Subcommand::Groups - } else if matches.get_flag(cmd::MAN) { - Subcommand::Man - } else if let Some(path) = matches.get_many::(cmd::SHOW) { - Subcommand::Show { - path: Self::parse_module_path(path)?, - } + } 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 { @@ -722,6 +717,24 @@ impl Config { 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::(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::(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 { @@ -731,20 +744,10 @@ impl Config { } }; - let shell_args = if matches.get_flag(arg::CLEAR_SHELL_ARGS) { - Some(Vec::new()) - } else { - matches - .get_many::(arg::SHELL_ARG) - .map(|s| s.map(Into::into).collect()) - }; - - let unstable = matches.get_flag(arg::UNSTABLE); - Ok(Self { check: matches.get_flag(arg::CHECK), - color, - command_color, + color: Self::color_from_matches(matches)?, + command_color: Self::command_color_from_matches(matches)?, dotenv_filename: matches .get_one::(arg::DOTENV_FILENAME) .map(Into::into), @@ -752,7 +755,7 @@ impl Config { 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, + invocation_directory: env::current_dir().context(config_error::CurrentDirContext)?, list_heading: matches .get_one::(arg::LIST_HEADING) .map_or_else(|| "Available recipes:\n".into(), Into::into), @@ -765,7 +768,13 @@ impl Config { no_dependencies: matches.get_flag(arg::NO_DEPS), search_config, shell: matches.get_one::(arg::SHELL).map(Into::into), - shell_args, + shell_args: if matches.get_flag(arg::CLEAR_SHELL_ARGS) { + Some(Vec::new()) + } else { + matches + .get_many::(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), @@ -774,13 +783,17 @@ impl Config { .unwrap() .into(), unsorted: matches.get_flag(arg::UNSORTED), - unstable, - verbosity, + 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) -> Result<(), Error<'static>> { + pub(crate) fn require_unstable(&self, message: &str) -> RunResult<'static> { if self.unstable { Ok(()) } else { @@ -790,7 +803,7 @@ impl Config { } } - pub(crate) fn run(self, loader: &Loader) -> Result<(), Error> { + pub(crate) fn run(self, loader: &Loader) -> RunResult { if let Err(error) = InterruptHandler::install(self.verbosity) { warn!("Failed to set CTRL-C handler: {error}"); } diff --git a/src/function.rs b/src/function.rs index 206faf8..9226762 100644 --- a/src/function.rs +++ b/src/function.rs @@ -11,13 +11,13 @@ use { }; pub(crate) enum Function { - Nullary(fn(Context) -> Result), - Unary(fn(Context, &str) -> Result), - UnaryOpt(fn(Context, &str, Option<&str>) -> Result), - UnaryPlus(fn(Context, &str, &[String]) -> Result), - Binary(fn(Context, &str, &str) -> Result), - BinaryPlus(fn(Context, &str, &str, &[String]) -> Result), - Ternary(fn(Context, &str, &str, &str) -> Result), + Nullary(fn(Context) -> FunctionResult), + Unary(fn(Context, &str) -> FunctionResult), + UnaryOpt(fn(Context, &str, Option<&str>) -> FunctionResult), + UnaryPlus(fn(Context, &str, &[String]) -> FunctionResult), + Binary(fn(Context, &str, &str) -> FunctionResult), + BinaryPlus(fn(Context, &str, &str, &[String]) -> FunctionResult), + Ternary(fn(Context, &str, &str, &str) -> FunctionResult), } pub(crate) struct Context<'src: 'run, 'run> { @@ -119,7 +119,7 @@ impl Function { } } -fn absolute_path(context: Context, path: &str) -> Result { +fn absolute_path(context: Context, path: &str) -> FunctionResult { let abs_path_unchecked = context .evaluator .context @@ -136,7 +136,7 @@ fn absolute_path(context: Context, path: &str) -> Result { } } -fn append(_context: Context, suffix: &str, s: &str) -> Result { +fn append(_context: Context, suffix: &str, s: &str) -> FunctionResult { Ok( s.split_whitespace() .map(|s| format!("{s}{suffix}")) @@ -145,15 +145,15 @@ fn append(_context: Context, suffix: &str, s: &str) -> Result { ) } -fn arch(_context: Context) -> Result { +fn arch(_context: Context) -> FunctionResult { Ok(target::arch().to_owned()) } -fn blake3(_context: Context, s: &str) -> Result { +fn blake3(_context: Context, s: &str) -> FunctionResult { Ok(blake3::hash(s.as_bytes()).to_string()) } -fn blake3_file(context: Context, path: &str) -> Result { +fn blake3_file(context: Context, path: &str) -> FunctionResult { let path = context .evaluator .context @@ -167,7 +167,7 @@ fn blake3_file(context: Context, path: &str) -> Result { Ok(hasher.finalize().to_string()) } -fn canonicalize(_context: Context, path: &str) -> Result { +fn canonicalize(_context: Context, path: &str) -> FunctionResult { let canonical = std::fs::canonicalize(path).map_err(|err| format!("I/O error canonicalizing path: {err}"))?; @@ -179,7 +179,7 @@ fn canonicalize(_context: Context, path: &str) -> Result { }) } -fn capitalize(_context: Context, s: &str) -> Result { +fn capitalize(_context: Context, s: &str) -> FunctionResult { let mut capitalized = String::new(); for (i, c) in s.chars().enumerate() { if i == 0 { @@ -191,7 +191,7 @@ fn capitalize(_context: Context, s: &str) -> Result { Ok(capitalized) } -fn choose(_context: Context, n: &str, alphabet: &str) -> Result { +fn choose(_context: Context, n: &str, alphabet: &str) -> FunctionResult { if alphabet.is_empty() { return Err("empty alphabet".into()); } @@ -215,11 +215,11 @@ fn choose(_context: Context, n: &str, alphabet: &str) -> Result Ok((0..n).map(|_| alphabet.choose(&mut rng).unwrap()).collect()) } -fn clean(_context: Context, path: &str) -> Result { +fn clean(_context: Context, path: &str) -> FunctionResult { Ok(Path::new(path).lexiclean().to_str().unwrap().to_owned()) } -fn dir(name: &'static str, f: fn() -> Option) -> Result { +fn dir(name: &'static str, f: fn() -> Option) -> FunctionResult { match f() { Some(path) => path .as_os_str() @@ -235,7 +235,7 @@ fn dir(name: &'static str, f: fn() -> Option) -> Result } } -fn encode_uri_component(_context: Context, s: &str) -> Result { +fn encode_uri_component(_context: Context, s: &str) -> FunctionResult { static PERCENT_ENCODE: percent_encoding::AsciiSet = percent_encoding::NON_ALPHANUMERIC .remove(b'-') .remove(b'_') @@ -249,7 +249,7 @@ fn encode_uri_component(_context: Context, s: &str) -> Result { Ok(percent_encoding::utf8_percent_encode(s, &PERCENT_ENCODE).to_string()) } -fn env_var(context: Context, key: &str) -> Result { +fn env_var(context: Context, key: &str) -> FunctionResult { use std::env::VarError::*; if let Some(value) = context.evaluator.context.dotenv.get(key) { @@ -265,7 +265,7 @@ fn env_var(context: Context, key: &str) -> Result { } } -fn env_var_or_default(context: Context, key: &str, default: &str) -> Result { +fn env_var_or_default(context: Context, key: &str, default: &str) -> FunctionResult { use std::env::VarError::*; if let Some(value) = context.evaluator.context.dotenv.get(key) { @@ -281,39 +281,39 @@ fn env_var_or_default(context: Context, key: &str, default: &str) -> Result) -> Result { +fn env(context: Context, key: &str, default: Option<&str>) -> FunctionResult { match default { Some(val) => env_var_or_default(context, key, val), None => env_var(context, key), } } -fn error(_context: Context, message: &str) -> Result { +fn error(_context: Context, message: &str) -> FunctionResult { Err(message.to_owned()) } -fn extension(_context: Context, path: &str) -> Result { +fn extension(_context: Context, path: &str) -> FunctionResult { Utf8Path::new(path) .extension() .map(str::to_owned) .ok_or_else(|| format!("Could not extract extension from `{path}`")) } -fn file_name(_context: Context, path: &str) -> Result { +fn file_name(_context: Context, path: &str) -> FunctionResult { Utf8Path::new(path) .file_name() .map(str::to_owned) .ok_or_else(|| format!("Could not extract file name from `{path}`")) } -fn file_stem(_context: Context, path: &str) -> Result { +fn file_stem(_context: Context, path: &str) -> FunctionResult { Utf8Path::new(path) .file_stem() .map(str::to_owned) .ok_or_else(|| format!("Could not extract file stem from `{path}`")) } -fn invocation_directory(context: Context) -> Result { +fn invocation_directory(context: Context) -> FunctionResult { Platform::convert_native_path( &context.evaluator.context.search.working_directory, &context.evaluator.context.config.invocation_directory, @@ -321,7 +321,7 @@ fn invocation_directory(context: Context) -> Result { .map_err(|e| format!("Error getting shell path: {e}")) } -fn invocation_directory_native(context: Context) -> Result { +fn invocation_directory_native(context: Context) -> FunctionResult { context .evaluator .context @@ -342,11 +342,11 @@ fn invocation_directory_native(context: Context) -> Result { }) } -fn is_dependency(context: Context) -> Result { +fn is_dependency(context: Context) -> FunctionResult { Ok(context.evaluator.is_dependency.to_string()) } -fn prepend(_context: Context, prefix: &str, s: &str) -> Result { +fn prepend(_context: Context, prefix: &str, s: &str) -> FunctionResult { Ok( s.split_whitespace() .map(|s| format!("{prefix}{s}")) @@ -355,7 +355,7 @@ fn prepend(_context: Context, prefix: &str, s: &str) -> Result { ) } -fn join(_context: Context, base: &str, with: &str, and: &[String]) -> Result { +fn join(_context: Context, base: &str, with: &str, and: &[String]) -> FunctionResult { let mut result = Utf8Path::new(base).join(with); for arg in and { result.push(arg); @@ -363,7 +363,7 @@ fn join(_context: Context, base: &str, with: &str, and: &[String]) -> Result Result { +fn just_executable(_context: Context) -> FunctionResult { let exe_path = env::current_exe().map_err(|e| format!("Error getting current executable: {e}"))?; @@ -375,11 +375,11 @@ fn just_executable(_context: Context) -> Result { }) } -fn just_pid(_context: Context) -> Result { +fn just_pid(_context: Context) -> FunctionResult { Ok(std::process::id().to_string()) } -fn justfile(context: Context) -> Result { +fn justfile(context: Context) -> FunctionResult { context .evaluator .context @@ -395,7 +395,7 @@ fn justfile(context: Context) -> Result { }) } -fn justfile_directory(context: Context) -> Result { +fn justfile_directory(context: Context) -> FunctionResult { let justfile_directory = context .evaluator .context @@ -420,19 +420,19 @@ fn justfile_directory(context: Context) -> Result { }) } -fn kebabcase(_context: Context, s: &str) -> Result { +fn kebabcase(_context: Context, s: &str) -> FunctionResult { Ok(s.to_kebab_case()) } -fn lowercamelcase(_context: Context, s: &str) -> Result { +fn lowercamelcase(_context: Context, s: &str) -> FunctionResult { Ok(s.to_lower_camel_case()) } -fn lowercase(_context: Context, s: &str) -> Result { +fn lowercase(_context: Context, s: &str) -> FunctionResult { Ok(s.to_lowercase()) } -fn module_directory(context: Context) -> Result { +fn module_directory(context: Context) -> FunctionResult { context .evaluator .context @@ -459,7 +459,7 @@ fn module_directory(context: Context) -> Result { }) } -fn module_file(context: Context) -> Result { +fn module_file(context: Context) -> FunctionResult { context .evaluator .context @@ -478,27 +478,27 @@ fn module_file(context: Context) -> Result { }) } -fn num_cpus(_context: Context) -> Result { +fn num_cpus(_context: Context) -> FunctionResult { let num = num_cpus::get(); Ok(num.to_string()) } -fn os(_context: Context) -> Result { +fn os(_context: Context) -> FunctionResult { Ok(target::os().to_owned()) } -fn os_family(_context: Context) -> Result { +fn os_family(_context: Context) -> FunctionResult { Ok(target::family().to_owned()) } -fn parent_directory(_context: Context, path: &str) -> Result { +fn parent_directory(_context: Context, path: &str) -> FunctionResult { Utf8Path::new(path) .parent() .map(Utf8Path::to_string) .ok_or_else(|| format!("Could not extract parent directory from `{path}`")) } -fn path_exists(context: Context, path: &str) -> Result { +fn path_exists(context: Context, path: &str) -> FunctionResult { Ok( context .evaluator @@ -511,20 +511,15 @@ fn path_exists(context: Context, path: &str) -> Result { ) } -fn quote(_context: Context, s: &str) -> Result { +fn quote(_context: Context, s: &str) -> FunctionResult { Ok(format!("'{}'", s.replace('\'', "'\\''"))) } -fn replace(_context: Context, s: &str, from: &str, to: &str) -> Result { +fn replace(_context: Context, s: &str, from: &str, to: &str) -> FunctionResult { Ok(s.replace(from, to)) } -fn replace_regex( - _context: Context, - s: &str, - regex: &str, - replacement: &str, -) -> Result { +fn replace_regex(_context: Context, s: &str, regex: &str, replacement: &str) -> FunctionResult { Ok( Regex::new(regex) .map_err(|err| err.to_string())? @@ -533,7 +528,7 @@ fn replace_regex( ) } -fn sha256(_context: Context, s: &str) -> Result { +fn sha256(_context: Context, s: &str) -> FunctionResult { use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); hasher.update(s); @@ -541,7 +536,7 @@ fn sha256(_context: Context, s: &str) -> Result { Ok(format!("{hash:x}")) } -fn sha256_file(context: Context, path: &str) -> Result { +fn sha256_file(context: Context, path: &str) -> FunctionResult { use sha2::{Digest, Sha256}; let path = context .evaluator @@ -558,7 +553,7 @@ fn sha256_file(context: Context, path: &str) -> Result { Ok(format!("{hash:x}")) } -fn shell(context: Context, command: &str, args: &[String]) -> Result { +fn shell(context: Context, command: &str, args: &[String]) -> FunctionResult { let args = iter::once(command) .chain(args.iter().map(String::as_str)) .collect::>(); @@ -569,19 +564,19 @@ fn shell(context: Context, command: &str, args: &[String]) -> Result Result { +fn shoutykebabcase(_context: Context, s: &str) -> FunctionResult { Ok(s.to_shouty_kebab_case()) } -fn shoutysnakecase(_context: Context, s: &str) -> Result { +fn shoutysnakecase(_context: Context, s: &str) -> FunctionResult { Ok(s.to_shouty_snake_case()) } -fn snakecase(_context: Context, s: &str) -> Result { +fn snakecase(_context: Context, s: &str) -> FunctionResult { Ok(s.to_snake_case()) } -fn source_directory(context: Context) -> Result { +fn source_directory(context: Context) -> FunctionResult { context .evaluator .context @@ -602,7 +597,7 @@ fn source_directory(context: Context) -> Result { }) } -fn source_file(context: Context) -> Result { +fn source_file(context: Context) -> FunctionResult { context .evaluator .context @@ -621,51 +616,51 @@ fn source_file(context: Context) -> Result { }) } -fn titlecase(_context: Context, s: &str) -> Result { +fn titlecase(_context: Context, s: &str) -> FunctionResult { Ok(s.to_title_case()) } -fn trim(_context: Context, s: &str) -> Result { +fn trim(_context: Context, s: &str) -> FunctionResult { Ok(s.trim().to_owned()) } -fn trim_end(_context: Context, s: &str) -> Result { +fn trim_end(_context: Context, s: &str) -> FunctionResult { Ok(s.trim_end().to_owned()) } -fn trim_end_match(_context: Context, s: &str, pat: &str) -> Result { +fn trim_end_match(_context: Context, s: &str, pat: &str) -> FunctionResult { Ok(s.strip_suffix(pat).unwrap_or(s).to_owned()) } -fn trim_end_matches(_context: Context, s: &str, pat: &str) -> Result { +fn trim_end_matches(_context: Context, s: &str, pat: &str) -> FunctionResult { Ok(s.trim_end_matches(pat).to_owned()) } -fn trim_start(_context: Context, s: &str) -> Result { +fn trim_start(_context: Context, s: &str) -> FunctionResult { Ok(s.trim_start().to_owned()) } -fn trim_start_match(_context: Context, s: &str, pat: &str) -> Result { +fn trim_start_match(_context: Context, s: &str, pat: &str) -> FunctionResult { Ok(s.strip_prefix(pat).unwrap_or(s).to_owned()) } -fn trim_start_matches(_context: Context, s: &str, pat: &str) -> Result { +fn trim_start_matches(_context: Context, s: &str, pat: &str) -> FunctionResult { Ok(s.trim_start_matches(pat).to_owned()) } -fn uppercamelcase(_context: Context, s: &str) -> Result { +fn uppercamelcase(_context: Context, s: &str) -> FunctionResult { Ok(s.to_upper_camel_case()) } -fn uppercase(_context: Context, s: &str) -> Result { +fn uppercase(_context: Context, s: &str) -> FunctionResult { Ok(s.to_uppercase()) } -fn uuid(_context: Context) -> Result { +fn uuid(_context: Context) -> FunctionResult { Ok(uuid::Uuid::new_v4().to_string()) } -fn without_extension(_context: Context, path: &str) -> Result { +fn without_extension(_context: Context, path: &str) -> FunctionResult { let parent = Utf8Path::new(path) .parent() .ok_or_else(|| format!("Could not extract parent from `{path}`"))?; @@ -679,7 +674,7 @@ fn without_extension(_context: Context, path: &str) -> Result { /// Check whether a string processes properly as semver (e.x. "0.1.0") /// and matches a given semver requirement (e.x. ">=0.1.0") -fn semver_matches(_context: Context, version: &str, requirement: &str) -> Result { +fn semver_matches(_context: Context, version: &str, requirement: &str) -> FunctionResult { Ok( requirement .parse::() diff --git a/src/lib.rs b/src/lib.rs index 5275cec..b5b17f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,10 +86,11 @@ pub use crate::run::run; #[doc(hidden)] pub use unindent::unindent; -pub(crate) type CompileResult<'a, T = ()> = Result>; -pub(crate) type ConfigResult = Result; -pub(crate) type RunResult<'a, T = ()> = Result>; -pub(crate) type SearchResult = Result; +type CompileResult<'a, T = ()> = Result>; +type ConfigResult = Result; +type FunctionResult = Result; +type RunResult<'a, T = ()> = Result>; +type SearchResult = Result; #[cfg(test)] #[macro_use] diff --git a/src/platform.rs b/src/platform.rs index 36be7fc..01bf74b 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -19,7 +19,7 @@ impl PlatformInterface for Platform { Ok(cmd) } - fn set_execute_permission(path: &Path) -> Result<(), io::Error> { + fn set_execute_permission(path: &Path) -> io::Result<()> { use std::os::unix::fs::PermissionsExt; // get current permissions @@ -38,7 +38,7 @@ impl PlatformInterface for Platform { exit_status.signal() } - fn convert_native_path(_working_directory: &Path, path: &Path) -> Result { + fn convert_native_path(_working_directory: &Path, path: &Path) -> FunctionResult { path .to_str() .map(str::to_string) @@ -85,7 +85,7 @@ impl PlatformInterface for Platform { Ok(cmd) } - fn set_execute_permission(_path: &Path) -> Result<(), io::Error> { + fn set_execute_permission(_path: &Path) -> io::Result<()> { // it is not necessary to set an execute permission on a script on windows, so // this is a nop Ok(()) @@ -97,7 +97,7 @@ impl PlatformInterface for Platform { None } - fn convert_native_path(working_directory: &Path, path: &Path) -> Result { + fn convert_native_path(working_directory: &Path, path: &Path) -> FunctionResult { // Translate path from windows style to unix style let mut cygpath = Command::new("cygpath"); cygpath.current_dir(working_directory); diff --git a/src/platform_interface.rs b/src/platform_interface.rs index 4a984ff..463f665 100644 --- a/src/platform_interface.rs +++ b/src/platform_interface.rs @@ -10,12 +10,12 @@ pub(crate) trait PlatformInterface { ) -> Result; /// Set the execute permission on the file pointed to by `path` - fn set_execute_permission(path: &Path) -> Result<(), io::Error>; + fn set_execute_permission(path: &Path) -> io::Result<()>; /// Extract the signal from a process exit status, if it was terminated by a /// signal fn signal_from_exit_status(exit_status: ExitStatus) -> Option; /// Translate a path from a "native" path to a path the interpreter expects - fn convert_native_path(working_directory: &Path, path: &Path) -> Result; + fn convert_native_path(working_directory: &Path, path: &Path) -> FunctionResult; } diff --git a/src/subcommand.rs b/src/subcommand.rs index a595249..9b9a246 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -42,11 +42,7 @@ pub(crate) enum Subcommand { } impl Subcommand { - pub(crate) fn execute<'src>( - &self, - config: &Config, - loader: &'src Loader, - ) -> Result<(), Error<'src>> { + pub(crate) fn execute<'src>(&self, config: &Config, loader: &'src Loader) -> RunResult<'src> { use Subcommand::*; match self { @@ -107,7 +103,7 @@ impl Subcommand { loader: &'src Loader, arguments: &[String], overrides: &BTreeMap, - ) -> Result<(), Error<'src>> { + ) -> RunResult<'src> { if matches!( config.search_config, SearchConfig::FromInvocationDirectory | SearchConfig::FromSearchDirectory { .. } @@ -192,7 +188,7 @@ impl Subcommand { config: &Config, loader: &'src Loader, search: &Search, - ) -> Result, Error<'src>> { + ) -> RunResult<'src, Compilation<'src>> { let compilation = Compiler::compile(config.unstable, loader, &search.justfile)?; if config.verbosity.loud() { @@ -214,7 +210,7 @@ impl Subcommand { search: &Search, overrides: &BTreeMap, chooser: Option<&str>, - ) -> Result<(), Error<'src>> { + ) -> RunResult<'src> { let mut recipes = Vec::<&Recipe>::new(); let mut stack = vec![justfile]; while let Some(module) = stack.pop() { @@ -304,7 +300,7 @@ impl Subcommand { Ok(()) } - fn dump(config: &Config, ast: &Ast, justfile: &Justfile) -> Result<(), Error<'static>> { + fn dump(config: &Config, ast: &Ast, justfile: &Justfile) -> RunResult<'static> { match config.dump_format { DumpFormat::Json => { serde_json::to_writer(io::stdout(), justfile) @@ -316,7 +312,7 @@ impl Subcommand { Ok(()) } - fn edit(search: &Search) -> Result<(), Error<'static>> { + fn edit(search: &Search) -> RunResult<'static> { let editor = env::var_os("VISUAL") .or_else(|| env::var_os("EDITOR")) .unwrap_or_else(|| "vim".into()); @@ -338,7 +334,7 @@ impl Subcommand { Ok(()) } - fn format(config: &Config, search: &Search, src: &str, ast: &Ast) -> Result<(), Error<'static>> { + fn format(config: &Config, search: &Search, src: &str, ast: &Ast) -> RunResult<'static> { config.require_unstable("The `--fmt` command is currently unstable.")?; let formatted = ast.to_string(); @@ -383,7 +379,7 @@ impl Subcommand { Ok(()) } - fn init(config: &Config) -> Result<(), Error<'static>> { + fn init(config: &Config) -> RunResult<'static> { let search = Search::init(&config.search_config, &config.invocation_directory)?; if search.justfile.is_file() { @@ -403,7 +399,7 @@ impl Subcommand { } } - fn man() -> Result<(), Error<'static>> { + fn man() -> RunResult<'static> { let mut buffer = Vec::::new(); Man::new(Config::app()) @@ -423,7 +419,7 @@ impl Subcommand { Ok(()) } - fn list(config: &Config, mut module: &Justfile, path: &ModulePath) -> Result<(), Error<'static>> { + fn list(config: &Config, mut module: &Justfile, path: &ModulePath) -> RunResult<'static> { for name in &path.path { module = module .modules @@ -585,7 +581,7 @@ impl Subcommand { config: &Config, mut module: &Justfile<'src>, path: &ModulePath, - ) -> Result<(), Error<'src>> { + ) -> RunResult<'src> { for name in &path.path[0..path.path.len() - 1] { module = module .modules diff --git a/src/summary.rs b/src/summary.rs index 472feb0..65a28ba 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -25,7 +25,7 @@ mod full { }; } -pub fn summary(path: &Path) -> Result, io::Error> { +pub fn summary(path: &Path) -> io::Result> { let loader = Loader::new(); match Compiler::compile(false, &loader, path) { diff --git a/src/thunk.rs b/src/thunk.rs index 2ab203a..8266899 100644 --- a/src/thunk.rs +++ b/src/thunk.rs @@ -6,42 +6,42 @@ pub(crate) enum Thunk<'src> { Nullary { name: Name<'src>, #[derivative(Debug = "ignore", PartialEq = "ignore")] - function: fn(function::Context) -> Result, + function: fn(function::Context) -> FunctionResult, }, Unary { name: Name<'src>, #[derivative(Debug = "ignore", PartialEq = "ignore")] - function: fn(function::Context, &str) -> Result, + function: fn(function::Context, &str) -> FunctionResult, arg: Box>, }, UnaryOpt { name: Name<'src>, #[derivative(Debug = "ignore", PartialEq = "ignore")] - function: fn(function::Context, &str, Option<&str>) -> Result, + function: fn(function::Context, &str, Option<&str>) -> FunctionResult, args: (Box>, Box>>), }, UnaryPlus { name: Name<'src>, #[derivative(Debug = "ignore", PartialEq = "ignore")] - function: fn(function::Context, &str, &[String]) -> Result, + function: fn(function::Context, &str, &[String]) -> FunctionResult, args: (Box>, Vec>), }, Binary { name: Name<'src>, #[derivative(Debug = "ignore", PartialEq = "ignore")] - function: fn(function::Context, &str, &str) -> Result, + function: fn(function::Context, &str, &str) -> FunctionResult, args: [Box>; 2], }, BinaryPlus { name: Name<'src>, #[derivative(Debug = "ignore", PartialEq = "ignore")] - function: fn(function::Context, &str, &str, &[String]) -> Result, + function: fn(function::Context, &str, &str, &[String]) -> FunctionResult, args: ([Box>; 2], Vec>), }, Ternary { name: Name<'src>, #[derivative(Debug = "ignore", PartialEq = "ignore")] - function: fn(function::Context, &str, &str, &str) -> Result, + function: fn(function::Context, &str, &str, &str) -> FunctionResult, args: [Box>; 3], }, }