Skip to main content

shrub_core/shell/
builder.rs

1//! Module defining the builder for creating shell instances.
2
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6pub use shell_builder::State as ShellBuilderState;
7
8use super::Shell;
9use crate::shell::KeyBindingsHelper;
10use crate::{
11    ProfileLoadBehavior, RcLoadBehavior, ShellFd, ShellVariable, builtins, callstack, completion,
12    env, error, extensions, functions, jobs, openfiles, options, pathcache, traps,
13};
14
15impl<SE: extensions::ShellExtensions, S: shell_builder::IsComplete> ShellBuilder<SE, S> {
16    /// Returns a new shell instance created with the options provided. Runs any
17    /// configuration loading as well.
18    ///
19    /// # Errors
20    ///
21    /// - An I/O error if the working directory is not given and the current
22    ///   directory cannot be determined.
23    /// - An error from initializing the shell's environment, such as inheriting
24    ///   variables or setting the built-in ones.
25    /// - Any error from [`Shell::load_config`], unless both profile and rc
26    ///   loading are skipped.
27    pub async fn build(self) -> Result<Shell<SE>, error::Error> {
28        let mut options = self.build_settings();
29
30        let profile = std::mem::take(&mut options.profile);
31        let rc = std::mem::take(&mut options.rc);
32
33        // Construct the shell.
34        let mut shell = Shell::new(options)?;
35
36        // Load profiles/configuration (and then history), unless skipped.
37        // Callers that skip both are expected to call `load_config`
38        // themselves before needing history.
39        if !profile.skip() || !rc.skip() {
40            shell.load_config(&profile, &rc).await?;
41        }
42
43        Ok(shell)
44    }
45}
46
47impl<SE: extensions::ShellExtensions, S: shell_builder::State> ShellBuilder<SE, S> {
48    /// Add a disabled option
49    pub fn disable_option(mut self, option: impl Into<String>) -> Self {
50        self.disabled_options.push(option.into());
51        self
52    }
53
54    /// Add an enabled option
55    pub fn enable_option(mut self, option: impl Into<String>) -> Self {
56        self.enabled_options.push(option.into());
57        self
58    }
59
60    /// Add many disabled options
61    pub fn disable_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
62        self.disabled_options
63            .extend(options.into_iter().map(Into::into));
64        self
65    }
66
67    /// Add many enabled options
68    pub fn enable_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
69        self.enabled_options
70            .extend(options.into_iter().map(Into::into));
71        self
72    }
73
74    /// Add a disabled shopt option
75    pub fn disable_shopt_option(mut self, option: impl Into<String>) -> Self {
76        self.disabled_shopt_options.push(option.into());
77        self
78    }
79
80    /// Add an enabled shopt option
81    pub fn enable_shopt_option(mut self, option: impl Into<String>) -> Self {
82        self.enabled_shopt_options.push(option.into());
83        self
84    }
85
86    /// Add many disabled shopt options
87    pub fn disable_shopt_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
88        self.disabled_shopt_options
89            .extend(options.into_iter().map(Into::into));
90        self
91    }
92
93    /// Add many enabled shopt options
94    pub fn enable_shopt_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
95        self.enabled_shopt_options
96            .extend(options.into_iter().map(Into::into));
97        self
98    }
99
100    /// Add a single builtin registration
101    pub fn builtin(mut self, name: impl Into<String>, reg: builtins::Registration<SE>) -> Self {
102        self.builtins.insert(name.into(), reg);
103        self
104    }
105
106    /// Add many builtin registrations
107    pub fn builtins(
108        mut self,
109        builtins: impl IntoIterator<Item = (String, builtins::Registration<SE>)>,
110    ) -> Self {
111        self.builtins.extend(builtins);
112        self
113    }
114
115    /// Adds a single variable to be initialized in the shell.
116    pub fn var(mut self, name: impl Into<String>, variable: ShellVariable) -> Self {
117        self.vars.insert(name.into(), variable);
118        self
119    }
120}
121
122/// Options for creating a new shell.
123#[derive(Default, bon::Builder)]
124#[builder(
125    builder_type(
126        name = ShellBuilder,
127        doc {
128        /// Builder for [Shell]
129    }),
130    finish_fn(
131        name = build_settings,
132        vis = "pub(self)",
133    ),
134    start_fn(
135        vis = "pub(self)"
136    )
137)]
138pub struct CreateOptions<SE: extensions::ShellExtensions = extensions::DefaultShellExtensions> {
139    /// Disabled options.
140    #[builder(field)]
141    pub disabled_options: Vec<String>,
142    /// Enabled options.
143    #[builder(field)]
144    pub enabled_options: Vec<String>,
145    /// Disabled shopt options.
146    #[builder(field)]
147    pub disabled_shopt_options: Vec<String>,
148    /// Enabled shopt options.
149    #[builder(field)]
150    pub enabled_shopt_options: Vec<String>,
151    /// Registered builtins.
152    #[builder(field)]
153    pub builtins: HashMap<String, builtins::Registration<SE>>,
154    /// Provides a set of variables to be initialized in the shell. If present,
155    /// they are assigned *after* inherited or well-known variables are set
156    /// (when applicable).
157    #[builder(field)]
158    pub vars: HashMap<String, ShellVariable>,
159    /// Error behavior implementation.
160    #[builder(default)]
161    pub error_formatter: SE::ErrorFormatter,
162    /// Disallow overwriting regular files via output redirection.
163    #[builder(default)]
164    pub disallow_overwriting_regular_files_via_output_redirection: bool,
165    /// Do not execute commands.
166    #[builder(default)]
167    pub do_not_execute_commands: bool,
168    /// Exit after one command.
169    #[builder(default)]
170    pub exit_after_one_command: bool,
171    /// Whether the shell is interactive.
172    #[builder(default)]
173    pub interactive: bool,
174    /// Whether the shell is a login shell.
175    #[builder(default)]
176    pub login: bool,
177    /// Whether to skip using a readline-like interface for input.
178    #[builder(default)]
179    pub no_editing: bool,
180    /// System profile loading behavior.
181    #[builder(default)]
182    pub profile: ProfileLoadBehavior,
183    /// Rc file loading behavior.
184    #[builder(default)]
185    pub rc: RcLoadBehavior,
186    /// Whether to skip inheriting environment variables from the calling
187    /// process.
188    #[builder(default)]
189    pub do_not_inherit_env: bool,
190    /// Whether to skip initializing well-known variables.
191    #[builder(default)]
192    pub skip_well_known_vars: bool,
193    /// Provides a set of initial open files to be tracked by the shell.
194    #[builder(default)]
195    pub fds: HashMap<ShellFd, openfiles::OpenFile>,
196    /// Whether to launch external commands as session leaders.
197    #[builder(default)]
198    pub external_cmd_leads_session: bool,
199    /// Whether externally spawned commands should be killed when their spawning
200    /// shell is dropped. Defaults to `false` (children outlive the shell, as a
201    /// real shell requires); enable for embedded shells, where a surviving
202    /// child leaks and can hold the shell's output pipe open past teardown.
203    #[builder(default)]
204    pub kill_external_commands_on_drop: bool,
205    /// Initial working dir for the shell. If left unspecified, will be
206    /// populated from the host environment.
207    pub working_dir: Option<PathBuf>,
208    /// Whether the shell is in POSIX compliance mode.
209    #[builder(default)]
210    pub posix: bool,
211    /// Whether to print commands and arguments as they are read.
212    #[builder(default)]
213    pub print_commands_and_arguments: bool,
214    /// Whether commands are being read from stdin.
215    #[builder(default)]
216    pub read_commands_from_stdin: bool,
217    /// The name of the shell.
218    pub shell_name: Option<String>,
219    /// Base positional arguments for the shell (not including the shell name).
220    pub shell_args: Option<Vec<String>>,
221    /// Optionally provides a display string describing the version and variant
222    /// of the shell.
223    pub shell_product_display_str: Option<String>,
224    /// Whether to run in maximal POSIX sh compatibility mode.
225    #[builder(default)]
226    pub sh_mode: bool,
227    /// Whether to treat expansion of unset variables as an error.
228    #[builder(default)]
229    pub treat_unset_variables_as_error: bool,
230    /// Whether to enable error-on-exit behavior.
231    #[builder(default)]
232    pub exit_on_nonzero_command_exit: bool,
233    /// Whether to disable pathname expansion.
234    #[builder(default)]
235    pub disable_pathname_expansion: bool,
236    /// Whether to print verbose output.
237    #[builder(default)]
238    pub verbose: bool,
239    /// Parser implementation to use.
240    #[builder(default)]
241    pub parser: crate::parser::ParserImpl,
242    /// Whether the shell is in command string mode (-c).
243    #[builder(default)]
244    pub command_string_mode: bool,
245    /// Maximum function call depth.
246    pub max_function_call_depth: Option<usize>,
247    /// Key bindings helper for the shell to use.
248    pub key_bindings: Option<KeyBindingsHelper>,
249    /// Shrub implementation version.
250    pub shell_version: Option<String>,
251}
252
253impl<SE: extensions::ShellExtensions> Default for Shell<SE> {
254    fn default() -> Self {
255        Self {
256            error_formatter: SE::ErrorFormatter::default(),
257            traps: traps::TrapHandlerConfig::default(),
258            open_files: openfiles::OpenFiles::default(),
259            working_dir: PathBuf::default(),
260            env: env::ShellEnvironment::default(),
261            funcs: functions::FunctionEnv::default(),
262            options: options::RuntimeOptions::default(),
263            jobs: jobs::JobManager::default(),
264            aliases: HashMap::default(),
265            last_exit_status: 0,
266            last_exit_status_change_count: 0,
267            last_pipeline_statuses: vec![0],
268            depth: 0,
269            name: None,
270            args: vec![],
271            version: None,
272            product_display_str: None,
273            call_stack: callstack::CallStack::new(),
274            directory_stack: vec![],
275            completion: completion::State::default(),
276            builtins: HashMap::default(),
277            program_location_cache: pathcache::PathCache::default(),
278            last_stopwatch_time: std::time::SystemTime::now(),
279            last_stopwatch_offset: 0,
280            parser_impl: crate::parser::ParserImpl::default(),
281            key_bindings: None,
282            history: None,
283        }
284    }
285}
286
287impl Shell {
288    /// Create an instance of [Shell] using the builder syntax
289    pub fn builder() -> ShellBuilder<extensions::DefaultShellExtensions, shell_builder::Empty> {
290        CreateOptions::builder()
291    }
292
293    /// Create an instance of [Shell] using the builder syntax, with custom
294    /// extensions.
295    pub fn builder_with_extensions<SE: extensions::ShellExtensions>()
296    -> ShellBuilder<SE, shell_builder::Empty> {
297        CreateOptions::builder()
298    }
299}