1#![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#[derive(Clone, Debug)]
43pub enum Error {
44 NonUtf8Argument,
46
47 MissingArgument,
49
50 MissingOption(Keys),
52
53 OptionWithoutAValue(&'static str),
55
56 #[allow(missing_docs)]
58 Utf8ArgumentParsingFailed { value: String, cause: String },
59
60 #[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#[derive(Clone, Debug)]
107pub struct Arguments(Vec<OsString>);
108
109impl Arguments {
110 pub fn from_vec(args: Vec<OsString>) -> Self {
117 Arguments(args)
118 }
119
120 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 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 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 {
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 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 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 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 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 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 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 #[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 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 let value = &self.0[idx];
319
320 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 #[cfg(not(feature = "short-space-opt"))]
335 return Err(Error::OptionWithoutAValue(key));
336 }
337
338 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 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 if value_range.end - value_range.start == 0 {
354 return Err(Error::OptionWithoutAValue(key));
355 }
356
357 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 #[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 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 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 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 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 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 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 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 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 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 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 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 #[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 #[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 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 #[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 #[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 pub fn finish(self) -> Vec<OsString> {
686 self.0
687 }
688}
689
690#[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; }
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#[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}