Skip to main content

sccache/compiler/
args.rs

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/// Representation of a parsed argument
67/// The type parameter T contains the parsed information for this argument,
68/// for use during argument handling (typically an enum to allow switching
69/// on the different kinds of argument). `Flag`s may contain a simple
70/// variant which influences how to do caching, whereas `WithValue`s could
71/// be a struct variant with parsed data from the value.
72#[derive(PartialEq, Eq, Clone, Debug)]
73pub enum Argument<T> {
74    /// Unknown non-flag argument ; e.g. "foo"
75    Raw(OsString),
76    /// Unknown flag argument ; e.g. "-foo"
77    UnknownFlag(OsString),
78    /// Known flag argument ; e.g. "-bar"
79    Flag(&'static str, T),
80    /// Known argument with a value ; e.g. "-qux bar", where the way the
81    /// value is passed is described by the ArgDisposition type.
82    WithValue(&'static str, T, ArgDisposition),
83}
84
85/// How a value is passed to an argument with a value.
86#[derive(PartialEq, Eq, Clone, Debug)]
87pub enum ArgDisposition {
88    /// As "-arg value"
89    Separated,
90    /// As "-arg value", but "-arg<delimiter>value" would be valid too
91    CanBeConcatenated(Delimiter),
92    /// As "-arg<delimiter>value", but "-arg value" would be valid too
93    CanBeSeparated(Delimiter),
94    /// As "-arg<delimiter>value"
95    Concatenated(Delimiter),
96}
97
98pub enum NormalizedDisposition {
99    Separated,
100    Concatenated,
101}
102
103impl<T: ArgumentValue> Argument<T> {
104    /// For arguments that allow both a concatenated or separated disposition,
105    /// normalize a parsed argument to a preferred disposition.
106    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    /// Transforms a parsed argument into an iterator.
144    pub fn iter_os_strings(&self) -> Iter<'_, T> {
145        Iter {
146            arg: self,
147            emitted: 0,
148        }
149    }
150
151    /// Transforms a parsed argument into an iterator over strings, with transformed paths.
152    #[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    // Collected all the arms, time to create the match
267    { __matchify $var:ident $fn:ident ($( $fnarg:ident )*) ($( $arms:tt )*) } => {
268        match $var {
269            $( $arms )*
270        }
271    };
272    // Unit variant
273    { __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    // Tuple variant
281    { __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    // PartialEq necessary for tests
301    { 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
318// The value associated with a parsed argument
319pub 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/// The description of how an argument may be parsed
390#[derive(PartialEq, Eq, Clone, Debug)]
391#[allow(unpredictable_function_pointer_comparisons)]
392pub enum ArgInfo<T> {
393    /// An simple flag argument, of the form "-foo"
394    Flag(&'static str, T),
395    /// An argument with a value ; e.g. "-qux bar", where the way the
396    /// value is passed is described by the ArgDisposition type.
397    TakeArg(
398        &'static str,
399        fn(OsString) -> ArgParseResult<T>,
400        ArgDisposition,
401    ),
402}
403
404impl<T: ArgumentValue> ArgInfo<T> {
405    /// Transform an argument description into a parsed Argument, given a
406    /// string. For arguments with a value, where the value is separate, the
407    /// `get_next_arg` function returns the next argument, in raw `OsString`
408    /// form.
409    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    /// Returns whether the given string matches the argument description, and if not,
464    /// how it differs.
465    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
492/// Binary search for a `key` in a sorted array of items, given a comparison
493/// function. This implementation is tweaked to handle the case where the
494/// comparison function does prefix matching, where multiple items in the array
495/// might match, but the last match is the one actually matching.
496fn 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
523/// Trait for generically search over a "set" of ArgInfos.
524pub trait SearchableArgInfo<T> {
525    fn search(&self, key: &str) -> Option<&ArgInfo<T>>;
526
527    #[cfg(debug_assertions)]
528    fn check(&self) -> bool;
529}
530
531/// Allow to search over a sorted array of ArgInfo items associated with extra
532/// data.
533impl<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
549/// Allow to search over a couple of arrays of ArgInfo, where the second
550/// complements or overrides the first one.
551impl<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
573/// An `Iterator` for parsed arguments
574pub 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    /// Create an `Iterator` for parsed arguments, given an iterator of raw
592    /// `OsString` arguments, and argument descriptions.
593    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
644/// Helper macro used to define ArgInfo::Flag's.
645/// Variant is an enum variant, e.g. enum ArgType { Variant }
646///     flag!("-foo", Variant)
647macro_rules! flag {
648    ($s:expr, $variant:expr) => {
649        ArgInfo::Flag($s, $variant)
650    };
651}
652
653/// Helper macro used to define ArgInfo::TakeArg's.
654/// Variant is an enum variant, e.g. enum ArgType { Variant(OsString) }
655///     take_arg!("-foo", OsString, Separated, Variant)
656///     take_arg!("-foo", OsString, Concatenated, Variant)
657///     take_arg!("-foo", OsString, Concatenated(b'='), Variant)
658macro_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        // Try again with an even number of items
888        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        // Once more, with prefix matches
894        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        // Try again with an even number of items
915        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        // Need to explicitly refer to enum because `use` doesn't work if it's in a module
962        // https://internals.rust-lang.org/t/pre-rfc-support-use-enum-for-function-local-enums/3853/13
963        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 doesn't take a separate value
979            "-hoge=value", // = is not recognized as a separator
980            "-hogevalue",
981            "-zorglub",
982            "-qux",
983            "value",
984            "-plop",
985            "-quxbar", // -quxbar is not -qux with a value of bar
986            "-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    // https://github.com/rust-lang/rust-clippy/issues/6550
1032    #[allow(clippy::from_iter_instead_of_collect)]
1033    #[test]
1034    fn test_argument_into_iter() {
1035        // Needs type annotation or ascription
1036        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}