1use std::collections::HashMap;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum Emulation {
16 Zsh = 1,
17 Csh = 2,
18 Ksh = 4,
19 Sh = 8,
20}
21
22const OPT_CSH: u8 = 1;
24const OPT_KSH: u8 = 2;
25const OPT_SH: u8 = 4;
26const OPT_ZSH: u8 = 8;
27const OPT_ALL: u8 = OPT_CSH | OPT_KSH | OPT_SH | OPT_ZSH;
28const OPT_BOURNE: u8 = OPT_KSH | OPT_SH;
29const OPT_BSHELL: u8 = OPT_KSH | OPT_SH | OPT_ZSH;
30const OPT_NONBOURNE: u8 = OPT_ALL & !OPT_BOURNE;
31const OPT_NONZSH: u8 = OPT_ALL & !OPT_ZSH;
32
33const OPT_EMULATE: u16 = 0x100; const OPT_SPECIAL: u16 = 0x200; const OPT_ALIAS: u16 = 0x400; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40#[repr(u16)]
41pub enum ShellOption {
42 Aliases = 1,
44 AliasFuncDef,
45 AllExport,
46 AlwaysLastPrompt,
47 AlwaysToEnd,
48 AppendCreate,
49 AppendHistory,
50 AutoCd,
51 AutoContinue,
52 AutoList,
53 AutoMenu,
54 AutoNamedDirs,
55 AutoParamKeys,
56 AutoParamSlash,
57 AutoPushd,
58 AutoRemoveSlash,
59 AutoResume,
60 BadPattern,
62 BangHist,
63 BareGlobQual,
64 BashAutoList,
65 BashRematch,
66 Beep,
67 BgNice,
68 BraceCcl,
69 BsdEcho,
70 CaseGlob,
72 CaseMatch,
73 CasePaths,
74 CBases,
75 CPrecedences,
76 CdAbleVars,
77 CdSilent,
78 ChaseDots,
79 ChaseLinks,
80 CheckJobs,
81 CheckRunningJobs,
82 Clobber,
83 ClobberEmpty,
84 CombiningChars,
85 CompleteAliases,
86 CompleteInWord,
87 ContinueOnError,
88 Correct,
89 CorrectAll,
90 CshJunkieHistory,
91 CshJunkieLoops,
92 CshJunkieQuotes,
93 CshNullCmd,
94 CshNullGlob,
95 DebugBeforeCmd,
97 Emacs,
99 Equals,
100 ErrExit,
101 ErrReturn,
102 Exec,
103 ExtendedGlob,
104 ExtendedHistory,
105 EvalLineno,
106 FlowControl,
108 ForceFloat,
109 FunctionArgZero,
110 Glob,
112 GlobalExport,
113 GlobalRcs,
114 GlobAssign,
115 GlobComplete,
116 GlobDots,
117 GlobStarShort,
118 GlobSubst,
119 HashCmds,
121 HashDirs,
122 HashExecutablesOnly,
123 HashListAll,
124 HistAllowClobber,
125 HistBeep,
126 HistExpireDupsFirst,
127 HistFcntlLock,
128 HistFindNoDups,
129 HistIgnoreAllDups,
130 HistIgnoreDups,
131 HistIgnoreSpace,
132 HistLexWords,
133 HistNoFunctions,
134 HistNoStore,
135 HistSubstPattern,
136 HistReduceBlanks,
137 HistSaveByCopy,
138 HistSaveNoDups,
139 HistVerify,
140 Hup,
141 IgnoreBraces,
143 IgnoreCloseBraces,
144 IgnoreEof,
145 IncAppendHistory,
146 IncAppendHistoryTime,
147 Interactive,
148 InteractiveComments,
149 KshArrays,
151 KshAutoload,
152 KshGlob,
153 KshOptionPrint,
154 KshTypeset,
155 KshZeroSubscript,
156 ListAmbiguous,
158 ListBeep,
159 ListPacked,
160 ListRowsFirst,
161 ListTypes,
162 LocalOptions,
163 LocalLoops,
164 LocalPatterns,
165 LocalTraps,
166 Login,
167 LongListJobs,
168 MagicEqualSubst,
170 MailWarning,
171 MarkDirs,
172 MenuComplete,
173 Monitor,
174 MultiByte,
175 MultiFuncDef,
176 MultiOs,
177 NoMatch,
179 Notify,
180 NullGlob,
181 NumericGlobSort,
182 OctalZeroes,
184 OverStrike,
185 PathDirs,
187 PathScript,
188 PipeFail,
189 PosixAliases,
190 PosixArgZero,
191 PosixBuiltins,
192 PosixCd,
193 PosixIdentifiers,
194 PosixJobs,
195 PosixStrings,
196 PosixTraps,
197 PrintEightBit,
198 PrintExitValue,
199 Privileged,
200 PromptBang,
201 PromptCr,
202 PromptPercent,
203 PromptSp,
204 PromptSubst,
205 PushdIgnoreDups,
206 PushdMinus,
207 PushdSilent,
208 PushdToHome,
209 RcExpandParam,
211 RcQuotes,
212 Rcs,
213 RecExact,
214 RematchPcre,
215 RmStarSilent,
216 RmStarWait,
217 ShareHistory,
219 ShFileExpansion,
220 ShGlob,
221 ShInstdin,
222 ShNullCmd,
223 ShOptionLetters,
224 ShortLoops,
225 ShortRepeat,
226 ShWordSplit,
227 SingleCommand,
228 SingleLineZle,
229 SourceTrace,
230 SunKeyboardHack,
231 TransientRprompt,
233 TrapsAsync,
234 TypesetSilent,
235 TypesetToUnset,
236 Unset,
238 Verbose,
240 Vi,
241 WarnCreateGlobal,
243 WarnNestedVar,
244 Xtrace,
246 Zle,
248 Dvorak,
249}
250
251impl ShellOption {
252 pub fn name(self) -> &'static str {
254 match self {
255 Self::Aliases => "aliases",
256 Self::AliasFuncDef => "aliasfuncdef",
257 Self::AllExport => "allexport",
258 Self::AlwaysLastPrompt => "alwayslastprompt",
259 Self::AlwaysToEnd => "alwaystoend",
260 Self::AppendCreate => "appendcreate",
261 Self::AppendHistory => "appendhistory",
262 Self::AutoCd => "autocd",
263 Self::AutoContinue => "autocontinue",
264 Self::AutoList => "autolist",
265 Self::AutoMenu => "automenu",
266 Self::AutoNamedDirs => "autonamedirs",
267 Self::AutoParamKeys => "autoparamkeys",
268 Self::AutoParamSlash => "autoparamslash",
269 Self::AutoPushd => "autopushd",
270 Self::AutoRemoveSlash => "autoremoveslash",
271 Self::AutoResume => "autoresume",
272 Self::BadPattern => "badpattern",
273 Self::BangHist => "banghist",
274 Self::BareGlobQual => "bareglobqual",
275 Self::BashAutoList => "bashautolist",
276 Self::BashRematch => "bashrematch",
277 Self::Beep => "beep",
278 Self::BgNice => "bgnice",
279 Self::BraceCcl => "braceccl",
280 Self::BsdEcho => "bsdecho",
281 Self::CaseGlob => "caseglob",
282 Self::CaseMatch => "casematch",
283 Self::CasePaths => "casepaths",
284 Self::CBases => "cbases",
285 Self::CPrecedences => "cprecedences",
286 Self::CdAbleVars => "cdablevars",
287 Self::CdSilent => "cdsilent",
288 Self::ChaseDots => "chasedots",
289 Self::ChaseLinks => "chaselinks",
290 Self::CheckJobs => "checkjobs",
291 Self::CheckRunningJobs => "checkrunningjobs",
292 Self::Clobber => "clobber",
293 Self::ClobberEmpty => "clobberempty",
294 Self::CombiningChars => "combiningchars",
295 Self::CompleteAliases => "completealiases",
296 Self::CompleteInWord => "completeinword",
297 Self::ContinueOnError => "continueonerror",
298 Self::Correct => "correct",
299 Self::CorrectAll => "correctall",
300 Self::CshJunkieHistory => "cshjunkiehistory",
301 Self::CshJunkieLoops => "cshjunkieloops",
302 Self::CshJunkieQuotes => "cshjunkiequotes",
303 Self::CshNullCmd => "cshnullcmd",
304 Self::CshNullGlob => "cshnullglob",
305 Self::DebugBeforeCmd => "debugbeforecmd",
306 Self::Emacs => "emacs",
307 Self::Equals => "equals",
308 Self::ErrExit => "errexit",
309 Self::ErrReturn => "errreturn",
310 Self::Exec => "exec",
311 Self::ExtendedGlob => "extendedglob",
312 Self::ExtendedHistory => "extendedhistory",
313 Self::EvalLineno => "evallineno",
314 Self::FlowControl => "flowcontrol",
315 Self::ForceFloat => "forcefloat",
316 Self::FunctionArgZero => "functionargzero",
317 Self::Glob => "glob",
318 Self::GlobalExport => "globalexport",
319 Self::GlobalRcs => "globalrcs",
320 Self::GlobAssign => "globassign",
321 Self::GlobComplete => "globcomplete",
322 Self::GlobDots => "globdots",
323 Self::GlobStarShort => "globstarshort",
324 Self::GlobSubst => "globsubst",
325 Self::HashCmds => "hashcmds",
326 Self::HashDirs => "hashdirs",
327 Self::HashExecutablesOnly => "hashexecutablesonly",
328 Self::HashListAll => "hashlistall",
329 Self::HistAllowClobber => "histallowclobber",
330 Self::HistBeep => "histbeep",
331 Self::HistExpireDupsFirst => "histexpiredupsfirst",
332 Self::HistFcntlLock => "histfcntllock",
333 Self::HistFindNoDups => "histfindnodups",
334 Self::HistIgnoreAllDups => "histignorealldups",
335 Self::HistIgnoreDups => "histignoredups",
336 Self::HistIgnoreSpace => "histignorespace",
337 Self::HistLexWords => "histlexwords",
338 Self::HistNoFunctions => "histnofunctions",
339 Self::HistNoStore => "histnostore",
340 Self::HistSubstPattern => "histsubstpattern",
341 Self::HistReduceBlanks => "histreduceblanks",
342 Self::HistSaveByCopy => "histsavebycopy",
343 Self::HistSaveNoDups => "histsavenodups",
344 Self::HistVerify => "histverify",
345 Self::Hup => "hup",
346 Self::IgnoreBraces => "ignorebraces",
347 Self::IgnoreCloseBraces => "ignoreclosebraces",
348 Self::IgnoreEof => "ignoreeof",
349 Self::IncAppendHistory => "incappendhistory",
350 Self::IncAppendHistoryTime => "incappendhistorytime",
351 Self::Interactive => "interactive",
352 Self::InteractiveComments => "interactivecomments",
353 Self::KshArrays => "ksharrays",
354 Self::KshAutoload => "kshautoload",
355 Self::KshGlob => "kshglob",
356 Self::KshOptionPrint => "kshoptionprint",
357 Self::KshTypeset => "kshtypeset",
358 Self::KshZeroSubscript => "kshzerosubscript",
359 Self::ListAmbiguous => "listambiguous",
360 Self::ListBeep => "listbeep",
361 Self::ListPacked => "listpacked",
362 Self::ListRowsFirst => "listrowsfirst",
363 Self::ListTypes => "listtypes",
364 Self::LocalOptions => "localoptions",
365 Self::LocalLoops => "localloops",
366 Self::LocalPatterns => "localpatterns",
367 Self::LocalTraps => "localtraps",
368 Self::Login => "login",
369 Self::LongListJobs => "longlistjobs",
370 Self::MagicEqualSubst => "magicequalsubst",
371 Self::MailWarning => "mailwarning",
372 Self::MarkDirs => "markdirs",
373 Self::MenuComplete => "menucomplete",
374 Self::Monitor => "monitor",
375 Self::MultiByte => "multibyte",
376 Self::MultiFuncDef => "multifuncdef",
377 Self::MultiOs => "multios",
378 Self::NoMatch => "nomatch",
379 Self::Notify => "notify",
380 Self::NullGlob => "nullglob",
381 Self::NumericGlobSort => "numericglobsort",
382 Self::OctalZeroes => "octalzeroes",
383 Self::OverStrike => "overstrike",
384 Self::PathDirs => "pathdirs",
385 Self::PathScript => "pathscript",
386 Self::PipeFail => "pipefail",
387 Self::PosixAliases => "posixaliases",
388 Self::PosixArgZero => "posixargzero",
389 Self::PosixBuiltins => "posixbuiltins",
390 Self::PosixCd => "posixcd",
391 Self::PosixIdentifiers => "posixidentifiers",
392 Self::PosixJobs => "posixjobs",
393 Self::PosixStrings => "posixstrings",
394 Self::PosixTraps => "posixtraps",
395 Self::PrintEightBit => "printeightbit",
396 Self::PrintExitValue => "printexitvalue",
397 Self::Privileged => "privileged",
398 Self::PromptBang => "promptbang",
399 Self::PromptCr => "promptcr",
400 Self::PromptPercent => "promptpercent",
401 Self::PromptSp => "promptsp",
402 Self::PromptSubst => "promptsubst",
403 Self::PushdIgnoreDups => "pushdignoredups",
404 Self::PushdMinus => "pushdminus",
405 Self::PushdSilent => "pushdsilent",
406 Self::PushdToHome => "pushdtohome",
407 Self::RcExpandParam => "rcexpandparam",
408 Self::RcQuotes => "rcquotes",
409 Self::Rcs => "rcs",
410 Self::RecExact => "recexact",
411 Self::RematchPcre => "rematchpcre",
412 Self::RmStarSilent => "rmstarsilent",
413 Self::RmStarWait => "rmstarwait",
414 Self::ShareHistory => "sharehistory",
415 Self::ShFileExpansion => "shfileexpansion",
416 Self::ShGlob => "shglob",
417 Self::ShInstdin => "shinstdin",
418 Self::ShNullCmd => "shnullcmd",
419 Self::ShOptionLetters => "shoptionletters",
420 Self::ShortLoops => "shortloops",
421 Self::ShortRepeat => "shortrepeat",
422 Self::ShWordSplit => "shwordsplit",
423 Self::SingleCommand => "singlecommand",
424 Self::SingleLineZle => "singlelinezle",
425 Self::SourceTrace => "sourcetrace",
426 Self::SunKeyboardHack => "sunkeyboardhack",
427 Self::TransientRprompt => "transientrprompt",
428 Self::TrapsAsync => "trapsasync",
429 Self::TypesetSilent => "typesetsilent",
430 Self::TypesetToUnset => "typesettounset",
431 Self::Unset => "unset",
432 Self::Verbose => "verbose",
433 Self::Vi => "vi",
434 Self::WarnCreateGlobal => "warncreateglobal",
435 Self::WarnNestedVar => "warnnestedvar",
436 Self::Xtrace => "xtrace",
437 Self::Zle => "zle",
438 Self::Dvorak => "dvorak",
439 }
440 }
441}
442
443pub static OPTION_ALIASES: &[(&str, &str, bool)] = &[
445 ("braceexpand", "ignorebraces", true), ("dotglob", "globdots", false), ("hashall", "hashcmds", false), ("histappend", "appendhistory", false), ("histexpand", "banghist", false), ("log", "histnofunctions", true), ("mailwarn", "mailwarning", false), ("onecmd", "singlecommand", false), ("physical", "chaselinks", false), ("promptvars", "promptsubst", false), ("stdin", "shinstdin", false), ("trackall", "hashcmds", false), ];
458
459pub static ZSH_LETTERS: &[(char, &str, bool)] = &[
461 ('0', "correct", false),
462 ('1', "printexitvalue", false),
463 ('2', "badpattern", true),
464 ('3', "nomatch", true),
465 ('4', "globdots", false),
466 ('5', "notify", false),
467 ('6', "bgnice", false),
468 ('7', "ignoreeof", false),
469 ('8', "markdirs", false),
470 ('9', "autolist", false),
471 ('B', "beep", true),
472 ('C', "clobber", true),
473 ('D', "pushdtohome", false),
474 ('E', "pushdsilent", false),
475 ('F', "glob", true),
476 ('G', "nullglob", false),
477 ('H', "rmstarsilent", false),
478 ('I', "ignorebraces", false),
479 ('J', "autocd", false),
480 ('K', "banghist", true),
481 ('L', "sunkeyboardhack", false),
482 ('M', "singlelinezle", false),
483 ('N', "autopushd", false),
484 ('O', "correctall", false),
485 ('P', "rcexpandparam", false),
486 ('Q', "pathdirs", false),
487 ('R', "longlistjobs", false),
488 ('S', "recexact", false),
489 ('T', "cdablevars", false),
490 ('U', "mailwarning", false),
491 ('V', "promptcr", true),
492 ('W', "autoresume", false),
493 ('X', "listtypes", false),
494 ('Y', "menucomplete", false),
495 ('Z', "zle", false),
496 ('a', "allexport", false),
497 ('d', "globalrcs", true),
498 ('e', "errexit", false),
499 ('f', "rcs", true),
500 ('g', "histignorespace", false),
501 ('h', "histignoredups", false),
502 ('i', "interactive", false),
503 ('k', "interactivecomments", false),
504 ('l', "login", false),
505 ('m', "monitor", false),
506 ('n', "exec", true),
507 ('p', "privileged", false),
508 ('s', "shinstdin", false),
509 ('t', "singlecommand", false),
510 ('u', "unset", true),
511 ('v', "verbose", false),
512 ('w', "chaselinks", false),
513 ('x', "xtrace", false),
514 ('y', "shwordsplit", false),
515];
516
517pub static KSH_LETTERS: &[(char, &str, bool)] = &[
519 ('C', "clobber", true),
520 ('T', "trapsasync", false),
521 ('X', "markdirs", false),
522 ('a', "allexport", false),
523 ('b', "notify", false),
524 ('e', "errexit", false),
525 ('f', "glob", true),
526 ('i', "interactive", false),
527 ('l', "login", false),
528 ('m', "monitor", false),
529 ('n', "exec", true),
530 ('p', "privileged", false),
531 ('s', "shinstdin", false),
532 ('t', "singlecommand", false),
533 ('u', "unset", true),
534 ('v', "verbose", false),
535 ('x', "xtrace", false),
536];
537
538#[derive(Debug, Clone)]
540pub struct ShellOptions {
541 options: HashMap<String, bool>,
543 pub emulation: Emulation,
545 pub fully_emulating: bool,
547}
548
549impl Default for ShellOptions {
550 fn default() -> Self {
551 Self::new()
552 }
553}
554
555impl ShellOptions {
556 pub fn new() -> Self {
558 let mut opts = ShellOptions {
559 options: HashMap::new(),
560 emulation: Emulation::Zsh,
561 fully_emulating: false,
562 };
563 opts.set_zsh_defaults();
564 opts
565 }
566
567 pub fn set_zsh_defaults(&mut self) {
569 let default_on = [
571 "aliases",
572 "alwayslastprompt",
573 "appendhistory",
574 "autolist",
575 "automenu",
576 "autoparamkeys",
577 "autoparamslash",
578 "autoremoveslash",
579 "bareglobqual",
580 "beep",
581 "bgnice",
582 "caseglob",
583 "casematch",
584 "checkjobs",
585 "checkrunningjobs",
586 "clobber",
587 "debugbeforecmd",
588 "equals",
589 "evallineno",
590 "exec",
591 "flowcontrol",
592 "functionargzero",
593 "glob",
594 "globalexport",
595 "globalrcs",
596 "hashcmds",
597 "hashdirs",
598 "hashlistall",
599 "histbeep",
600 "histsavebycopy",
601 "hup",
602 "interactive",
603 "listambiguous",
604 "listbeep",
605 "listtypes",
606 "multifuncdef",
607 "multios",
608 "nomatch",
609 "notify",
610 "promptcr",
611 "promptpercent",
612 "promptsp",
613 "rcs",
614 "shortloops",
615 "unset",
616 "zle",
617 ];
618
619 for opt in default_on {
620 self.options.insert(opt.to_string(), true);
621 }
622 }
623
624 pub fn lookup(&self, name: &str) -> Option<bool> {
626 let normalized = normalize_option_name(name);
627
628 if let Some(stripped) = normalized.strip_prefix("no") {
630 self.options.get(stripped).map(|v| !v)
631 } else {
632 self.options.get(&normalized).copied()
633 }
634 }
635
636 pub fn is_set(&self, name: &str) -> bool {
638 self.lookup(name).unwrap_or(false)
639 }
640
641 pub fn set(&mut self, name: &str, value: bool) -> Result<(), String> {
643 let normalized = normalize_option_name(name);
644
645 let (actual_name, actual_value) = if let Some(stripped) = normalized.strip_prefix("no") {
647 (stripped.to_string(), !value)
648 } else {
649 (normalized, value)
650 };
651
652 for (alias, target, negated) in OPTION_ALIASES {
654 if actual_name == *alias {
655 let target_value = if *negated {
656 !actual_value
657 } else {
658 actual_value
659 };
660 self.options.insert(target.to_string(), target_value);
661 return Ok(());
662 }
663 }
664
665 let special = ["interactive", "login", "shinstdin", "singlecommand"];
667 if special.contains(&actual_name.as_str()) {
668 if self.options.get(&actual_name) == Some(&actual_value) {
669 return Ok(());
670 }
671 return Err(format!("can't change option: {}", actual_name));
672 }
673
674 self.options.insert(actual_name, actual_value);
675 Ok(())
676 }
677
678 pub fn unset(&mut self, name: &str) -> Result<(), String> {
680 self.set(name, false)
681 }
682
683 pub fn lookup_letter(&self, c: char) -> Option<(&'static str, bool)> {
685 let letters = if self.is_set("shoptionletters") {
686 KSH_LETTERS
687 } else {
688 ZSH_LETTERS
689 };
690
691 for (ch, name, negated) in letters {
692 if *ch == c {
693 return Some((name, *negated));
694 }
695 }
696 None
697 }
698
699 pub fn set_by_letter(&mut self, c: char, value: bool) -> Result<(), String> {
701 if let Some((name, negated)) = self.lookup_letter(c) {
702 let actual_value = if negated { !value } else { value };
703 self.set(name, actual_value)
704 } else {
705 Err(format!("bad option: -{}", c))
706 }
707 }
708
709 pub fn emulate(&mut self, mode: &str, fully: bool) {
711 let ch = mode.chars().next().unwrap_or('z');
712 let ch = if ch == 'r' {
713 mode.chars().nth(1).unwrap_or('z')
714 } else {
715 ch
716 };
717
718 self.emulation = match ch {
719 'c' => Emulation::Csh,
720 'k' => Emulation::Ksh,
721 's' | 'b' => Emulation::Sh,
722 _ => Emulation::Zsh,
723 };
724 self.fully_emulating = fully;
725
726 self.install_emulation_defaults();
728 }
729
730 fn install_emulation_defaults(&mut self) {
732 match self.emulation {
735 Emulation::Sh | Emulation::Ksh => {
736 self.options.insert("shwordsplit".to_string(), true);
737 self.options.insert("globsubst".to_string(), true);
738 self.options.insert("ksharrays".to_string(), true);
739 self.options.insert("posixbuiltins".to_string(), true);
740 self.options.insert("promptpercent".to_string(), false);
741 self.options.insert("banghist".to_string(), false);
742 }
743 Emulation::Csh => {
744 self.options.insert("cshjunkiehistory".to_string(), true);
745 self.options.insert("cshjunkieloops".to_string(), true);
746 self.options.insert("cshnullcmd".to_string(), true);
747 }
748 Emulation::Zsh => {
749 self.set_zsh_defaults();
750 }
751 }
752 }
753
754 pub fn dash_string(&self) -> String {
756 let mut result = String::new();
757 let letters = if self.is_set("shoptionletters") {
758 KSH_LETTERS
759 } else {
760 ZSH_LETTERS
761 };
762
763 for (c, name, negated) in letters {
764 let is_set = self.is_set(name);
765 if (*negated && !is_set) || (!*negated && is_set) {
766 result.push(*c);
767 }
768 }
769 result
770 }
771
772 pub fn list(&self) -> Vec<(String, bool)> {
774 let mut result: Vec<_> = self.options.iter().map(|(k, v)| (k.clone(), *v)).collect();
775 result.sort_by(|a, b| a.0.cmp(&b.0));
776 result
777 }
778
779 pub fn all_names(&self) -> Vec<&str> {
781 let mut names: Vec<_> = self.options.keys().map(|s| s.as_str()).collect();
783 names.sort();
784 names
785 }
786}
787
788pub fn normalize_option_name(name: &str) -> String {
790 name.chars()
791 .filter(|&c| c != '_')
792 .flat_map(|c| c.to_lowercase())
793 .collect()
794}
795
796pub fn parse_option_args(
798 opts: &mut ShellOptions,
799 args: &[&str],
800 is_unset: bool,
801) -> Result<(), Vec<String>> {
802 let mut errors = Vec::new();
803
804 for arg in args {
805 let (name, value) = if let Some(stripped) = arg.strip_prefix("no") {
806 (stripped, is_unset) } else {
808 (*arg, !is_unset)
809 };
810
811 if let Err(e) = opts.set(name, value) {
812 errors.push(e);
813 }
814 }
815
816 if errors.is_empty() {
817 Ok(())
818 } else {
819 Err(errors)
820 }
821}
822
823#[cfg(test)]
824mod tests {
825 use super::*;
826
827 #[test]
828 fn test_default_options() {
829 let opts = ShellOptions::new();
830 assert!(opts.is_set("glob"));
831 assert!(opts.is_set("exec"));
832 assert!(opts.is_set("zle"));
833 assert!(!opts.is_set("xtrace"));
834 }
835
836 #[test]
837 fn test_set_option() {
838 let mut opts = ShellOptions::new();
839 opts.set("xtrace", true).unwrap();
840 assert!(opts.is_set("xtrace"));
841 opts.set("xtrace", false).unwrap();
842 assert!(!opts.is_set("xtrace"));
843 }
844
845 #[test]
846 fn test_no_prefix() {
847 let mut opts = ShellOptions::new();
848 opts.set("noglob", true).unwrap();
849 assert!(!opts.is_set("glob"));
850
851 assert!(opts.lookup("noglob") == Some(true));
852 }
853
854 #[test]
855 fn test_case_insensitive() {
856 let opts = ShellOptions::new();
857 assert_eq!(opts.lookup("GLOB"), opts.lookup("glob"));
858 assert_eq!(opts.lookup("GlOb"), opts.lookup("glob"));
859 }
860
861 #[test]
862 fn test_underscore_ignored() {
863 let opts = ShellOptions::new();
864 assert_eq!(opts.lookup("auto_list"), opts.lookup("autolist"));
865 assert_eq!(opts.lookup("AUTO_LIST"), opts.lookup("autolist"));
866 }
867
868 #[test]
869 fn test_option_alias() {
870 let mut opts = ShellOptions::new();
871
872 opts.set("braceexpand", true).unwrap();
874 assert!(!opts.is_set("ignorebraces"));
875 }
876
877 #[test]
878 fn test_single_letter() {
879 let mut opts = ShellOptions::new();
880
881 opts.set_by_letter('x', true).unwrap();
883 assert!(opts.is_set("xtrace"));
884
885 opts.set_by_letter('n', true).unwrap();
887 assert!(!opts.is_set("exec"));
888 }
889
890 #[test]
891 fn test_emulation() {
892 let mut opts = ShellOptions::new();
893
894 opts.emulate("sh", true);
895 assert_eq!(opts.emulation, Emulation::Sh);
896 assert!(opts.is_set("shwordsplit"));
897
898 opts.emulate("zsh", true);
899 assert_eq!(opts.emulation, Emulation::Zsh);
900 }
901
902 #[test]
903 fn test_dash_string() {
904 let mut opts = ShellOptions::new();
905 opts.set("interactive", true).unwrap();
906 opts.set("monitor", true).unwrap();
907
908 let dash = opts.dash_string();
909 assert!(dash.contains('i'));
910 assert!(dash.contains('m'));
911 }
912
913 #[test]
914 fn test_normalize_name() {
915 assert_eq!(normalize_option_name("AUTO_LIST"), "autolist");
916 assert_eq!(normalize_option_name("AutoList"), "autolist");
917 assert_eq!(normalize_option_name("auto__list"), "autolist");
918 }
919}