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 append_usage_if_unknown(msg: &str) -> String {
218 let with_usage = msg.to_string() + &usage_append();
219 if msg.starts_with("error: unknown option `")
220 || msg.starts_with("error: unknown switch `")
221 || msg.starts_with("ambiguous option:")
222 {
223 with_usage
224 } else {
225 msg.to_string()
226 }
227}
228
229fn map_parse_fatal(e: ParseOptionsToolError) -> ParseOptionsToolError {
230 match e {
231 ParseOptionsToolError::Fatal(m) => {
232 ParseOptionsToolError::Fatal(append_usage_if_unknown(&m))
233 }
234 o => o,
235 }
236}
237
238fn collect_expect(
239 map: &mut HashMap<String, String>,
240 arg: &str,
241) -> Result<(), ParseOptionsToolError> {
242 let Some(colon) = arg.find(':') else {
243 return Err(ParseOptionsToolError::Fatal(
244 "malformed --expect option\n".to_string() + &usage_append(),
245 ));
246 };
247 let key = arg[..colon].to_string();
248 if map.insert(key, arg.to_string()).is_some() {
249 return Err(ParseOptionsToolError::Fatal(format!(
250 "malformed --expect option, duplicate {}\n",
251 &arg[..colon]
252 )));
253 }
254 Ok(())
255}
256
257pub fn run_parse_options(args: &[String]) -> Result<ParseOptionsStatus, ParseOptionsToolError> {
259 let disallow_abbrev = env_disallow_abbrev();
260 let mut st = PoState::default();
261 let argv = args;
262 if argv.is_empty() {
263 return Err(ParseOptionsToolError::Fatal(
264 "usage: test-tool parse-options <options>\n".to_string() + &usage_append(),
265 ));
266 }
267 let mut i = 1usize;
268 let prefix = "prefix/";
269 let mut rest: Vec<String> = Vec::new();
270
271 while i < argv.len() {
272 let arg = &argv[i];
273 if arg == "-h" || arg == "--help" {
274 print!("{PARSE_OPTIONS_HELP}");
275 return Err(ParseOptionsToolError::Help);
276 }
277 if arg == "--help-all" {
278 print!("{PARSE_OPTIONS_HELP}");
279 return Err(ParseOptionsToolError::Help);
280 }
281 if arg == "--" {
282 i += 1;
283 rest.extend(argv[i..].iter().cloned());
284 return st.dump(&st.expect.clone(), &rest);
285 }
286 if let Some(rest_arg) = arg.strip_prefix("--") {
287 if rest_arg == "end-of-options" {
288 i += 1;
289 rest.extend(argv[i..].iter().cloned());
290 return st.dump(&st.expect.clone(), &rest);
291 }
292 let (name, eq_val) = if let Some(p) = rest_arg.find('=') {
293 (&rest_arg[..p], Some(rest_arg[p + 1..].to_string()))
294 } else {
295 (rest_arg, None)
296 };
297 if name == "expect" {
298 let v = eq_val.ok_or_else(|| {
299 ParseOptionsToolError::Fatal(
300 "error: option `expect' requires a value\n".to_string(),
301 )
302 })?;
303 collect_expect(&mut st.expect, &v)?;
304 i += 1;
305 continue;
306 }
307 match parse_long(&mut st, name, eq_val, argv, &mut i, prefix, disallow_abbrev) {
308 Ok(()) => {}
309 Err(e) => return Err(map_parse_fatal(e)),
310 }
311 continue;
312 }
313 if arg.starts_with('-') && arg.len() > 1 {
314 if arg == "-" {
315 i += 1;
316 rest.extend(argv[i..].iter().cloned());
317 return st.dump(&st.expect.clone(), &rest);
318 }
319 let tail = &arg[1..];
321 if !tail.is_empty() && tail.chars().all(|c| c.is_ascii_digit()) {
322 let n: i32 = -tail.parse::<i32>().map_err(|_| {
323 ParseOptionsToolError::Fatal("error: invalid number\n".to_string())
324 })?;
325 st.touch_integer(
326 n,
327 OptMeta {
328 name: "NUM",
329 is_cmdmode: false,
330 },
331 None,
332 false,
333 )?;
334 i += 1;
335 continue;
336 }
337 i = match parse_short(&mut st, argv, i, prefix, disallow_abbrev) {
338 Ok(n) => n,
339 Err(e) => return Err(map_parse_fatal(e)),
340 };
341 continue;
342 }
343 if arg == "+" {
345 st.boolean = st.boolean.saturating_add(1);
346 i += 1;
347 continue;
348 }
349 rest.push(arg.clone());
350 i += 1;
351 }
352
353 st.dump(&st.expect.clone(), &rest)
354}
355
356fn parse_long(
357 st: &mut PoState,
358 name: &str,
359 eq_val: Option<String>,
360 argv: &[String],
361 i: &mut usize,
362 prefix: &str,
363 disallow_abbrev: bool,
364) -> Result<(), ParseOptionsToolError> {
365 let arg_end = name.find('=').unwrap_or(name.len());
366 let original_key = name;
367 let mut flags_unset = false;
368 let mut arg_starts_with_no_no = false;
369 let mut s = name;
370 if let Some(x) = s.strip_prefix("no-") {
371 if let Some(x2) = x.strip_prefix("no-") {
372 arg_starts_with_no_no = true;
373 s = x2;
374 } else {
375 flags_unset = true;
376 s = x;
377 }
378 }
379
380 let _ = arg_starts_with_no_no;
381
382 let matched = long_exact(st, s, flags_unset, eq_val.clone(), argv, i, prefix)?;
383 if matched {
384 return Ok(());
385 }
386
387 if !disallow_abbrev {
388 let m = long_abbrev(st, s, arg_end, flags_unset, eq_val.clone(), argv, i, prefix)?;
389 if m {
390 return Ok(());
391 }
392 }
393
394 Err(unknown_long(original_key))
395}
396
397fn unknown_long(name: &str) -> ParseOptionsToolError {
398 ParseOptionsToolError::Fatal(format!("error: unknown option `{name}'\n"))
399}
400
401fn long_exact(
402 st: &mut PoState,
403 full: &str,
404 flags_unset: bool,
405 eq_val: Option<String>,
406 argv: &[String],
407 i: &mut usize,
408 prefix: &str,
409) -> Result<bool, ParseOptionsToolError> {
410 let u = flags_unset;
411 let mut hit = false;
412 match full {
413 "yes" => {
414 let err_name = if u { "no-yes" } else { "yes" };
415 no_eq(eq_val.as_deref(), err_name, u)?;
416 let unset = u ^ false;
417 st.boolean = if unset { 0 } else { 1 };
418 hit = true;
419 }
420 "doubt" => {
421 let err_name = if u { "no-doubt" } else { "doubt" };
422 no_eq(eq_val.as_deref(), err_name, u)?;
423 let unset = u ^ true;
424 st.boolean = if unset { 0 } else { 1 };
425 hit = true;
426 }
427 "no-fear" => {
428 no_eq(eq_val.as_deref(), "no-fear", u)?;
429 st.boolean = 1;
430 hit = true;
431 }
432 "boolean" => {
433 no_eq(eq_val.as_deref(), "boolean", u)?;
434 if u {
435 st.boolean = 0;
436 } else {
437 st.boolean = st.boolean.saturating_add(1);
438 }
439 hit = true;
440 }
441 "or4" => {
442 no_eq(eq_val.as_deref(), "or4", u)?;
443 if u {
444 st.boolean &= !4;
445 } else {
446 st.boolean |= 4;
447 }
448 hit = true;
449 }
450 "neg-or4" => {
451 no_eq(eq_val.as_deref(), "neg-or4", u)?;
452 if u {
453 st.boolean |= 4;
454 } else {
455 st.boolean &= !4;
456 }
457 hit = true;
458 }
459 "integer" => {
460 let v = take_val(eq_val, argv, i, "integer")?;
461 set_int(st, &v, "integer")?;
462 hit = true;
463 }
464 "i16" => {
465 let v = take_val(eq_val, argv, i, "i16")?;
466 set_i16(st, &v)?;
467 hit = true;
468 }
469 "unsigned" => {
470 let v = take_val(eq_val, argv, i, "unsigned")?;
471 set_unsigned(st, &v)?;
472 hit = true;
473 }
474 "u16" => {
475 let v = take_val(eq_val, argv, i, "u16")?;
476 set_u16(st, &v)?;
477 hit = true;
478 }
479 "set23" => {
480 no_eq(eq_val.as_deref(), "set23", u)?;
481 st.touch_integer(if u { 0 } else { 23 }, opt("set23", false), None, u)?;
482 hit = true;
483 }
484 "mode1" => {
485 no_eq(eq_val.as_deref(), "mode1", u)?;
486 st.touch_integer(if u { 0 } else { 1 }, opt("mode1", true), None, u)?;
487 hit = true;
488 }
489 "mode2" => {
490 no_eq(eq_val.as_deref(), "mode2", u)?;
491 st.touch_integer(if u { 0 } else { 2 }, opt("mode2", true), None, u)?;
492 hit = true;
493 }
494 "mode34" => {
495 let v = take_val(eq_val, argv, i, "mode34")?;
496 if u {
497 st.touch_integer(0, opt("mode34", true), Some("0"), true)?;
498 } else if v == "3" {
499 st.touch_integer(3, opt("mode34", true), Some("3"), false)?;
500 } else if v == "4" {
501 st.touch_integer(4, opt("mode34", true), Some("4"), false)?;
502 } else {
503 return Err(ParseOptionsToolError::Fatal(format!(
504 "error: invalid value for '--mode34': '{v}'\n"
505 )));
506 }
507 hit = true;
508 }
509 "length" => {
510 let v = take_val(eq_val, argv, i, "length")?;
511 if u {
512 return Err(ParseOptionsToolError::Fatal("error: option `no-length' isn't available\n".to_string()));
513 }
514 st.length_cb_called = true;
515 st.length_cb_arg = Some(v.clone());
516 st.length_cb_unset = false;
517 st.touch_integer(v.len() as i32, opt("length", false), None, false)?;
518 hit = true;
519 }
520 "file" => {
521 let v = take_val(eq_val, argv, i, "file")?;
522 if u {
523 st.file = None;
524 } else {
525 st.file = Some(format!("{prefix}{v}"));
526 }
527 hit = true;
528 }
529 "string" | "string2" | "st" => {
530 let v = take_val(eq_val, argv, i, "string")?;
531 if u {
532 st.string = None;
533 } else {
534 st.string = Some(v);
535 }
536 hit = true;
537 }
538 "obsolete" => {
539 no_eq(eq_val.as_deref(), "obsolete", false)?;
540 hit = true;
541 }
542 "longhelp" => {
543 no_eq(eq_val.as_deref(), "longhelp", u)?;
544 st.touch_integer(0, opt("longhelp", false), None, u)?;
545 hit = true;
546 }
547 "list" => {
548 let v = take_val(eq_val, argv, i, "list")?;
549 if u {
550 st.list.clear();
551 } else {
552 st.list.push(v);
553 }
554 hit = true;
555 }
556 "ambiguous" => {
557 no_eq(eq_val.as_deref(), "ambiguous", false)?;
558 st.ambiguous = st.ambiguous.saturating_add(1);
559 hit = true;
560 }
561 "no-ambiguous" => {
562 no_eq(eq_val.as_deref(), "no-ambiguous", false)?;
563 st.ambiguous = 0;
564 hit = true;
565 }
566 "abbrev" => {
567 if u {
568 st.abbrev = 0;
569 } else if let Some(ev) = eq_val {
570 parse_abbrev(&ev, &mut st.abbrev)?;
571 } else if *i + 1 < argv.len() {
572 let v = take_val(None, argv, i, "abbrev")?;
573 parse_abbrev(&v, &mut st.abbrev)?;
574 } else {
575 st.abbrev = 7;
576 }
577 hit = true;
578 }
579 "verbose" => {
580 no_eq(eq_val.as_deref(), "verbose", u)?;
581 if u {
582 st.verbose = 0;
583 } else {
584 st.verbose = if st.verbose < 0 { 1 } else { st.verbose + 1 };
585 }
586 hit = true;
587 }
588 "quiet" => {
589 no_eq(eq_val.as_deref(), "quiet", u)?;
590 if u {
591 st.quiet = 0;
592 } else if st.quiet <= 0 {
593 st.quiet -= 1;
594 } else {
595 st.quiet = -1;
596 }
597 hit = true;
598 }
599 "dry-run" => {
600 no_eq(eq_val.as_deref(), "dry-run", u)?;
601 st.dry_run = if u { 0 } else { 1 };
602 hit = true;
603 }
604 "alias-source" | "alias-target" => {
605 let v = take_val(eq_val, argv, i, "alias-source")?;
606 if u {
607 st.string = None;
608 } else {
609 st.string = Some(v);
610 }
611 hit = true;
612 }
613 _ => {}
614 }
615 if hit {
616 *i += 1;
617 }
618 Ok(hit)
619}
620
621fn opt(name: &'static str, is_cmdmode: bool) -> OptMeta {
622 OptMeta { name, is_cmdmode }
623}
624
625const LONG_NAMES: &[&str] = &[
627 "yes",
628 "no-doubt",
629 "doubt",
630 "no-fear",
631 "boolean",
632 "or4",
633 "neg-or4",
634 "integer",
635 "i16",
636 "unsigned",
637 "u16",
638 "set23",
639 "mode1",
640 "mode2",
641 "mode34",
642 "length",
643 "file",
644 "string",
645 "string2",
646 "st",
647 "obsolete",
648 "longhelp",
649 "list",
650 "ambiguous",
651 "no-ambiguous",
652 "abbrev",
653 "verbose",
654 "quiet",
655 "dry-run",
656 "expect",
657 "alias-source",
658 "alias-target",
659];
660
661fn is_alias_pair(a: &str, b: &str) -> bool {
662 (a == "alias-source" && b == "alias-target") || (a == "alias-target" && b == "alias-source")
663}
664
665fn long_abbrev(
666 st: &mut PoState,
667 s: &str,
668 _arg_end: usize,
669 flags_unset: bool,
670 eq_val: Option<String>,
671 argv: &[String],
672 i: &mut usize,
673 prefix: &str,
674) -> Result<bool, ParseOptionsToolError> {
675 let user_len = s.len();
676 if user_len == 0 {
677 return Ok(false);
678 }
679 let mut matches: Vec<&'static str> = Vec::new();
680 for &ln in LONG_NAMES {
681 let mut long_name = ln;
682 let mut opt_unset = false;
683 if let Some(x) = long_name.strip_prefix("no-") {
684 long_name = x;
685 opt_unset = true;
686 }
687 let allow_unset = !matches!(
688 ln,
689 "no-fear" | "obsolete" | "longhelp" | "ambiguous" | "no-ambiguous"
690 );
691 if (flags_unset ^ opt_unset) && !allow_unset {
692 continue;
693 }
694 if long_name.len() >= user_len && long_name.as_bytes().get(..user_len) == Some(s.as_bytes())
695 {
696 matches.push(ln);
697 }
698 }
699 matches.sort_unstable();
700 matches.dedup();
701 if matches.is_empty() {
702 return Ok(false);
703 }
704 let mut abbrev: Option<&str> = None;
705 let mut ambiguous: Option<(&str, &str)> = None;
706 for m in &matches {
707 match abbrev {
708 None => abbrev = Some(m),
709 Some(a) => {
710 if !is_alias_pair(a, m) {
711 ambiguous = Some((a, m));
712 break;
713 }
714 abbrev = Some(m);
715 }
716 }
717 }
718 if let Some((a, b)) = ambiguous {
719 return Err(ParseOptionsToolError::Fatal(format!(
720 "ambiguous option: {s} (could be --{a} or --{b})\n"
721 )));
722 }
723 let Some(only) = abbrev else {
724 return Ok(false);
725 };
726 let key = match only {
727 "no-doubt" => "doubt",
728 o => o,
729 };
730 long_exact(st, key, flags_unset, eq_val, argv, i, prefix)?;
731 Ok(true)
732}
733
734fn no_eq(eq: Option<&str>, name: &str, unset: bool) -> Result<(), ParseOptionsToolError> {
735 if let Some(x) = eq {
736 if !x.is_empty() || unset {
737 return Err(ParseOptionsToolError::Fatal(format!(
738 "error: option `{name}' takes no value\n"
739 )));
740 }
741 }
742 Ok(())
743}
744
745fn take_val(
746 eq_val: Option<String>,
747 argv: &[String],
748 i: &mut usize,
749 optname: &str,
750) -> Result<String, ParseOptionsToolError> {
751 if let Some(v) = eq_val {
752 return Ok(v);
753 }
754 if *i + 1 >= argv.len() {
755 return Err(ParseOptionsToolError::Fatal(format!(
756 "error: option `{optname}' requires a value\n"
757 )));
758 }
759 *i += 1;
760 Ok(argv[*i].clone())
761}
762
763fn parse_abbrev(s: &str, out: &mut i32) -> Result<(), ParseOptionsToolError> {
764 if s.is_empty() {
765 return Err(ParseOptionsToolError::Fatal("error: option `abbrev' expects a numerical value\n".to_string()));
766 }
767 let v: i32 = s.parse().map_err(|_| {
768 ParseOptionsToolError::Fatal("error: option `abbrev' expects a numerical value\n".to_string())
769 })?;
770 *out = if v != 0 && v < 4 { 4 } else { v };
771 Ok(())
772}
773
774fn set_int(st: &mut PoState, raw: &str, optname: &str) -> Result<(), ParseOptionsToolError> {
775 let (lo, hi) = int_bounds_32();
776 let opt_meta = match optname {
777 "integer" | "j" => opt("integer", false),
778 _ => {
779 return Err(ParseOptionsToolError::Fatal(format!(
780 "internal error: unknown option name for set_int: {optname}\n"
781 )));
782 }
783 };
784 match git_parse_signed(raw, hi) {
785 Ok(v) if v >= lo && v <= hi => {
786 st.touch_integer(v as i32, opt_meta, None, false)?;
787 Ok(())
788 }
789 Err(std::io::ErrorKind::InvalidData) => Err(ParseOptionsToolError::Fatal(format!(
790 "error: value {raw} for option `{optname}' not in range [{lo},{hi}]\n"
791 ))),
792 _ => Err(ParseOptionsToolError::Fatal(format!(
793 "error: option `{optname}' expects an integer value with an optional k/m/g suffix\n"
794 ))),
795 }
796}
797
798fn set_i16(st: &mut PoState, raw: &str) -> Result<(), ParseOptionsToolError> {
799 match git_parse_signed(raw, i16::MAX as i128) {
800 Ok(v) if v >= i16::MIN as i128 && v <= i16::MAX as i128 => {
801 st.i16 = v as i16;
802 Ok(())
803 }
804 Err(std::io::ErrorKind::InvalidData) | Ok(_) => Err(ParseOptionsToolError::Fatal(format!(
805 "error: value {raw} for option `i16' not in range [-32768,32767]\n"
806 ))),
807 _ => Err(ParseOptionsToolError::Fatal(
808 "error: option `i16' expects an integer value with an optional k/m/g suffix\n"
809 .to_string(),
810 )),
811 }
812}
813
814fn set_unsigned(st: &mut PoState, raw: &str) -> Result<(), ParseOptionsToolError> {
815 match git_parse_unsigned(raw, u64::MAX as u128) {
816 Ok(v) => {
817 st.unsigned_integer = v as u64;
818 Ok(())
819 }
820 Err(std::io::ErrorKind::InvalidData) => Err(ParseOptionsToolError::Fatal(format!(
821 "error: value {raw} for option `unsigned' not in range [0,{}]\n",
822 u64::MAX
823 ))),
824 _ => Err(ParseOptionsToolError::Fatal(
825 "error: option `unsigned' expects a non-negative integer value with an optional k/m/g suffix\n"
826 .to_string(),
827 )),
828 }
829}
830
831fn set_u16(st: &mut PoState, raw: &str) -> Result<(), ParseOptionsToolError> {
832 match git_parse_unsigned(raw, u16::MAX as u128) {
833 Ok(v) if v <= u16::MAX as u128 => {
834 st.u16 = v as u16;
835 Ok(())
836 }
837 _ => Err(ParseOptionsToolError::Fatal(format!(
838 "error: value {raw} for option `u16' not in range [0,65535]\n"
839 ))),
840 }
841}
842
843fn check_typos(full_suffix: &str) -> Result<(), ParseOptionsToolError> {
845 if full_suffix.len() < 3 {
846 return Ok(());
847 }
848 if full_suffix.starts_with("no-") {
849 return Err(ParseOptionsToolError::Fatal(format!(
850 "error: did you mean `--{full_suffix}` (with two dashes)?\n"
851 )));
852 }
853 for ln in LONG_NAMES {
854 if ln.starts_with(full_suffix) {
855 return Err(ParseOptionsToolError::Fatal(format!(
856 "error: did you mean `--{full_suffix}` (with two dashes)?\n"
857 )));
858 }
859 }
860 Ok(())
861}
862
863fn parse_short(
864 st: &mut PoState,
865 argv: &[String],
866 i: usize,
867 prefix: &str,
868 _disallow_abbrev: bool,
869) -> Result<usize, ParseOptionsToolError> {
870 let arg = &argv[i];
871 let full_suffix = &arg[1..];
872 if full_suffix.is_empty() {
873 return Err(ParseOptionsToolError::Fatal(
874 "error: unknown switch\n".to_string(),
875 ));
876 }
877
878 let mut o = 0usize;
879 let mut local_i = i;
880
881 let first = full_suffix.chars().next().unwrap();
883 let flen = first.len_utf8();
884 match first {
885 'h' => {
886 print!("{PARSE_OPTIONS_HELP}");
887 return Err(ParseOptionsToolError::Help);
888 }
889 's' => {
890 let v = if full_suffix.len() > flen {
891 full_suffix[flen..].to_string()
892 } else if local_i + 1 < argv.len() {
893 local_i += 1;
894 argv[local_i].clone()
895 } else {
896 return Err(ParseOptionsToolError::Fatal("error: switch `s' requires a value\n".to_string()));
897 };
898 o = full_suffix.len();
899 st.string = Some(v);
900 }
901 'b' => {
902 st.boolean = st.boolean.saturating_add(1);
903 o += flen;
904 }
905 'i' => {
906 let tail = &full_suffix[flen..];
907 let v = if !tail.is_empty() {
908 tail.to_string()
909 } else if local_i + 1 < argv.len() {
910 local_i += 1;
911 argv[local_i].clone()
912 } else {
913 return Err(ParseOptionsToolError::Fatal("error: switch `i' requires a value\n".to_string()));
914 };
915 set_int(st, &v, "integer")?;
916 o = full_suffix.len();
917 }
918 'j' => {
919 let tail = &full_suffix[flen..];
920 let v = if !tail.is_empty() {
921 tail.to_string()
922 } else if local_i + 1 < argv.len() {
923 local_i += 1;
924 argv[local_i].clone()
925 } else {
926 return Err(ParseOptionsToolError::Fatal("error: switch `j' requires a value\n".to_string()));
927 };
928 set_int(st, &v, "j")?;
929 o = full_suffix.len();
930 }
931 'u' => {
932 let tail = &full_suffix[flen..];
933 let v = if !tail.is_empty() {
934 tail.to_string()
935 } else if local_i + 1 < argv.len() {
936 local_i += 1;
937 argv[local_i].clone()
938 } else {
939 return Err(ParseOptionsToolError::Fatal("error: switch `u' requires a value\n".to_string()));
940 };
941 set_unsigned(st, &v)?;
942 o = full_suffix.len();
943 }
944 'v' => {
945 st.verbose = if st.verbose < 0 { 1 } else { st.verbose + 1 };
946 o += flen;
947 }
948 'n' => {
949 st.dry_run = 1;
950 o += flen;
951 }
952 'q' => {
953 if st.quiet <= 0 {
954 st.quiet -= 1;
955 } else {
956 st.quiet = -1;
957 }
958 o += flen;
959 }
960 'D' => {
961 st.boolean = 1;
962 o += flen;
963 }
964 'B' => {
965 st.boolean = 1;
966 o += flen;
967 }
968 '4' => {
969 st.boolean |= 4;
970 o += flen;
971 }
972 'L' => {
973 let v = if full_suffix.len() > flen {
974 full_suffix[flen..].to_string()
975 } else if local_i + 1 < argv.len() {
976 local_i += 1;
977 argv[local_i].clone()
978 } else {
979 return Err(ParseOptionsToolError::Fatal("error: switch `L' requires a value\n".to_string()));
980 };
981 o = full_suffix.len();
982 st.length_cb_called = true;
983 st.length_cb_arg = Some(v.clone());
984 st.length_cb_unset = false;
985 st.touch_integer(v.len() as i32, opt("length", false), None, false)?;
986 }
987 'F' => {
988 let v = if full_suffix.len() > flen {
989 full_suffix[flen..].to_string()
990 } else if local_i + 1 < argv.len() {
991 local_i += 1;
992 argv[local_i].clone()
993 } else {
994 return Err(ParseOptionsToolError::Fatal("error: switch `F' requires a value\n".to_string()));
995 };
996 o = full_suffix.len();
997 st.file = Some(format!("{prefix}{v}"));
998 }
999 'A' => {
1000 let v = if full_suffix.len() > flen {
1001 full_suffix[flen..].to_string()
1002 } else if local_i + 1 < argv.len() {
1003 local_i += 1;
1004 argv[local_i].clone()
1005 } else {
1006 return Err(ParseOptionsToolError::Fatal("error: switch `A' requires a value\n".to_string()));
1007 };
1008 o = full_suffix.len();
1009 st.string = Some(v);
1010 }
1011 'Z' => {
1012 let v = if full_suffix.len() > flen {
1013 full_suffix[flen..].to_string()
1014 } else if local_i + 1 < argv.len() {
1015 local_i += 1;
1016 argv[local_i].clone()
1017 } else {
1018 return Err(ParseOptionsToolError::Fatal("error: switch `Z' requires a value\n".to_string()));
1019 };
1020 o = full_suffix.len();
1021 st.string = Some(v);
1022 }
1023 'o' => {
1024 let v = if full_suffix.len() > flen {
1025 full_suffix[flen..].to_string()
1026 } else if local_i + 1 < argv.len() {
1027 local_i += 1;
1028 argv[local_i].clone()
1029 } else {
1030 return Err(ParseOptionsToolError::Fatal("error: switch `o' requires a value\n".to_string()));
1031 };
1032 o = full_suffix.len();
1033 st.string = Some(v);
1034 }
1035 '+' => {
1036 st.boolean = st.boolean.saturating_add(1);
1037 o += flen;
1038 }
1039 _ => {
1040 return Err(ParseOptionsToolError::Fatal(format!(
1041 "error: unknown switch `{first}'\n"
1042 )));
1043 }
1044 }
1045
1046 if o < full_suffix.len() {
1047 check_typos(full_suffix)?;
1048 }
1049
1050 while o < full_suffix.len() {
1051 let c = full_suffix[o..].chars().next().unwrap();
1052 let clen = c.len_utf8();
1053 match c {
1054 'b' => {
1055 st.boolean = st.boolean.saturating_add(1);
1056 o += clen;
1057 }
1058 'v' => {
1059 st.verbose = if st.verbose < 0 { 1 } else { st.verbose + 1 };
1060 o += clen;
1061 }
1062 'n' => {
1063 st.dry_run = 1;
1064 o += clen;
1065 }
1066 'q' => {
1067 if st.quiet <= 0 {
1068 st.quiet -= 1;
1069 } else {
1070 st.quiet = -1;
1071 }
1072 o += clen;
1073 }
1074 '4' => {
1075 st.boolean |= 4;
1076 o += clen;
1077 }
1078 '+' => {
1079 st.boolean = st.boolean.saturating_add(1);
1080 o += clen;
1081 }
1082 _ => {
1083 return Err(ParseOptionsToolError::Fatal(format!(
1084 "error: unknown switch `{c}'\n"
1085 )));
1086 }
1087 }
1088 }
1089
1090 Ok(local_i + 1)
1091}