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: &str) = text.to_str() {
701 if s.get(0..prefix.len()) == Some(prefix) {
702 if s.as_bytes().get(index: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(short_key: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: Chars<'_> = short_key[1..].chars();
793 if let Some(first: char) = 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(short_key:v);
805 }
806 Keys([v, ""])
807 }
808}
809