1use clap::Parser;
4use clap::builder::styling;
5use futures::future::BoxFuture;
6
7use crate::ExecutionResult;
8use crate::commands;
9use crate::error;
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;
44#[cfg(any(unix, windows))]
45mod printf;
46mod pushd;
47mod pwd;
48mod read;
49mod return_;
50mod set;
51mod shift;
52mod shopt;
53#[cfg(unix)]
54mod suspend;
55mod test;
56mod times;
57mod trap;
58mod true_;
59mod type_;
60#[cfg(unix)]
61mod ulimit;
62#[cfg(unix)]
63mod umask;
64mod unalias;
65mod unimp;
66mod unset;
67mod wait;
68
69pub(crate) use factory::get_default_builtins;
70pub use factory::{SimpleCommand, builtin, simple_builtin};
71
72#[macro_export]
81macro_rules! minus_or_plus_flag_arg {
82 ($struct_name:ident, $flag_char:literal, $desc:literal) => {
83 #[derive(clap::Parser)]
84 pub(crate) struct $struct_name {
85 #[arg(short = $flag_char, name = concat!(stringify!($struct_name), "_enable"), action = clap::ArgAction::SetTrue, help = $desc)]
86 _enable: bool,
87 #[arg(long = concat!("+", $flag_char), name = concat!(stringify!($struct_name), "_disable"), action = clap::ArgAction::SetTrue, hide = true)]
88 _disable: bool,
89 }
90
91 impl From<$struct_name> for Option<bool> {
92 fn from(value: $struct_name) -> Self {
93 value.to_bool()
94 }
95 }
96
97 impl $struct_name {
98 #[allow(dead_code)]
99 pub const fn is_some(&self) -> bool {
100 self._enable || self._disable
101 }
102
103 pub const fn to_bool(&self) -> Option<bool> {
104 match (self._enable, self._disable) {
105 (true, false) => Some(true),
106 (false, true) => Some(false),
107 _ => None,
108 }
109 }
110 }
111 };
112}
113
114pub(crate) use minus_or_plus_flag_arg;
115
116#[allow(clippy::module_name_repetitions)]
118pub struct BuiltinResult {
119 pub exit_code: ExitCode,
121}
122
123pub enum ExitCode {
125 Success,
127 InvalidUsage,
129 Unimplemented,
131 Custom(u8),
133 ExitShell(u8),
135 ReturnFromFunctionOrScript(u8),
138 ContinueLoop(u8),
140 BreakLoop(u8),
142}
143
144impl From<ExecutionResult> for ExitCode {
145 fn from(result: ExecutionResult) -> Self {
146 if let Some(count) = result.continue_loop {
147 Self::ContinueLoop(count)
148 } else if let Some(count) = result.break_loop {
149 Self::BreakLoop(count)
150 } else if result.return_from_function_or_script {
151 Self::ReturnFromFunctionOrScript(result.exit_code)
152 } else if result.exit_shell {
153 Self::ExitShell(result.exit_code)
154 } else if result.exit_code == 0 {
155 Self::Success
156 } else {
157 Self::Custom(result.exit_code)
158 }
159 }
160}
161
162pub type CommandExecuteFunc = fn(
169 commands::ExecutionContext<'_>,
170 Vec<commands::CommandArg>,
171) -> BoxFuture<'_, Result<BuiltinResult, error::Error>>;
172
173pub type CommandContentFunc = fn(&str, ContentType) -> Result<String, error::Error>;
180
181pub trait Command: Parser {
183 fn new<I>(args: I) -> Result<Self, clap::Error>
189 where
190 I: IntoIterator<Item = String>,
191 {
192 if !Self::takes_plus_options() {
193 Self::try_parse_from(args)
194 } else {
195 let mut updated_args = vec![];
198 for arg in args {
199 if let Some(plus_options) = arg.strip_prefix("+") {
200 for c in plus_options.chars() {
201 updated_args.push(format!("--+{c}"));
202 }
203 } else {
204 updated_args.push(arg);
205 }
206 }
207
208 Self::try_parse_from(updated_args)
209 }
210 }
211
212 fn takes_plus_options() -> bool {
214 false
215 }
216
217 fn execute(
224 &self,
225 context: commands::ExecutionContext<'_>,
226 ) -> impl std::future::Future<Output = Result<ExitCode, error::Error>> + std::marker::Send;
227
228 fn get_content(name: &str, content_type: ContentType) -> Result<String, error::Error> {
235 let mut clap_command = Self::command().styles(brush_help_styles());
236 clap_command.set_bin_name(name);
237
238 let s = match content_type {
239 ContentType::DetailedHelp => clap_command.render_help().ansi().to_string(),
240 ContentType::ShortUsage => get_builtin_short_usage(name, &clap_command),
241 ContentType::ShortDescription => get_builtin_short_description(name, &clap_command),
242 ContentType::ManPage => get_builtin_man_page(name, &clap_command)?,
243 };
244
245 Ok(s)
246 }
247}
248
249pub trait DeclarationCommand: Command {
252 fn set_declarations(&mut self, declarations: Vec<commands::CommandArg>);
258}
259
260pub enum ContentType {
262 DetailedHelp,
264 ShortUsage,
266 ShortDescription,
268 ManPage,
270}
271
272#[derive(Clone)]
274pub struct Registration {
275 pub execute_func: CommandExecuteFunc,
277
278 pub content_func: CommandContentFunc,
280
281 pub disabled: bool,
283
284 pub special_builtin: bool,
286
287 pub declaration_builtin: bool,
289}
290
291impl Registration {
292 #[must_use]
294 pub const fn special(self) -> Self {
295 Self {
296 special_builtin: true,
297 ..self
298 }
299 }
300}
301
302const fn get_builtin_man_page(
303 _name: &str,
304 _command: &clap::Command,
305) -> Result<String, error::Error> {
306 error::unimp("man page rendering is not yet implemented")
307}
308
309fn get_builtin_short_description(name: &str, command: &clap::Command) -> String {
310 let about = command
311 .get_about()
312 .map_or_else(String::new, |s| s.to_string());
313
314 std::format!("{name} - {about}\n")
315}
316
317fn get_builtin_short_usage(name: &str, command: &clap::Command) -> String {
318 let mut usage = String::new();
319
320 let mut needs_space = false;
321
322 let mut optional_short_opts = vec![];
323 let mut required_short_opts = vec![];
324 for opt in command.get_opts() {
325 if opt.is_hide_set() {
326 continue;
327 }
328
329 if let Some(c) = opt.get_short() {
330 if !opt.is_required_set() {
331 optional_short_opts.push(c);
332 } else {
333 required_short_opts.push(c);
334 }
335 }
336 }
337
338 if !optional_short_opts.is_empty() {
339 if needs_space {
340 usage.push(' ');
341 }
342
343 usage.push('[');
344 usage.push('-');
345 for c in optional_short_opts {
346 usage.push(c);
347 }
348
349 usage.push(']');
350 needs_space = true;
351 }
352
353 if !required_short_opts.is_empty() {
354 if needs_space {
355 usage.push(' ');
356 }
357
358 usage.push('-');
359 for c in required_short_opts {
360 usage.push(c);
361 }
362
363 needs_space = true;
364 }
365
366 for pos in command.get_positionals() {
367 if pos.is_hide_set() {
368 continue;
369 }
370
371 if !pos.is_required_set() {
372 if needs_space {
373 usage.push(' ');
374 }
375
376 usage.push('[');
377 needs_space = false;
378 }
379
380 if let Some(names) = pos.get_value_names() {
381 for name in names {
382 if needs_space {
383 usage.push(' ');
384 }
385
386 usage.push_str(name);
387 needs_space = true;
388 }
389 }
390
391 if !pos.is_required_set() {
392 usage.push(']');
393 needs_space = true;
394 }
395 }
396
397 std::format!("{name}: {name} {usage}\n")
398}
399
400fn brush_help_styles() -> clap::builder::Styles {
401 styling::Styles::styled()
402 .header(
403 styling::AnsiColor::Yellow.on_default()
404 | styling::Effects::BOLD
405 | styling::Effects::UNDERLINE,
406 )
407 .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
408 .literal(styling::AnsiColor::Magenta.on_default() | styling::Effects::BOLD)
409 .placeholder(styling::AnsiColor::Cyan.on_default())
410}
411
412pub fn parse_known<T: Parser, S>(
441 args: impl IntoIterator<Item = S>,
442) -> (T, Option<impl Iterator<Item = S>>)
443where
444 S: Into<std::ffi::OsString> + Clone + PartialEq<&'static str>,
445{
446 let mut args = args.into_iter();
447 let mut hyphen = None;
451 let args_before_hyphen = args.by_ref().take_while(|a| {
452 let is_hyphen = *a == "--";
453 if is_hyphen {
454 hyphen = Some(a.clone());
455 }
456 !is_hyphen
457 });
458 let parsed_args = T::parse_from(args_before_hyphen);
459 let raw_args = hyphen.map(|hyphen| std::iter::once(hyphen).chain(args));
460 (parsed_args, raw_args)
461}
462
463pub fn try_parse_known<T: Parser>(
467 args: impl IntoIterator<Item = String>,
468) -> Result<(T, Option<impl Iterator<Item = String>>), clap::Error> {
469 let mut args = args.into_iter();
470 let mut hyphen = None;
471 let args_before_hyphen = args.by_ref().take_while(|a| {
472 let is_hyphen = a == "--";
473 if is_hyphen {
474 hyphen = Some(a.clone());
475 }
476 !is_hyphen
477 });
478 let parsed_args = T::try_parse_from(args_before_hyphen)?;
479
480 let raw_args = hyphen.map(|hyphen| std::iter::once(hyphen).chain(args));
481 Ok((parsed_args, raw_args))
482}