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