1 | /*! |
2 | An ultra simple CLI arguments parser. |
3 | |
4 | If 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 | |
36 | use std::ffi::{OsString, OsStr}; |
37 | use std::fmt::{self, Display}; |
38 | use std::str::FromStr; |
39 | |
40 | |
41 | /// A list of possible errors. |
42 | #[derive (Clone, Debug)] |
43 | pub 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 | |
65 | impl 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 | |
94 | impl std::error::Error for Error {} |
95 | |
96 | |
97 | #[derive (Clone, Copy, PartialEq)] |
98 | enum 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)] |
107 | pub struct Arguments(Vec<OsString>); |
108 | |
109 | impl 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)] |
693 | fn error_to_string<E: Display>(e: E) -> String { |
694 | e.to_string() |
695 | } |
696 | |
697 | #[cfg (feature = "eq-separator" )] |
698 | #[inline (never)] |
699 | fn 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)] |
713 | fn 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 ] |
728 | fn 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 ] |
733 | fn 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 ] |
738 | fn 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 ] |
744 | fn 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 ] |
753 | fn 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)] |
763 | pub struct Keys([&'static str; 2]); |
764 | |
765 | impl 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 | |
777 | impl 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 | |
791 | fn 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 | |
799 | impl 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 | |