1use 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#[derive(Clone, Copy, clap::ValueEnum)]
33pub enum InputBackend {
34 Reedline,
36 Basic,
38 Minimal,
40}
41
42#[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 #[clap(long = "help", action = clap::ArgAction::HelpShort)]
56 pub help: Option<bool>,
57
58 #[clap(long = "version", action = clap::ArgAction::Version)]
60 pub version: Option<bool>,
61
62 #[arg(short = 'C', help_heading = HEADING_STANDARD_OPTIONS)]
64 pub disallow_overwriting_regular_files_via_output_redirection: bool,
65
66 #[arg(short = 'c', value_name = "COMMAND", help_heading = HEADING_STANDARD_OPTIONS)]
68 pub command: Option<String>,
69
70 #[clap(short = 'i', help_heading = HEADING_STANDARD_OPTIONS)]
72 pub interactive: bool,
73
74 #[clap(long = "inherit-fd", value_name = "FD", help_heading = HEADING_STANDARD_OPTIONS)]
76 pub inherited_fds: Vec<i32>,
77
78 #[clap(short = 'l', long = "login", help_heading = HEADING_STANDARD_OPTIONS)]
80 pub login: bool,
81
82 #[clap(short = 'n', help_heading = HEADING_STANDARD_OPTIONS)]
84 pub do_not_execute_commands: bool,
85
86 #[clap(long = "noediting", help_heading = HEADING_STANDARD_OPTIONS)]
88 pub no_editing: bool,
89
90 #[clap(long = "noprofile", help_heading = HEADING_STANDARD_OPTIONS)]
93 pub no_profile: bool,
94
95 #[clap(long = "norc", help_heading = HEADING_STANDARD_OPTIONS)]
97 pub no_rc: bool,
98
99 #[clap(long = "noenv", help_heading = HEADING_STANDARD_OPTIONS)]
101 pub do_not_inherit_env: bool,
102
103 #[clap(short = 'o', value_name = "OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
105 pub enabled_options: Vec<String>,
106
107 #[clap(long = "+o", value_name = "OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
109 pub disabled_options: Vec<String>,
110
111 #[clap(short = 'O', value_name = "SHOPT_OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
113 pub enabled_shopt_options: Vec<String>,
114
115 #[clap(long = "+O", value_name = "SHOPT_OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
117 pub disabled_shopt_options: Vec<String>,
118
119 #[clap(long = "posix", help_heading = HEADING_STANDARD_OPTIONS)]
121 pub posix: bool,
122
123 #[clap(long = "rcfile", alias = "init-file", value_name = "FILE", help_heading = HEADING_STANDARD_OPTIONS)]
125 pub rc_file: Option<PathBuf>,
126
127 #[clap(short = 's', help_heading = HEADING_STANDARD_OPTIONS)]
129 pub read_commands_from_stdin: bool,
130
131 #[clap(long = "sh")]
133 pub sh_mode: bool,
134
135 #[clap(short = 't', help_heading = HEADING_STANDARD_OPTIONS)]
137 pub exit_after_one_command: bool,
138
139 #[clap(short = 'v', long = "verbose", help_heading = HEADING_STANDARD_OPTIONS)]
141 pub verbose: bool,
142
143 #[clap(short = 'x', help_heading = HEADING_STANDARD_OPTIONS)]
145 pub print_commands_and_arguments: bool,
146
147 #[clap(long = "disable-bracketed-paste", help_heading = HEADING_UI_OPTIONS)]
149 pub disable_bracketed_paste: bool,
150
151 #[clap(long = "disable-color", help_heading = HEADING_UI_OPTIONS)]
153 pub disable_color: bool,
154
155 #[clap(long = "enable-highlighting", help_heading = HEADING_UI_OPTIONS)]
157 pub enable_highlighting: bool,
158
159 #[clap(long = "input-backend", value_name = "BACKEND", help_heading = HEADING_UI_OPTIONS)]
161 pub input_backend: Option<InputBackend>,
162
163 #[clap(long = "debug", alias = "log-enable", value_name = "EVENT", help_heading = HEADING_UI_OPTIONS)]
165 pub enabled_debug_events: Vec<events::TraceEvent>,
166
167 #[clap(
169 long = "disable-event",
170 alias = "log-disable",
171 value_name = "EVENT",
172 hide_possible_values = true,
173 help_heading = HEADING_UI_OPTIONS
174 )]
175 pub disabled_events: Vec<events::TraceEvent>,
176
177 #[clap(
179 trailing_var_arg = true,
180 allow_hyphen_values = false,
181 value_name = "SCRIPT_PATH [SCRIPT_ARGS]..."
182 )]
183 pub script_args: Vec<String>,
184}
185
186impl CommandLineArgs {
187 pub fn is_interactive(&self) -> bool {
189 if self.interactive {
192 return true;
193 }
194
195 if self.command.is_some() || !self.script_args.is_empty() {
197 return false;
198 }
199
200 if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() {
202 return false;
203 }
204
205 true
207 }
208}
209
210#[doc(hidden)]
212fn brush_help_styles() -> clap::builder::Styles {
213 styling::Styles::styled()
214 .header(
215 styling::AnsiColor::Yellow.on_default()
216 | styling::Effects::BOLD
217 | styling::Effects::UNDERLINE,
218 )
219 .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
220 .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD)
221 .placeholder(styling::AnsiColor::Cyan.on_default())
222}