pico_args/
lib.rs

1/*!
2An ultra simple CLI arguments parser.
3
4If you think that this library doesn't support some feature, it's probably intentional.
5
6- No help generation
7- Only flags, options, free arguments and subcommands are supported
8- No combined flags (like `-vvv` or `-abc`)
9- Options can be separated by a space, `=` or nothing. See build features
10- Arguments can be in any order
11- Non UTF-8 arguments are supported
12
13## Build features
14
15- `eq-separator`
16
17  Allows parsing arguments separated by `=`<br/>
18  This feature adds about 1KiB to the resulting binary
19
20- `short-space-opt`
21
22  Makes the space between short keys and their values optional (e.g. `-w10`)<br/>
23  If `eq-separator` is enabled, then it takes precedence and the '=' is not included.<br/>
24  If `eq-separator` is disabled, then `-K=value` gives an error instead of returning `"=value"`.<br/>
25  The optional space is only applicable for short keys because `--keyvalue` would be ambiguous
26
27- `combined-flags`
28  Allows combination of flags, e.g. `-abc` instead of `-a -b -c`.<br/>
29  If `short-space-opt` or `eq-separator` are enabled, you must parse flags after values,
30  to prevent ambiguities.
31*/
32
33#![forbid(unsafe_code)]
34#![warn(missing_docs)]
35
36use std::ffi::{OsString, OsStr};
37use std::fmt::{self, Display};
38use std::str::FromStr;
39
40
41/// A list of possible errors.
42#[derive(Clone, Debug)]
43pub enum Error {
44    /// Arguments must be a valid UTF-8 strings.
45    NonUtf8Argument,
46
47    /// A missing free-standing argument.
48    MissingArgument,
49
50    /// A missing option.
51    MissingOption(Keys),
52
53    /// An option without a value.
54    OptionWithoutAValue(&'static str),
55
56    /// Failed to parse a UTF-8 free-standing argument.
57    #[allow(missing_docs)]
58    Utf8ArgumentParsingFailed { value: String, cause: String },
59
60    /// Failed to parse a raw free-standing argument.
61    #[allow(missing_docs)]
62    ArgumentParsingFailed { cause: String },
63}
64
65impl Display for Error {
66    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67        match self {
68            Error::NonUtf8Argument => {
69                write!(f, "argument is not a UTF-8 string")
70            }
71            Error::MissingArgument => {
72                write!(f, "free-standing argument is missing")
73            }
74            Error::MissingOption(key) => {
75                if key.second().is_empty() {
76                    write!(f, "the '{}' option must be set", key.first())
77                } else {
78                    write!(f, "the '{}/{}' option must be set", key.first(), key.second())
79                }
80            }
81            Error::OptionWithoutAValue(key) => {
82                write!(f, "the '{}' option doesn't have an associated value", key)
83            }
84            Error::Utf8ArgumentParsingFailed { value, cause } => {
85                write!(f, "failed to parse '{}': {}", value, cause)
86            }
87            Error::ArgumentParsingFailed { cause } => {
88                write!(f, "failed to parse a binary argument: {}", cause)
89            }
90        }
91    }
92}
93
94impl std::error::Error for Error {}
95
96
97#[derive(Clone, Copy, PartialEq)]
98enum PairKind {
99    #[cfg(any(feature = "eq-separator", feature = "short-space-opt"))]
100    SingleArgument,
101    TwoArguments,
102}
103
104
105/// An arguments parser.
106#[derive(Clone, Debug)]
107pub struct Arguments(Vec<OsString>);
108
109impl Arguments {
110    /// Creates a parser from a vector of arguments.
111    ///
112    /// The executable path **must** be removed.
113    ///
114    /// This can be used for supporting `--` arguments to forward to another program.
115    /// See `examples/dash_dash.rs` for an example.
116    pub fn from_vec(args: Vec<OsString>) -> Self {
117        Arguments(args)
118    }
119
120    /// Creates a parser from [`env::args_os`].
121    ///
122    /// The executable path will be removed.
123    ///
124    /// [`env::args_os`]: https://doc.rust-lang.org/stable/std/env/fn.args_os.html
125    pub fn from_env() -> Self {
126        let mut args: Vec<_> = std::env::args_os().collect();
127        args.remove(0);
128        Arguments(args)
129    }
130
131    /// Parses the name of the subcommand, that is, the first positional argument.
132    ///
133    /// Returns `None` when subcommand starts with `-` or when there are no arguments left.
134    ///
135    /// # Errors
136    ///
137    /// - When arguments is not a UTF-8 string.
138    pub fn subcommand(&mut self) -> Result<Option<String>, Error> {
139        if self.0.is_empty() {
140            return Ok(None);
141        }
142
143        if let Some(s) = self.0[0].to_str() {
144            if s.starts_with('-') {
145                return Ok(None);
146            }
147        }
148
149        self.0.remove(0)
150            .into_string()
151            .map_err(|_| Error::NonUtf8Argument)
152            .map(Some)
153    }
154
155    /// Checks that arguments contain a specified flag.
156    ///
157    /// Searches through all arguments, not only the first/next one.
158    ///
159    /// Calling this method "consumes" the flag: if a flag is present `n`
160    /// times then the first `n` calls to `contains` for that flag will
161    /// return `true`, and subsequent calls will return `false`.
162    ///
163    /// When the "combined-flags" feature is used, repeated letters count
164    /// as repeated flags: `-vvv` is treated the same as `-v -v -v`.
165    pub fn contains<A: Into<Keys>>(&mut self, keys: A) -> bool {
166        self.contains_impl(keys.into())
167    }
168
169    #[inline(never)]
170    fn contains_impl(&mut self, keys: Keys) -> bool {
171        if let Some((idx, _)) = self.index_of(keys) {
172            self.0.remove(idx);
173            true
174        } else {
175            #[cfg(feature = "combined-flags")]
176            // Combined flags only work if the short flag is a single character
177            {
178                if keys.first().len() == 2 {
179                    let short_flag = &keys.first()[1..2];
180                    for (n, item) in self.0.iter().enumerate() {
181                        if let Some(s) = item.to_str() {
182                            if s.starts_with('-') && !s.starts_with("--") && s.contains(short_flag) {
183                                if s.len() == 2 {
184                                    // last flag
185                                    self.0.remove(n);
186                                } else {
187                                    self.0[n] = s.replacen(short_flag, "", 1).into();
188                                }
189                                return true;
190                            }
191                        }
192                    }
193                }
194            }
195            false
196        }
197    }
198
199    /// Parses a key-value pair using `FromStr` trait.
200    ///
201    /// This is a shorthand for `value_from_fn("--key", FromStr::from_str)`
202    pub fn value_from_str<A, T>(&mut self, keys: A) -> Result<T, Error>
203    where
204        A: Into<Keys>,
205        T: FromStr,
206        <T as FromStr>::Err: Display,
207    {
208        self.value_from_fn(keys, FromStr::from_str)
209    }
210
211    /// Parses a key-value pair using a specified function.
212    ///
213    /// Searches through all argument, not only the first/next one.
214    ///
215    /// When a key-value pair is separated by a space, the algorithm
216    /// will threat the next argument after the key as a value,
217    /// even if it has a `-/--` prefix.
218    /// So a key-value pair like `--key --value` is not an error.
219    ///
220    /// Must be used only once for each option.
221    ///
222    /// # Errors
223    ///
224    /// - When option is not present.
225    /// - When key or value is not a UTF-8 string. Use [`value_from_os_str`] instead.
226    /// - When value parsing failed.
227    /// - When key-value pair is separated not by space or `=`.
228    ///
229    /// [`value_from_os_str`]: struct.Arguments.html#method.value_from_os_str
230    pub fn value_from_fn<A: Into<Keys>, T, E: Display>(
231        &mut self,
232        keys: A,
233        f: fn(&str) -> Result<T, E>,
234    ) -> Result<T, Error> {
235        let keys = keys.into();
236        match self.opt_value_from_fn(keys, f) {
237            Ok(Some(v)) => Ok(v),
238            Ok(None) => Err(Error::MissingOption(keys)),
239            Err(e) => Err(e),
240        }
241    }
242
243    /// Parses an optional key-value pair using `FromStr` trait.
244    ///
245    /// This is a shorthand for `opt_value_from_fn("--key", FromStr::from_str)`
246    pub fn opt_value_from_str<A, T>(&mut self, keys: A) -> Result<Option<T>, Error>
247    where
248        A: Into<Keys>,
249        T: FromStr,
250        <T as FromStr>::Err: Display,
251    {
252        self.opt_value_from_fn(keys, FromStr::from_str)
253    }
254
255    /// Parses an optional key-value pair using a specified function.
256    ///
257    /// The same as [`value_from_fn`], but returns `Ok(None)` when option is not present.
258    ///
259    /// [`value_from_fn`]: struct.Arguments.html#method.value_from_fn
260    pub fn opt_value_from_fn<A: Into<Keys>, T, E: Display>(
261        &mut self,
262        keys: A,
263        f: fn(&str) -> Result<T, E>,
264    ) -> Result<Option<T>, Error> {
265        self.opt_value_from_fn_impl(keys.into(), f)
266    }
267
268    #[inline(never)]
269    fn opt_value_from_fn_impl<T, E: Display>(
270        &mut self,
271        keys: Keys,
272        f: fn(&str) -> Result<T, E>,
273    ) -> Result<Option<T>, Error> {
274        match self.find_value(keys)? {
275            Some((value, kind, idx)) => {
276                match f(value) {
277                    Ok(value) => {
278                        // Remove only when all checks are passed.
279                        self.0.remove(idx);
280                        if kind == PairKind::TwoArguments {
281                            self.0.remove(idx);
282                        }
283
284                        Ok(Some(value))
285                    }
286                    Err(e) => {
287                        Err(Error::Utf8ArgumentParsingFailed {
288                            value: value.to_string(),
289                            cause: error_to_string(e),
290                        })
291                    }
292                }
293            }
294            None => Ok(None),
295        }
296    }
297
298    // The whole logic must be type-independent to prevent monomorphization.
299    #[cfg(any(feature = "eq-separator", feature = "short-space-opt"))]
300    #[inline(never)]
301    fn find_value(
302        &mut self,
303        keys: Keys,
304    ) -> Result<Option<(&str, PairKind, usize)>, Error> {
305        if let Some((idx, key)) = self.index_of(keys) {
306            // Parse a `--key value` pair.
307
308            let value = match self.0.get(idx + 1) {
309                Some(v) => v,
310                None => return Err(Error::OptionWithoutAValue(key)),
311            };
312
313            let value = os_to_str(value)?;
314            Ok(Some((value, PairKind::TwoArguments, idx)))
315        } else if let Some((idx, key)) = self.index_of2(keys) {
316            // Parse a `--key=value` or `-Kvalue` pair.
317
318            let value = &self.0[idx];
319
320            // Only UTF-8 strings are supported in this method.
321            let value = value.to_str().ok_or_else(|| Error::NonUtf8Argument)?;
322
323            let mut value_range = key.len()..value.len();
324
325            if value.as_bytes().get(value_range.start) == Some(&b'=') {
326                #[cfg(feature = "eq-separator")]
327                {
328                    value_range.start += 1;
329                }
330                #[cfg(not(feature = "eq-separator"))]
331                return Err(Error::OptionWithoutAValue(key));
332            } else {
333                // Key must be followed by `=` if not `short-space-opt`
334                #[cfg(not(feature = "short-space-opt"))]
335                return Err(Error::OptionWithoutAValue(key));
336            }
337
338            // Check for quoted value.
339            if let Some(c) = value.as_bytes().get(value_range.start).cloned() {
340                if c == b'"' || c == b'\'' {
341                    value_range.start += 1;
342
343                    // A closing quote must be the same as an opening one.
344                    if ends_with(&value[value_range.start..], c) {
345                        value_range.end -= 1;
346                    } else {
347                        return Err(Error::OptionWithoutAValue(key));
348                    }
349                }
350            }
351
352            // Check length, otherwise String::drain will panic.
353            if value_range.end - value_range.start == 0 {
354                return Err(Error::OptionWithoutAValue(key));
355            }
356
357            // Extract `value` from `--key="value"`.
358            let value = &value[value_range];
359
360            if value.is_empty() {
361                return Err(Error::OptionWithoutAValue(key));
362            }
363
364            Ok(Some((value, PairKind::SingleArgument, idx)))
365        } else {
366            Ok(None)
367        }
368    }
369
370    // The whole logic must be type-independent to prevent monomorphization.
371    #[cfg(not(any(feature = "eq-separator", feature = "short-space-opt")))]
372    #[inline(never)]
373    fn find_value(
374        &mut self,
375        keys: Keys,
376    ) -> Result<Option<(&str, PairKind, usize)>, Error> {
377        if let Some((idx, key)) = self.index_of(keys) {
378            // Parse a `--key value` pair.
379
380            let value = match self.0.get(idx + 1) {
381                Some(v) => v,
382                None => return Err(Error::OptionWithoutAValue(key)),
383            };
384
385            let value = os_to_str(value)?;
386            Ok(Some((value, PairKind::TwoArguments, idx)))
387        } else {
388            Ok(None)
389        }
390    }
391
392    /// Parses multiple key-value pairs into the `Vec` using `FromStr` trait.
393    ///
394    /// This is a shorthand for `values_from_fn("--key", FromStr::from_str)`
395    pub fn values_from_str<A, T>(&mut self, keys: A) -> Result<Vec<T>, Error>
396        where
397            A: Into<Keys>,
398            T: FromStr,
399            <T as FromStr>::Err: Display,
400    {
401        self.values_from_fn(keys, FromStr::from_str)
402    }
403
404    /// Parses multiple key-value pairs into the `Vec` using a specified function.
405    ///
406    /// This functions can be used to parse arguments like:<br>
407    /// `--file /path1 --file /path2 --file /path3`<br>
408    /// But not `--file /path1 /path2 /path3`.
409    ///
410    /// Arguments can also be separated: `--file /path1 --some-flag --file /path2`
411    ///
412    /// This method simply executes [`opt_value_from_fn`] multiple times.
413    ///
414    /// An empty `Vec` is not an error.
415    ///
416    /// [`opt_value_from_fn`]: struct.Arguments.html#method.opt_value_from_fn
417    pub fn values_from_fn<A: Into<Keys>, T, E: Display>(
418        &mut self,
419        keys: A,
420        f: fn(&str) -> Result<T, E>,
421    ) -> Result<Vec<T>, Error> {
422        let keys = keys.into();
423
424        let mut values = Vec::new();
425        loop {
426            match self.opt_value_from_fn(keys, f) {
427                Ok(Some(v)) => values.push(v),
428                Ok(None) => break,
429                Err(e) => return Err(e),
430            }
431        }
432
433        Ok(values)
434    }
435
436    /// Parses a key-value pair using a specified function.
437    ///
438    /// Unlike [`value_from_fn`], parses `&OsStr` and not `&str`.
439    ///
440    /// Must be used only once for each option.
441    ///
442    /// # Errors
443    ///
444    /// - When option is not present.
445    /// - When value parsing failed.
446    /// - When key-value pair is separated not by space.
447    ///   Only [`value_from_fn`] supports `=` separator.
448    ///
449    /// [`value_from_fn`]: struct.Arguments.html#method.value_from_fn
450    pub fn value_from_os_str<A: Into<Keys>, T, E: Display>(
451        &mut self,
452        keys: A,
453        f: fn(&OsStr) -> Result<T, E>,
454    ) -> Result<T, Error> {
455        let keys = keys.into();
456        match self.opt_value_from_os_str(keys, f) {
457            Ok(Some(v)) => Ok(v),
458            Ok(None) => Err(Error::MissingOption(keys)),
459            Err(e) => Err(e),
460        }
461    }
462
463    /// Parses an optional key-value pair using a specified function.
464    ///
465    /// The same as [`value_from_os_str`], but returns `Ok(None)` when option is not present.
466    ///
467    /// [`value_from_os_str`]: struct.Arguments.html#method.value_from_os_str
468    pub fn opt_value_from_os_str<A: Into<Keys>, T, E: Display>(
469        &mut self,
470        keys: A,
471        f: fn(&OsStr) -> Result<T, E>,
472    ) -> Result<Option<T>, Error> {
473        self.opt_value_from_os_str_impl(keys.into(), f)
474    }
475
476    #[inline(never)]
477    fn opt_value_from_os_str_impl<T, E: Display>(
478        &mut self,
479        keys: Keys,
480        f: fn(&OsStr) -> Result<T, E>,
481    ) -> Result<Option<T>, Error> {
482        if let Some((idx, key)) = self.index_of(keys) {
483            // Parse a `--key value` pair.
484
485            let value = match self.0.get(idx + 1) {
486                Some(v) => v,
487                None => return Err(Error::OptionWithoutAValue(key)),
488            };
489
490            match f(value) {
491                Ok(value) => {
492                    // Remove only when all checks are passed.
493                    self.0.remove(idx);
494                    self.0.remove(idx);
495                    Ok(Some(value))
496                }
497                Err(e) => {
498                    Err(Error::ArgumentParsingFailed { cause: error_to_string(e) })
499                }
500            }
501        } else {
502            Ok(None)
503        }
504    }
505
506    /// Parses multiple key-value pairs into the `Vec` using a specified function.
507    ///
508    /// This method simply executes [`opt_value_from_os_str`] multiple times.
509    ///
510    /// Unlike [`values_from_fn`], parses `&OsStr` and not `&str`.
511    ///
512    /// An empty `Vec` is not an error.
513    ///
514    /// [`opt_value_from_os_str`]: struct.Arguments.html#method.opt_value_from_os_str
515    /// [`values_from_fn`]: struct.Arguments.html#method.values_from_fn
516    pub fn values_from_os_str<A: Into<Keys>, T, E: Display>(
517        &mut self,
518        keys: A,
519        f: fn(&OsStr) -> Result<T, E>,
520    ) -> Result<Vec<T>, Error> {
521        let keys = keys.into();
522        let mut values = Vec::new();
523        loop {
524            match self.opt_value_from_os_str(keys, f) {
525                Ok(Some(v)) => values.push(v),
526                Ok(None) => break,
527                Err(e) => return Err(e),
528            }
529        }
530
531        Ok(values)
532    }
533
534    #[inline(never)]
535    fn index_of(&self, keys: Keys) -> Option<(usize, &'static str)> {
536        // Do not unroll loop to save space, because it creates a bigger file.
537        // Which is strange, since `index_of2` actually benefits from it.
538
539        for key in &keys.0 {
540            if !key.is_empty() {
541                if let Some(i) = self.0.iter().position(|v| v == key) {
542                    return Some((i, key));
543                }
544            }
545        }
546
547        None
548    }
549
550    #[cfg(any(feature = "eq-separator", feature = "short-space-opt"))]
551    #[inline(never)]
552    fn index_of2(&self, keys: Keys) -> Option<(usize, &'static str)> {
553        // Loop unroll to save space.
554
555        if !keys.first().is_empty() {
556            if let Some(i) = self.0.iter().position(|v| index_predicate(v, keys.first())) {
557                return Some((i, keys.first()));
558            }
559        }
560
561        if !keys.second().is_empty() {
562            if let Some(i) = self.0.iter().position(|v| index_predicate(v, keys.second())) {
563                return Some((i, keys.second()));
564            }
565        }
566
567        None
568    }
569
570    /// Parses a free-standing argument using `FromStr` trait.
571    ///
572    /// This is a shorthand for `free_from_fn(FromStr::from_str)`
573    pub fn free_from_str<T>(&mut self) -> Result<T, Error>
574    where
575        T: FromStr,
576        <T as FromStr>::Err: Display,
577    {
578        self.free_from_fn(FromStr::from_str)
579    }
580
581    /// Parses a free-standing argument using a specified function.
582    ///
583    /// Parses the first argument from the list of remaining arguments.
584    /// Therefore, it's up to the caller to check if the argument is actually
585    /// a free-standing one and not an unused flag/option.
586    ///
587    /// Sadly, there is no way to automatically check for flag/option.
588    /// `-`, `--`, `-1`, `-0.5`, `--.txt` - all of this arguments can have different
589    /// meaning depending on the caller requirements.
590    ///
591    /// Must be used only once for each argument.
592    ///
593    /// # Errors
594    ///
595    /// - When argument is not a UTF-8 string. Use [`free_from_os_str`] instead.
596    /// - When argument parsing failed.
597    /// - When argument is not present.
598    ///
599    /// [`free_from_os_str`]: struct.Arguments.html#method.free_from_os_str
600    #[inline(never)]
601    pub fn free_from_fn<T, E: Display>(
602        &mut self,
603        f: fn(&str) -> Result<T, E>,
604    ) -> Result<T, Error> {
605        self.opt_free_from_fn(f)?.ok_or(Error::MissingArgument)
606    }
607
608    /// Parses a free-standing argument using a specified function.
609    ///
610    /// The same as [`free_from_fn`], but parses `&OsStr` instead of `&str`.
611    ///
612    /// [`free_from_fn`]: struct.Arguments.html#method.free_from_fn
613    #[inline(never)]
614    pub fn free_from_os_str<T, E: Display>(
615        &mut self,
616        f: fn(&OsStr) -> Result<T, E>,
617    ) -> Result<T, Error> {
618        self.opt_free_from_os_str(f)?.ok_or(Error::MissingArgument)
619    }
620
621    /// Parses an optional free-standing argument using `FromStr` trait.
622    ///
623    /// The same as [`free_from_str`], but returns `Ok(None)` when argument is not present.
624    ///
625    /// [`free_from_str`]: struct.Arguments.html#method.free_from_str
626    pub fn opt_free_from_str<T>(&mut self) -> Result<Option<T>, Error>
627        where
628            T: FromStr,
629            <T as FromStr>::Err: Display,
630    {
631        self.opt_free_from_fn(FromStr::from_str)
632    }
633
634    /// Parses an optional free-standing argument using a specified function.
635    ///
636    /// The same as [`free_from_fn`], but returns `Ok(None)` when argument is not present.
637    ///
638    /// [`free_from_fn`]: struct.Arguments.html#method.free_from_fn
639    #[inline(never)]
640    pub fn opt_free_from_fn<T, E: Display>(
641        &mut self,
642        f: fn(&str) -> Result<T, E>,
643    ) -> Result<Option<T>, Error> {
644        if self.0.is_empty() {
645            Ok(None)
646        } else {
647            let value = self.0.remove(0);
648            let value = os_to_str(value.as_os_str())?;
649            match f(&value) {
650                Ok(value) => Ok(Some(value)),
651                Err(e) => Err(Error::Utf8ArgumentParsingFailed {
652                    value: value.to_string(),
653                    cause: error_to_string(e),
654                }),
655            }
656        }
657    }
658
659    /// Parses a free-standing argument using a specified function.
660    ///
661    /// The same as [`free_from_os_str`], but returns `Ok(None)` when argument is not present.
662    ///
663    /// [`free_from_os_str`]: struct.Arguments.html#method.free_from_os_str
664    #[inline(never)]
665    pub fn opt_free_from_os_str<T, E: Display>(
666        &mut self,
667        f: fn(&OsStr) -> Result<T, E>,
668    ) -> Result<Option<T>, Error> {
669        if self.0.is_empty() {
670            Ok(None)
671        } else {
672            let value = self.0.remove(0);
673            match f(value.as_os_str()) {
674                Ok(value) => Ok(Some(value)),
675                Err(e) => Err(Error::ArgumentParsingFailed { cause: error_to_string(e) }),
676            }
677        }
678    }
679
680    /// Returns a list of remaining arguments.
681    ///
682    /// It's up to the caller what to do with them.
683    /// One can report an error about unused arguments,
684    /// other can use them for further processing.
685    pub fn finish(self) -> Vec<OsString> {
686        self.0
687    }
688}
689
690// Display::to_string() is usually inlined, so by wrapping it in a non-inlined
691// function we are reducing the size a bit.
692#[inline(never)]
693fn error_to_string<E: Display>(e: E) -> String {
694    e.to_string()
695}
696
697#[cfg(feature = "eq-separator")]
698#[inline(never)]
699fn starts_with_plus_eq(text: &OsStr, prefix: &str) -> bool {
700    if let Some(s) = text.to_str() {
701        if s.get(0..prefix.len()) == Some(prefix) {
702            if s.as_bytes().get(prefix.len()) == Some(&b'=') {
703                return true;
704            }
705        }
706    }
707
708    false
709}
710
711#[cfg(feature = "short-space-opt")]
712#[inline(never)]
713fn starts_with_short_prefix(text: &OsStr, prefix: &str) -> bool {
714    if prefix.starts_with("--") {
715        return false; // Only works for short keys
716    }
717    if let Some(s) = text.to_str() {
718        if s.get(0..prefix.len()) == Some(prefix) {
719            return true;
720        }
721    }
722
723    false
724}
725
726#[cfg(all(feature = "eq-separator", feature = "short-space-opt"))]
727#[inline]
728fn index_predicate(text: &OsStr, prefix: &str) -> bool {
729    starts_with_plus_eq(text, prefix) || starts_with_short_prefix(text, prefix)
730}
731#[cfg(all(feature = "eq-separator", not(feature = "short-space-opt")))]
732#[inline]
733fn index_predicate(text: &OsStr, prefix: &str) -> bool {
734    starts_with_plus_eq(text, prefix)
735}
736#[cfg(all(feature = "short-space-opt", not(feature = "eq-separator")))]
737#[inline]
738fn index_predicate(text: &OsStr, prefix: &str) -> bool {
739    starts_with_short_prefix(text, prefix)
740}
741
742#[cfg(any(feature = "eq-separator", feature = "short-space-opt"))]
743#[inline]
744fn ends_with(text: &str, c: u8) -> bool {
745    if text.is_empty() {
746        false
747    } else {
748        text.as_bytes()[text.len() - 1] == c
749    }
750}
751
752#[inline]
753fn os_to_str(text: &OsStr) -> Result<&str, Error> {
754    text.to_str().ok_or_else(|| Error::NonUtf8Argument)
755}
756
757
758/// A keys container.
759///
760/// Should not be used directly.
761#[doc(hidden)]
762#[derive(Clone, Copy, Debug)]
763pub struct Keys([&'static str; 2]);
764
765impl Keys {
766    #[inline]
767    fn first(&self) -> &'static str {
768        self.0[0]
769    }
770
771    #[inline]
772    fn second(&self) -> &'static str {
773        self.0[1]
774    }
775}
776
777impl From<[&'static str; 2]> for Keys {
778    #[inline]
779    fn from(v: [&'static str; 2]) -> Self {
780        debug_assert!(v[0].starts_with("-"), "an argument should start with '-'");
781        validate_shortflag(v[0]);
782        debug_assert!(
783            !v[0].starts_with("--"),
784            "the first argument should be short"
785        );
786        debug_assert!(v[1].starts_with("--"), "the second argument should be long");
787        Keys(v)
788    }
789}
790
791fn validate_shortflag(short_key: &'static str) {
792    let mut chars = short_key[1..].chars();
793    if let Some(first) = chars.next() {
794        debug_assert!(short_key.len() == 2 || chars.all(|c| c == first),
795            "short keys should be a single character or a repeated character");
796    }
797}
798
799impl From<&'static str> for Keys {
800    #[inline]
801    fn from(v: &'static str) -> Self {
802        debug_assert!(v.starts_with("-"), "an argument should start with '-'");
803        if !v.starts_with("--") {
804            validate_shortflag(v);
805        }
806        Keys([v, ""])
807    }
808}