1use itertools::Itertools;
4
5use crate::{CreateOptions, extensions, namedoptions};
6
7#[derive(Clone, Default)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[expect(clippy::module_name_repetitions)]
11pub struct RuntimeOptions {
12 pub export_variables_on_modification: bool,
16 pub notify_job_termination_immediately: bool,
18 pub exit_on_nonzero_command_exit: bool,
20 pub disable_filename_globbing: bool,
22 pub remember_command_locations: bool,
24 pub place_all_assignment_args_in_command_env: bool,
26 pub enable_job_control: bool,
28 pub do_not_execute_commands: bool,
30 pub real_effective_uid_mismatch: bool,
32 pub exit_after_one_command: bool,
34 pub treat_unset_variables_as_error: bool,
36 pub print_shell_input_lines: bool,
38 pub print_commands_and_arguments: bool,
40 pub perform_brace_expansion: bool,
42 pub disallow_overwriting_regular_files_via_output_redirection: bool,
44 pub shell_functions_inherit_err_trap: bool,
46 pub enable_bang_style_history_substitution: bool,
48 pub do_not_resolve_symlinks_when_changing_dir: bool,
50 pub shell_functions_inherit_debug_and_return_traps: bool,
52
53 pub emacs_mode: bool,
57 pub enable_command_history: bool,
59 pub ignore_eof: bool,
61 pub return_last_failure_from_pipeline: bool,
63 pub posix_mode: bool,
65 pub vi_mode: bool,
67
68 pub array_expand_once: bool,
72 pub assoc_expand_once: bool,
74 pub auto_cd: bool,
76 pub bash_source_full_path: bool,
78 pub cdable_vars: bool,
80 pub cd_autocorrect_spelling: bool,
82 pub check_hashtable_before_command_exec: bool,
84 pub check_jobs_before_exit: bool,
86 pub check_window_size_after_external_commands: bool,
88 pub save_multiline_cmds_in_history: bool,
90 pub compat31: bool,
92 pub compat32: bool,
94 pub compat40: bool,
96 pub compat41: bool,
98 pub compat42: bool,
100 pub compat43: bool,
102 pub compat44: bool,
104 pub quote_all_metachars_in_completion: bool,
106 pub expand_dir_names_on_completion: bool,
108 pub autocorrect_dir_spelling_on_completion: bool,
110 pub glob_matches_dotfiles: bool,
112 pub exit_on_exec_fail: bool,
114 pub expand_aliases: bool,
116 pub enable_debugger: bool,
118 pub extended_globbing: bool,
120 pub extquote: bool,
122 pub fail_expansion_on_globs_without_match: bool,
124 pub force_fignore: bool,
126 pub glob_ranges_use_c_locale: bool,
128 pub glob_skip_dots: bool,
130 pub enable_star_star_glob: bool,
132 pub errors_in_gnu_format: bool,
134 pub append_to_history_file: bool,
136 pub allow_reedit_failed_history_subst: bool,
138 pub allow_modifying_history_substitution: bool,
140 pub enable_hostname_completion: bool,
142 pub send_sighup_to_all_jobs_on_exit: bool,
144 pub command_subst_inherits_errexit: bool,
146 pub interactive_comments: bool,
148 pub run_last_pipeline_cmd_in_current_shell: bool,
150 pub embed_newlines_in_multiline_cmds_in_history: bool,
152 pub local_vars_inherit_value_and_attrs: bool,
154 pub localvar_unset: bool,
156 pub login_shell: bool,
158 pub mail_warn: bool,
160 pub no_empty_cmd_completion: bool,
162 pub case_insensitive_pathname_expansion: bool,
164 pub case_insensitive_conditionals: bool,
166 pub no_expand_translation: bool,
168 pub expand_non_matching_patterns_to_null: bool,
170 pub patsub_replacement: bool,
172 pub programmable_completion: bool,
174 pub programmable_completion_alias: bool,
176 pub expand_prompt_strings: bool,
178 pub restricted_shell: bool,
180 pub shift_verbose: bool,
182 pub source_builtin_searches_path: bool,
184 pub var_redir_close: bool,
186 pub echo_builtin_expands_escape_sequences: bool,
188
189 pub interactive: bool,
193 pub read_commands_from_stdin: bool,
195 pub command_string_mode: bool,
197 pub sh_mode: bool,
199 pub external_cmd_leads_session: bool,
201 pub kill_external_commands_on_drop: bool,
204 pub max_function_call_depth: Option<usize>,
206}
207
208impl RuntimeOptions {
209 pub fn defaults_from<SE: extensions::ShellExtensions>(
216 create_options: &CreateOptions<SE>,
217 ) -> Self {
218 let mut options = Self {
220 interactive: create_options.interactive,
221 disallow_overwriting_regular_files_via_output_redirection: create_options
222 .disallow_overwriting_regular_files_via_output_redirection,
223 do_not_execute_commands: create_options.do_not_execute_commands,
224 enable_command_history: create_options.interactive,
225 enable_job_control: create_options.interactive,
226 exit_after_one_command: create_options.exit_after_one_command,
227 read_commands_from_stdin: create_options.read_commands_from_stdin,
228 command_string_mode: create_options.command_string_mode,
229 sh_mode: create_options.sh_mode,
230 posix_mode: create_options.posix,
231 print_commands_and_arguments: create_options.print_commands_and_arguments,
232 print_shell_input_lines: create_options.verbose,
233 treat_unset_variables_as_error: create_options.treat_unset_variables_as_error,
234 exit_on_nonzero_command_exit: create_options.exit_on_nonzero_command_exit,
235 external_cmd_leads_session: create_options.external_cmd_leads_session,
236 kill_external_commands_on_drop: create_options.kill_external_commands_on_drop,
237 login_shell: create_options.login,
238 disable_filename_globbing: create_options.disable_pathname_expansion,
239 remember_command_locations: true,
240 check_window_size_after_external_commands: true,
241 save_multiline_cmds_in_history: true,
242 extquote: true,
243 force_fignore: true,
244 case_insensitive_pathname_expansion:
245 crate::sys::fs::default_case_insensitive_path_expansion(),
246 enable_hostname_completion: true,
247 interactive_comments: true,
248 expand_prompt_strings: true,
249 source_builtin_searches_path: true,
250 perform_brace_expansion: true,
251 quote_all_metachars_in_completion: true,
252 programmable_completion: true,
253 glob_ranges_use_c_locale: true,
254 glob_skip_dots: true,
255 patsub_replacement: true,
256 max_function_call_depth: create_options.max_function_call_depth,
257 ..Self::default()
258 };
259
260 if create_options.interactive {
262 options.enable_bang_style_history_substitution = true;
263 options.emacs_mode = !create_options.no_editing;
264 options.expand_aliases = true;
265 }
266
267 for enabled_option in &create_options.enabled_options {
269 if let Some(option) = namedoptions::options(namedoptions::ShellOptionKind::SetO)
270 .get(enabled_option.as_str())
271 {
272 option.set(&mut options, true);
273 }
274 }
275 for disabled_option in &create_options.disabled_options {
276 if let Some(option) = namedoptions::options(namedoptions::ShellOptionKind::SetO)
277 .get(disabled_option.as_str())
278 {
279 option.set(&mut options, false);
280 }
281 }
282
283 for enabled_option in &create_options.enabled_shopt_options {
285 if let Some(shopt_option) = namedoptions::options(namedoptions::ShellOptionKind::Shopt)
286 .get(enabled_option.as_str())
287 {
288 shopt_option.set(&mut options, true);
289 }
290 }
291 for disabled_option in &create_options.disabled_shopt_options {
292 if let Some(shopt_option) = namedoptions::options(namedoptions::ShellOptionKind::Shopt)
293 .get(disabled_option.as_str())
294 {
295 shopt_option.set(&mut options, false);
296 }
297 }
298
299 options
300 }
301
302 pub fn option_flags(&self) -> String {
305 let mut cs = vec![];
306
307 for o in namedoptions::options(namedoptions::ShellOptionKind::Set).iter() {
308 if o.definition.get(self)
309 && let Some(c) = o.name.chars().next()
310 {
311 cs.push(c);
312 }
313 }
314
315 cs.sort_by_key(|flag| option_flag_sort_key(*flag));
317
318 cs.into_iter().collect()
319 }
320
321 pub fn seto_optstr(&self) -> String {
323 let mut cs = vec![];
324
325 for option in namedoptions::options(namedoptions::ShellOptionKind::SetO).iter() {
326 if option.definition.get(self) {
327 cs.push(option.name);
328 }
329 }
330
331 cs.sort_unstable();
332 cs.into_iter().join(":")
333 }
334
335 pub fn shopt_optstr(&self) -> String {
337 let mut cs = vec![];
338
339 for option in namedoptions::options(namedoptions::ShellOptionKind::Shopt).iter() {
340 if option.definition.get(self) {
341 cs.push(option.name);
342 }
343 }
344
345 cs.sort_unstable();
346 cs.into_iter().join(":")
347 }
348}
349
350const fn option_flag_sort_key(ch: char) -> (u8, char) {
356 let group = if ch.is_ascii_lowercase() && !matches!(ch, 'c' | 's') {
361 0
362 } else if ch.is_ascii_uppercase() {
363 1
364 } else {
365 2
366 };
367
368 (group, ch)
369}
370
371#[cfg(test)]
372mod tests {
373 use super::option_flag_sort_key;
374
375 #[test]
376 fn lowercase_excluding_c_and_s_sort_first() {
377 let mut flags = vec!['b', 'A', 'Z', 's', 'c', 'a'];
378 flags.sort_by_key(|flag| option_flag_sort_key(*flag));
379
380 assert_eq!(flags, vec!['a', 'b', 'A', 'Z', 'c', 's']);
381 }
382
383 #[test]
384 fn uppercase_sorted_before_miscellaneous() {
385 let mut flags = vec!['P', 'B', '1', 'T'];
386 flags.sort_by_key(|flag| option_flag_sort_key(*flag));
387
388 assert_eq!(flags, vec!['B', 'P', 'T', '1']);
389 }
390
391 #[test]
392 fn miscellaneous_characters_respect_ascii_order() {
393 let mut flags = vec!['s', 'c', '%', ':'];
394 flags.sort_by_key(|flag| option_flag_sort_key(*flag));
395
396 assert_eq!(flags, vec!['%', ':', 'c', 's']);
397 }
398}