Skip to main content

shrub_core/
namedoptions.rs

1//! Defines shell options.
2
3use std::collections::HashMap;
4use std::sync::LazyLock;
5
6use crate::options::RuntimeOptions;
7
8type OptionGetter = fn(shell: &RuntimeOptions) -> bool;
9type OptionSetter = fn(shell: &mut RuntimeOptions, value: bool) -> ();
10
11/// Defines an option.
12pub struct ShellOptionDef {
13    /// Getter function that retrieves the current value of the option.
14    getter: OptionGetter,
15    /// Setter function that may be used to set the current value of the option.
16    setter: OptionSetter,
17}
18
19impl ShellOptionDef {
20    /// Constructs a new option definition.
21    ///
22    /// # Arguments
23    ///
24    /// * `getter` - A function that retrieves the current value of the option.
25    /// * `setter` - A function that sets the current value of the option.
26    fn new(getter: OptionGetter, setter: OptionSetter) -> Self {
27        Self { getter, setter }
28    }
29
30    /// Retrieves the current value of this option from the given runtime
31    /// options.
32    ///
33    /// # Arguments
34    ///
35    /// * `options` - The runtime options to retrieve the value from.
36    pub fn get(&self, options: &RuntimeOptions) -> bool {
37        (self.getter)(options)
38    }
39
40    /// Sets the value of this option in the given runtime options.
41    ///
42    /// # Arguments
43    ///
44    /// * `options` - The runtime options to modify.
45    /// * `value` - The new value to set for the option.
46    pub fn set(&self, options: &mut RuntimeOptions, value: bool) {
47        (self.setter)(options, value);
48    }
49}
50
51/// Describes a shell option.
52pub struct ShellOption {
53    /// The name of the option.
54    pub name: &'static str,
55    /// The definition of the option.
56    pub definition: &'static ShellOptionDef,
57}
58
59/// Describes a set of shell options.
60pub struct ShellOptionSet {
61    inner: &'static HashMap<&'static str, ShellOptionDef>,
62}
63
64/// Kind of shell option.
65#[derive(Clone, Copy)]
66pub enum ShellOptionKind {
67    /// `set` option.
68    Set,
69    /// `set -o` option.
70    SetO,
71    /// `shopt` option.
72    Shopt,
73}
74
75/// Returns the options for the given shell option kind.
76///
77/// # Arguments
78///
79/// * `kind` - The kind of shell options to retrieve.
80pub fn options(kind: ShellOptionKind) -> ShellOptionSet {
81    match kind {
82        ShellOptionKind::Set => ShellOptionSet {
83            inner: &SET_OPTIONS,
84        },
85        ShellOptionKind::SetO => ShellOptionSet {
86            inner: &SET_O_OPTIONS,
87        },
88        ShellOptionKind::Shopt => ShellOptionSet {
89            inner: &SHOPT_OPTIONS,
90        },
91    }
92}
93
94impl ShellOptionSet {
95    /// Returns an iterator over the options defined in this set.
96    pub fn iter(&self) -> impl Iterator<Item = ShellOption> {
97        self.inner
98            .iter()
99            .map(|(&name, definition)| ShellOption { name, definition })
100    }
101
102    /// Returns the option with the given name, if it exists.
103    ///
104    /// # Arguments
105    ///
106    /// * `name` - The name of the option to retrieve.
107    pub fn get(&self, name: &str) -> Option<&'static ShellOptionDef> {
108        self.inner.get(name)
109    }
110}
111
112static SET_OPTIONS: LazyLock<HashMap<&'static str, ShellOptionDef>> = LazyLock::new(|| {
113    HashMap::from([
114        (
115            "a",
116            ShellOptionDef::new(
117                |options| options.export_variables_on_modification,
118                |options, value| options.export_variables_on_modification = value,
119            ),
120        ),
121        (
122            "b",
123            ShellOptionDef::new(
124                |options| options.notify_job_termination_immediately,
125                |options, value| options.notify_job_termination_immediately = value,
126            ),
127        ),
128        (
129            "c",
130            ShellOptionDef::new(
131                |options| options.command_string_mode,
132                |options, value| options.command_string_mode = value,
133            ),
134        ),
135        (
136            "e",
137            ShellOptionDef::new(
138                |options| options.exit_on_nonzero_command_exit,
139                |options, value| options.exit_on_nonzero_command_exit = value,
140            ),
141        ),
142        (
143            "f",
144            ShellOptionDef::new(
145                |options| options.disable_filename_globbing,
146                |options, value| options.disable_filename_globbing = value,
147            ),
148        ),
149        (
150            "h",
151            ShellOptionDef::new(
152                |options| options.remember_command_locations,
153                |options, value| options.remember_command_locations = value,
154            ),
155        ),
156        (
157            "i",
158            ShellOptionDef::new(
159                |options| options.interactive,
160                |options, value| options.interactive = value,
161            ),
162        ),
163        (
164            "k",
165            ShellOptionDef::new(
166                |options| options.place_all_assignment_args_in_command_env,
167                |options, value| options.place_all_assignment_args_in_command_env = value,
168            ),
169        ),
170        (
171            "m",
172            ShellOptionDef::new(
173                |options| options.enable_job_control,
174                |options, value| options.enable_job_control = value,
175            ),
176        ),
177        (
178            "n",
179            ShellOptionDef::new(
180                |options| options.do_not_execute_commands,
181                |options, value| options.do_not_execute_commands = value,
182            ),
183        ),
184        (
185            "p",
186            ShellOptionDef::new(
187                |options| options.real_effective_uid_mismatch,
188                |options, value| options.real_effective_uid_mismatch = value,
189            ),
190        ),
191        (
192            "t",
193            ShellOptionDef::new(
194                |options| options.exit_after_one_command,
195                |options, value| options.exit_after_one_command = value,
196            ),
197        ),
198        (
199            "u",
200            ShellOptionDef::new(
201                |options| options.treat_unset_variables_as_error,
202                |options, value| options.treat_unset_variables_as_error = value,
203            ),
204        ),
205        (
206            "v",
207            ShellOptionDef::new(
208                |options| options.print_shell_input_lines,
209                |options, value| options.print_shell_input_lines = value,
210            ),
211        ),
212        (
213            "x",
214            ShellOptionDef::new(
215                |options| options.print_commands_and_arguments,
216                |options, value| options.print_commands_and_arguments = value,
217            ),
218        ),
219        (
220            "B",
221            ShellOptionDef::new(
222                |options| options.perform_brace_expansion,
223                |options, value| options.perform_brace_expansion = value,
224            ),
225        ),
226        (
227            "C",
228            ShellOptionDef::new(
229                |options| options.disallow_overwriting_regular_files_via_output_redirection,
230                |options, value| {
231                    options.disallow_overwriting_regular_files_via_output_redirection = value;
232                },
233            ),
234        ),
235        (
236            "E",
237            ShellOptionDef::new(
238                |options| options.shell_functions_inherit_err_trap,
239                |options, value| options.shell_functions_inherit_err_trap = value,
240            ),
241        ),
242        (
243            "H",
244            ShellOptionDef::new(
245                |options| options.enable_bang_style_history_substitution,
246                |options, value| options.enable_bang_style_history_substitution = value,
247            ),
248        ),
249        (
250            "P",
251            ShellOptionDef::new(
252                |options| options.do_not_resolve_symlinks_when_changing_dir,
253                |options, value| options.do_not_resolve_symlinks_when_changing_dir = value,
254            ),
255        ),
256        (
257            "T",
258            ShellOptionDef::new(
259                |options| options.shell_functions_inherit_debug_and_return_traps,
260                |options, value| options.shell_functions_inherit_debug_and_return_traps = value,
261            ),
262        ),
263        (
264            "s",
265            ShellOptionDef::new(
266                |options| options.read_commands_from_stdin,
267                |options, value| options.read_commands_from_stdin = value,
268            ),
269        ),
270    ])
271});
272
273static SET_O_OPTIONS: LazyLock<HashMap<&'static str, ShellOptionDef>> = LazyLock::new(|| {
274    HashMap::from([
275        (
276            "allexport",
277            ShellOptionDef::new(
278                |options| options.export_variables_on_modification,
279                |options, value| options.export_variables_on_modification = value,
280            ),
281        ),
282        (
283            "braceexpand",
284            ShellOptionDef::new(
285                |options| options.perform_brace_expansion,
286                |options, value| options.perform_brace_expansion = value,
287            ),
288        ),
289        (
290            "emacs",
291            ShellOptionDef::new(
292                |options| options.emacs_mode,
293                |options, value| options.emacs_mode = value,
294            ),
295        ),
296        (
297            "errexit",
298            ShellOptionDef::new(
299                |options| options.exit_on_nonzero_command_exit,
300                |options, value| options.exit_on_nonzero_command_exit = value,
301            ),
302        ),
303        (
304            "errtrace",
305            ShellOptionDef::new(
306                |options| options.shell_functions_inherit_err_trap,
307                |options, value| options.shell_functions_inherit_err_trap = value,
308            ),
309        ),
310        (
311            "functrace",
312            ShellOptionDef::new(
313                |options| options.shell_functions_inherit_debug_and_return_traps,
314                |options, value| options.shell_functions_inherit_debug_and_return_traps = value,
315            ),
316        ),
317        (
318            "hashall",
319            ShellOptionDef::new(
320                |options| options.remember_command_locations,
321                |options, value| options.remember_command_locations = value,
322            ),
323        ),
324        (
325            "histexpand",
326            ShellOptionDef::new(
327                |options| options.enable_bang_style_history_substitution,
328                |options, value| options.enable_bang_style_history_substitution = value,
329            ),
330        ),
331        (
332            "history",
333            ShellOptionDef::new(
334                |options| options.enable_command_history,
335                |options, value| options.enable_command_history = value,
336            ),
337        ),
338        (
339            "ignoreeof",
340            ShellOptionDef::new(
341                |options| options.ignore_eof,
342                |options, value| options.ignore_eof = value,
343            ),
344        ),
345        (
346            "interactive-comments",
347            ShellOptionDef::new(
348                |options| options.interactive_comments,
349                |options, value| options.interactive_comments = value,
350            ),
351        ),
352        (
353            "keyword",
354            ShellOptionDef::new(
355                |options| options.place_all_assignment_args_in_command_env,
356                |options, value| options.place_all_assignment_args_in_command_env = value,
357            ),
358        ),
359        (
360            "monitor",
361            ShellOptionDef::new(
362                |options| options.enable_job_control,
363                |options, value| options.enable_job_control = value,
364            ),
365        ),
366        (
367            "noclobber",
368            ShellOptionDef::new(
369                |options| options.disallow_overwriting_regular_files_via_output_redirection,
370                |options, value| {
371                    options.disallow_overwriting_regular_files_via_output_redirection = value;
372                },
373            ),
374        ),
375        (
376            "noexec",
377            ShellOptionDef::new(
378                |options| options.do_not_execute_commands,
379                |options, value| options.do_not_execute_commands = value,
380            ),
381        ),
382        (
383            "noglob",
384            ShellOptionDef::new(
385                |options| options.disable_filename_globbing,
386                |options, value| options.disable_filename_globbing = value,
387            ),
388        ),
389        ("nolog", ShellOptionDef::new(|_| false, |_, _| ())),
390        (
391            "notify",
392            ShellOptionDef::new(
393                |options| options.notify_job_termination_immediately,
394                |options, value| options.notify_job_termination_immediately = value,
395            ),
396        ),
397        (
398            "nounset",
399            ShellOptionDef::new(
400                |options| options.treat_unset_variables_as_error,
401                |options, value| options.treat_unset_variables_as_error = value,
402            ),
403        ),
404        (
405            "onecmd",
406            ShellOptionDef::new(
407                |options| options.exit_after_one_command,
408                |options, value| options.exit_after_one_command = value,
409            ),
410        ),
411        (
412            "physical",
413            ShellOptionDef::new(
414                |options| options.do_not_resolve_symlinks_when_changing_dir,
415                |options, value| options.do_not_resolve_symlinks_when_changing_dir = value,
416            ),
417        ),
418        (
419            "pipefail",
420            ShellOptionDef::new(
421                |options| options.return_last_failure_from_pipeline,
422                |options, value| options.return_last_failure_from_pipeline = value,
423            ),
424        ),
425        (
426            "posix",
427            ShellOptionDef::new(
428                |options| options.posix_mode,
429                |options, value| options.posix_mode = value,
430            ),
431        ),
432        (
433            "privileged",
434            ShellOptionDef::new(
435                |options| options.real_effective_uid_mismatch,
436                |options, value| options.real_effective_uid_mismatch = value,
437            ),
438        ),
439        (
440            "verbose",
441            ShellOptionDef::new(
442                |options| options.print_shell_input_lines,
443                |options, value| options.print_shell_input_lines = value,
444            ),
445        ),
446        (
447            "vi",
448            ShellOptionDef::new(
449                |options| options.vi_mode,
450                |options, value| options.vi_mode = value,
451            ),
452        ),
453        (
454            "xtrace",
455            ShellOptionDef::new(
456                |options| options.print_commands_and_arguments,
457                |options, value| options.print_commands_and_arguments = value,
458            ),
459        ),
460    ])
461});
462
463static SHOPT_OPTIONS: LazyLock<HashMap<&'static str, ShellOptionDef>> = LazyLock::new(|| {
464    HashMap::from([
465        (
466            "autocd",
467            ShellOptionDef::new(
468                |options| options.auto_cd,
469                |options, value| options.auto_cd = value,
470            ),
471        ),
472        (
473            "array_expand_once",
474            ShellOptionDef::new(
475                |options| options.array_expand_once,
476                |options, value| options.array_expand_once = value,
477            ),
478        ),
479        (
480            "assoc_expand_once",
481            ShellOptionDef::new(
482                |options| options.assoc_expand_once,
483                |options, value| options.assoc_expand_once = value,
484            ),
485        ),
486        (
487            "bash_source_fullpath",
488            ShellOptionDef::new(
489                |options| options.bash_source_full_path,
490                |options, value| options.bash_source_full_path = value,
491            ),
492        ),
493        (
494            "cdable_vars",
495            ShellOptionDef::new(
496                |options| options.cdable_vars,
497                |options, value| options.cdable_vars = value,
498            ),
499        ),
500        (
501            "cdspell",
502            ShellOptionDef::new(
503                |options| options.cd_autocorrect_spelling,
504                |options, value| options.cd_autocorrect_spelling = value,
505            ),
506        ),
507        (
508            "checkhash",
509            ShellOptionDef::new(
510                |options| options.check_hashtable_before_command_exec,
511                |options, value| options.check_hashtable_before_command_exec = value,
512            ),
513        ),
514        (
515            "checkjobs",
516            ShellOptionDef::new(
517                |options| options.check_jobs_before_exit,
518                |options, value| options.check_jobs_before_exit = value,
519            ),
520        ),
521        (
522            "checkwinsize",
523            ShellOptionDef::new(
524                |options| options.check_window_size_after_external_commands,
525                |options, value| options.check_window_size_after_external_commands = value,
526            ),
527        ),
528        (
529            "cmdhist",
530            ShellOptionDef::new(
531                |options| options.save_multiline_cmds_in_history,
532                |options, value| options.save_multiline_cmds_in_history = value,
533            ),
534        ),
535        (
536            "compat31",
537            ShellOptionDef::new(
538                |options| options.compat31,
539                |options, value| options.compat31 = value,
540            ),
541        ),
542        (
543            "compat32",
544            ShellOptionDef::new(
545                |options| options.compat32,
546                |options, value| options.compat32 = value,
547            ),
548        ),
549        (
550            "compat40",
551            ShellOptionDef::new(
552                |options| options.compat40,
553                |options, value| options.compat40 = value,
554            ),
555        ),
556        (
557            "compat41",
558            ShellOptionDef::new(
559                |options| options.compat41,
560                |options, value| options.compat41 = value,
561            ),
562        ),
563        (
564            "compat42",
565            ShellOptionDef::new(
566                |options| options.compat42,
567                |options, value| options.compat42 = value,
568            ),
569        ),
570        (
571            "compat43",
572            ShellOptionDef::new(
573                |options| options.compat43,
574                |options, value| options.compat43 = value,
575            ),
576        ),
577        (
578            "compat44",
579            ShellOptionDef::new(
580                |options| options.compat44,
581                |options, value| options.compat44 = value,
582            ),
583        ),
584        (
585            "complete_fullquote",
586            ShellOptionDef::new(
587                |options| options.quote_all_metachars_in_completion,
588                |options, value| options.quote_all_metachars_in_completion = value,
589            ),
590        ),
591        (
592            "direxpand",
593            ShellOptionDef::new(
594                |options| options.expand_dir_names_on_completion,
595                |options, value| options.expand_dir_names_on_completion = value,
596            ),
597        ),
598        (
599            "dirspell",
600            ShellOptionDef::new(
601                |options| options.autocorrect_dir_spelling_on_completion,
602                |options, value| options.autocorrect_dir_spelling_on_completion = value,
603            ),
604        ),
605        (
606            "dotglob",
607            ShellOptionDef::new(
608                |options| options.glob_matches_dotfiles,
609                |options, value| options.glob_matches_dotfiles = value,
610            ),
611        ),
612        (
613            "execfail",
614            ShellOptionDef::new(
615                |options| options.exit_on_exec_fail,
616                |options, value| options.exit_on_exec_fail = value,
617            ),
618        ),
619        (
620            "expand_aliases",
621            ShellOptionDef::new(
622                |options| options.expand_aliases,
623                |options, value| options.expand_aliases = value,
624            ),
625        ),
626        (
627            "extdebug",
628            ShellOptionDef::new(
629                |options| options.enable_debugger,
630                |options, value| options.enable_debugger = value,
631            ),
632        ),
633        (
634            "extglob",
635            ShellOptionDef::new(
636                |options| options.extended_globbing,
637                |options, value| options.extended_globbing = value,
638            ),
639        ),
640        (
641            "extquote",
642            ShellOptionDef::new(
643                |options| options.extquote,
644                |options, value| options.extquote = value,
645            ),
646        ),
647        (
648            "failglob",
649            ShellOptionDef::new(
650                |options| options.fail_expansion_on_globs_without_match,
651                |options, value| options.fail_expansion_on_globs_without_match = value,
652            ),
653        ),
654        (
655            "force_fignore",
656            ShellOptionDef::new(
657                |options| options.force_fignore,
658                |options, value| options.force_fignore = value,
659            ),
660        ),
661        (
662            "globasciiranges",
663            ShellOptionDef::new(
664                |options| options.glob_ranges_use_c_locale,
665                |options, value| options.glob_ranges_use_c_locale = value,
666            ),
667        ),
668        (
669            "globskipdots",
670            ShellOptionDef::new(
671                |options| options.glob_skip_dots,
672                |options, value| options.glob_skip_dots = value,
673            ),
674        ),
675        (
676            "globstar",
677            ShellOptionDef::new(
678                |options| options.enable_star_star_glob,
679                |options, value| options.enable_star_star_glob = value,
680            ),
681        ),
682        (
683            "gnu_errfmt",
684            ShellOptionDef::new(
685                |options| options.errors_in_gnu_format,
686                |options, value| options.errors_in_gnu_format = value,
687            ),
688        ),
689        (
690            "histappend",
691            ShellOptionDef::new(
692                |options| options.append_to_history_file,
693                |options, value| options.append_to_history_file = value,
694            ),
695        ),
696        (
697            "histreedit",
698            ShellOptionDef::new(
699                |options| options.allow_reedit_failed_history_subst,
700                |options, value| options.allow_reedit_failed_history_subst = value,
701            ),
702        ),
703        (
704            "histverify",
705            ShellOptionDef::new(
706                |options| options.allow_modifying_history_substitution,
707                |options, value| options.allow_modifying_history_substitution = value,
708            ),
709        ),
710        (
711            "hostcomplete",
712            ShellOptionDef::new(
713                |options| options.enable_hostname_completion,
714                |options, value| options.enable_hostname_completion = value,
715            ),
716        ),
717        (
718            "huponexit",
719            ShellOptionDef::new(
720                |options| options.send_sighup_to_all_jobs_on_exit,
721                |options, value| options.send_sighup_to_all_jobs_on_exit = value,
722            ),
723        ),
724        (
725            "inherit_errexit",
726            ShellOptionDef::new(
727                |options| options.command_subst_inherits_errexit,
728                |options, value| options.command_subst_inherits_errexit = value,
729            ),
730        ),
731        (
732            "interactive_comments",
733            ShellOptionDef::new(
734                |options| options.interactive_comments,
735                |options, value| options.interactive_comments = value,
736            ),
737        ),
738        (
739            "lastpipe",
740            ShellOptionDef::new(
741                |options| options.run_last_pipeline_cmd_in_current_shell,
742                |options, value| options.run_last_pipeline_cmd_in_current_shell = value,
743            ),
744        ),
745        (
746            "lithist",
747            ShellOptionDef::new(
748                |options| options.embed_newlines_in_multiline_cmds_in_history,
749                |options, value| options.embed_newlines_in_multiline_cmds_in_history = value,
750            ),
751        ),
752        (
753            "localvar_inherit",
754            ShellOptionDef::new(
755                |options| options.local_vars_inherit_value_and_attrs,
756                |options, value| options.local_vars_inherit_value_and_attrs = value,
757            ),
758        ),
759        (
760            "localvar_unset",
761            ShellOptionDef::new(
762                |options| options.localvar_unset,
763                |options, value| options.localvar_unset = value,
764            ),
765        ),
766        (
767            "login_shell",
768            ShellOptionDef::new(
769                |options| options.login_shell,
770                |options, value| options.login_shell = value,
771            ),
772        ),
773        (
774            "mailwarn",
775            ShellOptionDef::new(
776                |options| options.mail_warn,
777                |options, value| options.mail_warn = value,
778            ),
779        ),
780        (
781            "no_empty_cmd_completion",
782            ShellOptionDef::new(
783                |options| options.no_empty_cmd_completion,
784                |options, value| options.no_empty_cmd_completion = value,
785            ),
786        ),
787        (
788            "nocaseglob",
789            ShellOptionDef::new(
790                |options| options.case_insensitive_pathname_expansion,
791                |options, value| options.case_insensitive_pathname_expansion = value,
792            ),
793        ),
794        (
795            "nocasematch",
796            ShellOptionDef::new(
797                |options| options.case_insensitive_conditionals,
798                |options, value| options.case_insensitive_conditionals = value,
799            ),
800        ),
801        (
802            "noexpand_translation",
803            ShellOptionDef::new(
804                |options| options.no_expand_translation,
805                |options, value| options.no_expand_translation = value,
806            ),
807        ),
808        (
809            "nullglob",
810            ShellOptionDef::new(
811                |options| options.expand_non_matching_patterns_to_null,
812                |options, value| options.expand_non_matching_patterns_to_null = value,
813            ),
814        ),
815        (
816            "patsub_replacement",
817            ShellOptionDef::new(
818                |options| options.patsub_replacement,
819                |options, value| options.patsub_replacement = value,
820            ),
821        ),
822        (
823            "progcomp",
824            ShellOptionDef::new(
825                |options| options.programmable_completion,
826                |options, value| options.programmable_completion = value,
827            ),
828        ),
829        (
830            "progcomp_alias",
831            ShellOptionDef::new(
832                |options| options.programmable_completion_alias,
833                |options, value| options.programmable_completion_alias = value,
834            ),
835        ),
836        (
837            "promptvars",
838            ShellOptionDef::new(
839                |options| options.expand_prompt_strings,
840                |options, value| options.expand_prompt_strings = value,
841            ),
842        ),
843        (
844            "restricted_shell",
845            ShellOptionDef::new(
846                |options| options.restricted_shell,
847                |options, value| options.restricted_shell = value,
848            ),
849        ),
850        (
851            "shift_verbose",
852            ShellOptionDef::new(
853                |options| options.shift_verbose,
854                |options, value| options.shift_verbose = value,
855            ),
856        ),
857        (
858            "sourcepath",
859            ShellOptionDef::new(
860                |options| options.source_builtin_searches_path,
861                |options, value| options.source_builtin_searches_path = value,
862            ),
863        ),
864        (
865            "varredir_close",
866            ShellOptionDef::new(
867                |options| options.var_redir_close,
868                |options, value| options.var_redir_close = value,
869            ),
870        ),
871        (
872            "xpg_echo",
873            ShellOptionDef::new(
874                |options| options.echo_builtin_expands_escape_sequences,
875                |options, value| options.echo_builtin_expands_escape_sequences = value,
876            ),
877        ),
878    ])
879});