use clap::{Parser, builder::styling};
use std::io::IsTerminal;
use std::path::PathBuf;
use crate::{events, productinfo};
const SHORT_DESCRIPTION: &str = "Bo[u]rn[e] RUsty SHell 🦀 (https://brush.sh)";
const LONG_DESCRIPTION: &str = r"brush is a bash-compatible, Rust-implemented, POSIX-style shell.
brush is distributed under the terms of the MIT license. If you encounter any issues or discrepancies in behavior from bash, please report them at https://github.com/reubeno/brush.
For more information, visit https://brush.sh.";
const USAGE: &str = color_print::cstr!(
"<bold>brush</bold> <italics>[OPTIONS]</italics>... <italics>[SCRIPT_PATH [SCRIPT_ARGS]...]</italics>"
);
const VERSION: &str = const_format::concatcp!(
productinfo::PRODUCT_VERSION,
" (",
productinfo::PRODUCT_GIT_VERSION,
")"
);
const HEADING_STANDARD_OPTIONS: &str = "Standard shell options";
const HEADING_CONFIG_OPTIONS: &str = "Configuration options";
const HEADING_UI_OPTIONS: &str = "User interface options";
const HEADING_EXPERIMENTAL_OPTIONS: &str = "*Experimental* options (unstable)";
#[derive(Clone, Copy, clap::ValueEnum)]
pub enum InputBackendType {
Reedline,
Basic,
Minimal,
}
#[derive(Clone, Parser)]
#[clap(name = productinfo::PRODUCT_NAME,
version = VERSION,
about = SHORT_DESCRIPTION,
long_about = LONG_DESCRIPTION,
author,
override_usage = USAGE,
disable_help_flag = true,
disable_version_flag = true,
styles = brush_help_styles())]
pub struct CommandLineArgs {
#[clap(long = "help", action = clap::ArgAction::HelpShort)]
pub help: Option<bool>,
#[clap(long = "version", action = clap::ArgAction::Version)]
pub version: Option<bool>,
#[clap(long = "config", value_name = "FILE", help_heading = HEADING_CONFIG_OPTIONS)]
pub config_file: Option<PathBuf>,
#[clap(long = "no-config", help_heading = HEADING_CONFIG_OPTIONS)]
pub no_config: bool,
#[arg(short = 'C', help_heading = HEADING_STANDARD_OPTIONS)]
pub disallow_overwriting_regular_files_via_output_redirection: bool,
#[arg(short = 'c', value_name = "COMMAND", help_heading = HEADING_STANDARD_OPTIONS)]
pub command: Option<String>,
#[clap(short = 'e', help_heading = HEADING_STANDARD_OPTIONS)]
pub exit_on_nonzero_command_exit: bool,
#[clap(short = 'f', help_heading = HEADING_STANDARD_OPTIONS)]
pub disable_pathname_expansion: bool,
#[clap(short = 'i', help_heading = HEADING_STANDARD_OPTIONS)]
pub interactive: bool,
#[clap(long = "inherit-fd", value_name = "FD", help_heading = HEADING_STANDARD_OPTIONS)]
pub inherited_fds: Vec<i32>,
#[clap(short = 'l', long = "login", help_heading = HEADING_STANDARD_OPTIONS)]
pub login: bool,
#[clap(short = 'n', help_heading = HEADING_STANDARD_OPTIONS)]
pub do_not_execute_commands: bool,
#[clap(long = "noediting", help_heading = HEADING_STANDARD_OPTIONS)]
pub no_editing: bool,
#[clap(long = "noprofile", help_heading = HEADING_STANDARD_OPTIONS)]
pub no_profile: bool,
#[clap(long = "norc", help_heading = HEADING_STANDARD_OPTIONS)]
pub no_rc: bool,
#[clap(long = "noenv", help_heading = HEADING_STANDARD_OPTIONS)]
pub do_not_inherit_env: bool,
#[clap(short = 'o', value_name = "OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
pub enabled_options: Vec<String>,
#[clap(long = "+o", value_name = "OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
pub disabled_options: Vec<String>,
#[clap(short = 'O', value_name = "SHOPT_OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
pub enabled_shopt_options: Vec<String>,
#[clap(long = "+O", value_name = "SHOPT_OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
pub disabled_shopt_options: Vec<String>,
#[clap(long = "posix", help_heading = HEADING_STANDARD_OPTIONS)]
pub posix: bool,
#[clap(long = "rcfile", alias = "init-file", value_name = "FILE", help_heading = HEADING_STANDARD_OPTIONS)]
pub rc_file: Option<PathBuf>,
#[clap(short = 's', help_heading = HEADING_STANDARD_OPTIONS)]
pub read_commands_from_stdin: bool,
#[clap(long = "sh")]
pub sh_mode: bool,
#[clap(short = 't', help_heading = HEADING_STANDARD_OPTIONS)]
pub exit_after_one_command: bool,
#[clap(short = 'u', help_heading = HEADING_STANDARD_OPTIONS)]
pub treat_unset_variables_as_error: bool,
#[clap(short = 'v', long = "verbose", help_heading = HEADING_STANDARD_OPTIONS)]
pub verbose: bool,
#[clap(short = 'x', help_heading = HEADING_STANDARD_OPTIONS)]
pub print_commands_and_arguments: bool,
#[clap(long = "xtrace-file", value_name = "FILE", help_heading = HEADING_UI_OPTIONS)]
pub xtrace_file_path: Option<PathBuf>,
#[clap(long = "disable-bracketed-paste", help_heading = HEADING_UI_OPTIONS)]
pub disable_bracketed_paste: bool,
#[clap(long = "disable-color", help_heading = HEADING_UI_OPTIONS)]
pub disable_color: bool,
#[clap(long = "enable-highlighting", help_heading = HEADING_UI_OPTIONS, default_value_t = crate::entry::DEFAULT_ENABLE_HIGHLIGHTING)]
pub enable_highlighting: bool,
#[cfg(feature = "experimental-parser")]
#[clap(long = "experimental-parser", help_heading = HEADING_EXPERIMENTAL_OPTIONS)]
pub experimental_parser: bool,
#[clap(long = "enable-terminal-integration", help_heading = HEADING_EXPERIMENTAL_OPTIONS)]
pub terminal_shell_integration: bool,
#[clap(long = "enable-zsh-hooks", help_heading = HEADING_EXPERIMENTAL_OPTIONS)]
pub zsh_style_hooks: bool,
#[clap(long = "input-backend", value_name = "BACKEND", help_heading = HEADING_UI_OPTIONS)]
pub input_backend: Option<InputBackendType>,
#[cfg(feature = "experimental-load")]
#[clap(long = "load", value_name = "FILE", help_heading = HEADING_EXPERIMENTAL_OPTIONS)]
pub load_file: Option<PathBuf>,
#[clap(long = "debug", alias = "log-enable", value_name = "EVENT", help_heading = HEADING_UI_OPTIONS)]
pub enabled_debug_events: Vec<events::TraceEvent>,
#[clap(
long = "disable-event",
alias = "log-disable",
value_name = "EVENT",
hide_possible_values = true,
help_heading = HEADING_UI_OPTIONS
)]
pub disabled_events: Vec<events::TraceEvent>,
#[clap(
trailing_var_arg = true,
allow_hyphen_values = false,
value_name = "SCRIPT_PATH [SCRIPT_ARGS]..."
)]
pub script_args: Vec<String>,
}
impl CommandLineArgs {
#[must_use]
#[allow(
clippy::missing_panics_doc,
reason = "parsing defaults should not panic"
)]
pub fn default_values() -> Self {
use clap::Parser;
#[allow(clippy::expect_used)]
Self::try_parse_from(["brush"]).expect("parsing defaults should never fail")
}
pub fn is_interactive(&self) -> bool {
if self.interactive {
return true;
}
if self.command.is_some() || !self.script_args.is_empty() {
return false;
}
if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() {
return false;
}
true
}
}
#[doc(hidden)]
fn brush_help_styles() -> clap::builder::Styles {
styling::Styles::styled()
.header(
styling::AnsiColor::Yellow.on_default()
| styling::Effects::BOLD
| styling::Effects::UNDERLINE,
)
.usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
.literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD)
.placeholder(styling::AnsiColor::Cyan.on_default())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_values() {
let args = CommandLineArgs::default_values();
assert!(!args.interactive);
assert!(!args.login);
assert!(args.command.is_none());
assert!(args.script_args.is_empty());
}
}