1#![forbid(unsafe_code)]
34#![warn(missing_docs)]
35#![no_std]
36
37#[cfg(feature = "std")]
38extern crate std;
39
40extern crate alloc;
41
42use alloc::string::String;
43use alloc::string::ToString;
44use alloc::vec::Vec;
45use core::fmt;
46use core::fmt::Display;
47use core::str::FromStr;
48
49#[derive(Clone, Debug)]
51pub enum Error {
52 MissingArgument,
54
55 MissingOption(Keys),
57
58 OptionWithoutAValue(&'static str),
60
61 #[allow(missing_docs)]
63 Utf8ArgumentParsingFailed { value: String, cause: String },
64
65 #[allow(missing_docs)]
67 ArgumentParsingFailed { cause: String },
68}
69
70impl Display for Error {
71 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72 match self {
73 Error::MissingArgument => {
74 write!(f, "free-standing argument is missing")
75 }
76 Error::MissingOption(key) => {
77 if key.second().is_empty() {
78 write!(f, "the '{}' option must be set", key.first())
79 } else {
80 write!(
81 f,
82 "the '{}/{}' option must be set",
83 key.first(),
84 key.second()
85 )
86 }
87 }
88 Error::OptionWithoutAValue(key) => {
89 write!(f, "the '{}' option doesn't have an associated value", key)
90 }
91 Error::Utf8ArgumentParsingFailed { value, cause } => {
92 write!(f, "failed to parse '{}': {}", value, cause)
93 }
94 Error::ArgumentParsingFailed { cause } => {
95 write!(f, "failed to parse a binary argument: {}", cause)
96 }
97 }
98 }
99}
100
101impl core::error::Error for Error {}
102
103#[derive(Clone, Copy, PartialEq)]
104enum PairKind {
105 #[cfg(any(feature = "eq-separator", feature = "short-space-opt"))]
106 SingleArgument,
107 TwoArguments,
108}
109
110#[derive(Clone, Debug)]
112pub struct Arguments(Vec<String>);
113
114impl Arguments {
115 pub fn from_vec(args: Vec<String>) -> Self {
122 Arguments(args)
123 }
124
125 pub fn from_string(string: String) -> Self {
129 Self::from_vec(string.split(' ').map(str::to_string).collect())
130 }
131
132 #[cfg(feature = "std")]
138 pub fn from_env() -> Self {
139 let mut args: Vec<_> = std::env::args_os()
140 .map(|s| s.into_string().expect("Non-UTF-8 argument"))
141 .collect();
142 args.remove(0);
143 Arguments(args)
144 }
145
146 pub fn subcommand(&mut self) -> Option<String> {
154 if self.0.is_empty() {
155 return None;
156 }
157
158 let s = self.0[0].as_str();
159 if s.starts_with('-') {
160 return None;
161 }
162
163 Some(self.0.remove(0))
164 }
165
166 pub fn contains<A: Into<Keys>>(&mut self, keys: A) -> bool {
177 self.contains_impl(keys.into())
178 }
179
180 #[inline(never)]
181 fn contains_impl(&mut self, keys: Keys) -> bool {
182 if let Some((idx, _)) = self.index_of(keys) {
183 self.0.remove(idx);
184 true
185 } else {
186 #[cfg(feature = "combined-flags")]
187 {
189 if keys.first().len() == 2 {
190 let short_flag = &keys.first()[1..2];
191 for (n, item) in self.0.iter().enumerate() {
192 let s = item.as_str();
193
194 if s.starts_with('-') && !s.starts_with("--") && s.contains(short_flag) {
195 if s.len() == 2 {
196 self.0.remove(n);
198 } else {
199 self.0[n] = s.replacen(short_flag, "", 1);
200 }
201 return true;
202 }
203 }
204 }
205 }
206 false
207 }
208 }
209
210 pub fn value_from_str<A, T>(&mut self, keys: A) -> Result<T, Error>
214 where
215 A: Into<Keys>,
216 T: FromStr,
217 <T as FromStr>::Err: Display,
218 {
219 self.value_from_fn(keys, FromStr::from_str)
220 }
221
222 pub fn value_from_fn<A: Into<Keys>, T, E: Display>(
241 &mut self,
242 keys: A,
243 f: fn(&str) -> Result<T, E>,
244 ) -> Result<T, Error> {
245 let keys = keys.into();
246 match self.opt_value_from_fn(keys, f) {
247 Ok(Some(v)) => Ok(v),
248 Ok(None) => Err(Error::MissingOption(keys)),
249 Err(e) => Err(e),
250 }
251 }
252
253 pub fn opt_value_from_str<A, T>(&mut self, keys: A) -> Result<Option<T>, Error>
257 where
258 A: Into<Keys>,
259 T: FromStr,
260 <T as FromStr>::Err: Display,
261 {
262 self.opt_value_from_fn(keys, FromStr::from_str)
263 }
264
265 pub fn opt_value_from_fn<A: Into<Keys>, T, E: Display>(
271 &mut self,
272 keys: A,
273 f: fn(&str) -> Result<T, E>,
274 ) -> Result<Option<T>, Error> {
275 self.opt_value_from_fn_impl(keys.into(), f)
276 }
277
278 #[inline(never)]
279 fn opt_value_from_fn_impl<T, E: Display>(
280 &mut self,
281 keys: Keys,
282 f: fn(&str) -> Result<T, E>,
283 ) -> Result<Option<T>, Error> {
284 match self.find_value(keys)? {
285 Some((value, kind, idx)) => {
286 match f(value) {
287 Ok(value) => {
288 self.0.remove(idx);
290 if kind == PairKind::TwoArguments {
291 self.0.remove(idx);
292 }
293
294 Ok(Some(value))
295 }
296 Err(e) => Err(Error::Utf8ArgumentParsingFailed {
297 value: value.to_string(),
298 cause: error_to_string(e),
299 }),
300 }
301 }
302 None => Ok(None),
303 }
304 }
305
306 #[cfg(any(feature = "eq-separator", feature = "short-space-opt"))]
308 #[inline(never)]
309 fn find_value(&mut self, keys: Keys) -> Result<Option<(&str, PairKind, usize)>, Error> {
310 if let Some((idx, key)) = self.index_of(keys) {
311 let value = match self.0.get(idx + 1) {
314 Some(v) => v,
315 None => return Err(Error::OptionWithoutAValue(key)),
316 };
317
318 Ok(Some((value, PairKind::TwoArguments, idx)))
319 } else if let Some((idx, key)) = self.index_of2(keys) {
320 let value = &self.0[idx];
323
324 let value = value.as_str();
326
327 let mut value_range = key.len()..value.len();
328
329 if value.as_bytes().get(value_range.start) == Some(&b'=') {
330 #[cfg(feature = "eq-separator")]
331 {
332 value_range.start += 1;
333 }
334 #[cfg(not(feature = "eq-separator"))]
335 return Err(Error::OptionWithoutAValue(key));
336 } else {
337 #[cfg(not(feature = "short-space-opt"))]
339 return Err(Error::OptionWithoutAValue(key));
340 }
341
342 if let Some(c) = value.as_bytes().get(value_range.start).cloned() {
344 if c == b'"' || c == b'\'' {
345 value_range.start += 1;
346
347 if ends_with(&value[value_range.start..], c) {
349 value_range.end -= 1;
350 } else {
351 return Err(Error::OptionWithoutAValue(key));
352 }
353 }
354 }
355
356 if value_range.end - value_range.start == 0 {
358 return Err(Error::OptionWithoutAValue(key));
359 }
360
361 let value = &value[value_range];
363
364 if value.is_empty() {
365 return Err(Error::OptionWithoutAValue(key));
366 }
367
368 Ok(Some((value, PairKind::SingleArgument, idx)))
369 } else {
370 Ok(None)
371 }
372 }
373
374 #[cfg(not(any(feature = "eq-separator", feature = "short-space-opt")))]
376 #[inline(never)]
377 fn find_value(&mut self, keys: Keys) -> Result<Option<(&str, PairKind, usize)>, Error> {
378 if let Some((idx, key)) = self.index_of(keys) {
379 let value = match self.0.get(idx + 1) {
382 Some(v) => v,
383 None => return Err(Error::OptionWithoutAValue(key)),
384 };
385 Ok(Some((value, PairKind::TwoArguments, idx)))
386 } else {
387 Ok(None)
388 }
389 }
390
391 pub fn values_from_str<A, T>(&mut self, keys: A) -> Result<Vec<T>, Error>
395 where
396 A: Into<Keys>,
397 T: FromStr,
398 <T as FromStr>::Err: Display,
399 {
400 self.values_from_fn(keys, FromStr::from_str)
401 }
402
403 pub fn values_from_fn<A: Into<Keys>, T, E: Display>(
415 &mut self,
416 keys: A,
417 f: fn(&str) -> Result<T, E>,
418 ) -> Result<Vec<T>, Error> {
419 let keys = keys.into();
420
421 let mut values = Vec::new();
422 loop {
423 match self.opt_value_from_fn(keys, f) {
424 Ok(Some(v)) => values.push(v),
425 Ok(None) => break,
426 Err(e) => return Err(e),
427 }
428 }
429
430 Ok(values)
431 }
432
433 #[inline(never)]
434 fn index_of(&self, keys: Keys) -> Option<(usize, &'static str)> {
435 for key in &keys.0 {
439 if !key.is_empty() {
440 if let Some(i) = self.0.iter().position(|v| v == key) {
441 return Some((i, key));
442 }
443 }
444 }
445
446 None
447 }
448
449 #[cfg(any(feature = "eq-separator", feature = "short-space-opt"))]
450 #[inline(never)]
451 fn index_of2(&self, keys: Keys) -> Option<(usize, &'static str)> {
452 if !keys.first().is_empty() {
455 if let Some(i) = self.0.iter().position(|v| index_predicate(v, keys.first())) {
456 return Some((i, keys.first()));
457 }
458 }
459
460 if !keys.second().is_empty() {
461 if let Some(i) = self
462 .0
463 .iter()
464 .position(|v| index_predicate(v, keys.second()))
465 {
466 return Some((i, keys.second()));
467 }
468 }
469
470 None
471 }
472
473 pub fn free_from_str<T>(&mut self) -> Result<T, Error>
477 where
478 T: FromStr,
479 <T as FromStr>::Err: Display,
480 {
481 self.free_from_fn(FromStr::from_str)
482 }
483
484 #[inline(never)]
501 pub fn free_from_fn<T, E: Display>(&mut self, f: fn(&str) -> Result<T, E>) -> Result<T, Error> {
502 self.opt_free_from_fn(f)?.ok_or(Error::MissingArgument)
503 }
504
505 pub fn opt_free_from_str<T>(&mut self) -> Result<Option<T>, Error>
509 where
510 T: FromStr,
511 <T as FromStr>::Err: Display,
512 {
513 self.opt_free_from_fn(FromStr::from_str)
514 }
515
516 #[inline(never)]
520 pub fn opt_free_from_fn<T, E: Display>(
521 &mut self,
522 f: fn(&str) -> Result<T, E>,
523 ) -> Result<Option<T>, Error> {
524 if self.0.is_empty() {
525 Ok(None)
526 } else {
527 let value = self.0.remove(0);
528 match f(&value) {
529 Ok(value) => Ok(Some(value)),
530 Err(e) => Err(Error::Utf8ArgumentParsingFailed {
531 value: value.to_string(),
532 cause: error_to_string(e),
533 }),
534 }
535 }
536 }
537
538 pub fn finish(self) -> Vec<String> {
544 self.0
545 }
546}
547
548#[inline(never)]
551fn error_to_string<E: Display>(e: E) -> String {
552 e.to_string()
553}
554
555#[cfg(feature = "eq-separator")]
556#[inline(never)]
557fn starts_with_plus_eq(text: &str, prefix: &str) -> bool {
558 if text.get(0..prefix.len()) == Some(prefix) && text.as_bytes().get(prefix.len()) == Some(&b'=')
559 {
560 return true;
561 }
562
563 false
564}
565
566#[cfg(feature = "short-space-opt")]
567#[inline(never)]
568fn starts_with_short_prefix(text: &str, prefix: &str) -> bool {
569 if prefix.starts_with("--") {
570 return false; }
572 if text.get(0..prefix.len()) == Some(prefix) {
573 return true;
574 }
575
576 false
577}
578
579#[cfg(all(feature = "eq-separator", feature = "short-space-opt"))]
580#[inline]
581fn index_predicate(text: &str, prefix: &str) -> bool {
582 starts_with_plus_eq(text, prefix) || starts_with_short_prefix(text, prefix)
583}
584#[cfg(all(feature = "eq-separator", not(feature = "short-space-opt")))]
585#[inline]
586fn index_predicate(text: &str, prefix: &str) -> bool {
587 starts_with_plus_eq(text, prefix)
588}
589#[cfg(all(feature = "short-space-opt", not(feature = "eq-separator")))]
590#[inline]
591fn index_predicate(text: &str, prefix: &str) -> bool {
592 starts_with_short_prefix(text, prefix)
593}
594
595#[cfg(any(feature = "eq-separator", feature = "short-space-opt"))]
596#[inline]
597fn ends_with(text: &str, c: u8) -> bool {
598 if text.is_empty() {
599 false
600 } else {
601 text.as_bytes()[text.len() - 1] == c
602 }
603}
604
605#[doc(hidden)]
609#[derive(Clone, Copy, Debug)]
610pub struct Keys([&'static str; 2]);
611
612impl Keys {
613 #[inline]
614 fn first(&self) -> &'static str {
615 self.0[0]
616 }
617
618 #[inline]
619 fn second(&self) -> &'static str {
620 self.0[1]
621 }
622}
623
624impl From<[&'static str; 2]> for Keys {
625 #[inline]
626 fn from(v: [&'static str; 2]) -> Self {
627 debug_assert!(v[0].starts_with("-"), "an argument should start with '-'");
628 validate_shortflag(v[0]);
629 debug_assert!(
630 !v[0].starts_with("--"),
631 "the first argument should be short"
632 );
633 debug_assert!(v[1].starts_with("--"), "the second argument should be long");
634 Keys(v)
635 }
636}
637
638fn validate_shortflag(short_key: &'static str) {
639 let mut chars = short_key[1..].chars();
640 if let Some(first) = chars.next() {
641 debug_assert!(
642 short_key.len() == 2 || chars.all(|c| c == first),
643 "short keys should be a single character or a repeated character"
644 );
645 }
646}
647
648impl From<&'static str> for Keys {
649 #[inline]
650 fn from(v: &'static str) -> Self {
651 debug_assert!(v.starts_with("-"), "an argument should start with '-'");
652 if !v.starts_with("--") {
653 validate_shortflag(v);
654 }
655 Keys([v, ""])
656 }
657}