1use std::cmp::Ordering;
2use std::error::Error;
3use std::ffi::OsString;
4use std::fmt::{self, Debug, Display};
5use std::marker::PhantomData;
6use std::path::{Path, PathBuf};
7use std::result::Result as StdResult;
8use std::str;
9
10pub type ArgParseResult<T> = StdResult<T, ArgParseError>;
11pub type ArgToStringResult = StdResult<String, ArgToStringError>;
12pub type PathTransformerFn<'a> = &'a mut dyn FnMut(&Path) -> Option<String>;
13
14#[derive(Debug, PartialEq, Eq)]
15pub enum ArgParseError {
16 UnexpectedEndOfArgs,
17 InvalidUnicode(OsString),
18 Other(&'static str),
19}
20
21impl Display for ArgParseError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 let s = match self {
24 ArgParseError::UnexpectedEndOfArgs => "Unexpected end of args".into(),
25 ArgParseError::InvalidUnicode(s) => format!("String {:?} contained invalid unicode", s),
26 ArgParseError::Other(s) => format!("Arg-specific parsing failed: {}", s),
27 };
28 write!(f, "{}", s)
29 }
30}
31
32impl Error for ArgParseError {
33 fn cause(&self) -> Option<&dyn Error> {
34 None
35 }
36}
37
38#[derive(Debug, PartialEq, Eq)]
39pub enum ArgToStringError {
40 FailedPathTransform(PathBuf),
41 InvalidUnicode(OsString),
42}
43
44impl Display for ArgToStringError {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 let s = match self {
47 ArgToStringError::FailedPathTransform(p) => {
48 format!("Path {:?} could not be transformed", p)
49 }
50 ArgToStringError::InvalidUnicode(s) => {
51 format!("String {:?} contained invalid unicode", s)
52 }
53 };
54 write!(f, "{}", s)
55 }
56}
57
58impl Error for ArgToStringError {
59 fn source(&self) -> Option<&(dyn Error + 'static)> {
60 None
61 }
62}
63
64pub type Delimiter = Option<u8>;
65
66#[derive(PartialEq, Eq, Clone, Debug)]
73pub enum Argument<T> {
74 Raw(OsString),
76 UnknownFlag(OsString),
78 Flag(&'static str, T),
80 WithValue(&'static str, T, ArgDisposition),
83}
84
85#[derive(PartialEq, Eq, Clone, Debug)]
87pub enum ArgDisposition {
88 Separated,
90 CanBeConcatenated(Delimiter),
92 CanBeSeparated(Delimiter),
94 Concatenated(Delimiter),
96}
97
98pub enum NormalizedDisposition {
99 Separated,
100 Concatenated,
101}
102
103impl<T: ArgumentValue> Argument<T> {
104 pub fn normalize(self, disposition: NormalizedDisposition) -> Self {
107 match self {
108 Argument::WithValue(s, v, ArgDisposition::CanBeConcatenated(d))
109 | Argument::WithValue(s, v, ArgDisposition::CanBeSeparated(d)) => Argument::WithValue(
110 s,
111 v,
112 match disposition {
113 NormalizedDisposition::Separated => ArgDisposition::Separated,
114 NormalizedDisposition::Concatenated => ArgDisposition::Concatenated(d),
115 },
116 ),
117 a => a,
118 }
119 }
120
121 pub fn to_os_string(&self) -> OsString {
122 match *self {
123 Argument::Raw(ref s) | Argument::UnknownFlag(ref s) => s.clone(),
124 Argument::Flag(ref s, _) | Argument::WithValue(ref s, _, _) => s.into(),
125 }
126 }
127
128 pub fn flag_str(&self) -> Option<&'static str> {
129 match *self {
130 Argument::Flag(s, _) | Argument::WithValue(s, _, _) => Some(s),
131 _ => None,
132 }
133 }
134
135 pub fn get_data(&self) -> Option<&T> {
136 match *self {
137 Argument::Flag(_, ref d) => Some(d),
138 Argument::WithValue(_, ref d, _) => Some(d),
139 _ => None,
140 }
141 }
142
143 pub fn iter_os_strings(&self) -> Iter<'_, T> {
145 Iter {
146 arg: self,
147 emitted: 0,
148 }
149 }
150
151 #[cfg(feature = "dist-client")]
153 pub fn iter_strings<F: FnMut(&Path) -> Option<String>>(
154 &self,
155 path_transformer: F,
156 ) -> IterStrings<'_, T, F> {
157 IterStrings {
158 arg: self,
159 emitted: 0,
160 path_transformer,
161 }
162 }
163}
164
165pub struct Iter<'a, T> {
166 arg: &'a Argument<T>,
167 emitted: usize,
168}
169
170impl<T: ArgumentValue> Iterator for Iter<'_, T> {
171 type Item = OsString;
172
173 fn next(&mut self) -> Option<Self::Item> {
174 let result = match *self.arg {
175 Argument::Raw(ref s) | Argument::UnknownFlag(ref s) => match self.emitted {
176 0 => Some(s.clone()),
177 _ => None,
178 },
179 Argument::Flag(s, _) => match self.emitted {
180 0 => Some(s.into()),
181 _ => None,
182 },
183 Argument::WithValue(s, ref v, ref d) => match (self.emitted, d) {
184 (0, &ArgDisposition::CanBeSeparated(d)) | (0, &ArgDisposition::Concatenated(d)) => {
185 let mut s = OsString::from(s);
186 let v = v.clone().into_arg_os_string();
187 if let Some(d) = d {
188 if !v.is_empty() {
189 s.push(OsString::from(
190 str::from_utf8(&[d]).expect("delimiter should be ascii"),
191 ));
192 }
193 }
194 s.push(v);
195 Some(s)
196 }
197 (0, &ArgDisposition::Separated) | (0, &ArgDisposition::CanBeConcatenated(_)) => {
198 Some(s.into())
199 }
200 (1, &ArgDisposition::Separated) | (1, &ArgDisposition::CanBeConcatenated(_)) => {
201 Some(v.clone().into_arg_os_string())
202 }
203 _ => None,
204 },
205 };
206 if result.is_some() {
207 self.emitted += 1;
208 }
209 result
210 }
211}
212
213#[cfg(feature = "dist-client")]
214pub struct IterStrings<'a, T, F> {
215 arg: &'a Argument<T>,
216 emitted: usize,
217 path_transformer: F,
218}
219
220#[cfg(feature = "dist-client")]
221impl<T: ArgumentValue, F: FnMut(&Path) -> Option<String>> Iterator for IterStrings<'_, T, F> {
222 type Item = ArgToStringResult;
223
224 fn next(&mut self) -> Option<Self::Item> {
225 let result: Option<Self::Item> = match *self.arg {
226 Argument::Raw(ref s) | Argument::UnknownFlag(ref s) => match self.emitted {
227 0 => Some(s.clone().into_arg_string(&mut self.path_transformer)),
228 _ => None,
229 },
230 Argument::Flag(s, _) => match self.emitted {
231 0 => Some(Ok(s.to_owned())),
232 _ => None,
233 },
234 Argument::WithValue(s, ref v, ref d) => match (self.emitted, d) {
235 (0, &ArgDisposition::CanBeSeparated(d)) | (0, &ArgDisposition::Concatenated(d)) => {
236 let mut s = s.to_owned();
237 let v = match v.clone().into_arg_string(&mut self.path_transformer) {
238 Ok(s) => s,
239 Err(e) => return Some(Err(e)),
240 };
241 if let Some(d) = d {
242 if !v.is_empty() {
243 s.push_str(str::from_utf8(&[d]).expect("delimiter should be ascii"));
244 }
245 }
246 s.push_str(&v);
247 Some(Ok(s))
248 }
249 (0, &ArgDisposition::Separated) | (0, &ArgDisposition::CanBeConcatenated(_)) => {
250 Some(Ok(s.to_owned()))
251 }
252 (1, &ArgDisposition::Separated) | (1, &ArgDisposition::CanBeConcatenated(_)) => {
253 Some(v.clone().into_arg_string(&mut self.path_transformer))
254 }
255 _ => None,
256 },
257 };
258 if result.is_some() {
259 self.emitted += 1;
260 }
261 result
262 }
263}
264
265macro_rules! ArgData {
266 { __matchify $var:ident $fn:ident ($( $fnarg:ident )*) ($( $arms:tt )*) } => {
268 match $var {
269 $( $arms )*
270 }
271 };
272 { __matchify $var:ident $fn:ident ($( $fnarg:ident )*) ($( $arms:tt )*) $x:ident, $( $rest:tt )* } => {
274 ArgData!{
275 __matchify $var $fn ($($fnarg)*)
276 ($($arms)* ArgData::$x => ().$fn($( $fnarg )*),)
277 $($rest)*
278 }
279 };
280 { __matchify $var:ident $fn:ident ($( $fnarg:ident )*) ($( $arms:tt )*) $x:ident($y:ty), $( $rest:tt )* } => {
282 ArgData!{
283 __matchify $var $fn ($($fnarg)*)
284 ($($arms)* ArgData::$x(inner) => inner.$fn($( $fnarg )*),)
285 $($rest)*
286 }
287 };
288
289 { __impl $( $tok:tt )+ } => {
290 impl IntoArg for ArgData {
291 fn into_arg_os_string(self) -> OsString {
292 ArgData!{ __matchify self into_arg_os_string () () $($tok)+ }
293 }
294 fn into_arg_string(self, transformer: PathTransformerFn<'_>) -> ArgToStringResult {
295 ArgData!{ __matchify self into_arg_string (transformer) () $($tok)+ }
296 }
297 }
298 };
299
300 { pub $( $tok:tt )+ } => {
302 #[derive(Clone, Debug, PartialEq, Eq)]
303 pub enum ArgData {
304 $($tok)+
305 }
306 ArgData!{ __impl $( $tok )+ }
307 };
308 { $( $tok:tt )+ } => {
309 #[derive(Clone, Debug, PartialEq)]
310 #[allow(clippy::enum_variant_names)]
311 enum ArgData {
312 $($tok)+
313 }
314 ArgData!{ __impl $( $tok )+ }
315 };
316}
317
318pub trait ArgumentValue: IntoArg + Clone + Debug {}
320
321impl<T: IntoArg + Clone + Debug> ArgumentValue for T {}
322
323pub trait FromArg: Sized {
324 fn process(arg: OsString) -> ArgParseResult<Self>;
325}
326
327pub trait IntoArg: Sized {
328 fn into_arg_os_string(self) -> OsString;
329 fn into_arg_string(self, transformer: PathTransformerFn<'_>) -> ArgToStringResult;
330}
331
332impl FromArg for OsString {
333 fn process(arg: OsString) -> ArgParseResult<Self> {
334 Ok(arg)
335 }
336}
337impl FromArg for PathBuf {
338 fn process(arg: OsString) -> ArgParseResult<Self> {
339 Ok(arg.into())
340 }
341}
342impl FromArg for String {
343 fn process(arg: OsString) -> ArgParseResult<Self> {
344 arg.into_string().map_err(ArgParseError::InvalidUnicode)
345 }
346}
347
348impl IntoArg for OsString {
349 fn into_arg_os_string(self) -> OsString {
350 self
351 }
352 fn into_arg_string(self, _transformer: PathTransformerFn<'_>) -> ArgToStringResult {
353 self.into_string().map_err(ArgToStringError::InvalidUnicode)
354 }
355}
356impl IntoArg for PathBuf {
357 fn into_arg_os_string(self) -> OsString {
358 self.into()
359 }
360 fn into_arg_string(self, transformer: PathTransformerFn<'_>) -> ArgToStringResult {
361 transformer(&self).ok_or(ArgToStringError::FailedPathTransform(self))
362 }
363}
364impl IntoArg for String {
365 fn into_arg_os_string(self) -> OsString {
366 self.into()
367 }
368 fn into_arg_string(self, _transformer: PathTransformerFn<'_>) -> ArgToStringResult {
369 Ok(self)
370 }
371}
372impl IntoArg for () {
373 fn into_arg_os_string(self) -> OsString {
374 OsString::new()
375 }
376 fn into_arg_string(self, _transformer: PathTransformerFn<'_>) -> ArgToStringResult {
377 Ok(String::new())
378 }
379}
380
381pub fn split_os_string_arg(val: OsString, split: &str) -> ArgParseResult<(String, Option<String>)> {
382 let val = val.into_string().map_err(ArgParseError::InvalidUnicode)?;
383 let mut split_it = val.splitn(2, split);
384 let s1 = split_it.next().expect("splitn with no values");
385 let maybe_s2 = split_it.next();
386 Ok((s1.to_owned(), maybe_s2.map(|s| s.to_owned())))
387}
388
389#[derive(PartialEq, Eq, Clone, Debug)]
391#[allow(unpredictable_function_pointer_comparisons)]
392pub enum ArgInfo<T> {
393 Flag(&'static str, T),
395 TakeArg(
398 &'static str,
399 fn(OsString) -> ArgParseResult<T>,
400 ArgDisposition,
401 ),
402}
403
404impl<T: ArgumentValue> ArgInfo<T> {
405 fn process<F>(self, arg: &str, get_next_arg: F) -> ArgParseResult<Argument<T>>
410 where
411 F: FnOnce() -> Option<OsString>,
412 {
413 Ok(match self {
414 ArgInfo::Flag(s, variant) => {
415 debug_assert_eq!(s, arg);
416 Argument::Flag(s, variant)
417 }
418 ArgInfo::TakeArg(s, create, ArgDisposition::Separated) => {
419 debug_assert_eq!(s, arg);
420 if let Some(a) = get_next_arg() {
421 Argument::WithValue(s, create(a)?, ArgDisposition::Separated)
422 } else {
423 return Err(ArgParseError::UnexpectedEndOfArgs);
424 }
425 }
426 ArgInfo::TakeArg(s, create, ArgDisposition::Concatenated(d)) => {
427 let mut len = s.len();
428 debug_assert_eq!(&arg[..len], s);
429 if let Some(d) = d {
430 if arg.as_bytes().get(len) == Some(&d) {
431 len += 1;
432 }
433 }
434 Argument::WithValue(
435 s,
436 create(arg[len..].into())?,
437 ArgDisposition::Concatenated(d),
438 )
439 }
440 ArgInfo::TakeArg(s, create, ArgDisposition::CanBeSeparated(d))
441 | ArgInfo::TakeArg(s, create, ArgDisposition::CanBeConcatenated(d)) => {
442 let derived = if arg == s {
443 ArgInfo::TakeArg(s, create, ArgDisposition::Separated)
444 } else {
445 ArgInfo::TakeArg(s, create, ArgDisposition::Concatenated(d))
446 };
447 match derived.process(arg, get_next_arg) {
448 Err(ArgParseError::UnexpectedEndOfArgs) if d.is_none() => {
449 Argument::WithValue(s, create("".into())?, ArgDisposition::Concatenated(d))
450 }
451 Ok(Argument::WithValue(s, v, ArgDisposition::Concatenated(d))) => {
452 Argument::WithValue(s, v, ArgDisposition::CanBeSeparated(d))
453 }
454 Ok(Argument::WithValue(s, v, ArgDisposition::Separated)) => {
455 Argument::WithValue(s, v, ArgDisposition::CanBeConcatenated(d))
456 }
457 a => a?,
458 }
459 }
460 })
461 }
462
463 fn cmp(&self, arg: &str) -> Ordering {
466 match self {
467 &ArgInfo::TakeArg(s, _, ArgDisposition::CanBeSeparated(None))
468 | &ArgInfo::TakeArg(s, _, ArgDisposition::Concatenated(None))
469 | &ArgInfo::TakeArg(s, _, ArgDisposition::CanBeConcatenated(None))
470 if arg.starts_with(s) =>
471 {
472 Ordering::Equal
473 }
474 &ArgInfo::TakeArg(s, _, ArgDisposition::CanBeSeparated(Some(d)))
475 | &ArgInfo::TakeArg(s, _, ArgDisposition::Concatenated(Some(d)))
476 | &ArgInfo::TakeArg(s, _, ArgDisposition::CanBeConcatenated(Some(d)))
477 if arg.len() > s.len() && arg.starts_with(s) =>
478 {
479 arg.as_bytes()[s.len()].cmp(&d)
480 }
481 _ => self.flag_str().cmp(arg),
482 }
483 }
484
485 fn flag_str(&self) -> &'static str {
486 match self {
487 &ArgInfo::Flag(s, _) | &ArgInfo::TakeArg(s, _, _) => s,
488 }
489 }
490}
491
492fn bsearch<K, T, F>(key: K, items: &[T], cmp: F) -> Option<&T>
497where
498 F: Fn(&T, &K) -> Ordering,
499{
500 let mut slice = items;
501 while !slice.is_empty() {
502 let middle = slice.len() / 2;
503 match cmp(&slice[middle], &key) {
504 Ordering::Equal => {
505 let found_after = if slice.len() == 1 {
506 None
507 } else {
508 bsearch(key, &slice[middle + 1..], cmp)
509 };
510 return found_after.or(Some(&slice[middle]));
511 }
512 Ordering::Greater => {
513 slice = &slice[..middle];
514 }
515 Ordering::Less => {
516 slice = &slice[middle + 1..];
517 }
518 }
519 }
520 None
521}
522
523pub trait SearchableArgInfo<T> {
525 fn search(&self, key: &str) -> Option<&ArgInfo<T>>;
526
527 #[cfg(debug_assertions)]
528 fn check(&self) -> bool;
529}
530
531impl<T: ArgumentValue> SearchableArgInfo<T> for &'static [ArgInfo<T>] {
534 fn search(&self, key: &str) -> Option<&ArgInfo<T>> {
535 bsearch(key, self, |i, k| i.cmp(k))
536 }
537
538 #[cfg(debug_assertions)]
539 fn check(&self) -> bool {
540 self.windows(2).all(|w| {
541 let a = w[0].flag_str();
542 let b = w[1].flag_str();
543 assert!(a < b, "{} can't precede {}", a, b);
544 true
545 })
546 }
547}
548
549impl<T: ArgumentValue> SearchableArgInfo<T> for (&'static [ArgInfo<T>], &'static [ArgInfo<T>]) {
552 fn search(&self, key: &str) -> Option<&ArgInfo<T>> {
553 match (self.0.search(key), self.1.search(key)) {
554 (None, None) => None,
555 (Some(a), None) => Some(a),
556 (None, Some(a)) => Some(a),
557 (Some(a), Some(b)) => {
558 if a.flag_str() > b.flag_str() {
559 Some(a)
560 } else {
561 Some(b)
562 }
563 }
564 }
565 }
566
567 #[cfg(debug_assertions)]
568 fn check(&self) -> bool {
569 self.0.check() && self.1.check()
570 }
571}
572
573pub struct ArgsIter<I, T, S>
575where
576 I: Iterator<Item = OsString>,
577 S: SearchableArgInfo<T>,
578{
579 arguments: I,
580 arg_info: S,
581 seen_double_dashes: Option<bool>,
582 phantom: PhantomData<T>,
583}
584
585impl<I, T, S> ArgsIter<I, T, S>
586where
587 I: Iterator<Item = OsString>,
588 T: ArgumentValue,
589 S: SearchableArgInfo<T>,
590{
591 pub fn new(arguments: I, arg_info: S) -> Self {
594 #[cfg(debug_assertions)]
595 debug_assert!(arg_info.check());
596 ArgsIter {
597 arguments,
598 arg_info,
599 seen_double_dashes: None,
600 phantom: PhantomData,
601 }
602 }
603
604 pub fn with_double_dashes(mut self) -> Self {
605 self.seen_double_dashes = Some(false);
606 self
607 }
608}
609
610impl<I, T, S> Iterator for ArgsIter<I, T, S>
611where
612 I: Iterator<Item = OsString>,
613 T: ArgumentValue,
614 S: SearchableArgInfo<T>,
615{
616 type Item = ArgParseResult<Argument<T>>;
617
618 fn next(&mut self) -> Option<Self::Item> {
619 if let Some(arg) = self.arguments.next() {
620 if let Some(seen_double_dashes) = &mut self.seen_double_dashes {
621 if !*seen_double_dashes && arg == "--" {
622 *seen_double_dashes = true;
623 }
624 if *seen_double_dashes {
625 return Some(Ok(Argument::Raw(arg)));
626 }
627 }
628 let s = arg.to_string_lossy();
629 let arguments = &mut self.arguments;
630 Some(match self.arg_info.search(&s[..]) {
631 Some(i) => i.clone().process(&s[..], || arguments.next()),
632 None => Ok(if s.starts_with('-') {
633 Argument::UnknownFlag(arg.clone())
634 } else {
635 Argument::Raw(arg.clone())
636 }),
637 })
638 } else {
639 None
640 }
641 }
642}
643
644macro_rules! flag {
648 ($s:expr, $variant:expr) => {
649 ArgInfo::Flag($s, $variant)
650 };
651}
652
653macro_rules! take_arg {
659 ($s:expr, $vtype:ident, Separated, $variant:expr) => {
660 ArgInfo::TakeArg(
661 $s,
662 |arg: OsString| $vtype::process(arg).map($variant),
663 ArgDisposition::Separated,
664 )
665 };
666 ($s:expr, $vtype:ident, $d:ident, $variant:expr) => {
667 ArgInfo::TakeArg(
668 $s,
669 |arg: OsString| $vtype::process(arg).map($variant),
670 ArgDisposition::$d(None),
671 )
672 };
673 ($s:expr, $vtype:ident, $d:ident($x:expr), $variant:expr) => {
674 ArgInfo::TakeArg(
675 $s,
676 |arg: OsString| $vtype::process(arg).map($variant),
677 ArgDisposition::$d(Some($x)),
678 )
679 };
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685 use itertools::{Diff, diff_with};
686 use std::iter::FromIterator;
687
688 macro_rules! arg {
689 ($name:ident($x:expr)) => {
690 Argument::$name($x.into())
691 };
692
693 ($name:ident($x:expr, $v:ident)) => {
694 Argument::$name($x.into(), $v)
695 };
696 ($name:ident($x:expr, $v:ident($y:expr))) => {
697 Argument::$name($x.into(), $v($y.into()))
698 };
699 ($name:ident($x:expr, $v:ident($y:expr), Separated)) => {
700 Argument::$name($x, $v($y.into()), ArgDisposition::Separated)
701 };
702 ($name:ident($x:expr, $v:ident($y:expr), $d:ident)) => {
703 Argument::$name($x, $v($y.into()), ArgDisposition::$d(None))
704 };
705 ($name:ident($x:expr, $v:ident($y:expr), $d:ident($z:expr))) => {
706 Argument::$name($x, $v($y.into()), ArgDisposition::$d(Some($z as u8)))
707 };
708
709 ($name:ident($x:expr, $v:ident::$w:ident)) => {
710 Argument::$name($x.into(), $v::$w)
711 };
712 ($name:ident($x:expr, $v:ident::$w:ident($y:expr))) => {
713 Argument::$name($x.into(), $v::$w($y.into()))
714 };
715 ($name:ident($x:expr, $v:ident::$w:ident($y:expr), Separated)) => {
716 Argument::$name($x, $v::$w($y.into()), ArgDisposition::Separated)
717 };
718 ($name:ident($x:expr, $v:ident::$w:ident($y:expr), $d:ident)) => {
719 Argument::$name($x, $v::$w($y.into()), ArgDisposition::$d(None))
720 };
721 ($name:ident($x:expr, $v:ident::$w:ident($y:expr), $d:ident($z:expr))) => {
722 Argument::$name($x, $v::$w($y.into()), ArgDisposition::$d(Some($z as u8)))
723 };
724 }
725
726 ArgData! {
727 FooFlag,
728 Foo(OsString),
729 FooPath(PathBuf),
730 }
731
732 use self::ArgData::*;
733
734 #[test]
735 #[allow(clippy::cognitive_complexity)]
736 fn test_arginfo_cmp() {
737 let info = flag!("-foo", FooFlag);
738 assert_eq!(info.cmp("-foo"), Ordering::Equal);
739 assert_eq!(info.cmp("bar"), Ordering::Less);
740 assert_eq!(info.cmp("-bar"), Ordering::Greater);
741 assert_eq!(info.cmp("-qux"), Ordering::Less);
742 assert_eq!(info.cmp("-foobar"), Ordering::Less);
743 assert_eq!(info.cmp("-foo="), Ordering::Less);
744 assert_eq!(info.cmp("-foo=bar"), Ordering::Less);
745
746 let info = take_arg!("-foo", OsString, Separated, Foo);
747 assert_eq!(info.cmp("-foo"), Ordering::Equal);
748 assert_eq!(info.cmp("bar"), Ordering::Less);
749 assert_eq!(info.cmp("-bar"), Ordering::Greater);
750 assert_eq!(info.cmp("-qux"), Ordering::Less);
751 assert_eq!(info.cmp("-foobar"), Ordering::Less);
752 assert_eq!(info.cmp("-foo="), Ordering::Less);
753 assert_eq!(info.cmp("-foo=bar"), Ordering::Less);
754
755 let info = take_arg!("-foo", OsString, Concatenated, Foo);
756 assert_eq!(info.cmp("-foo"), Ordering::Equal);
757 assert_eq!(info.cmp("bar"), Ordering::Less);
758 assert_eq!(info.cmp("-bar"), Ordering::Greater);
759 assert_eq!(info.cmp("-qux"), Ordering::Less);
760 assert_eq!(info.cmp("-foobar"), Ordering::Equal);
761 assert_eq!(info.cmp("-foo="), Ordering::Equal);
762 assert_eq!(info.cmp("-foo=bar"), Ordering::Equal);
763
764 let info = take_arg!("-foo", OsString, Concatenated(b'='), Foo);
765 assert_eq!(info.cmp("-foo"), Ordering::Equal);
766 assert_eq!(info.cmp("bar"), Ordering::Less);
767 assert_eq!(info.cmp("-bar"), Ordering::Greater);
768 assert_eq!(info.cmp("-qux"), Ordering::Less);
769 assert_eq!(info.cmp("-foobar"), Ordering::Greater);
770 assert_eq!(info.cmp("-foo="), Ordering::Equal);
771 assert_eq!(info.cmp("-foo=bar"), Ordering::Equal);
772
773 let info = take_arg!("-foo", OsString, CanBeConcatenated(b'='), Foo);
774 assert_eq!(info.cmp("-foo"), Ordering::Equal);
775 assert_eq!(info.cmp("bar"), Ordering::Less);
776 assert_eq!(info.cmp("-bar"), Ordering::Greater);
777 assert_eq!(info.cmp("-qux"), Ordering::Less);
778 assert_eq!(info.cmp("-foobar"), Ordering::Greater);
779 assert_eq!(info.cmp("-foo="), Ordering::Equal);
780 assert_eq!(info.cmp("-foo=bar"), Ordering::Equal);
781
782 let info = take_arg!("-foo", OsString, CanBeSeparated, Foo);
783 assert_eq!(info.cmp("-foo"), Ordering::Equal);
784 assert_eq!(info.cmp("bar"), Ordering::Less);
785 assert_eq!(info.cmp("-bar"), Ordering::Greater);
786 assert_eq!(info.cmp("-qux"), Ordering::Less);
787 assert_eq!(info.cmp("-foobar"), Ordering::Equal);
788 assert_eq!(info.cmp("-foo="), Ordering::Equal);
789 assert_eq!(info.cmp("-foo=bar"), Ordering::Equal);
790
791 let info = take_arg!("-foo", OsString, CanBeSeparated(b'='), Foo);
792 assert_eq!(info.cmp("-foo"), Ordering::Equal);
793 assert_eq!(info.cmp("bar"), Ordering::Less);
794 assert_eq!(info.cmp("-bar"), Ordering::Greater);
795 assert_eq!(info.cmp("-qux"), Ordering::Less);
796 assert_eq!(info.cmp("-foobar"), Ordering::Greater);
797 assert_eq!(info.cmp("-foo="), Ordering::Equal);
798 assert_eq!(info.cmp("-foo=bar"), Ordering::Equal);
799 }
800
801 #[test]
802 fn test_arginfo_process() {
803 let info = flag!("-foo", FooFlag);
804 assert_eq!(
805 info.process("-foo", || None).unwrap(),
806 arg!(Flag("-foo", FooFlag))
807 );
808
809 let info = take_arg!("-foo", OsString, Separated, Foo);
810 assert_eq!(
811 info.clone().process("-foo", || None).unwrap_err(),
812 ArgParseError::UnexpectedEndOfArgs
813 );
814 assert_eq!(
815 info.process("-foo", || Some("bar".into())).unwrap(),
816 arg!(WithValue("-foo", Foo("bar"), Separated))
817 );
818
819 let info = take_arg!("-foo", OsString, Concatenated, Foo);
820 assert_eq!(
821 info.clone().process("-foo", || None).unwrap(),
822 arg!(WithValue("-foo", Foo(""), Concatenated))
823 );
824 assert_eq!(
825 info.process("-foobar", || None).unwrap(),
826 arg!(WithValue("-foo", Foo("bar"), Concatenated))
827 );
828
829 let info = take_arg!("-foo", OsString, Concatenated(b'='), Foo);
830 assert_eq!(
831 info.clone().process("-foo=", || None).unwrap(),
832 arg!(WithValue("-foo", Foo(""), Concatenated(b'=')))
833 );
834 assert_eq!(
835 info.process("-foo=bar", || None).unwrap(),
836 arg!(WithValue("-foo", Foo("bar"), Concatenated(b'=')))
837 );
838
839 let info = take_arg!("-foo", OsString, CanBeSeparated, Foo);
840 assert_eq!(
841 info.clone().process("-foo", || None).unwrap(),
842 arg!(WithValue("-foo", Foo(""), Concatenated))
843 );
844 assert_eq!(
845 info.clone().process("-foobar", || None).unwrap(),
846 arg!(WithValue("-foo", Foo("bar"), CanBeSeparated))
847 );
848 assert_eq!(
849 info.process("-foo", || Some("bar".into())).unwrap(),
850 arg!(WithValue("-foo", Foo("bar"), CanBeConcatenated))
851 );
852
853 let info = take_arg!("-foo", OsString, CanBeSeparated(b'='), Foo);
854 assert_eq!(
855 info.clone().process("-foo", || None).unwrap_err(),
856 ArgParseError::UnexpectedEndOfArgs
857 );
858 assert_eq!(
859 info.clone().process("-foo=", || None).unwrap(),
860 arg!(WithValue("-foo", Foo(""), CanBeSeparated(b'=')))
861 );
862 assert_eq!(
863 info.clone().process("-foo=bar", || None).unwrap(),
864 arg!(WithValue("-foo", Foo("bar"), CanBeSeparated(b'=')))
865 );
866 assert_eq!(
867 info.process("-foo", || Some("bar".into())).unwrap(),
868 arg!(WithValue("-foo", Foo("bar"), CanBeConcatenated(b'=')))
869 );
870 }
871
872 #[test]
873 fn test_bsearch() {
874 let data = vec![
875 ("bar", 1),
876 ("foo", 2),
877 ("fuga", 3),
878 ("hoge", 4),
879 ("plop", 5),
880 ("qux", 6),
881 ("zorglub", 7),
882 ];
883 for item in &data {
884 assert_eq!(bsearch(item.0, &data, |i, k| i.0.cmp(k)), Some(item));
885 }
886
887 let data = &data[..6];
889 for item in data {
890 assert_eq!(bsearch(item.0, data, |i, k| i.0.cmp(k)), Some(item));
891 }
892
893 let data = vec![
895 ("a", 1),
896 ("ab", 2),
897 ("abc", 3),
898 ("abd", 4),
899 ("abe", 5),
900 ("abef", 6),
901 ("abefg", 7),
902 ];
903 for item in &data {
904 assert_eq!(
905 bsearch(item.0, &data, |i, k| if k.starts_with(i.0) {
906 Ordering::Equal
907 } else {
908 i.0.cmp(k)
909 }),
910 Some(item)
911 );
912 }
913
914 let data = &data[..6];
916 for item in data {
917 assert_eq!(
918 bsearch(item.0, data, |i, k| if k.starts_with(i.0) {
919 Ordering::Equal
920 } else {
921 i.0.cmp(k)
922 }),
923 Some(item)
924 );
925 }
926 }
927
928 #[test]
929 fn test_multi_search() {
930 static ARGS: [ArgInfo<ArgData>; 1] = [take_arg!("-include", OsString, Concatenated, Foo)];
931 static ARGS2: [ArgInfo<ArgData>; 1] =
932 [take_arg!("-include-pch", OsString, Concatenated, Foo)];
933 static ARGS3: [ArgInfo<ArgData>; 1] =
934 [take_arg!("-include", PathBuf, Concatenated, FooPath)];
935
936 assert_eq!((&ARGS[..], &ARGS2[..]).search("-include"), Some(&ARGS[0]));
937 assert_eq!(
938 (&ARGS[..], &ARGS2[..]).search("-include-pch"),
939 Some(&ARGS2[0])
940 );
941 assert_eq!((&ARGS2[..], &ARGS[..]).search("-include"), Some(&ARGS[0]));
942 assert_eq!(
943 (&ARGS2[..], &ARGS[..]).search("-include-pch"),
944 Some(&ARGS2[0])
945 );
946 assert_eq!((&ARGS[..], &ARGS3[..]).search("-include"), Some(&ARGS3[0]));
947 }
948
949 #[test]
950 fn test_argsiter() {
951 ArgData! {
952 Bar,
953 Foo(OsString),
954 Fuga,
955 Hoge(PathBuf),
956 Plop,
957 Qux(OsString),
958 Zorglub,
959 }
960
961 static ARGS: [ArgInfo<ArgData>; 7] = [
964 flag!("-bar", ArgData::Bar),
965 take_arg!("-foo", OsString, Separated, ArgData::Foo),
966 flag!("-fuga", ArgData::Fuga),
967 take_arg!("-hoge", PathBuf, Concatenated, ArgData::Hoge),
968 flag!("-plop", ArgData::Plop),
969 take_arg!("-qux", OsString, CanBeSeparated(b'='), ArgData::Qux),
970 flag!("-zorglub", ArgData::Zorglub),
971 ];
972
973 let args = [
974 "-nomatch",
975 "-foo",
976 "value",
977 "-hoge",
978 "value", "-hoge=value", "-hogevalue",
981 "-zorglub",
982 "-qux",
983 "value",
984 "-plop",
985 "-quxbar", "-qux=value",
987 "--",
988 "non_flag",
989 "-flag-after-double-dashes",
990 ];
991 let iter = ArgsIter::new(args.iter().map(OsString::from), &ARGS[..]).with_double_dashes();
992 let expected = vec![
993 arg!(UnknownFlag("-nomatch")),
994 arg!(WithValue("-foo", ArgData::Foo("value"), Separated)),
995 arg!(WithValue("-hoge", ArgData::Hoge(""), Concatenated)),
996 arg!(Raw("value")),
997 arg!(WithValue("-hoge", ArgData::Hoge("=value"), Concatenated)),
998 arg!(WithValue("-hoge", ArgData::Hoge("value"), Concatenated)),
999 arg!(Flag("-zorglub", ArgData::Zorglub)),
1000 arg!(WithValue(
1001 "-qux",
1002 ArgData::Qux("value"),
1003 CanBeConcatenated(b'=')
1004 )),
1005 arg!(Flag("-plop", ArgData::Plop)),
1006 arg!(UnknownFlag("-quxbar")),
1007 arg!(WithValue(
1008 "-qux",
1009 ArgData::Qux("value"),
1010 CanBeSeparated(b'=')
1011 )),
1012 arg!(Raw("--")),
1013 arg!(Raw("non_flag")),
1014 arg!(Raw("-flag-after-double-dashes")),
1015 ];
1016 match diff_with(iter, expected, |a, b| {
1017 assert_eq!(a.as_ref().unwrap(), b);
1018 true
1019 }) {
1020 None => {}
1021 Some(Diff::FirstMismatch(_, _, _)) => unreachable!(),
1022 Some(Diff::Shorter(_, i)) => {
1023 assert_eq!(i.map(|a| a.unwrap()).collect::<Vec<_>>(), vec![])
1024 }
1025 Some(Diff::Longer(_, i)) => {
1026 assert_eq!(Vec::<Argument<ArgData>>::new(), i.collect::<Vec<_>>())
1027 }
1028 }
1029 }
1030
1031 #[allow(clippy::from_iter_instead_of_collect)]
1033 #[test]
1034 fn test_argument_into_iter() {
1035 let raw: Argument<ArgData> = arg!(Raw("value"));
1037 let unknown: Argument<ArgData> = arg!(UnknownFlag("-foo"));
1038 assert_eq!(Vec::from_iter(raw.iter_os_strings()), ovec!["value"]);
1039 assert_eq!(Vec::from_iter(unknown.iter_os_strings()), ovec!["-foo"]);
1040 assert_eq!(
1041 Vec::from_iter(arg!(Flag("-foo", FooFlag)).iter_os_strings()),
1042 ovec!["-foo"]
1043 );
1044
1045 let arg = arg!(WithValue("-foo", Foo("bar"), Concatenated));
1046 assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foobar"]);
1047
1048 let arg = arg!(WithValue("-foo", Foo("bar"), Concatenated(b'=')));
1049 assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo=bar"]);
1050
1051 let arg = arg!(WithValue("-foo", Foo("bar"), CanBeSeparated));
1052 assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foobar"]);
1053
1054 let arg = arg!(WithValue("-foo", Foo("bar"), CanBeSeparated(b'=')));
1055 assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo=bar"]);
1056
1057 let arg = arg!(WithValue("-foo", Foo("bar"), CanBeConcatenated));
1058 assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo", "bar"]);
1059
1060 let arg = arg!(WithValue("-foo", Foo("bar"), CanBeConcatenated(b'=')));
1061 assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo", "bar"]);
1062
1063 let arg = arg!(WithValue("-foo", Foo("bar"), Separated));
1064 assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo", "bar"]);
1065 }
1066
1067 #[test]
1068 fn test_arginfo_process_take_concat_arg_delim_doesnt_crash() {
1069 let _ = take_arg!("-foo", OsString, Concatenated(b'='), Foo).process("-foo", || None);
1070 }
1071
1072 #[cfg(debug_assertions)]
1073 mod assert_tests {
1074 use super::*;
1075
1076 #[test]
1077 #[should_panic]
1078 fn test_arginfo_process_flag() {
1079 flag!("-foo", FooFlag).process("-bar", || None).unwrap();
1080 }
1081
1082 #[test]
1083 #[should_panic]
1084 fn test_arginfo_process_take_arg() {
1085 take_arg!("-foo", OsString, Separated, Foo)
1086 .process("-bar", || None)
1087 .unwrap();
1088 }
1089
1090 #[test]
1091 #[should_panic]
1092 fn test_arginfo_process_take_concat_arg() {
1093 take_arg!("-foo", OsString, Concatenated, Foo)
1094 .process("-bar", || None)
1095 .unwrap();
1096 }
1097
1098 #[test]
1099 #[should_panic]
1100 fn test_arginfo_process_take_concat_arg_delim() {
1101 take_arg!("-foo", OsString, Concatenated(b'='), Foo)
1102 .process("-bar", || None)
1103 .unwrap();
1104 }
1105
1106 #[test]
1107 #[should_panic]
1108 fn test_arginfo_process_take_maybe_concat_arg() {
1109 take_arg!("-foo", OsString, CanBeSeparated, Foo)
1110 .process("-bar", || None)
1111 .unwrap();
1112 }
1113
1114 #[test]
1115 #[should_panic]
1116 fn test_arginfo_process_take_maybe_concat_arg_delim() {
1117 take_arg!("-foo", OsString, CanBeSeparated(b'='), Foo)
1118 .process("-bar", || None)
1119 .unwrap();
1120 }
1121
1122 #[test]
1123 #[should_panic]
1124 fn test_args_iter_unsorted() {
1125 static ARGS: [ArgInfo<ArgData>; 2] = [flag!("-foo", FooFlag), flag!("-bar", FooFlag)];
1126 ArgsIter::new(Vec::<OsString>::new().into_iter(), &ARGS[..]);
1127 }
1128
1129 #[test]
1130 #[should_panic]
1131 fn test_args_iter_unsorted_2() {
1132 static ARGS: [ArgInfo<ArgData>; 2] = [flag!("-foo", FooFlag), flag!("-foo", FooFlag)];
1133 ArgsIter::new(Vec::<OsString>::new().into_iter(), &ARGS[..]);
1134 }
1135
1136 #[test]
1137 fn test_args_iter_no_conflict() {
1138 static ARGS: [ArgInfo<ArgData>; 2] = [flag!("-foo", FooFlag), flag!("-fooz", FooFlag)];
1139 ArgsIter::new(Vec::<OsString>::new().into_iter(), &ARGS[..]);
1140 }
1141 }
1142}