1use clap::builder::styling;
4use clap::Parser;
5use futures::future::BoxFuture;
6
7use crate::commands;
8use crate::error;
9use crate::ExecutionResult;
10
11mod alias;
12mod bg;
13mod bind;
14mod break_;
15mod brushinfo;
16mod builtin_;
17mod cd;
18mod colon;
19mod command;
20mod complete;
21mod continue_;
22mod declare;
23mod dirs;
24mod dot;
25mod echo;
26mod enable;
27mod eval;
28#[cfg(unix)]
29mod exec;
30mod exit;
31mod export;
32mod factory;
33mod false_;
34mod fg;
35mod getopts;
36mod hash;
37mod help;
38mod jobs;
39#[cfg(unix)]
40mod kill;
41mod let_;
42mod mapfile;
43mod popd;
44mod printf;
45mod pushd;
46mod pwd;
47mod read;
48mod return_;
49mod set;
50mod shift;
51mod shopt;
52#[cfg(unix)]
53mod suspend;
54mod test;
55mod times;
56mod trap;
57mod true_;
58mod type_;
59#[cfg(unix)]
60mod umask;
61mod unalias;
62mod unimp;
63mod unset;
64mod wait;
65
66pub(crate) use factory::get_default_builtins;
67pub use factory::{builtin, simple_builtin, SimpleCommand};
68
69#[macro_export]
78macro_rules! minus_or_plus_flag_arg {
79 ($struct_name:ident, $flag_char:literal, $desc:literal) => {
80 #[derive(clap::Parser)]
81 pub(crate) struct $struct_name {
82 #[arg(short = $flag_char, name = concat!(stringify!($struct_name), "_enable"), action = clap::ArgAction::SetTrue, help = $desc)]
83 _enable: bool,
84 #[arg(long = concat!("+", $flag_char), name = concat!(stringify!($struct_name), "_disable"), action = clap::ArgAction::SetTrue, hide = true)]
85 _disable: bool,
86 }
87
88 impl From<$struct_name> for Option<bool> {
89 fn from(value: $struct_name) -> Self {
90 value.to_bool()
91 }
92 }
93
94 impl $struct_name {
95 #[allow(dead_code)]
96 pub fn is_some(&self) -> bool {
97 self._enable || self._disable
98 }
99
100 pub fn to_bool(&self) -> Option<bool> {
101 match (self._enable, self._disable) {
102 (true, false) => Some(true),
103 (false, true) => Some(false),
104 _ => None,
105 }
106 }
107 }
108 };
109}
110
111pub(crate) use minus_or_plus_flag_arg;
112
113#[allow(clippy::module_name_repetitions)]
115pub struct BuiltinResult {
116 pub exit_code: ExitCode,
118}
119
120pub enum ExitCode {
122 Success,
124 InvalidUsage,
126 Unimplemented,
128 Custom(u8),
130 ExitShell(u8),
132 ReturnFromFunctionOrScript(u8),
135 ContinueLoop(u8),
137 BreakLoop(u8),
139}
140
141impl From<ExecutionResult> for ExitCode {
142 fn from(result: ExecutionResult) -> Self {
143 if let Some(count) = result.continue_loop {
144 ExitCode::ContinueLoop(count)
145 } else if let Some(count) = result.break_loop {
146 ExitCode::BreakLoop(count)
147 } else if result.return_from_function_or_script {
148 ExitCode::ReturnFromFunctionOrScript(result.exit_code)
149 } else if result.exit_shell {
150 ExitCode::ExitShell(result.exit_code)
151 } else if result.exit_code == 0 {
152 ExitCode::Success
153 } else {
154 ExitCode::Custom(result.exit_code)
155 }
156 }
157}
158
159pub type CommandExecuteFunc = fn(
166 commands::ExecutionContext<'_>,
167 Vec<commands::CommandArg>,
168) -> BoxFuture<'_, Result<BuiltinResult, error::Error>>;
169
170pub type CommandContentFunc = fn(&str, ContentType) -> Result<String, error::Error>;
177
178pub trait Command: Parser {
180 fn new<I>(args: I) -> Result<Self, clap::Error>
186 where
187 I: IntoIterator<Item = String>,
188 {
189 if !Self::takes_plus_options() {
190 Self::try_parse_from(args)
191 } else {
192 let mut updated_args = vec![];
195 for arg in args {
196 if let Some(plus_options) = arg.strip_prefix("+") {
197 for c in plus_options.chars() {
198 updated_args.push(format!("--+{c}"));
199 }
200 } else {
201 updated_args.push(arg);
202 }
203 }
204
205 Self::try_parse_from(updated_args)
206 }
207 }
208
209 fn takes_plus_options() -> bool {
211 false
212 }
213
214 fn execute(
221 &self,
222 context: commands::ExecutionContext<'_>,
223 ) -> impl std::future::Future<Output = Result<ExitCode, error::Error>> + std::marker::Send;
224
225 fn get_content(name: &str, content_type: ContentType) -> Result<String, error::Error> {
232 let mut clap_command = Self::command().styles(brush_help_styles());
233 clap_command.set_bin_name(name);
234
235 let s = match content_type {
236 ContentType::DetailedHelp => clap_command.render_long_help().ansi().to_string(),
237 ContentType::ShortUsage => get_builtin_short_usage(name, &clap_command),
238 ContentType::ShortDescription => get_builtin_short_description(name, &clap_command),
239 ContentType::ManPage => get_builtin_man_page(name, &clap_command)?,
240 };
241
242 Ok(s)
243 }
244}
245
246pub trait DeclarationCommand: Command {
249 fn set_declarations(&mut self, declarations: Vec<commands::CommandArg>);
255}
256
257pub enum ContentType {
259 DetailedHelp,
261 ShortUsage,
263 ShortDescription,
265 ManPage,
267}
268
269#[derive(Clone)]
271pub struct Registration {
272 pub execute_func: CommandExecuteFunc,
274
275 pub content_func: CommandContentFunc,
277
278 pub disabled: bool,
280
281 pub special_builtin: bool,
283
284 pub declaration_builtin: bool,
286}
287
288impl Registration {
289 #[must_use]
291 pub fn special(self) -> Self {
292 Self {
293 special_builtin: true,
294 ..self
295 }
296 }
297}
298
299fn get_builtin_man_page(_name: &str, _command: &clap::Command) -> Result<String, error::Error> {
300 error::unimp("man page rendering is not yet implemented")
301}
302
303fn get_builtin_short_description(name: &str, command: &clap::Command) -> String {
304 let about = command
305 .get_about()
306 .map_or_else(String::new, |s| s.to_string());
307
308 std::format!("{name} - {about}\n")
309}
310
311fn get_builtin_short_usage(name: &str, command: &clap::Command) -> String {
312 let mut usage = String::new();
313
314 let mut needs_space = false;
315
316 let mut optional_short_opts = vec![];
317 let mut required_short_opts = vec![];
318 for opt in command.get_opts() {
319 if opt.is_hide_set() {
320 continue;
321 }
322
323 if let Some(c) = opt.get_short() {
324 if !opt.is_required_set() {
325 optional_short_opts.push(c);
326 } else {
327 required_short_opts.push(c);
328 }
329 }
330 }
331
332 if !optional_short_opts.is_empty() {
333 if needs_space {
334 usage.push(' ');
335 }
336
337 usage.push('[');
338 usage.push('-');
339 for c in optional_short_opts {
340 usage.push(c);
341 }
342
343 usage.push(']');
344 needs_space = true;
345 }
346
347 if !required_short_opts.is_empty() {
348 if needs_space {
349 usage.push(' ');
350 }
351
352 usage.push('-');
353 for c in required_short_opts {
354 usage.push(c);
355 }
356
357 needs_space = true;
358 }
359
360 for pos in command.get_positionals() {
361 if pos.is_hide_set() {
362 continue;
363 }
364
365 if !pos.is_required_set() {
366 if needs_space {
367 usage.push(' ');
368 }
369
370 usage.push('[');
371 needs_space = false;
372 }
373
374 if let Some(names) = pos.get_value_names() {
375 for name in names {
376 if needs_space {
377 usage.push(' ');
378 }
379
380 usage.push_str(name);
381 needs_space = true;
382 }
383 }
384
385 if !pos.is_required_set() {
386 usage.push(']');
387 needs_space = true;
388 }
389 }
390
391 std::format!("{name}: {name} {usage}\n")
392}
393
394fn brush_help_styles() -> clap::builder::Styles {
395 styling::Styles::styled()
396 .header(
397 styling::AnsiColor::Yellow.on_default()
398 | styling::Effects::BOLD
399 | styling::Effects::UNDERLINE,
400 )
401 .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
402 .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD)
403 .placeholder(styling::AnsiColor::Cyan.on_default())
404}
405
406pub fn parse_known<T: Parser, S>(
435 args: impl IntoIterator<Item = S>,
436) -> (T, Option<impl Iterator<Item = S>>)
437where
438 S: Into<std::ffi::OsString> + Clone + PartialEq<&'static str>,
439{
440 let mut args = args.into_iter();
441 let mut hyphen = None;
445 let args_before_hyphen = args.by_ref().take_while(|a| {
446 let is_hyphen = *a == "--";
447 if is_hyphen {
448 hyphen = Some(a.clone());
449 }
450 !is_hyphen
451 });
452 let parsed_args = T::parse_from(args_before_hyphen);
453 let raw_args = hyphen.map(|hyphen| std::iter::once(hyphen).chain(args));
454 (parsed_args, raw_args)
455}
456
457pub fn try_parse_known<T: Parser>(
461 args: impl IntoIterator<Item = String>,
462) -> Result<(T, Option<impl Iterator<Item = String>>), clap::Error> {
463 let mut args = args.into_iter();
464 let mut hyphen = None;
465 let args_before_hyphen = args.by_ref().take_while(|a| {
466 let is_hyphen = a == "--";
467 if is_hyphen {
468 hyphen = Some(a.clone());
469 }
470 !is_hyphen
471 });
472 let parsed_args = T::try_parse_from(args_before_hyphen)?;
473
474 let raw_args = hyphen.map(|hyphen| std::iter::once(hyphen).chain(args));
475 Ok((parsed_args, raw_args))
476}