Allow setting more command-line options with environment variables (#2161)

This commit is contained in:
Casey Rodarmor 2024-06-14 16:11:22 -07:00 committed by GitHub
parent b05a75d168
commit 1547af08b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 267 additions and 262 deletions

View File

@ -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 <COMMAND-COLOR>"),
)
.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 <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')
@ -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 <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::LIST_HEADING)
.long("list-heading")
.help("Print <TEXT> before list")
.value_name("TEXT")
.action(ArgAction::Set),
)
.arg(
Arg::new(arg::LIST_PREFIX)
.long("list-prefix")
.help("Print <TEXT> 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 <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')
@ -311,15 +349,24 @@ impl Config {
.help("Invoke <COMMAND> 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 <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(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 <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")
)
.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<Color> {
@ -611,17 +635,6 @@ impl Config {
}
pub(crate) fn from_matches(matches: &ArgMatches) -> ConfigResult<Self> {
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::<String>(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::<completions::Shell>(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::<String>(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::<String>(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::<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 {
@ -731,20 +744,10 @@ impl Config {
}
};
let 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())
};
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::<String>(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::<String>(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::<String>(arg::SHELL).map(Into::into),
shell_args,
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),
@ -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}");
}

View File

@ -11,13 +11,13 @@ use {
};
pub(crate) enum Function {
Nullary(fn(Context) -> Result<String, String>),
Unary(fn(Context, &str) -> Result<String, String>),
UnaryOpt(fn(Context, &str, Option<&str>) -> Result<String, String>),
UnaryPlus(fn(Context, &str, &[String]) -> Result<String, String>),
Binary(fn(Context, &str, &str) -> Result<String, String>),
BinaryPlus(fn(Context, &str, &str, &[String]) -> Result<String, String>),
Ternary(fn(Context, &str, &str, &str) -> Result<String, String>),
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<String, String> {
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<String, String> {
}
}
fn append(_context: Context, suffix: &str, s: &str) -> Result<String, String> {
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<String, String> {
)
}
fn arch(_context: Context) -> Result<String, String> {
fn arch(_context: Context) -> FunctionResult {
Ok(target::arch().to_owned())
}
fn blake3(_context: Context, s: &str) -> Result<String, String> {
fn blake3(_context: Context, s: &str) -> FunctionResult {
Ok(blake3::hash(s.as_bytes()).to_string())
}
fn blake3_file(context: Context, path: &str) -> Result<String, String> {
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<String, String> {
Ok(hasher.finalize().to_string())
}
fn canonicalize(_context: Context, path: &str) -> Result<String, String> {
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<String, String> {
})
}
fn capitalize(_context: Context, s: &str) -> Result<String, String> {
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<String, String> {
Ok(capitalized)
}
fn choose(_context: Context, n: &str, alphabet: &str) -> Result<String, String> {
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<String, String>
Ok((0..n).map(|_| alphabet.choose(&mut rng).unwrap()).collect())
}
fn clean(_context: Context, path: &str) -> Result<String, String> {
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<PathBuf>) -> Result<String, String> {
fn dir(name: &'static str, f: fn() -> Option<PathBuf>) -> FunctionResult {
match f() {
Some(path) => path
.as_os_str()
@ -235,7 +235,7 @@ fn dir(name: &'static str, f: fn() -> Option<PathBuf>) -> Result<String, String>
}
}
fn encode_uri_component(_context: Context, s: &str) -> Result<String, String> {
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<String, String> {
Ok(percent_encoding::utf8_percent_encode(s, &PERCENT_ENCODE).to_string())
}
fn env_var(context: Context, key: &str) -> Result<String, String> {
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<String, String> {
}
}
fn env_var_or_default(context: Context, key: &str, default: &str) -> Result<String, String> {
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<Stri
}
}
fn env(context: Context, key: &str, default: Option<&str>) -> Result<String, String> {
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<String, String> {
fn error(_context: Context, message: &str) -> FunctionResult {
Err(message.to_owned())
}
fn extension(_context: Context, path: &str) -> Result<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
.map_err(|e| format!("Error getting shell path: {e}"))
}
fn invocation_directory_native(context: Context) -> Result<String, String> {
fn invocation_directory_native(context: Context) -> FunctionResult {
context
.evaluator
.context
@ -342,11 +342,11 @@ fn invocation_directory_native(context: Context) -> Result<String, String> {
})
}
fn is_dependency(context: Context) -> Result<String, String> {
fn is_dependency(context: Context) -> FunctionResult {
Ok(context.evaluator.is_dependency.to_string())
}
fn prepend(_context: Context, prefix: &str, s: &str) -> Result<String, String> {
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<String, String> {
)
}
fn join(_context: Context, base: &str, with: &str, and: &[String]) -> Result<String, String> {
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<Str
Ok(result.to_string())
}
fn just_executable(_context: Context) -> Result<String, String> {
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<String, String> {
})
}
fn just_pid(_context: Context) -> Result<String, String> {
fn just_pid(_context: Context) -> FunctionResult {
Ok(std::process::id().to_string())
}
fn justfile(context: Context) -> Result<String, String> {
fn justfile(context: Context) -> FunctionResult {
context
.evaluator
.context
@ -395,7 +395,7 @@ fn justfile(context: Context) -> Result<String, String> {
})
}
fn justfile_directory(context: Context) -> Result<String, String> {
fn justfile_directory(context: Context) -> FunctionResult {
let justfile_directory = context
.evaluator
.context
@ -420,19 +420,19 @@ fn justfile_directory(context: Context) -> Result<String, String> {
})
}
fn kebabcase(_context: Context, s: &str) -> Result<String, String> {
fn kebabcase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_kebab_case())
}
fn lowercamelcase(_context: Context, s: &str) -> Result<String, String> {
fn lowercamelcase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_lower_camel_case())
}
fn lowercase(_context: Context, s: &str) -> Result<String, String> {
fn lowercase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_lowercase())
}
fn module_directory(context: Context) -> Result<String, String> {
fn module_directory(context: Context) -> FunctionResult {
context
.evaluator
.context
@ -459,7 +459,7 @@ fn module_directory(context: Context) -> Result<String, String> {
})
}
fn module_file(context: Context) -> Result<String, String> {
fn module_file(context: Context) -> FunctionResult {
context
.evaluator
.context
@ -478,27 +478,27 @@ fn module_file(context: Context) -> Result<String, String> {
})
}
fn num_cpus(_context: Context) -> Result<String, String> {
fn num_cpus(_context: Context) -> FunctionResult {
let num = num_cpus::get();
Ok(num.to_string())
}
fn os(_context: Context) -> Result<String, String> {
fn os(_context: Context) -> FunctionResult {
Ok(target::os().to_owned())
}
fn os_family(_context: Context) -> Result<String, String> {
fn os_family(_context: Context) -> FunctionResult {
Ok(target::family().to_owned())
}
fn parent_directory(_context: Context, path: &str) -> Result<String, String> {
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<String, String> {
fn path_exists(context: Context, path: &str) -> FunctionResult {
Ok(
context
.evaluator
@ -511,20 +511,15 @@ fn path_exists(context: Context, path: &str) -> Result<String, String> {
)
}
fn quote(_context: Context, s: &str) -> Result<String, String> {
fn quote(_context: Context, s: &str) -> FunctionResult {
Ok(format!("'{}'", s.replace('\'', "'\\''")))
}
fn replace(_context: Context, s: &str, from: &str, to: &str) -> Result<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
Ok(format!("{hash:x}"))
}
fn sha256_file(context: Context, path: &str) -> Result<String, String> {
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<String, String> {
Ok(format!("{hash:x}"))
}
fn shell(context: Context, command: &str, args: &[String]) -> Result<String, String> {
fn shell(context: Context, command: &str, args: &[String]) -> FunctionResult {
let args = iter::once(command)
.chain(args.iter().map(String::as_str))
.collect::<Vec<&str>>();
@ -569,19 +564,19 @@ fn shell(context: Context, command: &str, args: &[String]) -> Result<String, Str
.map_err(|output_error| output_error.to_string())
}
fn shoutykebabcase(_context: Context, s: &str) -> Result<String, String> {
fn shoutykebabcase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_shouty_kebab_case())
}
fn shoutysnakecase(_context: Context, s: &str) -> Result<String, String> {
fn shoutysnakecase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_shouty_snake_case())
}
fn snakecase(_context: Context, s: &str) -> Result<String, String> {
fn snakecase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_snake_case())
}
fn source_directory(context: Context) -> Result<String, String> {
fn source_directory(context: Context) -> FunctionResult {
context
.evaluator
.context
@ -602,7 +597,7 @@ fn source_directory(context: Context) -> Result<String, String> {
})
}
fn source_file(context: Context) -> Result<String, String> {
fn source_file(context: Context) -> FunctionResult {
context
.evaluator
.context
@ -621,51 +616,51 @@ fn source_file(context: Context) -> Result<String, String> {
})
}
fn titlecase(_context: Context, s: &str) -> Result<String, String> {
fn titlecase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_title_case())
}
fn trim(_context: Context, s: &str) -> Result<String, String> {
fn trim(_context: Context, s: &str) -> FunctionResult {
Ok(s.trim().to_owned())
}
fn trim_end(_context: Context, s: &str) -> Result<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
fn uppercamelcase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_upper_camel_case())
}
fn uppercase(_context: Context, s: &str) -> Result<String, String> {
fn uppercase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_uppercase())
}
fn uuid(_context: Context) -> Result<String, String> {
fn uuid(_context: Context) -> FunctionResult {
Ok(uuid::Uuid::new_v4().to_string())
}
fn without_extension(_context: Context, path: &str) -> Result<String, String> {
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<String, String> {
/// 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<String, String> {
fn semver_matches(_context: Context, version: &str, requirement: &str) -> FunctionResult {
Ok(
requirement
.parse::<VersionReq>()

View File

@ -86,10 +86,11 @@ pub use crate::run::run;
#[doc(hidden)]
pub use unindent::unindent;
pub(crate) type CompileResult<'a, T = ()> = Result<T, CompileError<'a>>;
pub(crate) type ConfigResult<T> = Result<T, ConfigError>;
pub(crate) type RunResult<'a, T = ()> = Result<T, Error<'a>>;
pub(crate) type SearchResult<T> = Result<T, SearchError>;
type CompileResult<'a, T = ()> = Result<T, CompileError<'a>>;
type ConfigResult<T> = Result<T, ConfigError>;
type FunctionResult = Result<String, String>;
type RunResult<'a, T = ()> = Result<T, Error<'a>>;
type SearchResult<T> = Result<T, SearchError>;
#[cfg(test)]
#[macro_use]

View File

@ -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<String, String> {
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<String, String> {
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);

View File

@ -10,12 +10,12 @@ pub(crate) trait PlatformInterface {
) -> Result<Command, OutputError>;
/// 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<i32>;
/// Translate a path from a "native" path to a path the interpreter expects
fn convert_native_path(working_directory: &Path, path: &Path) -> Result<String, String>;
fn convert_native_path(working_directory: &Path, path: &Path) -> FunctionResult;
}

View File

@ -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<String, String>,
) -> 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<Compilation<'src>, 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<String, String>,
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::<u8>::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

View File

@ -25,7 +25,7 @@ mod full {
};
}
pub fn summary(path: &Path) -> Result<Result<Summary, String>, io::Error> {
pub fn summary(path: &Path) -> io::Result<Result<Summary, String>> {
let loader = Loader::new();
match Compiler::compile(false, &loader, path) {

View File

@ -6,42 +6,42 @@ pub(crate) enum Thunk<'src> {
Nullary {
name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
function: fn(function::Context) -> Result<String, String>,
function: fn(function::Context) -> FunctionResult,
},
Unary {
name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
function: fn(function::Context, &str) -> Result<String, String>,
function: fn(function::Context, &str) -> FunctionResult,
arg: Box<Expression<'src>>,
},
UnaryOpt {
name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
function: fn(function::Context, &str, Option<&str>) -> Result<String, String>,
function: fn(function::Context, &str, Option<&str>) -> FunctionResult,
args: (Box<Expression<'src>>, Box<Option<Expression<'src>>>),
},
UnaryPlus {
name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
function: fn(function::Context, &str, &[String]) -> Result<String, String>,
function: fn(function::Context, &str, &[String]) -> FunctionResult,
args: (Box<Expression<'src>>, Vec<Expression<'src>>),
},
Binary {
name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
function: fn(function::Context, &str, &str) -> Result<String, String>,
function: fn(function::Context, &str, &str) -> FunctionResult,
args: [Box<Expression<'src>>; 2],
},
BinaryPlus {
name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
function: fn(function::Context, &str, &str, &[String]) -> Result<String, String>,
function: fn(function::Context, &str, &str, &[String]) -> FunctionResult,
args: ([Box<Expression<'src>>; 2], Vec<Expression<'src>>),
},
Ternary {
name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
function: fn(function::Context, &str, &str, &str) -> Result<String, String>,
function: fn(function::Context, &str, &str, &str) -> FunctionResult,
args: [Box<Expression<'src>>; 3],
},
}