1use std::iter::Peekable;
66use thiserror::Error;
67use yash_env::source::pretty::{Report, ReportType, Snippet};
68use yash_env::source::{
69 Location,
70 pretty::{Span, SpanRole, add_span},
71};
72
73#[doc(no_inline)]
74pub use yash_env::semantics::Field;
75
76#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
78#[non_exhaustive]
79pub enum OptionArgumentSpec {
80 #[default]
82 None,
83 Required,
85 }
88
89#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
100pub struct OptionSpec<'a> {
101 short: Option<char>,
102 long: Option<&'a str>,
103 argument: OptionArgumentSpec,
104}
105
106impl OptionSpec<'static> {
107 pub const fn new() -> Self {
109 OptionSpec {
110 short: None,
111 long: None,
112 argument: OptionArgumentSpec::None,
113 }
114 }
115}
116
117#[test]
118fn new_option_spec_eq_default() {
119 assert_eq!(OptionSpec::new(), OptionSpec::default());
120}
121
122impl OptionSpec<'_> {
123 pub const fn get_short(&self) -> Option<char> {
125 self.short
126 }
127
128 pub fn set_short(&mut self, name: char) {
132 self.short = Some(name);
133 }
134
135 pub const fn short(mut self, name: char) -> Self {
137 self.short = Some(name);
138 self
139 }
140}
141
142impl<'a> OptionSpec<'a> {
143 pub const fn get_long(&self) -> Option<&'a str> {
145 self.long
146 }
147
148 pub fn set_long(&mut self, name: &'a str) {
152 self.long = Some(name);
153 }
154
155 pub const fn long(mut self, name: &'a str) -> Self {
157 self.long = Some(name);
158 self
159 }
160}
161
162impl OptionSpec<'_> {
163 pub const fn get_argument(&self) -> OptionArgumentSpec {
165 self.argument
166 }
167
168 pub fn set_argument(&mut self, argument: OptionArgumentSpec) {
170 self.argument = argument;
171 }
172
173 pub const fn argument(mut self, argument: OptionArgumentSpec) -> Self {
175 self.argument = argument;
176 self
177 }
178}
179
180impl std::fmt::Display for OptionSpec<'_> {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 if let Some(short) = self.short {
187 write!(f, "-{short}")?;
188 if let Some(long) = self.long {
189 write!(f, "/--{long}")?;
190 }
191 Ok(())
192 } else if let Some(long) = self.long {
193 write!(f, "--{long}")
194 } else {
195 write!(f, "?")
196 }
197 }
198}
199
200#[derive(Clone, Copy, Debug, Eq, PartialEq)]
201enum LongMatch {
202 None,
203 Partial,
204 Exact,
205}
206
207impl OptionSpec<'_> {
208 fn long_match(&self, name: &str) -> LongMatch {
209 if let Some(long) = self.long {
210 if long.starts_with(name) {
211 return if long.len() == name.len() {
212 LongMatch::Exact
213 } else {
214 LongMatch::Partial
215 };
216 }
217 }
218 LongMatch::None
219 }
220}
221
222#[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
245pub struct Mode {
246 long_options: bool,
247 }
251
252impl Mode {
253 pub const fn with_extensions() -> Self {
255 Mode { long_options: true }
256 }
257
258 pub fn with_env<S>(env: &yash_env::Env<S>) -> Self {
263 use yash_env::option::{Off, On, PosixlyCorrect};
264 match env.options.get(PosixlyCorrect) {
265 On => Self::default(),
266 Off => Self::with_extensions(),
267 }
268 }
269
270 pub const fn accepts_long_options(&self) -> bool {
272 self.long_options
273 }
274
275 pub fn accept_long_options(&mut self, accept: bool) -> &mut Self {
277 self.long_options = accept;
278 self
279 }
280}
281
282#[derive(Clone, Debug, Eq, PartialEq)]
284#[non_exhaustive]
285pub struct OptionOccurrence<'a> {
286 pub spec: &'a OptionSpec<'a>,
288
289 pub location: Location,
291
292 pub argument: Option<Field>,
301}
302
303#[derive(Clone, Debug, Eq, Error, PartialEq)]
305#[non_exhaustive]
306pub enum ParseError<'a> {
307 #[error("unknown option {0:?}")]
309 UnknownShortOption(char, Field),
310
311 #[error("unknown option {:?}", long_option_name(.0))]
313 UnknownLongOption(Field),
314
315 #[error("unsupported option {:?}", .0.value)]
319 UnsupportedLongOption(Field, &'a OptionSpec<'a>),
320
321 #[error("ambiguous option {:?}", long_option_name(.0))]
325 AmbiguousLongOption(Field, Vec<&'a OptionSpec<'a>>),
326
327 #[error("option {:?} missing an argument", .0.value)]
329 MissingOptionArgument(Field, &'a OptionSpec<'a>),
330
331 #[error("option {:?} with an unexpected argument", .0.value)]
333 UnexpectedOptionArgument(Field, &'a OptionSpec<'a>),
334}
335
336fn long_option_name(field: &Field) -> &str {
337 match field.value.find('=') {
338 None => &field.value,
339 Some(index) => &field.value[..index],
340 }
341}
342
343impl ParseError<'_> {
344 pub fn field(&self) -> &Field {
346 use ParseError::*;
347 match self {
348 UnknownShortOption(_char, field) => field,
349 UnknownLongOption(field) => field,
350 UnsupportedLongOption(field, _spec) => field,
351 AmbiguousLongOption(field, _specs) => field,
352 MissingOptionArgument(field, _spec) => field,
353 UnexpectedOptionArgument(field, _spec) => field,
354 }
355 }
356
357 #[must_use]
359 pub fn to_report(&self) -> Report<'_> {
360 let field = self.field();
361 let mut report = Report::new();
362 report.r#type = ReportType::Error;
363 report.title = self.to_string().into();
364 report.snippets = Snippet::with_primary_span(&field.origin, field.value.as_str().into());
365 report
367 }
368}
369
370impl<'a> From<&'a ParseError<'a>> for Report<'a> {
371 #[inline]
372 fn from(error: &'a ParseError<'a>) -> Self {
373 error.to_report()
374 }
375}
376
377fn parse_short_options<'a, I: Iterator<Item = Field>>(
386 option_specs: &'a [OptionSpec<'a>],
387 arguments: &mut Peekable<I>,
388 option_occurrences: &mut Vec<OptionOccurrence<'a>>,
389) -> Result<bool, ParseError<'a>> {
390 fn starts_with_single_hyphen(field: &Field) -> bool {
391 let mut chars = field.value.chars();
392 chars.next() == Some('-') && !matches!(chars.next(), None | Some('-'))
393 }
394
395 let field = match arguments.next_if(starts_with_single_hyphen) {
396 None => return Ok(false),
397 Some(field) => field,
398 };
399
400 let mut chars = field.value.chars();
401 chars.next(); while let Some(c) = chars.next() {
404 let spec = match option_specs.iter().find(|spec| spec.get_short() == Some(c)) {
405 None => return Err(ParseError::UnknownShortOption(c, field)),
406 Some(spec) => spec,
407 };
408 match spec.get_argument() {
409 OptionArgumentSpec::None => {
410 option_occurrences.push(OptionOccurrence {
411 spec,
412 location: field.origin.clone(),
413 argument: None,
414 });
415 }
416 OptionArgumentSpec::Required => {
417 let remainder_len = chars.as_str().len();
418 let location = field.origin.clone();
419 let argument = if remainder_len == 0 {
420 arguments
422 .next()
423 .ok_or(ParseError::MissingOptionArgument(field, spec))?
424 } else {
425 let prefix = field.value.len() - remainder_len;
427 let mut field = field;
428 field.value.drain(..prefix);
429 field
430 };
431 option_occurrences.push(OptionOccurrence {
432 spec,
433 location,
434 argument: Some(argument),
435 });
436 break;
437 }
438 };
439 }
440 Ok(true)
441}
442
443fn long_match<'a>(
447 option_specs: &'a [OptionSpec<'a>],
448 name: &str,
449) -> Result<&'a OptionSpec<'a>, Vec<&'a OptionSpec<'a>>> {
450 let mut matches = Vec::new();
451 for spec in option_specs {
452 match spec.long_match(name) {
453 LongMatch::None => (),
454 LongMatch::Partial => {
455 matches.push(spec);
456 }
457 LongMatch::Exact => return Ok(spec),
458 }
459 }
460 if matches.len() == 1 {
461 Ok(matches[0])
462 } else {
463 Err(matches)
464 }
465}
466
467fn parse_long_option<'a, I: Iterator<Item = Field>>(
474 option_specs: &'a [OptionSpec<'a>],
475 mode: Mode,
476 arguments: &mut Peekable<I>,
477) -> Result<Option<OptionOccurrence<'a>>, ParseError<'a>> {
478 fn starts_with_double_hyphen(field: &Field) -> bool {
479 match field.value.strip_prefix("--") {
480 Some(body) => !body.is_empty(),
481 None => false,
482 }
483 }
484
485 let field = match arguments.next_if(starts_with_double_hyphen) {
486 Some(field) => field,
487 None => return Ok(None),
488 };
489
490 let equal = field.value.find('=');
491
492 let name = match equal {
493 Some(index) => &field.value[2..index],
494 None => &field.value[2..],
495 };
496
497 let spec = match long_match(option_specs, name) {
498 Ok(spec) if mode.accepts_long_options() => spec,
499 Ok(spec) => return Err(ParseError::UnsupportedLongOption(field, spec)),
500 Err(matched_specs) => {
501 return Err(if matched_specs.is_empty() {
502 ParseError::UnknownLongOption(field)
503 } else {
504 ParseError::AmbiguousLongOption(field, matched_specs)
505 });
506 }
507 };
508
509 let location = field.origin.clone();
510
511 let argument = match (spec.get_argument(), equal) {
512 (OptionArgumentSpec::None, None) => None,
513 (OptionArgumentSpec::None, Some(_)) => {
514 return Err(ParseError::UnexpectedOptionArgument(field, spec));
515 }
516 (OptionArgumentSpec::Required, None) => {
517 let argument = arguments.next();
518 if argument.is_none() {
519 return Err(ParseError::MissingOptionArgument(field, spec));
520 }
521 argument
522 }
523 (OptionArgumentSpec::Required, Some(index)) => {
524 let mut field = field;
525 field.value.drain(..index + 1); Some(field)
527 }
528 };
529
530 Ok(Some(OptionOccurrence {
531 spec,
532 location,
533 argument,
534 }))
535}
536
537pub fn parse_arguments<'a>(
543 option_specs: &'a [OptionSpec<'a>],
544 mode: Mode,
545 arguments: Vec<Field>,
546) -> Result<(Vec<OptionOccurrence<'a>>, Vec<Field>), ParseError<'a>> {
547 let mut arguments = arguments.into_iter().peekable();
548
549 let mut option_occurrences = vec![];
550 loop {
551 if parse_short_options(option_specs, &mut arguments, &mut option_occurrences)? {
552 continue;
553 }
554 if let Some(occurrence) = parse_long_option(option_specs, mode, &mut arguments)? {
555 option_occurrences.push(occurrence);
556 continue;
557 }
558 break;
559 }
560
561 arguments.next_if(|argument| argument.value == "--");
562
563 let operands = arguments.collect();
564 Ok((option_occurrences, operands))
565}
566
567#[derive(Clone, Debug, Eq, Error, PartialEq)]
574#[error("conflicting options")]
575pub struct ConflictingOptionError<'a> {
576 options: Vec<OptionOccurrence<'a>>,
577}
578
579impl<'a> ConflictingOptionError<'a> {
580 #[must_use]
585 pub fn new<T: Into<Vec<OptionOccurrence<'a>>>>(options: T) -> Self {
586 let options = options.into();
587 Self { options }
588 }
589
590 #[must_use]
613 pub fn pick_from_indexes<const N: usize>(
614 mut options: Vec<OptionOccurrence<'a>>,
615 mut indexes: [usize; N],
616 ) -> Self {
617 indexes.sort();
618
619 let mut option_index = 0;
621 let mut index_index = 0;
622 options.retain(|_| {
623 if index_index >= N {
624 return false;
625 }
626 assert!(
627 option_index <= indexes[index_index],
628 "duplicate index {}",
629 indexes[index_index]
630 );
631 let pick = option_index == indexes[index_index];
632 option_index += 1;
633 if pick {
634 index_index += 1;
635 }
636 pick
637 });
638
639 Self { options }
640 }
641
642 #[must_use]
644 pub fn options(&self) -> &[OptionOccurrence<'a>] {
645 &self.options
646 }
647
648 #[must_use]
650 pub fn to_report(&'a self) -> Report<'a> {
651 let mut report = Report::new();
652 report.r#type = ReportType::Error;
653 report.title = self.to_string().into();
654 report.snippets = Snippet::with_primary_span(
655 &self.options[0].location,
656 format!("the {} option ...", &self.options[0].spec).into(),
657 );
658 for option in &self.options[1..] {
659 let span = Span {
660 range: option.location.byte_range(),
661 role: SpanRole::Primary {
662 label: format!("... cannot be used with the {} option", &option.spec).into(),
663 },
664 };
665 add_span(&option.location.code, span, &mut report.snippets);
666 }
667 report
668 }
669}
670
671impl<'a> From<Vec<OptionOccurrence<'a>>> for ConflictingOptionError<'a> {
672 fn from(options: Vec<OptionOccurrence<'a>>) -> Self {
677 ConflictingOptionError { options }
678 }
679}
680
681impl<'a> From<ConflictingOptionError<'a>> for Vec<OptionOccurrence<'a>> {
682 fn from(error: ConflictingOptionError<'a>) -> Self {
683 error.options
684 }
685}
686
687impl<'a> From<&'a ConflictingOptionError<'a>> for Report<'a> {
688 #[inline]
689 fn from(error: &'a ConflictingOptionError<'a>) -> Self {
690 error.to_report()
691 }
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697 use assert_matches::assert_matches;
698
699 #[test]
700 fn empty_arguments() {
701 let (options, operands) = parse_arguments(&[], Mode::default(), vec![]).unwrap();
702 assert_eq!(options, []);
703 assert_eq!(operands, []);
704 }
705
706 #[test]
707 fn only_operands() {
708 let arguments = Field::dummies([""]);
709 let (options, operands) = parse_arguments(&[], Mode::default(), arguments).unwrap();
710 assert_eq!(options, []);
711 assert_eq!(operands, Field::dummies([""]));
712
713 let arguments = Field::dummies(["foo", "bar", "", "baz"]);
714 let (options, operands) = parse_arguments(&[], Mode::default(), arguments).unwrap();
715 assert_eq!(options, []);
716 assert_eq!(operands, Field::dummies(["foo", "bar", "", "baz"]));
717 }
718
719 #[test]
720 fn operands_following_separator() {
721 let arguments = Field::dummies(["--"]);
722 let (options, operands) = parse_arguments(&[], Mode::default(), arguments).unwrap();
723 assert_eq!(options, []);
724 assert_eq!(operands, []);
725
726 let arguments = Field::dummies(["--", "1"]);
727 let (options, operands) = parse_arguments(&[], Mode::default(), arguments).unwrap();
728 assert_eq!(options, []);
729 assert_eq!(operands, Field::dummies(["1"]));
730
731 let arguments = Field::dummies(["--", "a", "", "z"]);
732 let (options, operands) = parse_arguments(&[], Mode::default(), arguments).unwrap();
733 assert_eq!(options, []);
734 assert_eq!(operands, Field::dummies(["a", "", "z"]));
735 }
736
737 #[test]
738 fn non_occurring_short_option() {
739 let specs = &[OptionSpec::new().short('a')];
740
741 let arguments = vec![];
742 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
743 assert_eq!(options, []);
744 assert_eq!(operands, []);
745
746 let arguments = Field::dummies([""]);
747 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
748 assert_eq!(options, []);
749 assert_eq!(operands, Field::dummies([""]));
750 }
751
752 #[test]
753 fn single_occurrence_of_short_option() {
754 let specs = &[OptionSpec::new().short('a')];
755
756 let arguments = Field::dummies(["-a"]);
757 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
758 assert_eq!(options.len(), 1, "options = {options:?}");
759 assert_eq!(options[0].spec.get_short(), Some('a'));
760 assert_eq!(options[0].location, Location::dummy("-a"));
761 assert_eq!(options[0].argument, None);
762 assert_eq!(operands, []);
763
764 let arguments = Field::dummies(["-a", "foo"]);
765 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
766 assert_eq!(options.len(), 1, "options = {options:?}");
767 assert_eq!(options[0].spec.get_short(), Some('a'));
768 assert_eq!(options[0].location, Location::dummy("-a"));
769 assert_eq!(options[0].argument, None);
770 assert_eq!(operands, Field::dummies(["foo"]));
771 }
772
773 #[test]
774 fn multiple_occurrences_of_same_option_spec_short() {
775 let specs = &[OptionSpec::new().short('b')];
776
777 let arguments = Field::dummies(["-b", "-b"]);
778 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
779 assert_eq!(options.len(), 2, "options = {options:?}");
780 assert_eq!(options[0].spec.get_short(), Some('b'));
781 assert_eq!(options[1].spec.get_short(), Some('b'));
782 assert_eq!(operands, []);
783
784 let arguments = Field::dummies(["-b", "-b", "argument"]);
785 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
786 assert_eq!(options.len(), 2, "options = {options:?}");
787 assert_eq!(options[0].spec.get_short(), Some('b'));
788 assert_eq!(options[1].spec.get_short(), Some('b'));
789 assert_eq!(operands, Field::dummies(["argument"]));
790 }
791
792 #[test]
793 fn occurrences_of_multiple_option_specs_short() {
794 let specs = &[OptionSpec::new().short('x'), OptionSpec::new().short('y')];
795
796 let arguments = Field::dummies(["-x", "-y", "!"]);
797 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
798 assert_eq!(options.len(), 2, "options = {options:?}");
799 assert_eq!(options[0].spec.get_short(), Some('x'));
800 assert_eq!(options[1].spec.get_short(), Some('y'));
801 assert_eq!(operands, Field::dummies(["!"]));
802
803 let arguments = Field::dummies(["-y", "-x", "-y"]);
804 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
805 assert_eq!(options.len(), 3, "options = {options:?}");
806 assert_eq!(options[0].spec.get_short(), Some('y'));
807 assert_eq!(options[1].spec.get_short(), Some('x'));
808 assert_eq!(options[2].spec.get_short(), Some('y'));
809 assert_eq!(operands, []);
810 }
811
812 #[test]
813 fn multiple_occurrences_of_short_options_in_single_argument() {
814 let specs = &[OptionSpec::new().short('p'), OptionSpec::new().short('q')];
815
816 let arguments = Field::dummies(["-pq", "!"]);
817 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
818 assert_eq!(options.len(), 2, "options = {options:?}");
819 assert_eq!(options[0].spec.get_short(), Some('p'));
820 assert_eq!(options[0].location, Location::dummy("-pq"));
821 assert_eq!(options[1].spec.get_short(), Some('q'));
822 assert_eq!(options[1].location, Location::dummy("-pq"));
823 assert_eq!(operands, Field::dummies(["!"]));
824
825 let arguments = Field::dummies(["-qpq"]);
826 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
827 assert_eq!(options.len(), 3, "options = {options:?}");
828 assert_eq!(options[0].spec.get_short(), Some('q'));
829 assert_eq!(options[1].spec.get_short(), Some('p'));
830 assert_eq!(options[2].spec.get_short(), Some('q'));
831 assert_eq!(operands, []);
832 }
833
834 #[test]
835 fn single_hyphen_argument_is_not_option() {
836 let specs = &[OptionSpec::new().short('a')];
837
838 let arguments = Field::dummies(["-"]);
839 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
840 assert_eq!(options, []);
841 assert_eq!(operands, Field::dummies(["-"]));
842
843 let arguments = Field::dummies(["-", "-"]);
844 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
845 assert_eq!(options, []);
846 assert_eq!(operands, Field::dummies(["-", "-"]));
847 }
848
849 #[test]
850 fn options_are_not_recognized_after_separator() {
851 let specs = &[OptionSpec::new().short('a')];
852
853 let arguments = Field::dummies(["-a", "--", "-a", "--", "-a"]);
854 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
855 assert_eq!(options.len(), 1, "options = {options:?}");
856 assert_eq!(options[0].spec.get_short(), Some('a'));
857 assert_eq!(operands, Field::dummies(["-a", "--", "-a"]));
858 }
859
860 #[test]
861 fn options_are_not_recognized_after_operand_by_default() {
862 let specs = &[OptionSpec::new().short('x'), OptionSpec::new().short('y')];
863
864 let arguments = Field::dummies(["-x", "foo", "-y", "bar"]);
865 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
866 assert_eq!(options.len(), 1, "options = {options:?}");
867 assert_eq!(options[0].spec.get_short(), Some('x'));
868 assert_eq!(operands, Field::dummies(["foo", "-y", "bar"]));
869 }
870
871 #[test]
872 fn adjacent_argument_to_short_option() {
873 let specs = &[OptionSpec::new()
874 .short('a')
875 .argument(OptionArgumentSpec::Required)];
876
877 let arguments = Field::dummies(["-afoo"]);
878 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
879 assert_eq!(options.len(), 1, "options = {options:?}");
880 assert_eq!(options[0].spec.get_short(), Some('a'));
881 assert_eq!(options[0].location, Location::dummy("-afoo"));
882 assert_matches!(options[0].argument, Some(ref field) => {
883 assert_eq!(field.value, "foo");
884 assert_eq!(field.origin, Location::dummy("-afoo"));
885 });
886 assert_eq!(operands, []);
887
888 let arguments = Field::dummies(["-a1", "-a2", "3"]);
889 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
890 assert_eq!(options.len(), 2, "options = {options:?}");
891 assert_eq!(options[0].spec.get_short(), Some('a'));
892 assert_matches!(options[0].argument, Some(ref field) => {
893 assert_eq!(field.value, "1");
894 assert_eq!(field.origin, Location::dummy("-a1"));
895 });
896 assert_eq!(options[1].spec.get_short(), Some('a'));
897 assert_matches!(options[1].argument, Some(ref field) => {
898 assert_eq!(field.value, "2");
899 assert_eq!(field.origin, Location::dummy("-a2"));
900 });
901 assert_eq!(operands, Field::dummies(["3"]));
902 }
903
904 #[test]
905 fn separate_argument_to_short_option() {
906 let specs = &[OptionSpec::new()
907 .short('a')
908 .argument(OptionArgumentSpec::Required)];
909
910 let arguments = Field::dummies(["-a", "foo"]);
911 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
912 assert_eq!(options.len(), 1, "options = {options:?}");
913 assert_eq!(options[0].spec.get_short(), Some('a'));
914 assert_matches!(options[0].argument, Some(ref field) => {
915 assert_eq!(field.value, "foo");
916 assert_eq!(field.origin, Location::dummy("foo"));
917 });
918 assert_eq!(operands, []);
919
920 let arguments = Field::dummies(["-a", "1", "-a", "2", "3"]);
921 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
922 assert_eq!(options.len(), 2, "options = {options:?}");
923 assert_eq!(options[0].spec.get_short(), Some('a'));
924 assert_matches!(options[0].argument, Some(ref field) => {
925 assert_eq!(field.value, "1");
926 assert_eq!(field.origin, Location::dummy("1"));
927 });
928 assert_eq!(options[1].spec.get_short(), Some('a'));
929 assert_matches!(options[1].argument, Some(ref field) => {
930 assert_eq!(field.value, "2");
931 assert_eq!(field.origin, Location::dummy("2"));
932 });
933 assert_eq!(operands, Field::dummies(["3"]));
934 }
935
936 #[test]
937 fn argument_taking_option_adjacent_to_another_option() {
938 let specs = &[
939 OptionSpec::new()
940 .short('a')
941 .argument(OptionArgumentSpec::None),
942 OptionSpec::new()
943 .short('b')
944 .argument(OptionArgumentSpec::None),
945 OptionSpec::new()
946 .short('c')
947 .argument(OptionArgumentSpec::Required),
948 ];
949
950 let arguments = Field::dummies(["-abcdef"]);
951 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
952 assert_eq!(options.len(), 3, "options = {options:?}");
953 assert_eq!(options[0].spec.get_short(), Some('a'));
954 assert_eq!(options[0].argument, None);
955 assert_eq!(options[1].spec.get_short(), Some('b'));
956 assert_eq!(options[1].argument, None);
957 assert_eq!(options[2].spec.get_short(), Some('c'));
958 assert_matches!(options[2].argument, Some(ref field) => {
959 assert_eq!(field.value, "def");
960 assert_eq!(field.origin, Location::dummy("-abcdef"));
961 });
962 assert_eq!(operands, []);
963 }
964
965 #[test]
966 fn empty_argument_to_short_option() {
967 let specs = &[OptionSpec::new()
968 .short('a')
969 .argument(OptionArgumentSpec::Required)];
970
971 let arguments = Field::dummies(["-a", ""]);
972 let (options, operands) = parse_arguments(specs, Mode::default(), arguments).unwrap();
973 assert_eq!(options.len(), 1, "options = {options:?}");
974 assert_eq!(options[0].spec.get_short(), Some('a'));
975 assert_matches!(options[0].argument, Some(ref field) => {
976 assert_eq!(field.value, "");
977 assert_eq!(field.origin, Location::dummy(""));
978 });
979 assert_eq!(operands, []);
980 }
981
982 #[test]
983 fn non_occurring_long_option() {
984 let specs = &[OptionSpec::new().long("option")];
985
986 let arguments = vec![];
987 let (options, operands) =
988 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
989 assert_eq!(options, []);
990 assert_eq!(operands, []);
991
992 let arguments = Field::dummies([""]);
993 let (options, operands) =
994 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
995 assert_eq!(options, []);
996 assert_eq!(operands, Field::dummies([""]));
997 }
998
999 #[test]
1000 fn single_occurrence_of_long_option() {
1001 let specs = &[OptionSpec::new().long("option")];
1002
1003 let arguments = Field::dummies(["--option"]);
1004 let (options, operands) =
1005 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1006 assert_eq!(options.len(), 1, "options = {options:?}");
1007 assert_eq!(options[0].spec.get_long(), Some("option"));
1008 assert_eq!(options[0].location, Location::dummy("--option"));
1009 assert_eq!(options[0].argument, None);
1010 assert_eq!(operands, []);
1011
1012 let arguments = Field::dummies(["--option", "foo"]);
1013 let (options, operands) =
1014 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1015 assert_eq!(options.len(), 1, "options = {options:?}");
1016 assert_eq!(options[0].spec.get_long(), Some("option"));
1017 assert_eq!(options[0].location, Location::dummy("--option"));
1018 assert_eq!(options[0].argument, None);
1019 assert_eq!(operands, Field::dummies(["foo"]));
1020 }
1021
1022 #[test]
1023 fn multiple_occurrences_of_same_option_spec_long() {
1024 let specs = &[OptionSpec::new().long("foo")];
1025
1026 let arguments = Field::dummies(["--foo", "--foo"]);
1027 let (options, operands) =
1028 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1029 assert_eq!(options.len(), 2, "options = {options:?}");
1030 assert_eq!(options[0].spec.get_long(), Some("foo"));
1031 assert_eq!(options[1].spec.get_long(), Some("foo"));
1032 assert_eq!(operands, []);
1033
1034 let arguments = Field::dummies(["--foo", "--foo", "argument"]);
1035 let (options, operands) =
1036 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1037 assert_eq!(options.len(), 2, "options = {options:?}");
1038 assert_eq!(options[0].spec.get_long(), Some("foo"));
1039 assert_eq!(options[1].spec.get_long(), Some("foo"));
1040 assert_eq!(operands, Field::dummies(["argument"]));
1041 }
1042
1043 #[test]
1044 fn occurrences_of_multiple_option_specs_long() {
1045 let specs = &[OptionSpec::new().long("foo"), OptionSpec::new().long("bar")];
1046
1047 let arguments = Field::dummies(["--foo", "--bar", "!"]);
1048 let (options, operands) =
1049 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1050 assert_eq!(options.len(), 2, "options = {options:?}");
1051 assert_eq!(options[0].spec.get_long(), Some("foo"));
1052 assert_eq!(options[1].spec.get_long(), Some("bar"));
1053 assert_eq!(operands, Field::dummies(["!"]));
1054
1055 let arguments = Field::dummies(["--bar", "--foo", "--bar"]);
1056 let (options, operands) =
1057 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1058 assert_eq!(options.len(), 3, "options = {options:?}");
1059 assert_eq!(options[0].spec.get_long(), Some("bar"));
1060 assert_eq!(options[1].spec.get_long(), Some("foo"));
1061 assert_eq!(options[2].spec.get_long(), Some("bar"));
1062 assert_eq!(operands, []);
1063 }
1064
1065 #[test]
1066 fn abbreviated_long_option_without_non_match() {
1067 let specs = &[OptionSpec::new().long("min")];
1068
1069 let arguments = Field::dummies(["--mi"]);
1070 let (options, operands) =
1071 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1072 assert_eq!(options.len(), 1, "options = {options:?}");
1073 assert_eq!(options[0].spec.get_long(), Some("min"));
1074 assert_eq!(operands, []);
1075 }
1076
1077 #[test]
1078 fn abbreviated_long_option_with_non_match() {
1079 let specs = &[OptionSpec::new().long("max"), OptionSpec::new().long("min")];
1080
1081 let arguments = Field::dummies(["--mi"]);
1082 let (options, operands) =
1083 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1084 assert_eq!(options.len(), 1, "options = {options:?}");
1085 assert_eq!(options[0].spec.get_long(), Some("min"));
1086 assert_eq!(operands, []);
1087 }
1088
1089 #[test]
1090 fn long_option_prefers_exact_match() {
1091 let specs = &[
1092 OptionSpec::new().long("many"),
1093 OptionSpec::new().long("man"),
1094 OptionSpec::new().long("manual"),
1095 ];
1096
1097 let arguments = Field::dummies(["--man"]);
1098 let (options, operands) =
1099 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1100 assert_eq!(options.len(), 1, "options = {options:?}");
1101 assert_eq!(options[0].spec.get_long(), Some("man"));
1102 assert_eq!(operands, []);
1103 }
1104
1105 #[test]
1106 fn adjacent_argument_to_long_option() {
1107 let specs = &[OptionSpec::new()
1108 .long("option")
1109 .argument(OptionArgumentSpec::Required)];
1110
1111 let arguments = Field::dummies(["--option="]);
1112 let (options, operands) =
1113 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1114 assert_eq!(options.len(), 1, "options = {options:?}");
1115 assert_eq!(options[0].spec.get_long(), Some("option"));
1116 assert_eq!(options[0].location, Location::dummy("--option="));
1117 assert_matches!(options[0].argument, Some(ref field) => {
1118 assert_eq!(field.value, "");
1119 assert_eq!(field.origin, Location::dummy("--option="));
1120 });
1121 assert_eq!(operands, []);
1122
1123 let arguments = Field::dummies(["--option=x", "--option=value", "argument"]);
1124 let (options, operands) =
1125 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1126 assert_eq!(options.len(), 2, "options = {options:?}");
1127 assert_eq!(options[0].spec.get_long(), Some("option"));
1128 assert_eq!(options[0].location, Location::dummy("--option=x"));
1129 assert_matches!(options[0].argument, Some(ref field) => {
1130 assert_eq!(field.value, "x");
1131 assert_eq!(field.origin, Location::dummy("--option=x"));
1132 });
1133 assert_eq!(options[1].spec.get_long(), Some("option"));
1134 assert_eq!(options[1].location, Location::dummy("--option=value"));
1135 assert_matches!(options[1].argument, Some(ref field) => {
1136 assert_eq!(field.value, "value");
1137 assert_eq!(field.origin, Location::dummy("--option=value"));
1138 });
1139 assert_eq!(operands, Field::dummies(["argument"]));
1140 }
1141
1142 #[test]
1143 fn separate_argument_to_long_option() {
1144 let specs = &[OptionSpec::new()
1145 .long("option")
1146 .argument(OptionArgumentSpec::Required)];
1147
1148 let arguments = Field::dummies(["--option", ""]);
1149 let (options, operands) =
1150 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1151 assert_eq!(options.len(), 1, "options = {options:?}");
1152 assert_eq!(options[0].spec.get_long(), Some("option"));
1153 assert_eq!(options[0].location, Location::dummy("--option"));
1154 assert_matches!(options[0].argument, Some(ref field) => {
1155 assert_eq!(field.value, "");
1156 assert_eq!(field.origin, Location::dummy(""));
1157 });
1158 assert_eq!(operands, []);
1159
1160 let arguments = Field::dummies(["--option", "x", "--option", "value", "argument"]);
1161 let (options, operands) =
1162 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1163 assert_eq!(options.len(), 2, "options = {options:?}");
1164 assert_eq!(options[0].spec.get_long(), Some("option"));
1165 assert_eq!(options[0].location, Location::dummy("--option"));
1166 assert_matches!(options[0].argument, Some(ref field) => {
1167 assert_eq!(field.value, "x");
1168 assert_eq!(field.origin, Location::dummy("x"));
1169 });
1170 assert_eq!(options[1].spec.get_long(), Some("option"));
1171 assert_eq!(options[1].location, Location::dummy("--option"));
1172 assert_matches!(options[1].argument, Some(ref field) => {
1173 assert_eq!(field.value, "value");
1174 assert_eq!(field.origin, Location::dummy("value"));
1175 });
1176 assert_eq!(operands, Field::dummies(["argument"]));
1177 }
1178
1179 #[test]
1180 fn option_argument_that_looks_like_separator() {
1181 let specs = &[OptionSpec::new()
1182 .short('a')
1183 .argument(OptionArgumentSpec::Required)];
1184
1185 let arguments = Field::dummies(["-a", "argument", "-a", "--", "--", "operand"]);
1186 let (options, operands) =
1187 parse_arguments(specs, Mode::with_extensions(), arguments).unwrap();
1188 assert_eq!(options.len(), 2, "options = {options:?}");
1189 assert_eq!(options[0].spec.get_short(), Some('a'));
1190 assert_matches!(options[0].argument, Some(ref field) => {
1191 assert_eq!(field.value, "argument");
1192 assert_eq!(field.origin, Location::dummy("argument"));
1193 });
1194 assert_eq!(options[1].spec.get_short(), Some('a'));
1195 assert_matches!(options[1].argument, Some(ref field) => {
1196 assert_eq!(field.value, "--");
1197 assert_eq!(field.origin, Location::dummy("--"));
1198 });
1199 assert_eq!(operands, Field::dummies(["operand"]));
1200 }
1201
1202 #[test]
1207 fn unknown_short_option() {
1208 let specs = &[OptionSpec::new().short('a')];
1209
1210 let arguments = Field::dummies(["-x"]);
1211 let error = parse_arguments(&[], Mode::default(), arguments).unwrap_err();
1212 assert_matches!(&error, ParseError::UnknownShortOption('x', field) => {
1213 assert_eq!(field.value, "-x");
1214 });
1215 assert_eq!(error.to_string(), "unknown option 'x'");
1216
1217 let arguments = Field::dummies(["-x"]);
1218 let error = parse_arguments(specs, Mode::default(), arguments).unwrap_err();
1219 assert_matches!(&error, ParseError::UnknownShortOption('x', field) => {
1220 assert_eq!(field.value, "-x");
1221 });
1222 assert_eq!(error.to_string(), "unknown option 'x'");
1223 }
1224
1225 #[test]
1226 fn unknown_long_option() {
1227 let specs = &[OptionSpec::new().long("one")];
1228
1229 let arguments = Field::dummies(["--two"]);
1230 let error = parse_arguments(&[], Mode::with_extensions(), arguments).unwrap_err();
1231 assert_matches!(&error, ParseError::UnknownLongOption(field) => {
1232 assert_eq!(field.value, "--two");
1233 });
1234 assert_eq!(error.to_string(), "unknown option \"--two\"");
1235
1236 let arguments = Field::dummies(["--two=three"]);
1237 let error = parse_arguments(specs, Mode::with_extensions(), arguments).unwrap_err();
1238 assert_matches!(&error, ParseError::UnknownLongOption(field) => {
1239 assert_eq!(field.value, "--two=three");
1240 });
1241 assert_eq!(error.to_string(), "unknown option \"--two\"");
1242 }
1243
1244 #[test]
1245 fn disabled_long_option() {
1246 let specs = &[OptionSpec::new().long("option")];
1247
1248 let mode = *Mode::with_extensions().accept_long_options(false);
1249 let arguments = Field::dummies(["--option"]);
1250 let error = parse_arguments(specs, mode, arguments).unwrap_err();
1251 assert_matches!(&error, &ParseError::UnsupportedLongOption(ref field, spec) => {
1252 assert_eq!(field.value, "--option");
1253 assert_eq!(spec, &specs[0]);
1254 });
1255 assert_eq!(error.to_string(), "unsupported option \"--option\"");
1256 }
1257
1258 #[test]
1259 fn ambiguous_long_option() {
1260 let specs = &[
1261 OptionSpec::new().long("max"),
1262 OptionSpec::new().long("min"),
1263 OptionSpec::new().long("value"),
1264 ];
1265
1266 let arguments = Field::dummies(["--m"]);
1267 let error = parse_arguments(specs, Mode::with_extensions(), arguments).unwrap_err();
1268 assert_matches!(&error, ParseError::AmbiguousLongOption(field, matched_specs) => {
1269 assert_eq!(field.value, "--m");
1270 assert_eq!(matched_specs.as_slice(), [&specs[0], &specs[1]]);
1271 });
1272 assert_eq!(error.to_string(), "ambiguous option \"--m\"");
1273 }
1274
1275 #[test]
1276 fn missing_argument_to_short_option() {
1277 use OptionArgumentSpec::Required;
1278 let specs = &[
1279 OptionSpec::new().short('a').argument(Required),
1280 OptionSpec::new().short('b'),
1281 ];
1282
1283 let arguments = Field::dummies(["-a"]);
1284 let error = parse_arguments(specs, Mode::default(), arguments).unwrap_err();
1285 assert_matches!(&error, &ParseError::MissingOptionArgument(ref field, spec) => {
1286 assert_eq!(field.value, "-a");
1287 assert_eq!(spec, &specs[0]);
1288 });
1289 assert_eq!(error.to_string(), "option \"-a\" missing an argument");
1290
1291 let arguments = Field::dummies(["-ba"]);
1292 let error = parse_arguments(specs, Mode::default(), arguments).unwrap_err();
1293 assert_matches!(&error, &ParseError::MissingOptionArgument(ref field, spec) => {
1294 assert_eq!(field.value, "-ba");
1295 assert_eq!(spec, &specs[0]);
1296 });
1297 assert_eq!(error.to_string(), "option \"-ba\" missing an argument");
1298 }
1299
1300 #[test]
1301 fn missing_argument_to_long_option() {
1302 use OptionArgumentSpec::Required;
1303 let specs = &[
1304 OptionSpec::new().long("foo").argument(Required),
1305 OptionSpec::new().long("bar"),
1306 ];
1307
1308 let arguments = Field::dummies(["--fo"]);
1309 let error = parse_arguments(specs, Mode::with_extensions(), arguments).unwrap_err();
1310 assert_matches!(&error, &ParseError::MissingOptionArgument(ref field, spec) => {
1311 assert_eq!(field.value, "--fo");
1312 assert_eq!(spec, &specs[0]);
1313 });
1314 assert_eq!(error.to_string(), "option \"--fo\" missing an argument");
1315 }
1316
1317 #[test]
1318 fn unexpected_argument_to_long_option() {
1319 use OptionArgumentSpec::Required;
1320 let specs = &[
1321 OptionSpec::new().long("foo").argument(Required),
1322 OptionSpec::new().long("bar"),
1323 ];
1324
1325 let arguments = Field::dummies(["--bar=baz"]);
1326 let error = parse_arguments(specs, Mode::with_extensions(), arguments).unwrap_err();
1327 assert_matches!(&error, &ParseError::UnexpectedOptionArgument(ref field, spec) => {
1328 assert_eq!(field.value, "--bar=baz");
1329 assert_eq!(spec, &specs[1]);
1330 });
1331 assert_eq!(
1332 error.to_string(),
1333 "option \"--bar=baz\" with an unexpected argument"
1334 );
1335 }
1336
1337 const OPTION_SPEC_A: OptionSpec = OptionSpec::new().short('a');
1338 const OPTION_SPEC_B: OptionSpec = OptionSpec::new().short('b');
1339 const OPTION_SPEC_C: OptionSpec = OptionSpec::new().short('c');
1340 const OPTION_SPEC_D: OptionSpec = OptionSpec::new().short('d');
1341 const OPTION_SPEC_E: OptionSpec = OptionSpec::new().short('e');
1342
1343 fn dummy_options() -> Vec<OptionOccurrence<'static>> {
1344 vec![
1345 OptionOccurrence {
1346 spec: &OPTION_SPEC_A,
1347 location: Location::dummy("-a"),
1348 argument: None,
1349 },
1350 OptionOccurrence {
1351 spec: &OPTION_SPEC_B,
1352 location: Location::dummy("-b"),
1353 argument: None,
1354 },
1355 OptionOccurrence {
1356 spec: &OPTION_SPEC_C,
1357 location: Location::dummy("-c"),
1358 argument: None,
1359 },
1360 OptionOccurrence {
1361 spec: &OPTION_SPEC_D,
1362 location: Location::dummy("-d"),
1363 argument: None,
1364 },
1365 OptionOccurrence {
1366 spec: &OPTION_SPEC_E,
1367 location: Location::dummy("-e"),
1368 argument: None,
1369 },
1370 ]
1371 }
1372
1373 #[test]
1374 fn pick_from_2_indexes() {
1375 let result = ConflictingOptionError::pick_from_indexes(dummy_options(), [1, 3]);
1376 let options = Vec::from(result);
1377 assert_matches!(options.as_slice(), [b, d] => {
1378 assert_eq!(b.spec, &OPTION_SPEC_B);
1379 assert_eq!(d.spec, &OPTION_SPEC_D);
1380 });
1381 }
1382
1383 #[test]
1384 fn pick_from_2_indexes_reversed() {
1385 let result = ConflictingOptionError::pick_from_indexes(dummy_options(), [3, 1]);
1386 let options = Vec::from(result);
1387 assert_matches!(options.as_slice(), [b, d] => {
1388 assert_eq!(b.spec, &OPTION_SPEC_B);
1389 assert_eq!(d.spec, &OPTION_SPEC_D);
1390 });
1391 }
1392
1393 #[test]
1394 fn pick_from_3_indexes() {
1395 let result = ConflictingOptionError::pick_from_indexes(dummy_options(), [0, 2, 4]);
1396 let options = Vec::from(result);
1397 assert_matches!(options.as_slice(), [a, c, e] => {
1398 assert_eq!(a.spec, &OPTION_SPEC_A);
1399 assert_eq!(c.spec, &OPTION_SPEC_C);
1400 assert_eq!(e.spec, &OPTION_SPEC_E);
1401 });
1402 }
1403
1404 #[test]
1405 fn pick_from_4_indexes_shuffled() {
1406 let result = ConflictingOptionError::pick_from_indexes(dummy_options(), [3, 0, 4, 2]);
1407 let options = Vec::from(result);
1408 assert_matches!(options.as_slice(), [a, c, d, e] => {
1409 assert_eq!(a.spec, &OPTION_SPEC_A);
1410 assert_eq!(c.spec, &OPTION_SPEC_C);
1411 assert_eq!(d.spec, &OPTION_SPEC_D);
1412 assert_eq!(e.spec, &OPTION_SPEC_E);
1413 });
1414 }
1415
1416 #[test]
1417 #[should_panic(expected = "duplicate index 1")]
1418 fn pick_from_duplicate_indexes() {
1419 let result = ConflictingOptionError::pick_from_indexes(dummy_options(), [1, 1]);
1420 unreachable!("{result:?}");
1421 }
1422}