brush_core/
options.rs

1//! Defines runtime options for the shell.
2
3use itertools::Itertools;
4
5use crate::{CreateOptions, namedoptions};
6
7/// Runtime changeable options for a shell instance.
8#[derive(Clone, Default)]
9#[expect(clippy::module_name_repetitions)]
10pub struct RuntimeOptions {
11    //
12    // Single-character options.
13    /// -a
14    pub export_variables_on_modification: bool,
15    /// -b
16    pub notify_job_termination_immediately: bool,
17    /// -e
18    pub exit_on_nonzero_command_exit: bool,
19    /// -f
20    pub disable_filename_globbing: bool,
21    /// -h
22    pub remember_command_locations: bool,
23    /// -k
24    pub place_all_assignment_args_in_command_env: bool,
25    /// -m
26    pub enable_job_control: bool,
27    /// -n
28    pub do_not_execute_commands: bool,
29    /// -p
30    pub real_effective_uid_mismatch: bool,
31    /// -t
32    pub exit_after_one_command: bool,
33    /// -u
34    pub treat_unset_variables_as_error: bool,
35    /// -v
36    pub print_shell_input_lines: bool,
37    /// -x
38    pub print_commands_and_arguments: bool,
39    /// -B
40    pub perform_brace_expansion: bool,
41    /// -C
42    pub disallow_overwriting_regular_files_via_output_redirection: bool,
43    /// -E
44    pub shell_functions_inherit_err_trap: bool,
45    /// -H
46    pub enable_bang_style_history_substitution: bool,
47    /// -P
48    pub do_not_resolve_symlinks_when_changing_dir: bool,
49    /// -T
50    pub shell_functions_inherit_debug_and_return_traps: bool,
51
52    //
53    // Options set through -o.
54    /// 'emacs'
55    pub emacs_mode: bool,
56    /// 'history'
57    pub enable_command_history: bool,
58    /// 'ignoreeof'
59    pub ignore_eof: bool,
60    /// 'pipefail'
61    pub return_first_failure_from_pipeline: bool,
62    /// 'posix'
63    pub posix_mode: bool,
64    /// 'vi'
65    pub vi_mode: bool,
66
67    //
68    // Options set through shopt.
69    /// `assoc_expand_once`
70    pub assoc_expand_once: bool,
71    /// 'autocd'
72    pub auto_cd: bool,
73    /// `cdable_vars`
74    pub cdable_vars: bool,
75    /// 'cdspell'
76    pub cd_autocorrect_spelling: bool,
77    /// 'checkhash'
78    pub check_hashtable_before_command_exec: bool,
79    /// 'checkjobs'
80    pub check_jobs_before_exit: bool,
81    /// 'checkwinsize'
82    pub check_window_size_after_external_commands: bool,
83    /// 'cmdhist'
84    pub save_multiline_cmds_in_history: bool,
85    /// 'compat31'
86    pub compat31: bool,
87    /// 'compat32'
88    pub compat32: bool,
89    /// 'compat40'
90    pub compat40: bool,
91    /// 'compat41'
92    pub compat41: bool,
93    /// 'compat42'
94    pub compat42: bool,
95    /// 'compat43'
96    pub compat43: bool,
97    /// 'compat44'
98    pub compat44: bool,
99    /// `complete_fullquote`
100    pub quote_all_metachars_in_completion: bool,
101    /// 'direxpand'
102    pub expand_dir_names_on_completion: bool,
103    /// 'dirspell'
104    pub autocorrect_dir_spelling_on_completion: bool,
105    /// 'dotglob'
106    pub glob_matches_dotfiles: bool,
107    /// 'execfail'
108    pub exit_on_exec_fail: bool,
109    /// `expand_aliases`
110    pub expand_aliases: bool,
111    /// 'extdebug'
112    pub enable_debugger: bool,
113    /// 'extglob'
114    pub extended_globbing: bool,
115    /// 'extquote'
116    pub extquote: bool,
117    /// 'failglob'
118    pub fail_expansion_on_globs_without_match: bool,
119    /// `force_fignore`
120    pub force_fignore: bool,
121    /// 'globasciiranges'
122    pub glob_ranges_use_c_locale: bool,
123    /// 'globstar'
124    pub enable_star_star_glob: bool,
125    /// `gnu_errfmt`
126    pub errors_in_gnu_format: bool,
127    /// 'histappend'
128    pub append_to_history_file: bool,
129    /// 'histreedit'
130    pub allow_reedit_failed_history_subst: bool,
131    /// 'histverify'
132    pub allow_modifying_history_substitution: bool,
133    /// 'hostcomplete'
134    pub enable_hostname_completion: bool,
135    /// 'huponexit'
136    pub send_sighup_to_all_jobs_on_exit: bool,
137    /// `inherit_errexit`
138    pub command_subst_inherits_errexit: bool,
139    /// `interactive_comments`
140    pub interactive_comments: bool,
141    /// 'lastpipe'
142    pub run_last_pipeline_cmd_in_current_shell: bool,
143    /// 'lithist'
144    pub embed_newlines_in_multiline_cmds_in_history: bool,
145    /// `localvar_inherit`
146    pub local_vars_inherit_value_and_attrs: bool,
147    /// `localvar_unset`
148    pub localvar_unset: bool,
149    /// `login_shell`
150    pub login_shell: bool,
151    /// 'mailwarn'
152    pub mail_warn: bool,
153    /// `no_empty_cmd_completion`
154    pub no_empty_cmd_completion: bool,
155    /// 'nocaseglob'
156    pub case_insensitive_pathname_expansion: bool,
157    /// 'nocasematch'
158    pub case_insensitive_conditionals: bool,
159    /// 'nullglob'
160    pub expand_non_matching_patterns_to_null: bool,
161    /// 'progcomp'
162    pub programmable_completion: bool,
163    /// `progcomp_alias`
164    pub programmable_completion_alias: bool,
165    /// 'promptvars'
166    pub expand_prompt_strings: bool,
167    /// `restricted_shell`
168    pub restricted_shell: bool,
169    /// `shift_verbose`
170    pub shift_verbose: bool,
171    /// `sourcepath`
172    pub source_builtin_searches_path: bool,
173    /// `xpg_echo`
174    pub echo_builtin_expands_escape_sequences: bool,
175
176    //
177    // Options set by the shell.
178    /// Whether or not the shell is interactive.
179    pub interactive: bool,
180    /// Whether or not the shell is reading commands from standard input.
181    pub read_commands_from_stdin: bool,
182    /// Whether or not the shell is in maximal `sh` compatibility mode.    
183    pub sh_mode: bool,
184    /// Maximum function call depth.
185    pub max_function_call_depth: Option<usize>,
186}
187
188impl RuntimeOptions {
189    /// Creates a default set of runtime options based on the given creation options.
190    ///
191    /// # Arguments
192    ///
193    /// * `create_options` - The options used to create the shell.
194    pub fn defaults_from(create_options: &CreateOptions) -> Self {
195        // There's a set of options enabled by default for all shells.
196        let mut options = Self {
197            interactive: create_options.interactive,
198            disallow_overwriting_regular_files_via_output_redirection: create_options
199                .disallow_overwriting_regular_files_via_output_redirection,
200            do_not_execute_commands: create_options.do_not_execute_commands,
201            enable_command_history: create_options.interactive,
202            enable_job_control: create_options.interactive,
203            exit_after_one_command: create_options.exit_after_one_command,
204            read_commands_from_stdin: create_options.read_commands_from_stdin,
205            sh_mode: create_options.sh_mode,
206            posix_mode: create_options.posix,
207            print_commands_and_arguments: create_options.print_commands_and_arguments,
208            print_shell_input_lines: create_options.verbose,
209            remember_command_locations: true,
210            check_window_size_after_external_commands: true,
211            save_multiline_cmds_in_history: true,
212            extquote: true,
213            force_fignore: true,
214            enable_hostname_completion: true,
215            interactive_comments: true,
216            expand_prompt_strings: true,
217            source_builtin_searches_path: true,
218            perform_brace_expansion: true,
219            quote_all_metachars_in_completion: true,
220            programmable_completion: true,
221            glob_ranges_use_c_locale: true,
222            max_function_call_depth: create_options.max_function_call_depth,
223            ..Self::default()
224        };
225
226        // Additional options are enabled by default for interactive shells.
227        if create_options.interactive {
228            options.enable_bang_style_history_substitution = true;
229            options.emacs_mode = !create_options.no_editing;
230            options.expand_aliases = true;
231        }
232
233        // Update any options.
234        for enabled_option in &create_options.enabled_options {
235            if let Some(option) = namedoptions::options(namedoptions::ShellOptionKind::SetO)
236                .get(enabled_option.as_str())
237            {
238                option.set(&mut options, true);
239            }
240        }
241        for disabled_option in &create_options.disabled_options {
242            if let Some(option) = namedoptions::options(namedoptions::ShellOptionKind::SetO)
243                .get(disabled_option.as_str())
244            {
245                option.set(&mut options, false);
246            }
247        }
248
249        // Update any shopt options.
250        for enabled_option in &create_options.enabled_shopt_options {
251            if let Some(shopt_option) = namedoptions::options(namedoptions::ShellOptionKind::Shopt)
252                .get(enabled_option.as_str())
253            {
254                shopt_option.set(&mut options, true);
255            }
256        }
257        for disabled_option in &create_options.disabled_shopt_options {
258            if let Some(shopt_option) = namedoptions::options(namedoptions::ShellOptionKind::Shopt)
259                .get(disabled_option.as_str())
260            {
261                shopt_option.set(&mut options, false);
262            }
263        }
264
265        options
266    }
267
268    /// Returns a string representing the current `set`-style option flags set in the shell.
269    pub fn option_flags(&self) -> String {
270        let mut cs = vec![];
271
272        for o in namedoptions::options(namedoptions::ShellOptionKind::Set).iter() {
273            if o.definition.get(self) {
274                cs.push(o.name.chars().next().unwrap());
275            }
276        }
277
278        // Sort the flags in a way that matches what bash does.
279        cs.sort_by(|a, b| {
280            if a == b {
281                std::cmp::Ordering::Equal
282            } else if *a == 's' {
283                std::cmp::Ordering::Greater
284            } else if *b == 's' {
285                std::cmp::Ordering::Less
286            } else if a.is_ascii_lowercase() && b.is_ascii_uppercase() {
287                std::cmp::Ordering::Less
288            } else if a.is_ascii_uppercase() && b.is_ascii_lowercase() {
289                std::cmp::Ordering::Greater
290            } else {
291                a.cmp(b)
292            }
293        });
294
295        cs.into_iter().collect()
296    }
297
298    /// Returns a colon-separated list of sorted 'set -o' options enabled.
299    pub fn seto_optstr(&self) -> String {
300        let mut cs = vec![];
301
302        for option in namedoptions::options(namedoptions::ShellOptionKind::SetO).iter() {
303            if option.definition.get(self) {
304                cs.push(option.name);
305            }
306        }
307
308        cs.sort_unstable();
309        cs.into_iter().join(":")
310    }
311
312    /// Returns a colon-separated list of sorted 'shopt' options enabled.
313    pub fn shopt_optstr(&self) -> String {
314        let mut cs = vec![];
315
316        for option in namedoptions::options(namedoptions::ShellOptionKind::Shopt).iter() {
317            if option.definition.get(self) {
318                cs.push(option.name);
319            }
320        }
321
322        cs.sort_unstable();
323        cs.into_iter().join(":")
324    }
325}