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 option (set -o option).
92    #[clap(short = 'o', value_name = "OPTION")]
93    pub enabled_options: Vec<String>,
94
95    /// Disable option (set -o option).
96    #[clap(long = "+o", hide = true)]
97    pub disabled_options: Vec<String>,
98
99    /// Enable shopt option.
100    #[clap(short = 'O', value_name = "SHOPT_OPTION")]
101    pub enabled_shopt_options: Vec<String>,
102
103    /// Disable shopt option.
104    #[clap(long = "+O", hide = true)]
105    pub disabled_shopt_options: Vec<String>,
106
107    /// Disable non-POSIX extensions.
108    #[clap(long = "posix")]
109    pub posix: bool,
110
111    /// Path to the rc file to load in interactive shells (instead of bash.bashrc and ~/.bashrc).
112    #[clap(long = "rcfile", alias = "init-file", value_name = "FILE")]
113    pub rc_file: Option<PathBuf>,
114
115    /// Read commands from standard input.
116    #[clap(short = 's')]
117    pub read_commands_from_stdin: bool,
118
119    /// Run in sh compatibility mode.
120    #[clap(long = "sh")]
121    pub sh_mode: bool,
122
123    /// Run only one command.
124    #[clap(short = 't')]
125    pub exit_after_one_command: bool,
126
127    /// Print input when it's processed.
128    #[clap(short = 'v', long = "verbose")]
129    pub verbose: bool,
130
131    /// Print commands as they execute.
132    #[clap(short = 'x')]
133    pub print_commands_and_arguments: bool,
134
135    /// Disable bracketed paste.
136    #[clap(long = "disable-bracketed-paste")]
137    pub disable_bracketed_paste: bool,
138
139    /// Disable colorized output.
140    #[clap(long = "disable-color")]
141    pub disable_color: bool,
142
143    /// Enable syntax highlighting (experimental).
144    #[clap(long = "enable-highlighting")]
145    pub enable_highlighting: bool,
146
147    /// Input backend.
148    #[clap(long = "input-backend")]
149    pub input_backend: Option<InputBackend>,
150
151    /// Enable debug logging for classes of tracing events.
152    #[clap(long = "debug", alias = "log-enable", value_name = "EVENT")]
153    pub enabled_debug_events: Vec<events::TraceEvent>,
154
155    /// Disable logging for classes of tracing events (takes same event types as --debug).
156    #[clap(
157        long = "disable-event",
158        alias = "log-disable",
159        value_name = "EVENT",
160        hide_possible_values = true
161    )]
162    pub disabled_events: Vec<events::TraceEvent>,
163
164    /// Path to script to execute.
165    // allow any string as command_name similar to sh
166    #[clap(allow_hyphen_values = true)]
167    pub script_path: Option<String>,
168
169    /// Arguments for script.
170    // `allow_hyphen_values`: do not strip `-` from flags
171    // `num_args=1..`: consume everything
172    #[clap(allow_hyphen_values = true, num_args=1..)]
173    pub script_args: Vec<String>,
174}
175
176impl CommandLineArgs {
177    /// Returns whether or not the arguments indicate that the shell should run in interactive mode.
178    pub fn is_interactive(&self) -> bool {
179        // If -i is provided, then that overrides any further consideration; it forces
180        // interactive mode.
181        if self.interactive {
182            return true;
183        }
184
185        // If -c or non-option arguments are provided, then we're not in interactive mode.
186        if self.command.is_some() || self.script_path.is_some() {
187            return false;
188        }
189
190        // If *either* stdin or stderr is not a terminal, then we're not in interactive mode.
191        if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() {
192            return false;
193        }
194
195        // In all other cases, we assume interactive mode.
196        true
197    }
198}
199
200/// Returns clap styling to be used for command-line help.
201#[doc(hidden)]
202fn brush_help_styles() -> clap::builder::Styles {
203    styling::Styles::styled()
204        .header(
205            styling::AnsiColor::Yellow.on_default()
206                | styling::Effects::BOLD
207                | styling::Effects::UNDERLINE,
208        )
209        .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
210        .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD)
211        .placeholder(styling::AnsiColor::Cyan.on_default())
212}