1use std::collections::HashMap;
4
5use super::git_number::{git_parse_signed, git_parse_unsigned};
6
7const PARSE_OPTIONS_HELP: &str = include_str!("parse_options_help.txt");
8
9pub type ParseOptionsStatus = i32;
11
12#[derive(Debug)]
13pub enum ParseOptionsToolError {
14 Help,
16 Fatal(String),
17 Bug(String),
18}
19
20impl std::fmt::Display for ParseOptionsToolError {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 ParseOptionsToolError::Help => f.write_str("(help)"),
24 ParseOptionsToolError::Fatal(s) | ParseOptionsToolError::Bug(s) => f.write_str(s),
25 }
26 }
27}
28
29impl std::error::Error for ParseOptionsToolError {}
30
31fn env_disallow_abbrev() -> bool {
32 std::env::var("GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS")
33 .ok()
34 .as_deref()
35 .map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on"))
36 .unwrap_or(false)
37}
38
39fn int_bounds_32() -> (i128, i128) {
40 (i32::MIN as i128, i32::MAX as i128)
41}
42
43struct CmdElem {
44 value: i32,
45 opt: Option<(OptMeta, Option<String>, bool)>,
46}
47
48#[derive(Clone, Copy)]
49struct OptMeta {
50 name: &'static str,
51 is_cmdmode: bool,
52}
53
54struct PoState {
55 boolean: i32,
56 integer: i32,
57 unsigned_integer: u64,
58 timestamp: i64,
59 i16: i16,
60 u16: u16,
61 abbrev: i32,
62 verbose: i32,
63 dry_run: i32,
64 quiet: i32,
65 string: Option<String>,
66 file: Option<String>,
67 ambiguous: i32,
68 list: Vec<String>,
69 length_cb_called: bool,
70 length_cb_arg: Option<String>,
71 length_cb_unset: bool,
72 expect: HashMap<String, String>,
73 cmd: CmdElem,
74}
75
76impl Default for PoState {
77 fn default() -> Self {
78 Self {
79 boolean: 0,
80 integer: 0,
81 unsigned_integer: 0,
82 timestamp: 0,
83 i16: 0,
84 u16: 0,
85 abbrev: 7,
86 verbose: -1,
87 dry_run: 0,
88 quiet: 0,
89 string: None,
90 file: None,
91 ambiguous: 0,
92 list: Vec::new(),
93 length_cb_called: false,
94 length_cb_arg: None,
95 length_cb_unset: false,
96 expect: HashMap::new(),
97 cmd: CmdElem {
98 value: 0,
99 opt: None,
100 },
101 }
102 }
103}
104
105impl PoState {
106 fn touch_integer(
107 &mut self,
108 new: i32,
109 meta: OptMeta,
110 arg: Option<&str>,
111 unset: bool,
112 ) -> Result<(), ParseOptionsToolError> {
113 self.integer = new;
114 let new_val = self.integer;
115 let e = &mut self.cmd;
116 if new_val == e.value {
117 return Ok(());
118 }
119 if let Some((prev, prev_arg, prev_unset)) = &e.opt {
120 if prev.is_cmdmode || meta.is_cmdmode {
121 let o1 = format_opt_display(meta.name, arg, unset);
122 let o2 = format_opt_display(prev.name, prev_arg.as_deref(), *prev_unset);
123 return Err(ParseOptionsToolError::Fatal(format!(
124 "error: options '{o1}' and '{o2}' cannot be used together\n"
125 )));
126 }
127 }
128 e.opt = Some((meta, arg.map(|s| s.to_string()), unset));
129 e.value = new_val;
130 Ok(())
131 }
132
133 fn show_line(&self, expect: &HashMap<String, String>, line: &str, bad: &mut bool) {
134 if expect.is_empty() {
135 println!("{line}");
136 return;
137 }
138 let Some(colon) = line.find(':') else {
139 println!("{line}");
140 return;
141 };
142 let key = &line[..colon];
143 let Some(expected_full) = expect.get(key) else {
144 println!("{line}");
145 return;
146 };
147 if expected_full != line {
148 println!("-{expected_full}");
149 println!("+{line}");
150 *bad = true;
151 }
152 }
153
154 fn dump(
155 &self,
156 expect: &HashMap<String, String>,
157 rest: &[String],
158 ) -> Result<ParseOptionsStatus, ParseOptionsToolError> {
159 let mut bad = false;
160 if self.length_cb_called {
161 let arg = self.length_cb_arg.as_deref().unwrap_or("not set");
162 let u = if self.length_cb_unset { 1 } else { 0 };
163 let line = format!("Callback: \"{arg}\", {u}");
164 self.show_line(expect, &line, &mut bad);
165 }
166 self.show_line(expect, &format!("boolean: {}", self.boolean), &mut bad);
167 self.show_line(expect, &format!("integer: {}", self.integer), &mut bad);
168 self.show_line(expect, &format!("i16: {}", self.i16), &mut bad);
169 self.show_line(
170 expect,
171 &format!("unsigned: {}", self.unsigned_integer),
172 &mut bad,
173 );
174 self.show_line(expect, &format!("u16: {}", self.u16), &mut bad);
175 self.show_line(expect, &format!("timestamp: {}", self.timestamp), &mut bad);
176 let s = self.string.as_deref().unwrap_or("(not set)");
177 self.show_line(expect, &format!("string: {s}"), &mut bad);
178 self.show_line(expect, &format!("abbrev: {}", self.abbrev), &mut bad);
179 self.show_line(expect, &format!("verbose: {}", self.verbose), &mut bad);
180 self.show_line(expect, &format!("quiet: {}", self.quiet), &mut bad);
181 self.show_line(
182 expect,
183 &format!("dry run: {}", if self.dry_run != 0 { "yes" } else { "no" }),
184 &mut bad,
185 );
186 let f = self.file.as_deref().unwrap_or("(not set)");
187 self.show_line(expect, &format!("file: {f}"), &mut bad);
188 for item in &self.list {
189 self.show_line(expect, &format!("list: {item}"), &mut bad);
190 }
191 for (i, a) in rest.iter().enumerate() {
192 self.show_line(expect, &format!("arg {i:02}: {a}"), &mut bad);
193 }
194 Ok(if bad { 1 } else { 0 })
195 }
196}
197
198fn format_opt_display(name: &'static str, arg: Option<&str>, unset: bool) -> String {
199 if name == "mode34" && !unset {
200 if let Some(a) = arg {
201 return format!("--mode34={a}");
202 }
203 }
204 format!("--{name}")
205}
206
207fn usage_append() -> String {
208 let mut s = String::new();
209 s.push('\n');
210 s.push_str(PARSE_OPTIONS_HELP);
211 s
212}
213
214fn collect_expect(
215 map: &mut HashMap<String, String>,
216 arg: &str,
217) -> Result<(), ParseOptionsToolError> {
218 let Some(colon) = arg.find(':') else {
219 return Err(ParseOptionsToolError::Fatal(
220 "malformed --expect option\n".to_string() + &usage_append(),
221 ));
222 };
223 let key = arg[..colon].to_string();
224 if map.insert(key, arg.to_string()).is_some() {
225 return Err(ParseOptionsToolError::Fatal(format!(
226 "malformed --expect option, duplicate {}\n",
227 &arg[..colon]
228 )));
229 }
230 Ok(())
231}
232
233pub fn run_parse_options(args: &[String]) -> Result<ParseOptionsStatus, ParseOptionsToolError> {
235 let disallow_abbrev = env_disallow_abbrev();
236 let mut st = PoState::default();
237 let argv = args;
238 if argv.is_empty() {
239 return Err(ParseOptionsToolError::Fatal(
240 "usage: test-tool parse-options <options>\n".to_string() + &usage_append(),
241 ));
242 }
243 let mut i = 1usize;
244 let prefix = "prefix/";
245
246 while i < argv.len() {
247 let arg = &argv[i];
248 if arg == "-h" || arg == "--help" {
249 print!("{PARSE_OPTIONS_HELP}");
250 return Err(ParseOptionsToolError::Help);
251 }
252 if arg == "--help-all" {
253 print!("{PARSE_OPTIONS_HELP}");
254 return Err(ParseOptionsToolError::Help);
255 }
256 if arg == "--" {
257 i += 1;
258 break;
259 }
260 if let Some(rest) = arg.strip_prefix("--") {
261 if rest == "end-of-options" {
262 i += 1;
263 break;
264 }
265 let (name, eq_val) = if let Some(p) = rest.find('=') {
266 (&rest[..p], Some(rest[p + 1..].to_string()))
267 } else {
268 (rest, None)
269 };
270 if name == "expect" {
271 let v = eq_val.ok_or_else(|| {
272 ParseOptionsToolError::Fatal(
273 "error: option `expect' requires a value\n".to_string() + &usage_append(),
274 )
275 })?;
276 collect_expect(&mut st.expect, &v)?;
277 i += 1;
278 continue;
279 }
280 match parse_long(&mut st, name, eq_val, argv, &mut i, prefix, disallow_abbrev) {
281 Ok(()) => {}
282 Err(ParseOptionsToolError::Fatal(msg)) => {
283 return Err(ParseOptionsToolError::Fatal(msg + &usage_append()));
284 }
285 Err(e) => return Err(e),
286 }
287 continue;
288 }
289 if arg.starts_with('-') && arg.len() > 1 {
290 if arg == "-" {
291 i += 1;
292 break;
293 }
294 let tail = &arg[1..];
296 if !tail.is_empty() && tail.chars().all(|c| c.is_ascii_digit()) {
297 let n: i32 = -tail.parse::<i32>().map_err(|_| {
298 ParseOptionsToolError::Fatal("error: invalid number\n".to_string())
299 })?;
300 st.touch_integer(
301 n,
302 OptMeta {
303 name: "NUM",
304 is_cmdmode: false,
305 },
306 None,
307 false,
308 )?;
309 i += 1;
310 continue;
311 }
312 i = parse_short(&mut st, argv, i, prefix, disallow_abbrev)?;
313 continue;
314 }
315 break;
316 }
317
318 let rest: Vec<String> = argv[i..].to_vec();
319 st.dump(&st.expect.clone(), &rest)
320}
321
322fn parse_long(
323 st: &mut PoState,
324 name: &str,
325 eq_val: Option<String>,
326 argv: &[String],
327 i: &mut usize,
328 prefix: &str,
329 disallow_abbrev: bool,
330) -> Result<(), ParseOptionsToolError> {
331 let arg_end = name.find('=').unwrap_or(name.len());
332 let original_key = name;
333 let mut flags_unset = false;
334 let mut arg_starts_with_no_no = false;
335 let mut s = name;
336 if let Some(x) = s.strip_prefix("no-") {
337 if let Some(x2) = x.strip_prefix("no-") {
338 arg_starts_with_no_no = true;
339 s = x2;
340 } else {
341 flags_unset = true;
342 s = x;
343 }
344 }
345
346 let _ = arg_starts_with_no_no;
347
348 let matched = long_exact(st, s, flags_unset, eq_val.clone(), argv, i, prefix)?;
349 if matched {
350 return Ok(());
351 }
352
353 if !disallow_abbrev {
354 let m = long_abbrev(st, s, arg_end, flags_unset, eq_val.clone(), argv, i, prefix)?;
355 if m {
356 return Ok(());
357 }
358 }
359
360 Err(unknown_long(original_key))
361}
362
363fn unknown_long(name: &str) -> ParseOptionsToolError {
364 ParseOptionsToolError::Fatal(format!("error: unknown option `{name}'\n"))
365}
366
367fn long_exact(
368 st: &mut PoState,
369 full: &str,
370 flags_unset: bool,
371 eq_val: Option<String>,
372 argv: &[String],
373 i: &mut usize,
374 prefix: &str,
375) -> Result<bool, ParseOptionsToolError> {
376 let u = flags_unset;
377 let mut hit = false;
378 match full {
379 "yes" => {
380 no_eq(eq_val.as_deref(), "yes", u)?;
381 let unset = u ^ false;
382 st.boolean = if unset { 0 } else { 1 };
383 hit = true;
384 }
385 "doubt" => {
386 no_eq(eq_val.as_deref(), "doubt", u)?;
387 let unset = u ^ true;
388 st.boolean = if unset { 0 } else { 1 };
389 hit = true;
390 }
391 "no-fear" => {
392 no_eq(eq_val.as_deref(), "no-fear", u)?;
393 st.boolean = 1;
394 hit = true;
395 }
396 "boolean" => {
397 no_eq(eq_val.as_deref(), "boolean", u)?;
398 if u {
399 st.boolean = 0;
400 } else {
401 st.boolean = st.boolean.saturating_add(1);
402 }
403 hit = true;
404 }
405 "or4" => {
406 no_eq(eq_val.as_deref(), "or4", u)?;
407 if u {
408 st.boolean &= !4;
409 } else {
410 st.boolean |= 4;
411 }
412 hit = true;
413 }
414 "neg-or4" => {
415 no_eq(eq_val.as_deref(), "neg-or4", u)?;
416 if u {
417 st.boolean |= 4;
418 } else {
419 st.boolean &= !4;
420 }
421 hit = true;
422 }
423 "integer" => {
424 let v = take_val(eq_val, argv, i, "integer")?;
425 set_int(st, &v, "integer")?;
426 hit = true;
427 }
428 "i16" => {
429 let v = take_val(eq_val, argv, i, "i16")?;
430 set_i16(st, &v)?;
431 hit = true;
432 }
433 "unsigned" => {
434 let v = take_val(eq_val, argv, i, "unsigned")?;
435 set_unsigned(st, &v)?;
436 hit = true;
437 }
438 "u16" => {
439 let v = take_val(eq_val, argv, i, "u16")?;
440 set_u16(st, &v)?;
441 hit = true;
442 }
443 "set23" => {
444 no_eq(eq_val.as_deref(), "set23", u)?;
445 st.touch_integer(if u { 0 } else { 23 }, opt("set23", false), None, u)?;
446 hit = true;
447 }
448 "mode1" => {
449 no_eq(eq_val.as_deref(), "mode1", u)?;
450 st.touch_integer(if u { 0 } else { 1 }, opt("mode1", true), None, u)?;
451 hit = true;
452 }
453 "mode2" => {
454 no_eq(eq_val.as_deref(), "mode2", u)?;
455 st.touch_integer(if u { 0 } else { 2 }, opt("mode2", true), None, u)?;
456 hit = true;
457 }
458 "mode34" => {
459 let v = take_val(eq_val, argv, i, "mode34")?;
460 if u {
461 st.touch_integer(0, opt("mode34", true), Some("0"), true)?;
462 } else if v == "3" {
463 st.touch_integer(3, opt("mode34", true), Some("3"), false)?;
464 } else if v == "4" {
465 st.touch_integer(4, opt("mode34", true), Some("4"), false)?;
466 } else {
467 return Err(ParseOptionsToolError::Fatal(format!(
468 "error: invalid value for '--mode34': '{v}'\n"
469 )));
470 }
471 hit = true;
472 }
473 "length" => {
474 let v = take_val(eq_val, argv, i, "length")?;
475 if u {
476 return Err(ParseOptionsToolError::Fatal(
477 "error: option `no-length' isn't available\n".to_string(),
478 ));
479 }
480 st.length_cb_called = true;
481 st.length_cb_arg = Some(v.clone());
482 st.length_cb_unset = false;
483 st.touch_integer(v.len() as i32, opt("length", false), None, false)?;
484 hit = true;
485 }
486 "file" => {
487 let v = take_val(eq_val, argv, i, "file")?;
488 if u {
489 st.file = None;
490 } else {
491 st.file = Some(format!("{prefix}{v}"));
492 }
493 hit = true;
494 }
495 "string" | "string2" | "st" => {
496 let v = take_val(eq_val, argv, i, "string")?;
497 if u {
498 st.string = None;
499 } else {
500 st.string = Some(v);
501 }
502 hit = true;
503 }
504 "obsolete" => {
505 no_eq(eq_val.as_deref(), "obsolete", false)?;
506 hit = true;
507 }
508 "longhelp" => {
509 no_eq(eq_val.as_deref(), "longhelp", u)?;
510 st.touch_integer(0, opt("longhelp", false), None, u)?;
511 hit = true;
512 }
513 "list" => {
514 let v = take_val(eq_val, argv, i, "list")?;
515 if u {
516 st.list.clear();
517 } else {
518 st.list.push(v);
519 }
520 hit = true;
521 }
522 "ambiguous" => {
523 no_eq(eq_val.as_deref(), "ambiguous", false)?;
524 st.ambiguous = st.ambiguous.saturating_add(1);
525 hit = true;
526 }
527 "no-ambiguous" => {
528 no_eq(eq_val.as_deref(), "no-ambiguous", false)?;
529 st.ambiguous = 0;
530 hit = true;
531 }
532 "abbrev" => {
533 if u {
534 st.abbrev = 0;
535 } else if let Some(ev) = eq_val {
536 parse_abbrev(&ev, &mut st.abbrev)?;
537 } else if *i + 1 < argv.len() {
538 let v = take_val(None, argv, i, "abbrev")?;
539 parse_abbrev(&v, &mut st.abbrev)?;
540 } else {
541 st.abbrev = 7;
542 }
543 hit = true;
544 }
545 "verbose" => {
546 no_eq(eq_val.as_deref(), "verbose", u)?;
547 if u {
548 st.verbose = 0;
549 } else {
550 st.verbose = if st.verbose < 0 { 1 } else { st.verbose + 1 };
551 }
552 hit = true;
553 }
554 "quiet" => {
555 no_eq(eq_val.as_deref(), "quiet", u)?;
556 if u {
557 st.quiet = 0;
558 } else if st.quiet <= 0 {
559 st.quiet -= 1;
560 } else {
561 st.quiet = -1;
562 }
563 hit = true;
564 }
565 "dry-run" => {
566 no_eq(eq_val.as_deref(), "dry-run", u)?;
567 st.dry_run = if u { 0 } else { 1 };
568 hit = true;
569 }
570 "alias-source" | "alias-target" => {
571 let v = take_val(eq_val, argv, i, "alias-source")?;
572 if u {
573 st.string = None;
574 } else {
575 st.string = Some(v);
576 }
577 hit = true;
578 }
579 _ => {}
580 }
581 if hit {
582 *i += 1;
583 }
584 Ok(hit)
585}
586
587fn opt(name: &'static str, is_cmdmode: bool) -> OptMeta {
588 OptMeta { name, is_cmdmode }
589}
590
591const LONG_NAMES: &[&str] = &[
593 "yes",
594 "no-doubt",
595 "doubt",
596 "no-fear",
597 "boolean",
598 "or4",
599 "neg-or4",
600 "integer",
601 "i16",
602 "unsigned",
603 "u16",
604 "set23",
605 "mode1",
606 "mode2",
607 "mode34",
608 "length",
609 "file",
610 "string",
611 "string2",
612 "st",
613 "obsolete",
614 "longhelp",
615 "list",
616 "ambiguous",
617 "no-ambiguous",
618 "abbrev",
619 "verbose",
620 "quiet",
621 "dry-run",
622 "expect",
623 "alias-source",
624 "alias-target",
625];
626
627fn is_alias_pair(a: &str, b: &str) -> bool {
628 (a == "alias-source" && b == "alias-target") || (a == "alias-target" && b == "alias-source")
629}
630
631fn long_abbrev(
632 st: &mut PoState,
633 s: &str,
634 _arg_end: usize,
635 flags_unset: bool,
636 eq_val: Option<String>,
637 argv: &[String],
638 i: &mut usize,
639 prefix: &str,
640) -> Result<bool, ParseOptionsToolError> {
641 let user_len = s.len();
642 if user_len == 0 {
643 return Ok(false);
644 }
645 let mut matches: Vec<&'static str> = Vec::new();
646 for &ln in LONG_NAMES {
647 let mut long_name = ln;
648 let mut opt_unset = false;
649 if let Some(x) = long_name.strip_prefix("no-") {
650 long_name = x;
651 opt_unset = true;
652 }
653 let allow_unset = !matches!(
654 ln,
655 "no-fear" | "obsolete" | "longhelp" | "ambiguous" | "no-ambiguous"
656 );
657 if (flags_unset ^ opt_unset) && !allow_unset {
658 continue;
659 }
660 if long_name.len() >= user_len && long_name.as_bytes().get(..user_len) == Some(s.as_bytes())
661 {
662 matches.push(ln);
663 }
664 }
665 matches.sort_unstable();
666 matches.dedup();
667 if matches.is_empty() {
668 return Ok(false);
669 }
670 let mut abbrev: Option<&str> = None;
671 let mut ambiguous: Option<(&str, &str)> = None;
672 for m in &matches {
673 match abbrev {
674 None => abbrev = Some(m),
675 Some(a) => {
676 if !is_alias_pair(a, m) {
677 ambiguous = Some((a, m));
678 break;
679 }
680 abbrev = Some(m);
681 }
682 }
683 }
684 if let Some((a, b)) = ambiguous {
685 return Err(ParseOptionsToolError::Fatal(format!(
686 "ambiguous option: {s} (could be --{a} or --{b})\n"
687 )));
688 }
689 let Some(only) = abbrev else {
690 return Ok(false);
691 };
692 let key = match only {
693 "no-doubt" => "doubt",
694 o => o,
695 };
696 long_exact(st, key, flags_unset, eq_val, argv, i, prefix)?;
697 Ok(true)
698}
699
700fn no_eq(eq: Option<&str>, name: &str, unset: bool) -> Result<(), ParseOptionsToolError> {
701 if let Some(x) = eq {
702 if !x.is_empty() || unset {
703 return Err(ParseOptionsToolError::Fatal(format!(
704 "error: option `{name}' takes no value\n"
705 )));
706 }
707 }
708 Ok(())
709}
710
711fn take_val(
712 eq_val: Option<String>,
713 argv: &[String],
714 i: &mut usize,
715 optname: &str,
716) -> Result<String, ParseOptionsToolError> {
717 if let Some(v) = eq_val {
718 return Ok(v);
719 }
720 if *i + 1 >= argv.len() {
721 return Err(ParseOptionsToolError::Fatal(format!(
722 "error: option `{optname}' requires a value\n"
723 )));
724 }
725 *i += 1;
726 Ok(argv[*i].clone())
727}
728
729fn parse_abbrev(s: &str, out: &mut i32) -> Result<(), ParseOptionsToolError> {
730 if s.is_empty() {
731 return Err(ParseOptionsToolError::Fatal(
732 "error: option `abbrev' expects a numerical value\n".to_string(),
733 ));
734 }
735 let v: i32 = s.parse().map_err(|_| {
736 ParseOptionsToolError::Fatal(
737 "error: option `abbrev' expects a numerical value\n".to_string(),
738 )
739 })?;
740 *out = if v != 0 && v < 4 { 4 } else { v };
741 Ok(())
742}
743
744fn set_int(st: &mut PoState, raw: &str, optname: &str) -> Result<(), ParseOptionsToolError> {
745 let (lo, hi) = int_bounds_32();
746 let opt_meta = match optname {
747 "integer" | "j" => opt("integer", false),
748 _ => {
749 return Err(ParseOptionsToolError::Fatal(format!(
750 "internal error: unknown option name for set_int: {optname}\n"
751 )));
752 }
753 };
754 match git_parse_signed(raw, hi) {
755 Ok(v) if v >= lo && v <= hi => {
756 st.touch_integer(v as i32, opt_meta, None, false)?;
757 Ok(())
758 }
759 Err(std::io::ErrorKind::InvalidData) => Err(ParseOptionsToolError::Fatal(format!(
760 "error: value {raw} for option `{optname}' not in range [{lo},{hi}]\n"
761 ))),
762 _ => Err(ParseOptionsToolError::Fatal(format!(
763 "error: option `{optname}' expects an integer value with an optional k/m/g suffix\n"
764 ))),
765 }
766}
767
768fn set_i16(st: &mut PoState, raw: &str) -> Result<(), ParseOptionsToolError> {
769 match git_parse_signed(raw, i16::MAX as i128) {
770 Ok(v) if v >= i16::MIN as i128 && v <= i16::MAX as i128 => {
771 st.i16 = v as i16;
772 Ok(())
773 }
774 Err(std::io::ErrorKind::InvalidData) | Ok(_) => Err(ParseOptionsToolError::Fatal(format!(
775 "error: value {raw} for option `i16' not in range [-32768,32767]\n"
776 ))),
777 _ => Err(ParseOptionsToolError::Fatal(
778 "error: option `i16' expects an integer value with an optional k/m/g suffix\n"
779 .to_string(),
780 )),
781 }
782}
783
784fn set_unsigned(st: &mut PoState, raw: &str) -> Result<(), ParseOptionsToolError> {
785 match git_parse_unsigned(raw, u64::MAX as u128) {
786 Ok(v) => {
787 st.unsigned_integer = v as u64;
788 Ok(())
789 }
790 Err(std::io::ErrorKind::InvalidData) => Err(ParseOptionsToolError::Fatal(format!(
791 "error: value {raw} for option `unsigned' not in range [0,{}]\n",
792 u64::MAX
793 ))),
794 _ => Err(ParseOptionsToolError::Fatal(
795 "error: option `unsigned' expects a non-negative integer value with an optional k/m/g suffix\n"
796 .to_string(),
797 )),
798 }
799}
800
801fn set_u16(st: &mut PoState, raw: &str) -> Result<(), ParseOptionsToolError> {
802 match git_parse_unsigned(raw, u16::MAX as u128) {
803 Ok(v) if v <= u16::MAX as u128 => {
804 st.u16 = v as u16;
805 Ok(())
806 }
807 _ => Err(ParseOptionsToolError::Fatal(format!(
808 "error: value {raw} for option `u16' not in range [0,65535]\n"
809 ))),
810 }
811}
812
813fn check_typos(full_suffix: &str) -> Result<(), ParseOptionsToolError> {
815 if full_suffix.len() < 3 {
816 return Ok(());
817 }
818 if full_suffix.starts_with("no-") {
819 return Err(ParseOptionsToolError::Fatal(format!(
820 "error: did you mean `--{full_suffix}` (with two dashes)?\n"
821 )));
822 }
823 for ln in LONG_NAMES {
824 if ln.starts_with(full_suffix) {
825 return Err(ParseOptionsToolError::Fatal(format!(
826 "error: did you mean `--{full_suffix}` (with two dashes)?\n"
827 )));
828 }
829 }
830 Ok(())
831}
832
833fn parse_short(
834 st: &mut PoState,
835 argv: &[String],
836 i: usize,
837 prefix: &str,
838 _disallow_abbrev: bool,
839) -> Result<usize, ParseOptionsToolError> {
840 let arg = &argv[i];
841 let full_suffix = &arg[1..];
842 if full_suffix.is_empty() {
843 return Err(ParseOptionsToolError::Fatal(
844 "error: unknown switch\n".to_string(),
845 ));
846 }
847
848 let mut o = 0usize;
849 let mut local_i = i;
850
851 let first = full_suffix.chars().next().unwrap();
853 let flen = first.len_utf8();
854 match first {
855 'h' => {
856 print!("{PARSE_OPTIONS_HELP}");
857 return Err(ParseOptionsToolError::Help);
858 }
859 's' => {
860 let v = if full_suffix.len() > flen {
861 full_suffix[flen..].to_string()
862 } else if local_i + 1 < argv.len() {
863 local_i += 1;
864 argv[local_i].clone()
865 } else {
866 return Err(ParseOptionsToolError::Fatal(
867 "error: switch `s' requires a value\n".to_string(),
868 ));
869 };
870 o = full_suffix.len();
871 st.string = Some(v);
872 }
873 'b' => {
874 st.boolean = st.boolean.saturating_add(1);
875 o += flen;
876 }
877 'i' => {
878 let tail = &full_suffix[flen..];
879 let v = if !tail.is_empty() {
880 tail.to_string()
881 } else if local_i + 1 < argv.len() {
882 local_i += 1;
883 argv[local_i].clone()
884 } else {
885 return Err(ParseOptionsToolError::Fatal(
886 "error: switch `i' requires a value\n".to_string(),
887 ));
888 };
889 set_int(st, &v, "integer")?;
890 o = full_suffix.len();
891 }
892 'j' => {
893 let tail = &full_suffix[flen..];
894 let v = if !tail.is_empty() {
895 tail.to_string()
896 } else if local_i + 1 < argv.len() {
897 local_i += 1;
898 argv[local_i].clone()
899 } else {
900 return Err(ParseOptionsToolError::Fatal(
901 "error: switch `j' requires a value\n".to_string(),
902 ));
903 };
904 set_int(st, &v, "j")?;
905 o = full_suffix.len();
906 }
907 'u' => {
908 let tail = &full_suffix[flen..];
909 let v = if !tail.is_empty() {
910 tail.to_string()
911 } else if local_i + 1 < argv.len() {
912 local_i += 1;
913 argv[local_i].clone()
914 } else {
915 return Err(ParseOptionsToolError::Fatal(
916 "error: switch `u' requires a value\n".to_string(),
917 ));
918 };
919 set_unsigned(st, &v)?;
920 o = full_suffix.len();
921 }
922 'v' => {
923 st.verbose = if st.verbose < 0 { 1 } else { st.verbose + 1 };
924 o += flen;
925 }
926 'n' => {
927 st.dry_run = 1;
928 o += flen;
929 }
930 'q' => {
931 if st.quiet <= 0 {
932 st.quiet -= 1;
933 } else {
934 st.quiet = -1;
935 }
936 o += flen;
937 }
938 'D' => {
939 st.boolean = 1;
940 o += flen;
941 }
942 'B' => {
943 st.boolean = 1;
944 o += flen;
945 }
946 '4' => {
947 st.boolean |= 4;
948 o += flen;
949 }
950 'L' => {
951 let v = if full_suffix.len() > flen {
952 full_suffix[flen..].to_string()
953 } else if local_i + 1 < argv.len() {
954 local_i += 1;
955 argv[local_i].clone()
956 } else {
957 return Err(ParseOptionsToolError::Fatal(
958 "error: switch `L' requires a value\n".to_string(),
959 ));
960 };
961 o = full_suffix.len();
962 st.length_cb_called = true;
963 st.length_cb_arg = Some(v.clone());
964 st.length_cb_unset = false;
965 st.touch_integer(v.len() as i32, opt("length", false), None, false)?;
966 }
967 'F' => {
968 let v = if full_suffix.len() > flen {
969 full_suffix[flen..].to_string()
970 } else if local_i + 1 < argv.len() {
971 local_i += 1;
972 argv[local_i].clone()
973 } else {
974 return Err(ParseOptionsToolError::Fatal(
975 "error: switch `F' requires a value\n".to_string(),
976 ));
977 };
978 o = full_suffix.len();
979 st.file = Some(format!("{prefix}{v}"));
980 }
981 'A' => {
982 let v = if full_suffix.len() > flen {
983 full_suffix[flen..].to_string()
984 } else if local_i + 1 < argv.len() {
985 local_i += 1;
986 argv[local_i].clone()
987 } else {
988 return Err(ParseOptionsToolError::Fatal(
989 "error: switch `A' requires a value\n".to_string(),
990 ));
991 };
992 o = full_suffix.len();
993 st.string = Some(v);
994 }
995 'Z' => {
996 let v = if full_suffix.len() > flen {
997 full_suffix[flen..].to_string()
998 } else if local_i + 1 < argv.len() {
999 local_i += 1;
1000 argv[local_i].clone()
1001 } else {
1002 return Err(ParseOptionsToolError::Fatal(
1003 "error: switch `Z' requires a value\n".to_string(),
1004 ));
1005 };
1006 o = full_suffix.len();
1007 st.string = Some(v);
1008 }
1009 'o' => {
1010 let v = if full_suffix.len() > flen {
1011 full_suffix[flen..].to_string()
1012 } else if local_i + 1 < argv.len() {
1013 local_i += 1;
1014 argv[local_i].clone()
1015 } else {
1016 return Err(ParseOptionsToolError::Fatal(
1017 "error: switch `o' requires a value\n".to_string(),
1018 ));
1019 };
1020 o = full_suffix.len();
1021 st.string = Some(v);
1022 }
1023 '+' => {
1024 st.boolean = st.boolean.saturating_add(1);
1025 o += flen;
1026 }
1027 _ => {
1028 return Err(ParseOptionsToolError::Fatal(format!(
1029 "error: unknown switch `{first}`\n"
1030 )));
1031 }
1032 }
1033
1034 if o < full_suffix.len() {
1035 check_typos(full_suffix)?;
1036 }
1037
1038 while o < full_suffix.len() {
1039 let c = full_suffix[o..].chars().next().unwrap();
1040 let clen = c.len_utf8();
1041 match c {
1042 'b' => {
1043 st.boolean = st.boolean.saturating_add(1);
1044 o += clen;
1045 }
1046 'v' => {
1047 st.verbose = if st.verbose < 0 { 1 } else { st.verbose + 1 };
1048 o += clen;
1049 }
1050 'n' => {
1051 st.dry_run = 1;
1052 o += clen;
1053 }
1054 'q' => {
1055 if st.quiet <= 0 {
1056 st.quiet -= 1;
1057 } else {
1058 st.quiet = -1;
1059 }
1060 o += clen;
1061 }
1062 '4' => {
1063 st.boolean |= 4;
1064 o += clen;
1065 }
1066 '+' => {
1067 st.boolean = st.boolean.saturating_add(1);
1068 o += clen;
1069 }
1070 _ => {
1071 return Err(ParseOptionsToolError::Fatal(format!(
1072 "error: unknown switch `{c}`\n"
1073 )));
1074 }
1075 }
1076 }
1077
1078 Ok(local_i + 1)
1079}