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(short = 'l', long = "login", help_heading = HEADING_STANDARD_OPTIONS)]
76 pub login: bool,
77
78 #[clap(short = 'n', help_heading = HEADING_STANDARD_OPTIONS)]
80 pub do_not_execute_commands: bool,
81
82 #[clap(long = "noediting", help_heading = HEADING_STANDARD_OPTIONS)]
84 pub no_editing: bool,
85
86 #[clap(long = "noprofile", help_heading = HEADING_STANDARD_OPTIONS)]
89 pub no_profile: bool,
90
91 #[clap(long = "norc", help_heading = HEADING_STANDARD_OPTIONS)]
93 pub no_rc: bool,
94
95 #[clap(long = "noenv", help_heading = HEADING_STANDARD_OPTIONS)]
97 pub do_not_inherit_env: bool,
98
99 #[clap(short = 'o', value_name = "OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
101 pub enabled_options: Vec<String>,
102
103 #[clap(long = "+o", value_name = "OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
105 pub disabled_options: Vec<String>,
106
107 #[clap(short = 'O', value_name = "SHOPT_OPTION", help_heading = HEADING_STANDARD_OPTIONS)]
109 pub enabled_shopt_options: Vec<String>,
110
111 #[clap(long = "+O", value_name = "SHOPT_OPTION", hide = true, help_heading = HEADING_STANDARD_OPTIONS)]
113 pub disabled_shopt_options: Vec<String>,
114
115 #[clap(long = "posix", help_heading = HEADING_STANDARD_OPTIONS)]
117 pub posix: bool,
118
119 #[clap(long = "rcfile", alias = "init-file", value_name = "FILE", help_heading = HEADING_STANDARD_OPTIONS)]
121 pub rc_file: Option<PathBuf>,
122
123 #[clap(short = 's', help_heading = HEADING_STANDARD_OPTIONS)]
125 pub read_commands_from_stdin: bool,
126
127 #[clap(long = "sh")]
129 pub sh_mode: bool,
130
131 #[clap(short = 't', help_heading = HEADING_STANDARD_OPTIONS)]
133 pub exit_after_one_command: bool,
134
135 #[clap(short = 'v', long = "verbose", help_heading = HEADING_STANDARD_OPTIONS)]
137 pub verbose: bool,
138
139 #[clap(short = 'x', help_heading = HEADING_STANDARD_OPTIONS)]
141 pub print_commands_and_arguments: bool,
142
143 #[clap(long = "disable-bracketed-paste", help_heading = HEADING_UI_OPTIONS)]
145 pub disable_bracketed_paste: bool,
146
147 #[clap(long = "disable-color", help_heading = HEADING_UI_OPTIONS)]
149 pub disable_color: bool,
150
151 #[clap(long = "enable-highlighting", help_heading = HEADING_UI_OPTIONS)]
153 pub enable_highlighting: bool,
154
155 #[clap(long = "input-backend", value_name = "BACKEND", help_heading = HEADING_UI_OPTIONS)]
157 pub input_backend: Option<InputBackend>,
158
159 #[clap(long = "debug", alias = "log-enable", value_name = "EVENT", help_heading = HEADING_UI_OPTIONS)]
161 pub enabled_debug_events: Vec<events::TraceEvent>,
162
163 #[clap(
165 long = "disable-event",
166 alias = "log-disable",
167 value_name = "EVENT",
168 hide_possible_values = true,
169 help_heading = HEADING_UI_OPTIONS
170 )]
171 pub disabled_events: Vec<events::TraceEvent>,
172
173 #[clap(
175 trailing_var_arg = true,
176 allow_hyphen_values = false,
177 value_name = "SCRIPT_PATH [SCRIPT_ARGS]..."
178 )]
179 pub script_args: Vec<String>,
180}
181
182impl CommandLineArgs {
183 pub fn is_interactive(&self) -> bool {
185 if self.interactive {
188 return true;
189 }
190
191 if self.command.is_some() || !self.script_args.is_empty() {
193 return false;
194 }
195
196 if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() {
198 return false;
199 }
200
201 true
203 }
204}
205
206#[doc(hidden)]
208fn brush_help_styles() -> clap::builder::Styles {
209 styling::Styles::styled()
210 .header(
211 styling::AnsiColor::Yellow.on_default()
212 | styling::Effects::BOLD
213 | styling::Effects::UNDERLINE,
214 )
215 .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
216 .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD)
217 .placeholder(styling::AnsiColor::Cyan.on_default())
218}