1#![doc = include_str!("../examples/hello-parser.rs")]
10#![cfg_attr(docsrs, feature(doc_auto_cfg))]
13#![allow(clippy::result_unit_err)]
14#![warn(missing_debug_implementations)]
15#![warn(missing_docs)]
16#![warn(clippy::print_stderr)]
17#![warn(clippy::print_stdout)]
18
19mod ext;
20
21use std::ffi::OsStr;
22
23use ext::OsStrExt as _;
24
25#[derive(Debug, Clone)]
27pub struct Parser<'a> {
28 raw: &'a dyn RawArgs,
29 current: usize,
30 state: Option<State<'a>>,
31 was_attached: bool,
32}
33
34impl<'a> Parser<'a> {
35 pub fn new(raw: &'a dyn RawArgs) -> Self {
47 Parser {
48 raw,
49 current: 0,
50 state: None,
51 was_attached: false,
52 }
53 }
54
55 pub fn next_arg(&mut self) -> Option<Arg<'a>> {
65 self.was_attached = false;
67
68 match self.state {
69 Some(State::PendingValue(attached)) => {
70 self.state = None;
72 self.current += 1;
73 Some(Arg::Unexpected(attached))
74 }
75 Some(State::PendingShorts(valid, invalid, index)) => {
76 if let Some(next_index) = ceil_char_boundary(valid, index) {
79 if next_index < valid.len() {
80 self.state = Some(State::PendingShorts(valid, invalid, next_index));
81 } else if !invalid.is_empty() {
82 self.state = Some(State::PendingValue(invalid));
83 } else {
84 self.state = None;
86 self.current += 1;
87 }
88 let flag = &valid[index..next_index];
89 Some(Arg::Short(flag))
90 } else {
91 debug_assert_ne!(invalid, "");
92 if index == 0 {
93 panic!("there should have been a `-`")
94 } else if index == 1 {
95 let arg = self
97 .raw
98 .get(self.current)
99 .expect("`current` is valid if state is `Shorts`");
100 self.state = None;
101 self.current += 1;
102 Some(Arg::Unexpected(arg))
103 } else {
104 self.state = None;
105 self.current += 1;
106 Some(Arg::Unexpected(invalid))
107 }
108 }
109 }
110 Some(State::Escaped) => {
111 self.state = Some(State::Escaped);
112 self.next_raw_().map(Arg::Value)
113 }
114 None => {
115 let arg = self.raw.get(self.current)?;
116 if arg == "--" {
117 self.state = Some(State::Escaped);
118 self.current += 1;
119 Some(Arg::Escape(arg.to_str().expect("`--` is valid UTF-8")))
120 } else if arg == "-" {
121 self.state = None;
122 self.current += 1;
123 Some(Arg::Value(arg))
124 } else if let Some(long) = arg.strip_prefix("--") {
125 let (name, value) = long
126 .split_once("=")
127 .map(|(n, v)| (n, Some(v)))
128 .unwrap_or((long, None));
129 if name.is_empty() {
130 self.state = None;
131 self.current += 1;
132 Some(Arg::Unexpected(arg))
133 } else if let Ok(name) = name.try_str() {
134 if let Some(value) = value {
135 self.state = Some(State::PendingValue(value));
136 } else {
137 self.state = None;
138 self.current += 1;
139 }
140 Some(Arg::Long(name))
141 } else {
142 self.state = None;
143 self.current += 1;
144 Some(Arg::Unexpected(arg))
145 }
146 } else if arg.starts_with("-") {
147 let (valid, invalid) = split_nonutf8_once(arg);
148 let invalid = invalid.unwrap_or_default();
149 self.state = Some(State::PendingShorts(valid, invalid, 1));
150 self.next_arg()
151 } else {
152 self.state = None;
153 self.current += 1;
154 Some(Arg::Value(arg))
155 }
156 }
157 }
158 }
159
160 pub fn next_flag_value(&mut self) -> Option<&'a OsStr> {
172 if self.was_attached {
173 debug_assert!(!self.has_pending());
174 None
175 } else if let Some(value) = self.next_attached_value() {
176 Some(value)
177 } else {
178 self.next_detached_value()
179 }
180 }
181
182 pub fn next_attached_value(&mut self) -> Option<&'a OsStr> {
187 match self.state? {
188 State::PendingValue(attached) => {
189 self.state = None;
190 self.current += 1;
191 self.was_attached = true;
192 Some(attached)
193 }
194 State::PendingShorts(_, _, index) => {
195 let arg = self
196 .raw
197 .get(self.current)
198 .expect("`current` is valid if state is `Shorts`");
199 self.state = None;
200 self.current += 1;
201 if index == arg.len() {
202 None
203 } else {
204 let remainder = unsafe { ext::split_at(arg, index) }.1;
206 let remainder = remainder.strip_prefix("=").unwrap_or(remainder);
207 self.was_attached = true;
208 Some(remainder)
209 }
210 }
211 State::Escaped => None,
212 }
213 }
214
215 fn next_detached_value(&mut self) -> Option<&'a OsStr> {
216 if self.state == Some(State::Escaped) {
217 return None;
219 }
220
221 if self.peek_raw_()? == "--" {
222 None
223 } else {
224 self.next_raw_()
225 }
226 }
227
228 pub fn next_raw(&mut self) -> Result<Option<&'a OsStr>, ()> {
232 if self.has_pending() {
233 Err(())
234 } else {
235 self.was_attached = false;
236 Ok(self.next_raw_())
237 }
238 }
239
240 pub fn remaining_raw(&mut self) -> Result<impl Iterator<Item = &'a OsStr> + '_, ()> {
244 if self.has_pending() {
245 Err(())
246 } else {
247 self.was_attached = false;
248 Ok(std::iter::from_fn(|| self.next_raw_()))
249 }
250 }
251
252 pub fn peek_raw(&self) -> Result<Option<&'a OsStr>, ()> {
256 if self.has_pending() {
257 Err(())
258 } else {
259 Ok(self.peek_raw_())
260 }
261 }
262
263 fn peek_raw_(&self) -> Option<&'a OsStr> {
264 self.raw.get(self.current)
265 }
266
267 fn next_raw_(&mut self) -> Option<&'a OsStr> {
268 debug_assert!(!self.has_pending());
269 debug_assert!(!self.was_attached);
270
271 let next = self.raw.get(self.current)?;
272 self.current += 1;
273 Some(next)
274 }
275
276 fn has_pending(&self) -> bool {
277 self.state.as_ref().map(State::has_pending).unwrap_or(false)
278 }
279}
280
281pub trait RawArgs: std::fmt::Debug + private::Sealed {
283 fn get(&self, index: usize) -> Option<&OsStr>;
290
291 fn len(&self) -> usize;
293
294 fn is_empty(&self) -> bool;
296}
297
298impl<const C: usize, S> RawArgs for [S; C]
299where
300 S: AsRef<OsStr> + std::fmt::Debug,
301{
302 #[inline]
303 fn get(&self, index: usize) -> Option<&OsStr> {
304 self.as_slice().get(index).map(|s| s.as_ref())
305 }
306
307 #[inline]
308 fn len(&self) -> usize {
309 C
310 }
311
312 #[inline]
313 fn is_empty(&self) -> bool {
314 C != 0
315 }
316}
317
318impl<S> RawArgs for &'_ [S]
319where
320 S: AsRef<OsStr> + std::fmt::Debug,
321{
322 #[inline]
323 fn get(&self, index: usize) -> Option<&OsStr> {
324 (*self).get(index).map(|s| s.as_ref())
325 }
326
327 #[inline]
328 fn len(&self) -> usize {
329 (*self).len()
330 }
331
332 #[inline]
333 fn is_empty(&self) -> bool {
334 (*self).is_empty()
335 }
336}
337
338impl<S> RawArgs for Vec<S>
339where
340 S: AsRef<OsStr> + std::fmt::Debug,
341{
342 #[inline]
343 fn get(&self, index: usize) -> Option<&OsStr> {
344 self.as_slice().get(index).map(|s| s.as_ref())
345 }
346
347 #[inline]
348 fn len(&self) -> usize {
349 self.len()
350 }
351
352 #[inline]
353 fn is_empty(&self) -> bool {
354 self.is_empty()
355 }
356}
357
358#[derive(Debug, Copy, Clone, PartialEq, Eq)]
359enum State<'a> {
360 PendingValue(&'a OsStr),
362 PendingShorts(&'a str, &'a OsStr, usize),
367 Escaped,
369}
370
371impl State<'_> {
372 fn has_pending(&self) -> bool {
373 match self {
374 Self::PendingValue(_) | Self::PendingShorts(_, _, _) => true,
375 Self::Escaped => false,
376 }
377 }
378}
379
380#[derive(Debug, Copy, Clone, PartialEq, Eq)]
382pub enum Arg<'a> {
383 Short(&'a str),
385 Long(&'a str),
389 Value(&'a OsStr),
391 Escape(&'a str),
393 Unexpected(&'a OsStr),
395}
396
397fn split_nonutf8_once(b: &OsStr) -> (&str, Option<&OsStr>) {
398 match b.try_str() {
399 Ok(s) => (s, None),
400 Err(err) => {
401 let (valid, after_valid) = unsafe { ext::split_at(b, err.valid_up_to()) };
403 let valid = valid.try_str().unwrap();
404 (valid, Some(after_valid))
405 }
406 }
407}
408
409fn ceil_char_boundary(s: &str, curr_boundary: usize) -> Option<usize> {
410 (curr_boundary + 1..=s.len()).find(|i| s.is_char_boundary(*i))
411}
412
413mod private {
414 use super::OsStr;
415
416 #[allow(unnameable_types)]
417 pub trait Sealed {}
418 impl<const C: usize, S> Sealed for [S; C] where S: AsRef<OsStr> + std::fmt::Debug {}
419 impl<S> Sealed for &'_ [S] where S: AsRef<OsStr> + std::fmt::Debug {}
420 impl<S> Sealed for Vec<S> where S: AsRef<OsStr> + std::fmt::Debug {}
421}
422
423#[cfg(test)]
424mod tests {
425 use super::Arg::*;
426 use super::*;
427
428 #[test]
429 fn test_basic() {
430 let mut p = Parser::new(&["-n", "10", "foo", "-", "--", "baz", "-qux"]);
431 assert_eq!(p.next_arg().unwrap(), Short("n"));
432 assert_eq!(p.next_flag_value().unwrap(), "10");
433 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("foo")));
434 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-")));
435 assert_eq!(p.next_arg().unwrap(), Escape("--"));
436 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("baz")));
437 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-qux")));
438 assert_eq!(p.next_arg(), None);
439 assert_eq!(p.next_arg(), None);
440 assert_eq!(p.next_arg(), None);
441 }
442
443 #[test]
444 fn test_combined() {
445 let mut p = Parser::new(&["-abc", "-fvalue", "-xfvalue"]);
446 assert_eq!(p.next_arg().unwrap(), Short("a"));
447 assert_eq!(p.next_arg().unwrap(), Short("b"));
448 assert_eq!(p.next_arg().unwrap(), Short("c"));
449 assert_eq!(p.next_arg().unwrap(), Short("f"));
450 assert_eq!(p.next_flag_value().unwrap(), "value");
451 assert_eq!(p.next_arg().unwrap(), Short("x"));
452 assert_eq!(p.next_arg().unwrap(), Short("f"));
453 assert_eq!(p.next_flag_value().unwrap(), "value");
454 assert_eq!(p.next_arg(), None);
455 }
456
457 #[test]
458 fn test_long() {
459 let mut p = Parser::new(&["--foo", "--bar=qux", "--foobar=qux=baz"]);
460 assert_eq!(p.next_arg().unwrap(), Long("foo"));
461 assert_eq!(p.next_arg().unwrap(), Long("bar"));
462 assert_eq!(p.next_flag_value().unwrap(), "qux");
463 assert_eq!(p.next_flag_value(), None);
464 assert_eq!(p.next_arg().unwrap(), Long("foobar"));
465 assert_eq!(p.next_arg().unwrap(), Unexpected(OsStr::new("qux=baz")));
466 assert_eq!(p.next_arg(), None);
467 }
468
469 #[test]
470 fn test_dash_args() {
471 let mut p = Parser::new(&["-x", "--", "-y"]);
473 assert_eq!(p.next_arg().unwrap(), Short("x"));
474 assert_eq!(p.next_arg().unwrap(), Escape("--"));
475 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-y")));
476 assert_eq!(p.next_arg(), None);
477
478 let mut p = Parser::new(&["-x", "--", "-y"]);
480 assert_eq!(p.next_arg().unwrap(), Short("x"));
481 assert_eq!(p.next_flag_value(), None);
482 assert_eq!(p.next_arg().unwrap(), Escape("--"));
483 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-y")));
484 assert_eq!(p.next_arg(), None);
485
486 let mut p = Parser::new(&["-x", "-", "-y"]);
488 assert_eq!(p.next_arg().unwrap(), Short("x"));
489 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-")));
490 assert_eq!(p.next_arg().unwrap(), Short("y"));
491 assert_eq!(p.next_arg(), None);
492
493 let mut p = Parser::new(&["-x-y"]);
496 assert_eq!(p.next_arg().unwrap(), Short("x"));
497 assert_eq!(p.next_arg().unwrap(), Short("-"));
498 assert_eq!(p.next_arg().unwrap(), Short("y"));
499 assert_eq!(p.next_arg(), None);
500 }
501
502 #[test]
503 fn test_missing_value() {
504 let mut p = Parser::new(&["-o"]);
505 assert_eq!(p.next_arg().unwrap(), Short("o"));
506 assert_eq!(p.next_flag_value(), None);
507
508 let mut q = Parser::new(&["--out"]);
509 assert_eq!(q.next_arg().unwrap(), Long("out"));
510 assert_eq!(q.next_flag_value(), None);
511
512 let args: [&OsStr; 0] = [];
513 let mut r = Parser::new(&args);
514 assert_eq!(r.next_flag_value(), None);
515 }
516
517 #[test]
518 fn test_weird_args() {
519 let mut p = Parser::new(&[
520 "--=", "--=3", "-", "-x", "--", "-", "-x", "--", "", "-", "-x",
521 ]);
522 assert_eq!(p.next_arg().unwrap(), Unexpected(OsStr::new("--=")));
523 assert_eq!(p.next_arg().unwrap(), Unexpected(OsStr::new("--=3")));
524 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-")));
525 assert_eq!(p.next_arg().unwrap(), Short("x"));
526 assert_eq!(p.next_arg().unwrap(), Escape("--"));
527 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-")));
528 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-x")));
529 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("--")));
530 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("")));
531 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-")));
532 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("-x")));
533 assert_eq!(p.next_arg(), None);
534
535 let bad = bad_string("--=@");
536 let args = [&bad];
537 let mut q = Parser::new(&args);
538 assert_eq!(q.next_arg().unwrap(), Unexpected(OsStr::new(&bad)));
539
540 let mut r = Parser::new(&[""]);
541 assert_eq!(r.next_arg().unwrap(), Value(OsStr::new("")));
542 }
543
544 #[test]
545 fn test_unicode() {
546 let mut p = Parser::new(&["-aµ", "--µ=10", "µ", "--foo=µ"]);
547 assert_eq!(p.next_arg().unwrap(), Short("a"));
548 assert_eq!(p.next_arg().unwrap(), Short("µ"));
549 assert_eq!(p.next_arg().unwrap(), Long("µ"));
550 assert_eq!(p.next_flag_value().unwrap(), "10");
551 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("µ")));
552 assert_eq!(p.next_arg().unwrap(), Long("foo"));
553 assert_eq!(p.next_flag_value().unwrap(), "µ");
554 }
555
556 #[cfg(any(unix, target_os = "wasi", windows))]
557 #[test]
558 fn test_mixed_invalid() {
559 let args = [bad_string("--foo=@@@")];
560 let mut p = Parser::new(&args);
561 assert_eq!(p.next_arg().unwrap(), Long("foo"));
562 assert_eq!(p.next_flag_value().unwrap(), bad_string("@@@"));
563
564 let args = [bad_string("-💣@@@")];
565 let mut q = Parser::new(&args);
566 assert_eq!(q.next_arg().unwrap(), Short("💣"));
567 assert_eq!(q.next_flag_value().unwrap(), bad_string("@@@"));
568
569 let args = [bad_string("-f@@@")];
570 let mut r = Parser::new(&args);
571 assert_eq!(r.next_arg().unwrap(), Short("f"));
572 assert_eq!(r.next_arg().unwrap(), Unexpected(&bad_string("@@@")));
573 assert_eq!(r.next_arg(), None);
574
575 let args = [bad_string("--foo=bar=@@@")];
576 let mut s = Parser::new(&args);
577 assert_eq!(s.next_arg().unwrap(), Long("foo"));
578 assert_eq!(s.next_flag_value().unwrap(), bad_string("bar=@@@"));
579 }
580
581 #[cfg(any(unix, target_os = "wasi", windows))]
582 #[test]
583 fn test_separate_invalid() {
584 let args = [bad_string("--foo"), bad_string("@@@")];
585 let mut p = Parser::new(&args);
586 assert_eq!(p.next_arg().unwrap(), Long("foo"));
587 assert_eq!(p.next_flag_value().unwrap(), bad_string("@@@"));
588 }
589
590 #[cfg(any(unix, target_os = "wasi", windows))]
591 #[test]
592 fn test_invalid_long_option() {
593 let args = [bad_string("--@=10")];
594 let mut p = Parser::new(&args);
595 assert_eq!(p.next_arg().unwrap(), Unexpected(&args[0]));
596 assert_eq!(p.next_arg(), None);
597
598 let args = [bad_string("--@")];
599 let mut p = Parser::new(&args);
600 assert_eq!(p.next_arg().unwrap(), Unexpected(&args[0]));
601 assert_eq!(p.next_arg(), None);
602 }
603
604 #[cfg(any(unix, target_os = "wasi", windows))]
605 #[test]
606 fn test_invalid_short_option() {
607 let args = [bad_string("-@")];
608 let mut p = Parser::new(&args);
609 assert_eq!(p.next_arg().unwrap(), Unexpected(&args[0]));
610 assert_eq!(p.next_arg(), None);
611 }
612
613 #[test]
614 fn short_opt_equals_sign() {
615 let mut p = Parser::new(&["-a=b"]);
616 assert_eq!(p.next_arg().unwrap(), Short("a"));
617 assert_eq!(p.next_flag_value().unwrap(), OsStr::new("b"));
618 assert_eq!(p.next_arg(), None);
619
620 let mut p = Parser::new(&["-a=b", "c"]);
621 assert_eq!(p.next_arg().unwrap(), Short("a"));
622 assert_eq!(p.next_flag_value().unwrap(), OsStr::new("b"));
623 assert_eq!(p.next_flag_value(), None);
624 assert_eq!(p.next_arg().unwrap(), Value(OsStr::new("c")));
625 assert_eq!(p.next_arg(), None);
626
627 let mut p = Parser::new(&["-a=b"]);
628 assert_eq!(p.next_arg().unwrap(), Short("a"));
629 assert_eq!(p.next_arg().unwrap(), Short("="));
630 assert_eq!(p.next_arg().unwrap(), Short("b"));
631 assert_eq!(p.next_arg(), None);
632
633 let mut p = Parser::new(&["-a="]);
634 assert_eq!(p.next_arg().unwrap(), Short("a"));
635 assert_eq!(p.next_flag_value().unwrap(), OsStr::new(""));
636 assert_eq!(p.next_arg(), None);
637
638 let mut p = Parser::new(&["-a=="]);
639 assert_eq!(p.next_arg().unwrap(), Short("a"));
640 assert_eq!(p.next_flag_value().unwrap(), OsStr::new("="));
641 assert_eq!(p.next_arg(), None);
642
643 let mut p = Parser::new(&["-abc=de"]);
644 assert_eq!(p.next_arg().unwrap(), Short("a"));
645 assert_eq!(p.next_flag_value().unwrap(), OsStr::new("bc=de"));
646 assert_eq!(p.next_arg(), None);
647
648 let mut p = Parser::new(&["-abc==de"]);
649 assert_eq!(p.next_arg().unwrap(), Short("a"));
650 assert_eq!(p.next_arg().unwrap(), Short("b"));
651 assert_eq!(p.next_arg().unwrap(), Short("c"));
652 assert_eq!(p.next_flag_value().unwrap(), OsStr::new("=de"));
653 assert_eq!(p.next_arg(), None);
654
655 let mut p = Parser::new(&["-a="]);
656 assert_eq!(p.next_arg().unwrap(), Short("a"));
657 assert_eq!(p.next_arg().unwrap(), Short("="));
658 assert_eq!(p.next_arg(), None);
659
660 let mut p = Parser::new(&["-="]);
661 assert_eq!(p.next_arg().unwrap(), Short("="));
662 assert_eq!(p.next_arg(), None);
663
664 let mut p = Parser::new(&["-=a"]);
665 assert_eq!(p.next_arg().unwrap(), Short("="));
666 assert_eq!(p.next_arg().unwrap(), Short("a"));
667 assert_eq!(p.next_arg(), None);
668 }
669
670 #[cfg(any(unix, target_os = "wasi", windows))]
671 #[test]
672 fn short_opt_equals_sign_invalid() {
673 let bad = bad_string("@");
674 let args = [bad_string("-a=@")];
675 let mut p = Parser::new(&args);
676 assert_eq!(p.next_arg().unwrap(), Short("a"));
677 assert_eq!(p.next_flag_value().unwrap(), bad_string("@"));
678 assert_eq!(p.next_arg(), None);
679
680 let mut p = Parser::new(&args);
681 assert_eq!(p.next_arg().unwrap(), Short("a"));
682 assert_eq!(p.next_arg().unwrap(), Short("="));
683 assert_eq!(p.next_arg().unwrap(), Unexpected(&bad));
684 assert_eq!(p.next_arg(), None);
685 }
686
687 #[test]
688 fn remaining_raw() {
689 let mut p = Parser::new(&["-a", "b", "c", "d"]);
690 assert_eq!(
691 p.remaining_raw().unwrap().collect::<Vec<_>>(),
692 &["-a", "b", "c", "d"]
693 );
694 assert!(p.next_arg().is_none());
696 assert!(p.remaining_raw().is_ok());
697 assert_eq!(p.remaining_raw().unwrap().collect::<Vec<_>>().len(), 0);
698
699 let mut p = Parser::new(&["-ab", "c", "d"]);
700 p.next_arg().unwrap();
701 assert!(p.remaining_raw().is_err());
703 p.next_attached_value().unwrap();
704 assert_eq!(p.remaining_raw().unwrap().collect::<Vec<_>>(), &["c", "d"]);
705 assert!(p.next_arg().is_none());
707 assert_eq!(p.remaining_raw().unwrap().collect::<Vec<_>>().len(), 0);
708 }
709
710 fn bad_string(text: &str) -> std::ffi::OsString {
712 #[cfg(any(unix, target_os = "wasi"))]
713 {
714 #[cfg(unix)]
715 use std::os::unix::ffi::OsStringExt;
716 #[cfg(target_os = "wasi")]
717 use std::os::wasi::ffi::OsStringExt;
718 let mut text = text.as_bytes().to_vec();
719 for ch in &mut text {
720 if *ch == b'@' {
721 *ch = b'\xFF';
722 }
723 }
724 std::ffi::OsString::from_vec(text)
725 }
726 #[cfg(windows)]
727 {
728 use std::os::windows::ffi::OsStringExt;
729 let mut out = Vec::new();
730 for ch in text.chars() {
731 if ch == '@' {
732 out.push(0xD800);
733 } else {
734 let mut buf = [0; 2];
735 out.extend(&*ch.encode_utf16(&mut buf));
736 }
737 }
738 std::ffi::OsString::from_wide(&out)
739 }
740 #[cfg(not(any(unix, target_os = "wasi", windows)))]
741 {
742 if text.contains('@') {
743 unimplemented!("Don't know how to create invalid OsStrings on this platform");
744 }
745 text.into()
746 }
747 }
748
749 #[test]
762 fn basic_fuzz() {
763 #[cfg(any(windows, unix, target_os = "wasi"))]
764 const VOCABULARY: &[&str] = &[
765 "", "-", "--", "---", "a", "-a", "-aa", "@", "-@", "-a@", "-@a", "--a", "--@", "--a=a",
766 "--a=", "--a=@", "--@=a", "--=", "--=@", "--=a", "-@@", "-a=a", "-a=", "-=", "-a-",
767 ];
768 #[cfg(not(any(windows, unix, target_os = "wasi")))]
769 const VOCABULARY: &[&str] = &[
770 "", "-", "--", "---", "a", "-a", "-aa", "--a", "--a=a", "--a=", "--=", "--=a", "-a=a",
771 "-a=", "-=", "-a-",
772 ];
773 let args: [&OsStr; 0] = [];
774 exhaust(Parser::new(&args), vec![]);
775 let vocabulary: Vec<std::ffi::OsString> =
776 VOCABULARY.iter().map(|&s| bad_string(s)).collect();
777 let mut permutations = vec![vec![]];
778 for _ in 0..3 {
779 let mut new = Vec::new();
780 for old in permutations {
781 for word in &vocabulary {
782 let mut extended = old.clone();
783 extended.push(word);
784 new.push(extended);
785 }
786 }
787 permutations = new;
788 for permutation in &permutations {
789 println!("Starting {permutation:?}");
790 let p = Parser::new(permutation);
791 exhaust(p, vec![]);
792 }
793 }
794 }
795
796 fn exhaust(parser: Parser<'_>, path: Vec<String>) {
798 if path.len() > 100 {
799 panic!("Stuck in loop: {path:?}");
800 }
801
802 if parser.has_pending() {
803 {
804 let mut parser = parser.clone();
805 let next = parser.next_arg();
806 assert!(
807 matches!(next, Some(Unexpected(_)) | Some(Short(_))),
808 "{next:?} via {path:?}",
809 );
810 let mut path = path.clone();
811 path.push(format!("pending-next-{next:?}"));
812 exhaust(parser, path);
813 }
814
815 {
816 let mut parser = parser.clone();
817 let next = parser.next_flag_value();
818 assert!(next.is_some(), "{next:?} via {path:?}",);
819 let mut path = path;
820 path.push(format!("pending-value-{next:?}"));
821 exhaust(parser, path);
822 }
823 } else {
824 {
825 let mut parser = parser.clone();
826 let next = parser.next_arg();
827 match &next {
828 None => {
829 assert!(
830 matches!(parser.state, None | Some(State::Escaped)),
831 "{next:?} via {path:?}",
832 );
833 assert_eq!(parser.current, parser.raw.len(), "{next:?} via {path:?}",);
834 }
835 _ => {
836 let mut path = path.clone();
837 path.push(format!("next-{next:?}"));
838 exhaust(parser, path);
839 }
840 }
841 }
842
843 {
844 let mut parser = parser.clone();
845 let next = parser.next_flag_value();
846 match &next {
847 None => {
848 assert!(
849 matches!(parser.state, None | Some(State::Escaped)),
850 "{next:?} via {path:?}",
851 );
852 if parser.state.is_none()
853 && !parser.was_attached
854 && parser.peek_raw_() != Some(OsStr::new("--"))
855 {
856 assert_eq!(parser.current, parser.raw.len(), "{next:?} via {path:?}",);
857 }
858 }
859 Some(_) => {
860 assert!(
861 matches!(parser.state, None | Some(State::Escaped)),
862 "{next:?} via {path:?}",
863 );
864 let mut path = path;
865 path.push(format!("value-{next:?}"));
866 exhaust(parser, path);
867 }
868 }
869 }
870 }
871 }
872}
873
874#[doc = include_str!("../README.md")]
875#[cfg(doctest)]
876pub struct ReadmeDoctests;