brush_shell/
args.rs

1//! Types for brush command-line parsing.
2
3use clap::{Parser, builder::styling};
4use std::{io::IsTerminal, path::PathBuf};
5
6use crate::{events, productinfo};
7
8const SHORT_DESCRIPTION: &str = "Bo[u]rn[e] RUsty SHell 🦀 (https://brush.sh)";
9
10const LONG_DESCRIPTION: &str = r"brush is a bash-compatible, Rust-implemented, POSIX-style shell.
11
12brush 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.
13
14For more information, visit https://brush.sh.";
15
16const USAGE: &str = color_print::cstr!(
17    "<bold>brush</bold> <italics>[OPTIONS]</italics>... <italics>[SCRIPT_PATH [SCRIPT_ARGS]...]</italics>"
18);
19
20const VERSION: &str = const_format::concatcp!(
21    productinfo::PRODUCT_VERSION,
22    " (",
23    productinfo::PRODUCT_GIT_VERSION,
24    ")"
25);
26
27const HEADING_STANDARD_OPTIONS: &str = "Standard shell options";
28
29const HEADING_UI_OPTIONS: &str = "User interface options";
30
31/// Identifies the input backend to use for the shell.
32#[derive(Clone, Copy, clap::ValueEnum)]
33pub enum InputBackend {
34    /// Richest input backend, based on reedline.
35    Reedline,
36    /// Basic input backend that provides minimal completion support for testing.
37    Basic,
38    /// Most minimal input backend.
39    Minimal,
40}
41
42/// Parsed command-line arguments for the brush shell.
43#[derive(Parser)]
44#[clap(name = productinfo::PRODUCT_NAME,
45       version = VERSION,
46       about = SHORT_DESCRIPTION,
47       long_about = LONG_DESCRIPTION,
48       author,
49       override_usage = USAGE,
50       disable_help_flag = true,
51       disable_version_flag = true,
52       styles = brush_help_styles())]
53pub struct CommandLineArgs {
54    /// Display usage information.
55    #[clap(long = "help", action = clap::ArgAction::HelpShort)]
56    pub help: Option<bool>,
57
58    /// Display shell version.
59    #[clap(long = "version", action = clap::ArgAction::Version)]
60    pub version: Option<bool>,
61
62    /// Enable `noclobber` shell option.
63    #[arg(short = 'C', help_heading = HEADING_STANDARD_OPTIONS)]
64    pub disallow_overwriting_regular_files_via_output_redirection: bool,
65
66    /// Execute the provided command and then exit.
67    #[arg(short = 'c', value_name = "COMMAND", help_heading = HEADING_STANDARD_OPTIONS)]
68    pub command: Option<String>,
69
70    /// Run in interactive mode.
71    #[clap(short = 'i', help_heading = HEADING_STANDARD_OPTIONS)]
72    pub interactive: bool,
73
74    /// Make shell act as if it had been invoked as a login shell.
75    #[clap(short = 'l', long = "login", help_heading = HEADING_STANDARD_OPTIONS)]
76    pub login: bool,
77
78    /// Do not execute commands.
79    #[clap(short = 'n', help_heading = HEADING_STANDARD_OPTIONS)]
80    pub do_not_execute_commands: bool,
81
82    /// Don't use readline for input.
83    #[clap(long = "noediting", help_heading = HEADING_STANDARD_OPTIONS)]
84    pub no_editing: bool,
85
86    /// Don't process any profile/login files (`/etc/profile`, `~/.bash_profile`, `~/.bash_login`,
87    /// `~/.profile`).
88    #[clap(long = "noprofile", help_heading = HEADING_STANDARD_OPTIONS)]
89    pub no_profile: bool,
90
91    /// Don't process "rc" files if the shell is interactive (e.g., `~/.bashrc`, `~/.brushrc`).
92    #[clap(long = "norc", help_heading = HEADING_STANDARD_OPTIONS)]
93    pub no_rc: bool,
94
95    /// Don't inherit environment variables from the calling process.
96    #[clap(long = "noenv", help_heading = HEADING_STANDARD_OPTIONS)]
97    pub do_not_inherit_env: bool,
98
99    /// Enable option (`set -o` option).
100    #[clap(short = 'o', value_name = "OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
101    pub enabled_options: Vec<String>,
102
103    /// Disable option (`set -o` option).
104    #[clap(long = "+o", value_name = "OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
105    pub disabled_options: Vec<String>,
106
107    /// Enable `shopt` option.
108    #[clap(short = 'O', value_name = "SHOPT_OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
109    pub enabled_shopt_options: Vec<String>,
110
111    /// Disable `shopt` option.
112    #[clap(long = "+O", value_name = "SHOPT_OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
113    pub disabled_shopt_options: Vec<String>,
114
115    /// Disable non-POSIX extensions.
116    #[clap(long = "posix", help_heading = HEADING_STANDARD_OPTIONS)]
117    pub posix: bool,
118
119    /// Path to the rc file to load in interactive shells (instead of `bash.bashrc` and `~/.bashrc`).
120    #[clap(long = "rcfile", alias = "init-file", value_name = "FILE", help_heading = HEADING_STANDARD_OPTIONS)]
121    pub rc_file: Option<PathBuf>,
122
123    /// Read commands from standard input.
124    #[clap(short = 's', help_heading = HEADING_STANDARD_OPTIONS)]
125    pub read_commands_from_stdin: bool,
126
127    /// Run in `sh` compatibility mode, as if run as `/bin/sh`.
128    #[clap(long = "sh")]
129    pub sh_mode: bool,
130
131    /// Run only one command and then exit.
132    #[clap(short = 't', help_heading = HEADING_STANDARD_OPTIONS)]
133    pub exit_after_one_command: bool,
134
135    /// Print input when it's processed.
136    #[clap(short = 'v', long = "verbose", help_heading = HEADING_STANDARD_OPTIONS)]
137    pub verbose: bool,
138
139    /// Print commands as they execute.
140    #[clap(short = 'x', help_heading = HEADING_STANDARD_OPTIONS)]
141    pub print_commands_and_arguments: bool,
142
143    /// Disable bracketed paste.
144    #[clap(long = "disable-bracketed-paste", help_heading = HEADING_UI_OPTIONS)]
145    pub disable_bracketed_paste: bool,
146
147    /// Disable colorized output.
148    #[clap(long = "disable-color", help_heading = HEADING_UI_OPTIONS)]
149    pub disable_color: bool,
150
151    /// Enable syntax highlighting (experimental).
152    #[clap(long = "enable-highlighting", help_heading = HEADING_UI_OPTIONS)]
153    pub enable_highlighting: bool,
154
155    /// Input backend.
156    #[clap(long = "input-backend", value_name = "BACKEND", help_heading = HEADING_UI_OPTIONS)]
157    pub input_backend: Option<InputBackend>,
158
159    /// Enable debug logging for classes of tracing events.
160    #[clap(long = "debug", alias = "log-enable", value_name = "EVENT", help_heading = HEADING_UI_OPTIONS)]
161    pub enabled_debug_events: Vec<events::TraceEvent>,
162
163    /// Disable logging for classes of tracing events (takes same event types as `--debug`).
164    #[clap(
165        long = "disable-event",
166        alias = "log-disable",
167        value_name = "EVENT",
168        hide_possible_values = true,
169        help_heading = HEADING_UI_OPTIONS
170    )]
171    pub disabled_events: Vec<events::TraceEvent>,
172
173    /// Path and arguments for script to execute (optional).
174    #[clap(
175        trailing_var_arg = true,
176        allow_hyphen_values = false,
177        value_name = "SCRIPT_PATH [SCRIPT_ARGS]..."
178    )]
179    pub script_args: Vec<String>,
180}
181
182impl CommandLineArgs {
183    /// Returns whether or not the arguments indicate that the shell should run in interactive mode.
184    pub fn is_interactive(&self) -> bool {
185        // If -i is provided, then that overrides any further consideration; it forces
186        // interactive mode.
187        if self.interactive {
188            return true;
189        }
190
191        // If -c or non-option arguments are provided, then we're not in interactive mode.
192        if self.command.is_some() || !self.script_args.is_empty() {
193            return false;
194        }
195
196        // If *either* stdin or stderr is not a terminal, then we're not in interactive mode.
197        if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() {
198            return false;
199        }
200
201        // In all other cases, we assume interactive mode.
202        true
203    }
204}
205
206/// Returns clap styling to be used for command-line help.
207#[doc(hidden)]
208fn brush_help_styles() -> clap::builder::Styles {
209    styling::Styles::styled()
210        .header(
211            styling::AnsiColor::Yellow.on_default()
212                | styling::Effects::BOLD
213                | styling::Effects::UNDERLINE,
214        )
215        .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
216        .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD)
217        .placeholder(styling::AnsiColor::Cyan.on_default())
218}