1use clap::{Parser, builder::styling};
4use std::io::IsTerminal;
5use std::path::PathBuf;
6
7use crate::{events, productinfo};
8
9const SHORT_DESCRIPTION: &str = "Bo[u]rn[e] RUsty SHell 🦀 (https://brush.sh)";
10
11const LONG_DESCRIPTION: &str = r"brush is a bash-compatible, Rust-implemented, POSIX-style shell.
12
13brush 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.
14
15For more information, visit https://brush.sh.";
16
17const USAGE: &str = color_print::cstr!(
18 "<bold>brush</bold> <italics>[OPTIONS]</italics>... <italics>[SCRIPT_PATH [SCRIPT_ARGS]...]</italics>"
19);
20
21const VERSION: &str = const_format::concatcp!(
22 productinfo::PRODUCT_VERSION,
23 " (",
24 productinfo::PRODUCT_GIT_VERSION,
25 ")"
26);
27
28const HEADING_STANDARD_OPTIONS: &str = "Standard shell options";
29
30const HEADING_CONFIG_OPTIONS: &str = "Configuration options";
31
32const HEADING_UI_OPTIONS: &str = "User interface options";
33
34const HEADING_EXPERIMENTAL_OPTIONS: &str = "*Experimental* options (unstable)";
35
36#[derive(Clone, Copy, clap::ValueEnum)]
38pub enum InputBackendType {
39 Reedline,
41 Basic,
43 Minimal,
45}
46
47#[derive(Clone, Parser)]
49#[clap(name = productinfo::PRODUCT_NAME,
50 version = VERSION,
51 about = SHORT_DESCRIPTION,
52 long_about = LONG_DESCRIPTION,
53 author,
54 override_usage = USAGE,
55 disable_help_flag = true,
56 disable_version_flag = true,
57 styles = brush_help_styles())]
58pub struct CommandLineArgs {
59 #[clap(long = "help", action = clap::ArgAction::HelpShort)]
61 pub help: Option<bool>,
62
63 #[clap(long = "version", action = clap::ArgAction::Version)]
65 pub version: Option<bool>,
66
67 #[clap(long = "config", value_name = "FILE", help_heading = HEADING_CONFIG_OPTIONS)]
69 pub config_file: Option<PathBuf>,
70
71 #[clap(long = "no-config", help_heading = HEADING_CONFIG_OPTIONS)]
73 pub no_config: bool,
74
75 #[arg(short = 'C', help_heading = HEADING_STANDARD_OPTIONS)]
77 pub disallow_overwriting_regular_files_via_output_redirection: bool,
78
79 #[arg(short = 'c', value_name = "COMMAND", help_heading = HEADING_STANDARD_OPTIONS)]
81 pub command: Option<String>,
82
83 #[clap(short = 'e', help_heading = HEADING_STANDARD_OPTIONS)]
85 pub exit_on_nonzero_command_exit: bool,
86
87 #[clap(short = 'f', help_heading = HEADING_STANDARD_OPTIONS)]
89 pub disable_pathname_expansion: bool,
90
91 #[clap(short = 'i', help_heading = HEADING_STANDARD_OPTIONS)]
93 pub interactive: bool,
94
95 #[clap(long = "inherit-fd", value_name = "FD", help_heading = HEADING_STANDARD_OPTIONS)]
97 pub inherited_fds: Vec<i32>,
98
99 #[clap(short = 'l', long = "login", help_heading = HEADING_STANDARD_OPTIONS)]
101 pub login: bool,
102
103 #[clap(short = 'n', help_heading = HEADING_STANDARD_OPTIONS)]
105 pub do_not_execute_commands: bool,
106
107 #[clap(long = "noediting", help_heading = HEADING_STANDARD_OPTIONS)]
109 pub no_editing: bool,
110
111 #[clap(long = "noprofile", help_heading = HEADING_STANDARD_OPTIONS)]
114 pub no_profile: bool,
115
116 #[clap(long = "norc", help_heading = HEADING_STANDARD_OPTIONS)]
118 pub no_rc: bool,
119
120 #[clap(long = "noenv", help_heading = HEADING_STANDARD_OPTIONS)]
122 pub do_not_inherit_env: bool,
123
124 #[clap(short = 'o', value_name = "OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
126 pub enabled_options: Vec<String>,
127
128 #[clap(long = "+o", value_name = "OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
130 pub disabled_options: Vec<String>,
131
132 #[clap(short = 'O', value_name = "SHOPT_OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
134 pub enabled_shopt_options: Vec<String>,
135
136 #[clap(long = "+O", value_name = "SHOPT_OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
138 pub disabled_shopt_options: Vec<String>,
139
140 #[clap(long = "posix", help_heading = HEADING_STANDARD_OPTIONS)]
142 pub posix: bool,
143
144 #[clap(long = "rcfile", alias = "init-file", value_name = "FILE", help_heading = HEADING_STANDARD_OPTIONS)]
147 pub rc_file: Option<PathBuf>,
148
149 #[clap(short = 's', help_heading = HEADING_STANDARD_OPTIONS)]
151 pub read_commands_from_stdin: bool,
152
153 #[clap(long = "sh")]
155 pub sh_mode: bool,
156
157 #[clap(short = 't', help_heading = HEADING_STANDARD_OPTIONS)]
159 pub exit_after_one_command: bool,
160
161 #[clap(short = 'u', help_heading = HEADING_STANDARD_OPTIONS)]
163 pub treat_unset_variables_as_error: bool,
164
165 #[clap(short = 'v', long = "verbose", help_heading = HEADING_STANDARD_OPTIONS)]
167 pub verbose: bool,
168
169 #[clap(short = 'x', help_heading = HEADING_STANDARD_OPTIONS)]
171 pub print_commands_and_arguments: bool,
172
173 #[clap(long = "xtrace-file", value_name = "FILE", help_heading = HEADING_UI_OPTIONS)]
175 pub xtrace_file_path: Option<PathBuf>,
176
177 #[clap(long = "disable-bracketed-paste", help_heading = HEADING_UI_OPTIONS)]
179 pub disable_bracketed_paste: bool,
180
181 #[clap(long = "disable-color", help_heading = HEADING_UI_OPTIONS)]
183 pub disable_color: bool,
184
185 #[clap(long = "enable-highlighting", help_heading = HEADING_UI_OPTIONS, default_value_t = crate::entry::DEFAULT_ENABLE_HIGHLIGHTING)]
187 pub enable_highlighting: bool,
188
189 #[cfg(feature = "experimental-parser")]
191 #[clap(long = "experimental-parser", help_heading = HEADING_EXPERIMENTAL_OPTIONS)]
192 pub experimental_parser: bool,
193
194 #[clap(long = "enable-terminal-integration", help_heading = HEADING_EXPERIMENTAL_OPTIONS)]
196 pub terminal_shell_integration: bool,
197
198 #[clap(long = "enable-zsh-hooks", help_heading = HEADING_EXPERIMENTAL_OPTIONS)]
200 pub zsh_style_hooks: bool,
201
202 #[clap(long = "input-backend", value_name = "BACKEND", help_heading = HEADING_UI_OPTIONS)]
204 pub input_backend: Option<InputBackendType>,
205
206 #[cfg(feature = "experimental-load")]
209 #[clap(long = "load", value_name = "FILE", help_heading = HEADING_EXPERIMENTAL_OPTIONS)]
210 pub load_file: Option<PathBuf>,
211
212 #[clap(long = "debug", alias = "log-enable", value_name = "EVENT", help_heading = HEADING_UI_OPTIONS)]
214 pub enabled_debug_events: Vec<events::TraceEvent>,
215
216 #[clap(
218 long = "disable-event",
219 alias = "log-disable",
220 value_name = "EVENT",
221 hide_possible_values = true,
222 help_heading = HEADING_UI_OPTIONS
223 )]
224 pub disabled_events: Vec<events::TraceEvent>,
225
226 #[clap(
228 trailing_var_arg = true,
229 allow_hyphen_values = false,
230 value_name = "SCRIPT_PATH [SCRIPT_ARGS]..."
231 )]
232 pub script_args: Vec<String>,
233}
234
235impl CommandLineArgs {
236 #[must_use]
241 #[allow(
242 clippy::missing_panics_doc,
243 reason = "parsing defaults should not panic"
244 )]
245 pub fn default_values() -> Self {
246 use clap::Parser;
247 #[allow(clippy::expect_used)]
250 Self::try_parse_from(["brush"]).expect("parsing defaults should never fail")
251 }
252
253 pub fn is_interactive(&self) -> bool {
255 if self.interactive {
258 return true;
259 }
260
261 if self.command.is_some() || !self.script_args.is_empty() {
263 return false;
264 }
265
266 if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() {
268 return false;
269 }
270
271 true
273 }
274}
275
276#[doc(hidden)]
278fn brush_help_styles() -> clap::builder::Styles {
279 styling::Styles::styled()
280 .header(
281 styling::AnsiColor::Yellow.on_default()
282 | styling::Effects::BOLD
283 | styling::Effects::UNDERLINE,
284 )
285 .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
286 .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD)
287 .placeholder(styling::AnsiColor::Cyan.on_default())
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_default_values() {
296 let args = CommandLineArgs::default_values();
297 assert!(!args.interactive);
299 assert!(!args.login);
300 assert!(args.command.is_none());
301 assert!(args.script_args.is_empty());
302 }
303}