Skip to main content

frost_builtins/
lib.rs

1//! Built-in shell commands for frost.
2//!
3//! Provides a [`Builtin`] trait, a [`ShellEnvironment`] trait (so builtins
4//! stay decoupled from the executor), and a [`BuiltinRegistry`] that maps
5//! command names to their implementations.
6
7mod cd;
8mod colon;
9mod command;
10mod echo;
11mod eval;
12mod exit;
13mod export;
14mod print;
15mod read_builtin;
16mod readonly;
17mod return_builtin;
18mod set;
19mod setopt;
20mod stubs;
21mod test_builtin;
22mod true_false;
23mod typeset;
24mod unset;
25mod whence;
26
27use std::collections::HashMap;
28
29// ── Shell environment trait ──────────────────────────────────────────
30
31/// Trait that builtins use to interact with the shell environment.
32///
33/// Defined here (rather than in `frost-exec`) so builtins have no
34/// dependency on the execution engine.
35pub trait ShellEnvironment {
36    fn get_var(&self, name: &str) -> Option<&str>;
37    fn set_var(&mut self, name: &str, value: &str);
38    fn export_var(&mut self, name: &str);
39    fn unset_var(&mut self, name: &str);
40    fn exit_status(&self) -> i32;
41    fn set_exit_status(&mut self, status: i32);
42    fn chdir(&mut self, path: &str) -> Result<(), String>;
43    fn home_dir(&self) -> Option<&str>;
44}
45
46// ── Builtin trait ────────────────────────────────────────────────────
47
48/// A single built-in command.
49pub trait Builtin: Send + Sync {
50    /// The command name (e.g. `"cd"`, `"echo"`).
51    fn name(&self) -> &str;
52
53    /// Execute the builtin with the given arguments and environment.
54    ///
55    /// Returns an exit status (0 = success).
56    fn execute(&self, args: &[&str], env: &mut dyn ShellEnvironment) -> i32;
57}
58
59// ── Registry ─────────────────────────────────────────────────────────
60
61/// A registry mapping command names to [`Builtin`] implementations.
62pub struct BuiltinRegistry {
63    builtins: HashMap<String, Box<dyn Builtin>>,
64}
65
66impl BuiltinRegistry {
67    /// Create an empty registry.
68    pub fn new() -> Self {
69        Self {
70            builtins: HashMap::new(),
71        }
72    }
73
74    /// Register a builtin.
75    pub fn register(&mut self, builtin: Box<dyn Builtin>) {
76        self.builtins.insert(builtin.name().to_owned(), builtin);
77    }
78
79    /// Look up a builtin by name.
80    pub fn get(&self, name: &str) -> Option<&dyn Builtin> {
81        self.builtins.get(name).map(|b| b.as_ref())
82    }
83
84    /// Whether `name` is a registered builtin.
85    pub fn contains(&self, name: &str) -> bool {
86        self.builtins.contains_key(name)
87    }
88
89    /// Iterate over all registered builtins.
90    pub fn iter(&self) -> impl Iterator<Item = &dyn Builtin> {
91        self.builtins.values().map(|b| b.as_ref())
92    }
93}
94
95impl Default for BuiltinRegistry {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101// ── Default set ──────────────────────────────────────────────────────
102
103/// Build a registry populated with the standard builtins.
104pub fn default_builtins() -> BuiltinRegistry {
105    let mut reg = BuiltinRegistry::new();
106    reg.register(Box::new(cd::Cd));
107    reg.register(Box::new(colon::Colon));
108    reg.register(Box::new(command::CommandBuiltin));
109    reg.register(Box::new(echo::Echo));
110    reg.register(Box::new(eval::Eval));
111    reg.register(Box::new(exit::Exit));
112    reg.register(Box::new(export::Export));
113    reg.register(Box::new(print::Print));
114    reg.register(Box::new(read_builtin::ReadBuiltin));
115    reg.register(Box::new(return_builtin::Return));
116    reg.register(Box::new(set::Set));
117    reg.register(Box::new(set::Shift));
118    reg.register(Box::new(test_builtin::Test));
119    reg.register(Box::new(test_builtin::TestKeyword));
120    reg.register(Box::new(true_false::True));
121    reg.register(Box::new(true_false::False));
122    reg.register(Box::new(typeset::Typeset));
123    reg.register(Box::new(typeset::Local));
124    reg.register(Box::new(typeset::Declare));
125    reg.register(Box::new(unset::Unset));
126    reg.register(Box::new(whence::Whence));
127    reg.register(Box::new(whence::Which));
128    reg.register(Box::new(setopt::Setopt));
129    reg.register(Box::new(setopt::Unsetopt));
130    reg.register(Box::new(readonly::Readonly));
131    // Stubs — accept arguments silently and succeed.
132    reg.register(Box::new(stubs::Autoload));
133    reg.register(Box::new(stubs::Zmodload));
134    reg.register(Box::new(stubs::Integer));
135    reg.register(Box::new(stubs::Float));
136    reg.register(Box::new(stubs::Let));
137    reg.register(Box::new(stubs::Trap));
138    reg.register(Box::new(stubs::Hash));
139    reg.register(Box::new(stubs::Disable));
140    reg.register(Box::new(stubs::Enable));
141    reg.register(Box::new(stubs::Emulate));
142    reg.register(Box::new(stubs::Zle));
143    reg.register(Box::new(stubs::Bindkey));
144    reg.register(Box::new(stubs::Compdef));
145    reg.register(Box::new(stubs::Zstyle));
146    reg.register(Box::new(stubs::Alias));
147    reg.register(Box::new(stubs::Unalias));
148    reg.register(Box::new(stubs::BuiltinCmd));
149    reg.register(Box::new(stubs::Wait));
150    reg.register(Box::new(stubs::Fg));
151    reg.register(Box::new(stubs::Bg));
152    reg.register(Box::new(stubs::Jobs));
153    reg.register(Box::new(stubs::Suspend));
154    reg.register(Box::new(stubs::Times));
155    reg.register(Box::new(stubs::Umask));
156    reg.register(Box::new(stubs::Ulimit));
157    reg.register(Box::new(stubs::Getopts));
158    reg.register(Box::new(stubs::Pushd));
159    reg.register(Box::new(stubs::Popd));
160    reg.register(Box::new(stubs::Dirs));
161    reg.register(Box::new(stubs::Limit));
162    reg.register(Box::new(stubs::Unlimit));
163    reg.register(Box::new(stubs::Sched));
164    reg.register(Box::new(stubs::Rehash));
165    reg.register(Box::new(stubs::Noglob));
166    reg
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn default_registry_contains_expected_builtins() {
175        let reg = default_builtins();
176        assert!(reg.contains("cd"));
177        assert!(reg.contains(":"));
178        assert!(reg.contains("command"));
179        assert!(reg.contains("echo"));
180        assert!(reg.contains("eval"));
181        assert!(reg.contains("exit"));
182        assert!(reg.contains("export"));
183        assert!(reg.contains("print"));
184        assert!(reg.contains("read"));
185        assert!(reg.contains("return"));
186        assert!(reg.contains("set"));
187        assert!(reg.contains("shift"));
188        assert!(reg.contains("["));
189        assert!(reg.contains("test"));
190        assert!(reg.contains("true"));
191        assert!(reg.contains("false"));
192        assert!(reg.contains("typeset"));
193        assert!(reg.contains("local"));
194        assert!(reg.contains("declare"));
195        assert!(reg.contains("unset"));
196        assert!(reg.contains("whence"));
197        assert!(reg.contains("which"));
198        assert!(!reg.contains("nonexistent"));
199    }
200}