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())]
44pub struct CommandLineArgs {
45    /// Display usage information.
46    #[clap(long = "help", action = clap::ArgAction::HelpLong)]
47    pub help: Option<bool>,
48
49    /// Display shell version.
50    #[clap(long = "version", action = clap::ArgAction::Version)]
51    pub version: Option<bool>,
52
53    /// Enable noclobber shell option.
54    #[arg(short = 'C')]
55    pub disallow_overwriting_regular_files_via_output_redirection: bool,
56
57    /// Execute the provided command and then exit.
58    #[arg(short = 'c', value_name = "COMMAND")]
59    pub command: Option<String>,
60
61    /// Run in interactive mode.
62    #[clap(short = 'i')]
63    pub interactive: bool,
64
65    /// Make shell act as if it had been invoked as a login shell.
66    #[clap(short = 'l', long = "login")]
67    pub login: bool,
68
69    /// Do not execute commands.
70    #[clap(short = 'n')]
71    pub do_not_execute_commands: bool,
72
73    /// Don't use readline for input.
74    #[clap(long = "noediting")]
75    pub no_editing: bool,
76
77    /// Don't process any profile/login files (`/etc/profile`, `~/.bash_profile`, `~/.bash_login`,
78    /// `~/.profile`).
79    #[clap(long = "noprofile")]
80    pub no_profile: bool,
81
82    /// Don't process "rc" files if the shell is interactive (e.g., `~/.bashrc`, `~/.brushrc`).
83    #[clap(long = "norc")]
84    pub no_rc: bool,
85
86    /// Don't inherit environment variables from the calling process.
87    #[clap(long = "noenv")]
88    pub do_not_inherit_env: bool,
89
90    /// Enable option (set -o option).
91    #[clap(short = 'o', value_name = "OPTION")]
92    pub enabled_options: Vec<String>,
93
94    /// Disable option (set -o option).
95    #[clap(long = "+o", hide = true)]
96    pub disabled_options: Vec<String>,
97
98    /// Enable shopt option.
99    #[clap(short = 'O', value_name = "SHOPT_OPTION")]
100    pub enabled_shopt_options: Vec<String>,
101
102    /// Disable shopt option.
103    #[clap(long = "+O", hide = true)]
104    pub disabled_shopt_options: Vec<String>,
105
106    /// Disable non-POSIX extensions.
107    #[clap(long = "posix")]
108    pub posix: bool,
109
110    /// Path to the rc file to load in interactive shells (instead of bash.bashrc and ~/.bashrc).
111    #[clap(long = "rcfile", alias = "init-file", value_name = "FILE")]
112    pub rc_file: Option<PathBuf>,
113
114    /// Read commands from standard input.
115    #[clap(short = 's')]
116    pub read_commands_from_stdin: bool,
117
118    /// Run in sh compatibility mode.
119    #[clap(long = "sh")]
120    pub sh_mode: bool,
121
122    /// Run only one command.
123    #[clap(short = 't')]
124    pub exit_after_one_command: bool,
125
126    /// Print input when it's processed.
127    #[clap(short = 'v', long = "verbose")]
128    pub verbose: bool,
129
130    /// Print commands as they execute.
131    #[clap(short = 'x')]
132    pub print_commands_and_arguments: bool,
133
134    /// Disable bracketed paste.
135    #[clap(long = "disable-bracketed-paste")]
136    pub disable_bracketed_paste: bool,
137
138    /// Disable colorized output.
139    #[clap(long = "disable-color")]
140    pub disable_color: bool,
141
142    /// Enable syntax highlighting (experimental).
143    #[clap(long = "enable-highlighting")]
144    pub enable_highlighting: bool,
145
146    /// Input backend.
147    #[clap(long = "input-backend")]
148    pub input_backend: Option<InputBackend>,
149
150    /// Enable debug logging for classes of tracing events.
151    #[clap(long = "debug", alias = "log-enable", value_name = "EVENT")]
152    pub enabled_debug_events: Vec<events::TraceEvent>,
153
154    /// Disable logging for classes of tracing events (takes same event types as --debug).
155    #[clap(
156        long = "disable-event",
157        alias = "log-disable",
158        value_name = "EVENT",
159        hide_possible_values = true
160    )]
161    pub disabled_events: Vec<events::TraceEvent>,
162
163    /// Path and arguments for script to execute (optional).
164    #[clap(trailing_var_arg = true, allow_hyphen_values = true)]
165    pub script_args: Vec<String>,
166}
167
168impl CommandLineArgs {
169    /// Returns whether or not the arguments indicate that the shell should run in interactive mode.
170    pub fn is_interactive(&self) -> bool {
171        // If -i is provided, then that overrides any further consideration; it forces
172        // interactive mode.
173        if self.interactive {
174            return true;
175        }
176
177        // If -c or non-option arguments are provided, then we're not in interactive mode.
178        if self.command.is_some() || !self.script_args.is_empty() {
179            return false;
180        }
181
182        // If *either* stdin or stderr is not a terminal, then we're not in interactive mode.
183        if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() {
184            return false;
185        }
186
187        // In all other cases, we assume interactive mode.
188        true
189    }
190}
191
192/// Returns clap styling to be used for command-line help.
193#[doc(hidden)]
194fn brush_help_styles() -> clap::builder::Styles {
195    styling::Styles::styled()
196        .header(
197            styling::AnsiColor::Yellow.on_default()
198                | styling::Effects::BOLD
199                | styling::Effects::UNDERLINE,
200        )
201        .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
202        .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD)
203        .placeholder(styling::AnsiColor::Cyan.on_default())
204}