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