1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
8pub enum OptionValue {
9 On,
11 Off,
13 #[default]
15 Unknown,
16}
17
18impl OptionValue {
19 pub const fn is_definitely_on(self) -> bool {
21 matches!(self, Self::On)
22 }
23
24 pub const fn is_definitely_off(self) -> bool {
26 matches!(self, Self::Off)
27 }
28
29 #[inline]
35 pub const fn merge(self, other: Self) -> Self {
36 match (self, other) {
37 (Self::On, Self::On) => Self::On,
38 (Self::Off, Self::Off) => Self::Off,
39 _ => Self::Unknown,
40 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum ZshEmulationMode {
50 Zsh,
52 Sh,
54 Ksh,
56 Csh,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68pub struct ZshOptionState {
69 pub sh_word_split: OptionValue,
72 pub glob_subst: OptionValue,
74 pub rc_expand_param: OptionValue,
76 pub glob: OptionValue,
78 pub nomatch: OptionValue,
80 pub null_glob: OptionValue,
82 pub csh_null_glob: OptionValue,
84 pub extended_glob: OptionValue,
86 pub ksh_glob: OptionValue,
88 pub sh_glob: OptionValue,
90 pub bare_glob_qual: OptionValue,
92 pub glob_dots: OptionValue,
94 pub equals: OptionValue,
96 pub magic_equal_subst: OptionValue,
99 pub sh_file_expansion: OptionValue,
101 pub glob_assign: OptionValue,
103 pub ignore_braces: OptionValue,
106 pub ignore_close_braces: OptionValue,
108 pub brace_ccl: OptionValue,
110 pub ksh_arrays: OptionValue,
112 pub ksh_zero_subscript: OptionValue,
114 pub short_loops: OptionValue,
116 pub short_repeat: OptionValue,
118 pub rc_quotes: OptionValue,
120 pub interactive_comments: OptionValue,
122 pub c_bases: OptionValue,
124 pub octal_zeroes: OptionValue,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
129enum ZshOptionField {
130 ShWordSplit,
131 GlobSubst,
132 RcExpandParam,
133 Glob,
134 Nomatch,
135 NullGlob,
136 CshNullGlob,
137 ExtendedGlob,
138 KshGlob,
139 ShGlob,
140 BareGlobQual,
141 GlobDots,
142 Equals,
143 MagicEqualSubst,
144 ShFileExpansion,
145 GlobAssign,
146 IgnoreBraces,
147 IgnoreCloseBraces,
148 BraceCcl,
149 KshArrays,
150 KshZeroSubscript,
151 ShortLoops,
152 ShortRepeat,
153 RcQuotes,
154 InteractiveComments,
155 CBases,
156 OctalZeroes,
157}
158
159impl ZshOptionState {
160 pub const fn zsh_default() -> Self {
165 Self {
166 sh_word_split: OptionValue::Off,
167 glob_subst: OptionValue::Off,
168 rc_expand_param: OptionValue::Off,
169 glob: OptionValue::On,
170 nomatch: OptionValue::On,
171 null_glob: OptionValue::Off,
172 csh_null_glob: OptionValue::Off,
173 extended_glob: OptionValue::Off,
174 ksh_glob: OptionValue::Off,
175 sh_glob: OptionValue::Off,
176 bare_glob_qual: OptionValue::On,
177 glob_dots: OptionValue::Off,
178 equals: OptionValue::On,
179 magic_equal_subst: OptionValue::Off,
180 sh_file_expansion: OptionValue::Off,
181 glob_assign: OptionValue::Off,
182 ignore_braces: OptionValue::Off,
183 ignore_close_braces: OptionValue::Off,
184 brace_ccl: OptionValue::Off,
185 ksh_arrays: OptionValue::Off,
186 ksh_zero_subscript: OptionValue::Off,
187 short_loops: OptionValue::On,
188 short_repeat: OptionValue::On,
189 rc_quotes: OptionValue::Off,
190 interactive_comments: OptionValue::On,
191 c_bases: OptionValue::Off,
192 octal_zeroes: OptionValue::Off,
193 }
194 }
195
196 pub fn for_emulate(mode: ZshEmulationMode) -> Self {
202 let mut state = Self::zsh_default();
203 match mode {
204 ZshEmulationMode::Zsh => {}
205 ZshEmulationMode::Sh => {
206 state.sh_word_split = OptionValue::On;
207 state.glob_subst = OptionValue::On;
208 state.sh_glob = OptionValue::On;
209 state.sh_file_expansion = OptionValue::On;
210 state.bare_glob_qual = OptionValue::Off;
211 state.ksh_arrays = OptionValue::Off;
212 }
213 ZshEmulationMode::Ksh => {
214 state.sh_word_split = OptionValue::On;
215 state.glob_subst = OptionValue::On;
216 state.ksh_glob = OptionValue::On;
217 state.ksh_arrays = OptionValue::On;
218 state.sh_glob = OptionValue::On;
219 state.bare_glob_qual = OptionValue::Off;
220 }
221 ZshEmulationMode::Csh => {
222 state.csh_null_glob = OptionValue::On;
223 state.sh_word_split = OptionValue::Off;
224 state.glob_subst = OptionValue::Off;
225 }
226 }
227 state
228 }
229
230 pub fn apply_setopt(&mut self, name: &str) -> bool {
236 self.apply_named_option(name, true)
237 }
238
239 pub fn apply_unsetopt(&mut self, name: &str) -> bool {
245 self.apply_named_option(name, false)
246 }
247
248 fn set_field(&mut self, field: ZshOptionField, value: OptionValue) {
249 match field {
250 ZshOptionField::ShWordSplit => self.sh_word_split = value,
251 ZshOptionField::GlobSubst => self.glob_subst = value,
252 ZshOptionField::RcExpandParam => self.rc_expand_param = value,
253 ZshOptionField::Glob => self.glob = value,
254 ZshOptionField::Nomatch => self.nomatch = value,
255 ZshOptionField::NullGlob => self.null_glob = value,
256 ZshOptionField::CshNullGlob => self.csh_null_glob = value,
257 ZshOptionField::ExtendedGlob => self.extended_glob = value,
258 ZshOptionField::KshGlob => self.ksh_glob = value,
259 ZshOptionField::ShGlob => self.sh_glob = value,
260 ZshOptionField::BareGlobQual => self.bare_glob_qual = value,
261 ZshOptionField::GlobDots => self.glob_dots = value,
262 ZshOptionField::Equals => self.equals = value,
263 ZshOptionField::MagicEqualSubst => self.magic_equal_subst = value,
264 ZshOptionField::ShFileExpansion => self.sh_file_expansion = value,
265 ZshOptionField::GlobAssign => self.glob_assign = value,
266 ZshOptionField::IgnoreBraces => self.ignore_braces = value,
267 ZshOptionField::IgnoreCloseBraces => self.ignore_close_braces = value,
268 ZshOptionField::BraceCcl => self.brace_ccl = value,
269 ZshOptionField::KshArrays => self.ksh_arrays = value,
270 ZshOptionField::KshZeroSubscript => self.ksh_zero_subscript = value,
271 ZshOptionField::ShortLoops => self.short_loops = value,
272 ZshOptionField::ShortRepeat => self.short_repeat = value,
273 ZshOptionField::RcQuotes => self.rc_quotes = value,
274 ZshOptionField::InteractiveComments => self.interactive_comments = value,
275 ZshOptionField::CBases => self.c_bases = value,
276 ZshOptionField::OctalZeroes => self.octal_zeroes = value,
277 }
278 }
279
280 pub fn merge(&self, other: &Self) -> Self {
285 if self == other {
286 return *self;
287 }
288
289 Self {
290 sh_word_split: self.sh_word_split.merge(other.sh_word_split),
291 glob_subst: self.glob_subst.merge(other.glob_subst),
292 rc_expand_param: self.rc_expand_param.merge(other.rc_expand_param),
293 glob: self.glob.merge(other.glob),
294 nomatch: self.nomatch.merge(other.nomatch),
295 null_glob: self.null_glob.merge(other.null_glob),
296 csh_null_glob: self.csh_null_glob.merge(other.csh_null_glob),
297 extended_glob: self.extended_glob.merge(other.extended_glob),
298 ksh_glob: self.ksh_glob.merge(other.ksh_glob),
299 sh_glob: self.sh_glob.merge(other.sh_glob),
300 bare_glob_qual: self.bare_glob_qual.merge(other.bare_glob_qual),
301 glob_dots: self.glob_dots.merge(other.glob_dots),
302 equals: self.equals.merge(other.equals),
303 magic_equal_subst: self.magic_equal_subst.merge(other.magic_equal_subst),
304 sh_file_expansion: self.sh_file_expansion.merge(other.sh_file_expansion),
305 glob_assign: self.glob_assign.merge(other.glob_assign),
306 ignore_braces: self.ignore_braces.merge(other.ignore_braces),
307 ignore_close_braces: self.ignore_close_braces.merge(other.ignore_close_braces),
308 brace_ccl: self.brace_ccl.merge(other.brace_ccl),
309 ksh_arrays: self.ksh_arrays.merge(other.ksh_arrays),
310 ksh_zero_subscript: self.ksh_zero_subscript.merge(other.ksh_zero_subscript),
311 short_loops: self.short_loops.merge(other.short_loops),
312 short_repeat: self.short_repeat.merge(other.short_repeat),
313 rc_quotes: self.rc_quotes.merge(other.rc_quotes),
314 interactive_comments: self.interactive_comments.merge(other.interactive_comments),
315 c_bases: self.c_bases.merge(other.c_bases),
316 octal_zeroes: self.octal_zeroes.merge(other.octal_zeroes),
317 }
318 }
319
320 fn apply_named_option(&mut self, name: &str, enable: bool) -> bool {
321 let Some((field, value)) = parse_zsh_option_assignment(name, enable) else {
322 return false;
323 };
324 self.set_field(
325 field,
326 if value {
327 OptionValue::On
328 } else {
329 OptionValue::Off
330 },
331 );
332 true
333 }
334}
335
336fn parse_zsh_option_assignment(name: &str, enable: bool) -> Option<(ZshOptionField, bool)> {
337 let mut normalized = String::with_capacity(name.len());
338 for ch in name.chars() {
339 if matches!(ch, '_' | '-') {
340 continue;
341 }
342 normalized.push(ch.to_ascii_lowercase());
343 }
344
345 let (normalized, invert) = if let Some(rest) = normalized.strip_prefix("no") {
346 (rest, true)
347 } else {
348 (normalized.as_str(), false)
349 };
350
351 let field = match normalized {
352 "shwordsplit" => ZshOptionField::ShWordSplit,
353 "globsubst" => ZshOptionField::GlobSubst,
354 "rcexpandparam" => ZshOptionField::RcExpandParam,
355 "glob" | "noglob" => ZshOptionField::Glob,
356 "nomatch" => ZshOptionField::Nomatch,
357 "nullglob" => ZshOptionField::NullGlob,
358 "cshnullglob" => ZshOptionField::CshNullGlob,
359 "extendedglob" => ZshOptionField::ExtendedGlob,
360 "kshglob" => ZshOptionField::KshGlob,
361 "shglob" => ZshOptionField::ShGlob,
362 "bareglobqual" => ZshOptionField::BareGlobQual,
363 "globdots" => ZshOptionField::GlobDots,
364 "equals" => ZshOptionField::Equals,
365 "magicequalsubst" => ZshOptionField::MagicEqualSubst,
366 "shfileexpansion" => ZshOptionField::ShFileExpansion,
367 "globassign" => ZshOptionField::GlobAssign,
368 "ignorebraces" => ZshOptionField::IgnoreBraces,
369 "ignoreclosebraces" => ZshOptionField::IgnoreCloseBraces,
370 "braceccl" => ZshOptionField::BraceCcl,
371 "ksharrays" => ZshOptionField::KshArrays,
372 "kshzerosubscript" => ZshOptionField::KshZeroSubscript,
373 "shortloops" => ZshOptionField::ShortLoops,
374 "shortrepeat" => ZshOptionField::ShortRepeat,
375 "rcquotes" => ZshOptionField::RcQuotes,
376 "interactivecomments" => ZshOptionField::InteractiveComments,
377 "cbases" => ZshOptionField::CBases,
378 "octalzeroes" => ZshOptionField::OctalZeroes,
379 _ => return None,
380 };
381
382 Some((field, if invert { !enable } else { enable }))
383}