brush_core/builtins/
factory.rs

1use futures::future::BoxFuture;
2use std::collections::HashMap;
3use std::io::Write;
4
5#[allow(clippy::wildcard_imports)]
6use super::*;
7
8use crate::builtins;
9use crate::commands::{self, CommandArg};
10use crate::error;
11
12/// A simple command that can be registered as a built-in.
13pub trait SimpleCommand {
14    /// Returns the content of the built-in command.
15    fn get_content(name: &str, content_type: builtins::ContentType)
16        -> Result<String, error::Error>;
17
18    /// Executes the built-in command.
19    fn execute<I: Iterator<Item = S>, S: AsRef<str>>(
20        context: commands::ExecutionContext<'_>,
21        args: I,
22    ) -> Result<builtins::BuiltinResult, error::Error>;
23}
24
25/// Returns a built-in command registration, given an implementation of the
26/// `SimpleCommand` trait.
27pub fn simple_builtin<B: SimpleCommand + Send + Sync>() -> builtins::Registration {
28    builtins::Registration {
29        execute_func: exec_simple_builtin::<B>,
30        content_func: B::get_content,
31        disabled: false,
32        special_builtin: false,
33        declaration_builtin: false,
34    }
35}
36
37/// Returns a built-in command registration, given an implementation of the
38/// `Command` trait.
39pub fn builtin<B: builtins::Command + Send + Sync>() -> builtins::Registration {
40    builtins::Registration {
41        execute_func: exec_builtin::<B>,
42        content_func: get_builtin_content::<B>,
43        disabled: false,
44        special_builtin: false,
45        declaration_builtin: false,
46    }
47}
48
49fn decl_builtin<B: builtins::DeclarationCommand + Send + Sync>() -> builtins::Registration {
50    builtins::Registration {
51        execute_func: exec_declaration_builtin::<B>,
52        content_func: get_builtin_content::<B>,
53        disabled: false,
54        special_builtin: false,
55        declaration_builtin: true,
56    }
57}
58
59fn get_builtin_content<T: builtins::Command + Send + Sync>(
60    name: &str,
61    content_type: builtins::ContentType,
62) -> Result<String, error::Error> {
63    T::get_content(name, content_type)
64}
65
66fn exec_simple_builtin<T: SimpleCommand + Send + Sync>(
67    context: commands::ExecutionContext<'_>,
68    args: Vec<CommandArg>,
69) -> BoxFuture<'_, Result<builtins::BuiltinResult, error::Error>> {
70    Box::pin(async move { exec_simple_builtin_impl::<T>(context, args).await })
71}
72
73#[allow(clippy::unused_async)]
74async fn exec_simple_builtin_impl<T: SimpleCommand + Send + Sync>(
75    context: commands::ExecutionContext<'_>,
76    args: Vec<CommandArg>,
77) -> Result<builtins::BuiltinResult, error::Error> {
78    let plain_args = args.into_iter().map(|arg| match arg {
79        CommandArg::String(s) => s,
80        CommandArg::Assignment(a) => a.to_string(),
81    });
82
83    T::execute(context, plain_args)
84}
85
86fn exec_builtin<T: builtins::Command + Send + Sync>(
87    context: commands::ExecutionContext<'_>,
88    args: Vec<CommandArg>,
89) -> BoxFuture<'_, Result<builtins::BuiltinResult, error::Error>> {
90    Box::pin(async move { exec_builtin_impl::<T>(context, args).await })
91}
92
93async fn exec_builtin_impl<T: builtins::Command + Send + Sync>(
94    context: commands::ExecutionContext<'_>,
95    args: Vec<CommandArg>,
96) -> Result<builtins::BuiltinResult, error::Error> {
97    let plain_args = args.into_iter().map(|arg| match arg {
98        CommandArg::String(s) => s,
99        CommandArg::Assignment(a) => a.to_string(),
100    });
101
102    let result = T::new(plain_args);
103    let command = match result {
104        Ok(command) => command,
105        Err(e) => {
106            writeln!(context.stderr(), "{e}")?;
107            return Ok(builtins::BuiltinResult {
108                exit_code: builtins::ExitCode::InvalidUsage,
109            });
110        }
111    };
112
113    Ok(builtins::BuiltinResult {
114        exit_code: command.execute(context).await?,
115    })
116}
117
118fn exec_declaration_builtin<T: builtins::DeclarationCommand + Send + Sync>(
119    context: commands::ExecutionContext<'_>,
120    args: Vec<CommandArg>,
121) -> BoxFuture<'_, Result<builtins::BuiltinResult, error::Error>> {
122    Box::pin(async move { exec_declaration_builtin_impl::<T>(context, args).await })
123}
124
125async fn exec_declaration_builtin_impl<T: builtins::DeclarationCommand + Send + Sync>(
126    context: commands::ExecutionContext<'_>,
127    args: Vec<CommandArg>,
128) -> Result<builtins::BuiltinResult, error::Error> {
129    let mut options = vec![];
130    let mut declarations = vec![];
131
132    for (i, arg) in args.into_iter().enumerate() {
133        match arg {
134            CommandArg::String(s) if i == 0 || s.starts_with('-') || s.starts_with('+') => {
135                options.push(s);
136            }
137            _ => declarations.push(arg),
138        }
139    }
140
141    let result = T::new(options);
142    let mut command = match result {
143        Ok(command) => command,
144        Err(e) => {
145            writeln!(context.stderr(), "{e}")?;
146            return Ok(builtins::BuiltinResult {
147                exit_code: builtins::ExitCode::InvalidUsage,
148            });
149        }
150    };
151
152    command.set_declarations(declarations);
153
154    Ok(builtins::BuiltinResult {
155        exit_code: command.execute(context).await?,
156    })
157}
158
159#[allow(clippy::too_many_lines)]
160pub(crate) fn get_default_builtins(
161    options: &crate::CreateOptions,
162) -> HashMap<String, builtins::Registration> {
163    let mut m = HashMap::<String, builtins::Registration>::new();
164
165    //
166    // POSIX special builtins
167    //
168    // N.B. There seems to be some inconsistency as to whether 'times'
169    // should be a special built-in.
170    //
171
172    m.insert("break".into(), builtin::<break_::BreakCommand>().special());
173    m.insert(
174        ":".into(),
175        simple_builtin::<colon::ColonCommand>().special(),
176    );
177    m.insert(
178        "continue".into(),
179        builtin::<continue_::ContinueCommand>().special(),
180    );
181    m.insert(".".into(), builtin::<dot::DotCommand>().special());
182    m.insert("eval".into(), builtin::<eval::EvalCommand>().special());
183    #[cfg(unix)]
184    m.insert("exec".into(), builtin::<exec::ExecCommand>().special());
185    m.insert("exit".into(), builtin::<exit::ExitCommand>().special());
186    m.insert(
187        "export".into(),
188        decl_builtin::<export::ExportCommand>().special(),
189    );
190    m.insert(
191        "return".into(),
192        builtin::<return_::ReturnCommand>().special(),
193    );
194    m.insert("set".into(), builtin::<set::SetCommand>().special());
195    m.insert("shift".into(), builtin::<shift::ShiftCommand>().special());
196    m.insert("trap".into(), builtin::<trap::TrapCommand>().special());
197    m.insert("unset".into(), builtin::<unset::UnsetCommand>().special());
198
199    m.insert(
200        "readonly".into(),
201        decl_builtin::<declare::DeclareCommand>().special(),
202    );
203    m.insert("times".into(), builtin::<times::TimesCommand>().special());
204
205    //
206    // Non-special builtins
207    //
208
209    m.insert("alias".into(), builtin::<alias::AliasCommand>()); // TODO: should be exec_declaration_builtin
210    m.insert("bg".into(), builtin::<bg::BgCommand>());
211    m.insert("cd".into(), builtin::<cd::CdCommand>());
212    m.insert("command".into(), builtin::<command::CommandCommand>());
213    m.insert("false".into(), builtin::<false_::FalseCommand>());
214    m.insert("fg".into(), builtin::<fg::FgCommand>());
215    m.insert("getopts".into(), builtin::<getopts::GetOptsCommand>());
216    m.insert("hash".into(), builtin::<hash::HashCommand>());
217    m.insert("help".into(), builtin::<help::HelpCommand>());
218    m.insert("jobs".into(), builtin::<jobs::JobsCommand>());
219    #[cfg(unix)]
220    m.insert("kill".into(), builtin::<kill::KillCommand>());
221    m.insert("local".into(), decl_builtin::<declare::DeclareCommand>());
222    m.insert("pwd".into(), builtin::<pwd::PwdCommand>());
223    m.insert("read".into(), builtin::<read::ReadCommand>());
224    m.insert("true".into(), builtin::<true_::TrueCommand>());
225    m.insert("type".into(), builtin::<type_::TypeCommand>());
226    #[cfg(unix)]
227    m.insert("umask".into(), builtin::<umask::UmaskCommand>());
228    m.insert("unalias".into(), builtin::<unalias::UnaliasCommand>());
229    m.insert("wait".into(), builtin::<wait::WaitCommand>());
230
231    // TODO: Unimplemented non-special builtins
232    m.insert("fc".into(), builtin::<unimp::UnimplementedCommand>());
233    m.insert("ulimit".into(), builtin::<unimp::UnimplementedCommand>());
234
235    if !options.sh_mode {
236        m.insert("builtin".into(), builtin::<builtin_::BuiltinCommand>());
237        m.insert("declare".into(), decl_builtin::<declare::DeclareCommand>());
238        m.insert("echo".into(), builtin::<echo::EchoCommand>());
239        m.insert("enable".into(), builtin::<enable::EnableCommand>());
240        m.insert("let".into(), builtin::<let_::LetCommand>());
241        m.insert("mapfile".into(), builtin::<mapfile::MapFileCommand>());
242        m.insert("printf".into(), builtin::<printf::PrintfCommand>());
243        m.insert("shopt".into(), builtin::<shopt::ShoptCommand>());
244        m.insert("source".into(), builtin::<dot::DotCommand>().special());
245        #[cfg(unix)]
246        m.insert("suspend".into(), builtin::<suspend::SuspendCommand>());
247        m.insert("test".into(), builtin::<test::TestCommand>());
248        m.insert("[".into(), builtin::<test::TestCommand>());
249        m.insert("typeset".into(), builtin::<declare::DeclareCommand>());
250
251        // Completion builtins
252        m.insert("complete".into(), builtin::<complete::CompleteCommand>());
253        m.insert("compgen".into(), builtin::<complete::CompGenCommand>());
254        m.insert("compopt".into(), builtin::<complete::CompOptCommand>());
255
256        // Dir stack builtins
257        m.insert("dirs".into(), builtin::<dirs::DirsCommand>());
258        m.insert("popd".into(), builtin::<popd::PopdCommand>());
259        m.insert("pushd".into(), builtin::<pushd::PushdCommand>());
260
261        // Input configuration builtins
262        m.insert("bind".into(), builtin::<bind::BindCommand>());
263
264        // TODO: Unimplemented builtins
265        m.insert("caller".into(), builtin::<unimp::UnimplementedCommand>());
266        m.insert("disown".into(), builtin::<unimp::UnimplementedCommand>());
267        m.insert("history".into(), builtin::<unimp::UnimplementedCommand>());
268        m.insert("logout".into(), builtin::<unimp::UnimplementedCommand>());
269        m.insert("readarray".into(), builtin::<unimp::UnimplementedCommand>());
270    }
271
272    //
273    // Brush-specific builtins.
274    //
275    m.insert("brushinfo".into(), builtin::<brushinfo::BrushInfoCommand>());
276
277    m
278}