shovel/opts.rs
1//! Optional argument parsing and specification.
2//!
3//! This module provides the [`Opts`] trait and related types for defining and parsing
4//! optional command-line arguments (flags and options). Options are parameters that
5//! can appear anywhere in the command line and are typically prefixed with `-` or `--`.
6//!
7//! # Core Types
8//!
9//! ## [`Opts`] - Optional Argument Trait
10//!
11//! The main trait for defining optional arguments. Typically implemented using
12//! the `#[derive(Opts)]` macro for automatic parsing and validation.
13//!
14//! ## [`Opt`] - Option Specification
15//!
16//! Describes an individual option with its properties:
17//! - Short and long forms (`-v`, `--verbose`)
18//! - Value requirements (flag vs. valued option)
19//! - Type information and parsing rules
20//! - Help text and descriptions
21//!
22//! ## [`RawOpts<'a, S>`] - Raw Option Container
23//!
24//! Internal type that holds unparsed command-line options and manages parsing state.
25//!
26//! ## [`RawOptsIter<'a, S>`] - Option Iterator
27//!
28//! Iterator over raw command-line options for processing.
29//!
30//! # Option Types
31//!
32//! ## Boolean Flags
33//! Simple on/off switches:
34//! ```
35//! # use shovel::*;
36//! #[derive(Opts)]
37//! struct MyOpts {
38//! /// Enable verbose output
39//! #[short = 'v']
40//! verbose: bool,
41//! }
42//! ```
43//!
44//! ## Valued Options
45//! Options that accept values:
46//! ```
47//! # use shovel::*;
48//!
49//! #[derive(Opts)]
50//! struct MyOpts {
51//! /// Output file path
52//! #[short = 'o']
53//! output: Option<String>,
54//!
55//! /// Number of threads
56//! #[short = 'j']
57//! jobs: Option<u32>,
58//!
59//! /// Tags
60//! tags: Option<Vec<String>>,
61//! }
62//! ```
63//!
64//! ## Required Options
65//! Options that must be provided:
66//! ```
67//! # use shovel::*;
68//!
69//! #[derive(Opts)]
70//! struct MyOpts {
71//! /// Configuration file (required)
72//! #[short = 'c']
73//! config: String,
74//!
75//! /// Tags (required)
76//! tags: Vec<String>,
77//! }
78//! ```
79//!
80//! ## Multiple Values
81//! Options that can be specified multiple times:
82//! ```
83//! # use shovel::*;
84//! #[derive(Opts)]
85//! struct MyOpts {
86//! /// Include directories
87//! #[short = 'I']
88//! #[format = "<DIR>,..."]
89//! include: String,
90//! }
91//! ```
92//!
93//! # Option Syntax
94//!
95//! ## Short Options
96//! Single character options prefixed with `-`:
97//! ```bash
98//! myapp -v -o output.txt -j 4
99//! myapp -vj 4 -o output.txt # Short options can be combined
100//! ```
101//!
102//! ## Long Options
103//! Multi-character options prefixed with `--`:
104//! ```bash
105//! myapp --verbose --output output.txt --jobs 4
106//! myapp --output=output.txt # Values can use = syntax
107//! ```
108//!
109//! ## Mixed Usage
110//! Short and long forms can be used interchangeably:
111//! ```bash
112//! myapp -v --output output.txt -j 4
113//! ```
114//!
115//! # Attribute Syntax
116//!
117//! The `#[derive(Opts)]` macro supports several attributes:
118//!
119//! ## `#[short = 'c']`
120//! Defines the short form of the option:
121//! ```
122//! # use shovel::*;
123//! #[derive(Opts)]
124//! struct MyOpts {
125//! #[short = 'v']
126//! verbose: bool,
127//! }
128//! ```
129//!
130//! ## `#[short]`
131//! Uses the first character of the field name:
132//! ```
133//! # use shovel::*;
134//! #[derive(Opts)]
135//! struct MyOpts {
136//! #[short] // Becomes -v
137//! verbose: bool,
138//! }
139//! ```
140//!
141//!
142//! # Built-in Options
143//!
144//! Shovel automatically provides several built-in options:
145//!
146//! ## Help Options
147//! - `-h, --help` - Show help information
148//! - Available on all commands automatically
149//!
150//! ## Version Options
151//! - `-V, --version` - Show version information
152//! - Available on the root application
153//!
154//! ## Completion Options (when enabled)
155//! - `--completion <SHELL>` - Generate completion scripts
156//! - Available when the `completion` feature is enabled
157//!
158//! # Parsing Behavior
159//!
160//! - **Type Safety**: Options are parsed to their target types with validation
161//! - **Default Values**: `Option<T>` fields default to `None`, `bool` fields to `false`
162//! - **Error Handling**: Invalid values or unknown options result in parse errors
163//! - **Flexible Syntax**: Supports various command-line conventions
164//!
165//! # Global vs. Local Options
166//!
167//! ## Local Options
168//! Specific to individual commands:
169//! ```rust
170//! # use shovel::*;
171//! #[derive(Opts)]
172//! struct BuildOpts {
173//! #[short = 'r']
174//! release: bool,
175//! }
176//! ```
177//!
178//! ## Global Options
179//!
180//! Available to all commands in the application:
181//!
182//! ```rust
183//! use shovel::*;
184//!
185//! #[derive(Opts)]
186//! pub struct GlobalOpts {
187//! /// Enable verbose output
188//! #[short = 'v']
189//! pub verbose: bool,
190//! /// Set log level
191//! #[short = 'l']
192//! pub log_level: Option<String>,
193//! }
194//!
195//!
196//! # fn main() {
197//! let action: Action<(), (), GlobalOpts, ()> = Action::global_opts(|global_opts: GlobalOpts| {
198//! if global_opts.verbose {
199//! println!("Verbose mode enabled");
200//! }
201//! if let Some(level) = global_opts.log_level {
202//! println!("Setting log level to: {}", level);
203//! // Here you would typically set up your logging framework
204//! }
205//! });
206//! # }
207//! ```
208//!
209//! # Examples
210//!
211//! ## Compiler-like Options
212//! ```
213//! # use shovel::*;
214//! #[derive(Opts)]
215//! struct CompilerOpts {
216//! /// Optimization level
217//! #[short = 'O']
218//! optimize: Option<u32>,
219//!
220//! /// Enable warnings
221//! #[short = 'W']
222//! warnings: bool,
223//!
224//! /// Output file
225//! #[short = 'o']
226//! output: Option<String>,
227//!
228//! /// Include directories
229//! #[short = 'I']
230//! #[format = "<DIR>,..."]
231//! include: String,
232//! }
233//!
234//! let action: Action<CompilerOpts, (), (), ()> = Action::opts(|opts: CompilerOpts| {
235//! if opts.warnings {
236//! println!("Warnings enabled");
237//! }
238//!
239//! if let Some(level) = opts.optimize {
240//! println!("Optimization level: {}", level);
241//! }
242//!
243//! for dir in opts.include.split(',') {
244//! println!("Include directory: {}", dir);
245//! }
246//! });
247//! ```
248//!
249//! ## Server Configuration Options
250//! ```
251//! # use shovel::*;
252//! #[derive(Opts)]
253//! struct ServerOpts {
254//! /// Server port
255//! #[short = 'p']
256//! port: Option<u16>,
257//!
258//! /// Bind address
259//! #[short = 'b']
260//! bind: Option<String>,
261//!
262//! /// Enable TLS
263//! #[short = 's']
264//! secure: bool,
265//!
266//! /// Configuration file
267//! #[short = 'c']
268//! config: Option<String>,
269//! }
270//! ```
271//!
272//! See also: [`Args`](crate::Args), [`Action`](crate::Action), [`Context`](crate::Context)
273
274use core::cell::{RefCell, RefMut};
275use core::marker::PhantomData;
276
277use crate::ParseError;
278#[cfg(feature = "completion")]
279use crate::Shell;
280
281/// Represents a collection of optional arguments for a command.
282///
283/// This trait should be implemented by user-defined types that represent
284/// the optional arguments of a command. Each field of the implementing type
285/// corresponds to one optional argument.
286///
287/// - If a field is of type [`core::option::Option`], the argument is optional.
288/// - Otherwise, the argument is required.
289///
290/// # Implementing
291///
292/// This trait can be derived using the `#[derive(Opts)]` attribute, which is
293/// the recommended approach for most use cases. However, manual implementation
294/// is possible for more complex scenarios.
295///
296/// # Example
297///
298/// ```no_run
299/// use shovel::Opts;
300///
301/// #[derive(Opts)]
302/// pub struct TickOpts {
303/// /// The delay time (in seconds)
304/// #[short = 'd']
305/// pub delay: u64, // This field is required
306/// /// Number of times
307/// #[short = 'n']
308/// pub number: Option<u32>, // The field is optional.
309/// }
310/// ```
311///
312/// # Manual Implementation Example
313///
314/// For advanced use cases, you can manually implement the `Opts` trait:
315///
316/// ```no_run
317/// use shovel::{Opt, Opts, ParseError, RawOpts};
318///
319/// pub struct TickOpts {
320/// pub delay: u64,
321/// pub number: Option<u32>,
322/// }
323///
324/// impl Opts for TickOpts {
325/// fn parse<'a, 'b: 'a>(
326/// opts: &'a RawOpts<'a, 'b>,
327/// ) -> Result<(Self, usize), ParseError<'b>> {
328/// let mut delay = None;
329/// let mut number = None;
330///
331/// // Parse each option
332/// for opt in opts {
333/// match opt.0 {
334/// "delay" | "d" => {
335/// let value = opt.1.ok_or(ParseError::MissingValueForOption { name: "delay" })?;
336/// delay = Some(value.parse::<u64>().map_err(|_| {
337/// ParseError::InvalidOptionValue {
338/// name: "delay",
339/// value,
340/// expected: "a positive number",
341/// }
342/// })?);
343/// }
344/// "number" | "n" => {
345/// let value = opt.1.ok_or(ParseError::MissingValueForOption { name: "number" })?;
346/// number = Some(value.parse::<u32>().map_err(|_| {
347/// ParseError::InvalidOptionValue {
348/// name: "number",
349/// value,
350/// expected: "a positive integer",
351/// }
352/// })?);
353/// }
354/// unknown => return Err(ParseError::UnknownOption(unknown)),
355/// }
356/// }
357///
358/// // Validate required options
359/// let delay = delay.ok_or(ParseError::MissingRequiredOption { name: "delay" })?;
360///
361/// Ok((Self { delay, number }, opts.len_consumed()))
362/// }
363///
364/// fn specs() -> &'static [Opt] {
365/// &[
366/// Opt {
367/// name: "delay",
368/// ty: "u64",
369/// required: true,
370/// flag: false,
371/// description: "The delay time (in seconds)",
372/// short: Some('d'),
373/// format: "-d, --delay <SECONDS>",
374/// },
375/// Opt {
376/// name: "number",
377/// ty: "u32",
378/// required: false,
379/// flag: false,
380/// description: "Number of times to repeat",
381/// short: Some('n'),
382/// format: "-n, --number <COUNT>",
383/// },
384/// ]
385/// }
386/// }
387/// ```
388pub trait Opts: Sized + 'static {
389 /// Parses the raw optional arguments into this type.
390 ///
391 /// # Arguments
392 ///
393 /// * `opts` - The raw optional arguments to parse.
394 ///
395 /// # Returns
396 ///
397 /// Returns a `Result` containing:
398 /// - On success: A tuple with the parsed `Opts` instance and the number of consumed arguments.
399 /// - On failure: A `ParseError` describing the parsing failure.
400 ///
401 /// # Errors
402 ///
403 /// This function may return a `ParseError` in the following cases:
404 /// - If a required option is missing
405 /// - If an option value is invalid or cannot be parsed
406 /// - If an unknown option is encountered
407 fn parse<'a, 'b: 'a>(
408 opts: &'a RawOpts<'a, 'b>,
409 ) -> Result<(Self, usize), ParseError<'b>>;
410
411 /// Provides specifications for all optional arguments.
412 ///
413 /// # Returns
414 ///
415 /// A static slice of `Opt` structs, each describing an optional argument.
416 fn specs() -> &'static [Opt];
417}
418
419/// Specifies the properties of an individual optional argument.
420#[derive(Debug)]
421pub struct Opt {
422 /// The full name of the optional argument.
423 pub name: &'static str,
424 /// The expected type of the argument's value.
425 pub ty: &'static str,
426 /// Indicates whether the argument is required.
427 pub required: bool,
428 /// Indicates whether the argument is a flag (boolean, no value).
429 pub flag: bool,
430 /// A brief description of the argument's purpose.
431 pub description: &'static str,
432 /// The optional short (single-character) version of the argument.
433 pub short: Option<char>,
434 /// The formatted representation used in help messages.
435 pub format: &'static str,
436}
437
438/// An iterator over the raw optional arguments.
439///
440/// This struct is used internally to iterate over the raw arguments
441/// during the parsing process.
442#[derive(Debug)]
443pub struct RawOptsIter<'a, 'b: 'a> {
444 args: &'b [&'b str],
445 index: RefMut<'a, usize>,
446 multi_flags_index: Option<usize>,
447 specs: &'static [Opt],
448 is_end: RefMut<'a, bool>,
449}
450
451/// Represents the raw optional arguments of a command.
452///
453/// This struct holds the unparsed arguments and provides methods
454/// to iterate over them during the parsing process.
455#[derive(Debug)]
456pub struct RawOpts<'a, 'b: 'a> {
457 args: &'b [&'b str],
458 index: RefCell<usize>,
459 specs: &'static [Opt],
460 is_end: RefCell<bool>,
461 _lifetime: PhantomData<&'a ()>,
462}
463
464pub(crate) struct HelpOpt;
465
466pub(crate) struct VersionOpt;
467
468#[cfg(feature = "completion")]
469pub(crate) struct CompletionOpt;
470
471const BUILTIN_OPTIONS: &[&str] = &[
472 "help",
473 "h",
474 "version",
475 "V",
476 #[cfg(feature = "completion")]
477 "completion",
478];
479
480impl Opts for () {
481 fn parse<'a, 'b: 'a>(
482 opts: &'a RawOpts<'a, 'b>,
483 ) -> Result<(Self, usize), ParseError<'b>> {
484 for opt in opts {
485 if !BUILTIN_OPTIONS.contains(&opt.0) {
486 Err(ParseError::UnknownOption(opt.0))?;
487 }
488 }
489 Ok(((), opts.len_consumed()))
490 }
491
492 fn specs() -> &'static [Opt] {
493 &[]
494 }
495}
496
497pub(crate) static BUILTIN_OPTS: &[Opt] = &[
498 Opt {
499 name: "help",
500 ty: "bool",
501 required: false,
502 flag: true,
503 description: "Show this help",
504 short: Some('h'),
505 format: "-h, --help",
506 },
507 Opt {
508 name: "version",
509 ty: "bool",
510 required: false,
511 flag: true,
512 description: "Print version information and quit",
513 short: Some('V'),
514 format: "-V, --version",
515 },
516 #[cfg(feature = "completion")]
517 Opt {
518 name: "completion",
519 ty: "Shell:<bash|zsh|fish>",
520 required: false,
521 flag: false,
522 description: "Generate shell completion script",
523 short: None,
524 format: " --completion",
525 },
526];
527
528impl HelpOpt {
529 pub(crate) fn parse(opts: RawOpts<'_, '_>) -> bool {
530 let mut help = false;
531 for opt in &opts {
532 if opt.0 == "help" || opt.0 == "h" {
533 help = true;
534 break;
535 }
536 }
537 help
538 }
539}
540
541impl VersionOpt {
542 pub(crate) fn parse(opts: RawOpts<'_, '_>) -> bool {
543 let mut version = false;
544 for opt in &opts {
545 if opt.0 == "version" || opt.0 == "V" {
546 version = true;
547 break;
548 }
549 }
550 version
551 }
552}
553
554#[cfg(feature = "completion")]
555impl CompletionOpt {
556 pub(crate) fn parse(
557 opts: RawOpts<'_, '_>,
558 ) -> Option<Result<Shell, ParseError<'static>>> {
559 for opt in &opts {
560 if opt.0 == "completion" {
561 if let Some(s) = opt.1 {
562 return Some(s.parse().map_err(|_| {
563 ParseError::InvalidOptionValue {
564 name: "completion",
565 value: "Unknown shell",
566 expected: "a valid shell name",
567 }
568 }));
569 }
570 return Some(Err(ParseError::MissingValueForOption {
571 name: "completion",
572 }));
573 }
574 }
575 None
576 }
577}
578
579impl<'a, 'b: 'a> RawOpts<'a, 'b> {
580 pub(crate) fn new(args: &'b [&'b str], specs: &'static [Opt]) -> Self {
581 Self {
582 args,
583 index: RefCell::new(0),
584 specs,
585 is_end: RefCell::new(false),
586 _lifetime: PhantomData,
587 }
588 }
589
590 /// Returns an iterator over the raw optional arguments.
591 pub fn iter(&'a self) -> RawOptsIter<'a, 'b> {
592 RawOptsIter::new(self)
593 }
594
595 /// Returns the number of raw arguments that has been consumed.
596 pub fn len_consumed(&self) -> usize {
597 debug_assert!(*self.is_end.borrow());
598 *self.index.borrow()
599 }
600}
601
602impl<'a, 'b: 'a> IntoIterator for &'a RawOpts<'a, 'b> {
603 type Item = (&'b str, Option<&'b str>);
604 type IntoIter = RawOptsIter<'a, 'b>;
605
606 fn into_iter(self) -> Self::IntoIter {
607 self.iter()
608 }
609}
610
611impl<'a, 'b: 'a> RawOptsIter<'a, 'b> {
612 fn new(raw_opts: &'a RawOpts<'a, 'b>) -> Self {
613 Self {
614 args: raw_opts.args,
615 index: raw_opts.index.borrow_mut(),
616 multi_flags_index: None,
617 specs: raw_opts.specs,
618 is_end: raw_opts.is_end.borrow_mut(),
619 }
620 }
621
622 fn find_spec(&self, name: &str) -> Option<&'static Opt> {
623 for spec in self.specs {
624 if name == spec.name {
625 return Some(spec);
626 }
627 if let Some(s) = spec.short
628 && name.len() == s.len_utf8()
629 && name.starts_with(s)
630 {
631 return Some(spec);
632 }
633 }
634 None
635 }
636}
637
638impl<'a, 'b: 'a> Iterator for RawOptsIter<'a, 'b> {
639 type Item = (&'b str, Option<&'b str>);
640
641 fn next(&mut self) -> Option<Self::Item> {
642 if *self.is_end || *self.index >= self.args.len() {
643 *self.is_end = true;
644 return None;
645 }
646
647 let arg = self.args[*self.index];
648 if !arg.starts_with('-') {
649 *self.is_end = true;
650 return None;
651 }
652
653 // End of options
654 if arg == "--" {
655 *self.index += 1;
656 *self.is_end = true;
657 return None;
658 }
659
660 if !arg.is_ascii() && arg.starts_with('-') && !arg.starts_with("--") {
661 *self.index += 1;
662 return Some((arg.strip_prefix('-').unwrap_or(arg), None));
663 }
664
665 let name = if let Some(name) = arg.strip_prefix("--") {
666 // Handle --name=value syntax
667 if let Some((name, value)) = name.split_once('=') {
668 *self.index += 1;
669 return Some((name, Some(value)));
670 }
671 name
672 } else {
673 if arg.len() > 2 {
674 // Handle -n=value syntax for short options (only single character)
675 if let Some(eq_pos) = arg.find('=')
676 && eq_pos == 2
677 {
678 let name = &arg[1..2];
679 let value = &arg[eq_pos + 1..];
680 *self.index += 1;
681 return Some((name, Some(value)));
682 }
683
684 let index = self.multi_flags_index.unwrap_or(0);
685 if index < arg.len() - 2 {
686 self.multi_flags_index = Some(index + 1);
687 return Some((&arg[index + 1..=index + 1], None));
688 }
689 }
690 self.multi_flags_index = None;
691 &arg[arg.len() - 1..]
692 };
693
694 *self.index += 1;
695
696 match self.find_spec(name) {
697 None => {
698 if !BUILTIN_OPTIONS.contains(&name) {
699 *self.is_end = true;
700 }
701 Some((name, None))
702 }
703 Some(spec) => {
704 if *self.index >= self.args.len() {
705 return Some((name, None));
706 }
707
708 let value = self.args[*self.index];
709 if spec.flag || value.starts_with('-') {
710 return Some((name, None));
711 }
712
713 *self.index += 1;
714 Some((name, Some(value)))
715 }
716 }
717 }
718}
719
720/// A simple and fast config option parser.
721///
722/// # Example
723///
724/// ```edition2021
725/// use shovel::*;
726///
727/// # fn main() {
728/// App {
729/// name: "config",
730/// description: "An example of ConfigOpts",
731/// version: "0.1.0",
732/// authors: ["Author Name <author@example.com>"],
733/// copyright: "(c) 2026 Company Name",
734/// action: Action::opts(|opts: ConfigOpts| {
735/// match opts.config {
736/// Some(p) => println!("The path of config is '{}'", p.display()),
737/// None => println!("No config provided")
738/// }
739/// }),
740/// }
741/// .run()
742/// .expect("Failed to run application");
743/// # }
744/// ```
745#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
746#[cfg(feature = "std")]
747#[derive(Debug)]
748pub struct ConfigOpts {
749 /// The path of config.
750 pub config: Option<std::path::PathBuf>,
751}
752
753#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
754#[cfg(feature = "std")]
755impl Opts for ConfigOpts {
756 fn parse<'a, 'b: 'a>(
757 opts: &'a RawOpts<'a, 'b>,
758 ) -> Result<(Self, usize), ParseError<'b>> {
759 let mut config = None;
760
761 for opt in opts {
762 if opt.0 == "config" || opt.0 == "c" {
763 match opt.1 {
764 Some(s) => {
765 config =
766 Some(s.parse::<std::path::PathBuf>().map_err(
767 |_| ParseError::InvalidOptionValue {
768 name: "config",
769 value: s,
770 expected: "a valid file path",
771 },
772 )?);
773 }
774 None => {
775 Err(ParseError::MissingValueForOption {
776 name: "config",
777 })?;
778 }
779 }
780 } else {
781 Err(ParseError::UnknownOption(opt.0))?;
782 }
783 }
784
785 Ok((Self { config }, opts.len_consumed()))
786 }
787
788 fn specs() -> &'static [Opt] {
789 &[Opt {
790 name: "config",
791 description: "The path of config",
792 ty: "Path",
793 required: false,
794 flag: false,
795 short: Some('c'),
796 format: "-c, --config",
797 }]
798 }
799}
800
801#[cfg(test)]
802mod tests {
803 use super::*;
804
805 #[derive(Debug, PartialEq)]
806 struct TestOpts {
807 a: bool,
808 b: bool,
809 c: Option<u32>,
810 }
811
812 impl Opts for TestOpts {
813 fn specs() -> &'static [Opt] {
814 &[
815 Opt {
816 name: "a",
817 ty: "bool",
818 required: false,
819 flag: true,
820 description: "",
821 short: Some('a'),
822 format: "-a",
823 },
824 Opt {
825 name: "b",
826 ty: "bool",
827 required: false,
828 flag: true,
829 description: "",
830 short: Some('b'),
831 format: "-b",
832 },
833 Opt {
834 name: "c",
835 ty: "Option<u32>",
836 required: false,
837 flag: false,
838 description: "",
839 short: Some('c'),
840 format: "-c <u32>",
841 },
842 ]
843 }
844
845 fn parse<'a, 'b: 'a>(
846 opts: &'a RawOpts<'a, 'b>,
847 ) -> Result<(Self, usize), ParseError<'b>> {
848 let mut a = false;
849 let mut b = false;
850 let mut c = None;
851
852 for (name, value) in opts {
853 match name {
854 "a" => match value {
855 Some(_) => Err(ParseError::UnexpectedFlagValue {
856 name: "a",
857 value: value.unwrap(),
858 })?,
859 None => a = true,
860 },
861 "b" => match value {
862 Some(_) => Err(ParseError::UnexpectedFlagValue {
863 name: "b",
864 value: value.unwrap(),
865 })?,
866 None => b = true,
867 },
868 "c" => {
869 let v =
870 value.ok_or(ParseError::MissingValueForOption {
871 name: "c",
872 })?;
873 c = Some(v.parse().map_err(|_| {
874 ParseError::InvalidOptionValue {
875 name: "c",
876 value: v,
877 expected: "u32",
878 }
879 })?);
880 }
881 _ => (), // Ignore unknown options in test
882 }
883 }
884
885 Ok((TestOpts { a, b, c }, opts.len_consumed()))
886 }
887 }
888
889 #[test]
890 fn test_clustered_bool_flags() {
891 let args = &["-ab"];
892 let raw_opts = RawOpts::new(args, TestOpts::specs());
893 let (opts, len) = TestOpts::parse(&raw_opts).unwrap();
894 assert_eq!(
895 opts,
896 TestOpts {
897 a: true,
898 b: true,
899 c: None
900 }
901 );
902 assert_eq!(len, 1);
903 }
904
905 #[test]
906 fn test_clustered_short_options() {
907 let args = &["-ab", "-c", "123"];
908 let raw_opts = RawOpts::new(args, TestOpts::specs());
909 let (opts, len) = TestOpts::parse(&raw_opts).unwrap();
910 assert_eq!(
911 opts,
912 TestOpts {
913 a: true,
914 b: true,
915 c: Some(123)
916 }
917 );
918 assert_eq!(len, 3);
919 }
920
921 #[test]
922 fn test_clustered_short_options_with_value_at_end() {
923 let args = &["-ac", "456"];
924 let raw_opts = RawOpts::new(args, TestOpts::specs());
925 let (opts, len) = TestOpts::parse(&raw_opts).unwrap();
926 assert_eq!(
927 opts,
928 TestOpts {
929 a: true,
930 b: false,
931 c: Some(456)
932 }
933 );
934 assert_eq!(len, 2);
935 }
936
937 #[test]
938 fn test_single_flag() {
939 let args = &["-a"];
940 let raw_opts = RawOpts::new(args, TestOpts::specs());
941 let (opts, len) = TestOpts::parse(&raw_opts).unwrap();
942 assert_eq!(
943 opts,
944 TestOpts {
945 a: true,
946 b: false,
947 c: None
948 }
949 );
950 assert_eq!(len, 1);
951 }
952
953 #[test]
954 fn test_clustered_flags_and_value_option() {
955 let args = &["-abc", "789"];
956 let raw_opts = RawOpts::new(args, TestOpts::specs());
957 let (opts, len) = TestOpts::parse(&raw_opts).unwrap();
958 assert_eq!(
959 opts,
960 TestOpts {
961 a: true,
962 b: true,
963 c: Some(789)
964 }
965 );
966 assert_eq!(len, 2);
967 }
968
969 #[test]
970 fn test_long_option_with_equals() {
971 let args = &["--c=456"];
972 let raw_opts = RawOpts::new(args, TestOpts::specs());
973 let (opts, len) = TestOpts::parse(&raw_opts).unwrap();
974 assert_eq!(
975 opts,
976 TestOpts {
977 a: false,
978 b: false,
979 c: Some(456)
980 }
981 );
982 assert_eq!(len, 1);
983 }
984
985 #[test]
986 fn test_short_option_with_equals() {
987 let args = &["-c=789"];
988 let raw_opts = RawOpts::new(args, TestOpts::specs());
989 let (opts, len) = TestOpts::parse(&raw_opts).unwrap();
990 assert_eq!(
991 opts,
992 TestOpts {
993 a: false,
994 b: false,
995 c: Some(789)
996 }
997 );
998 assert_eq!(len, 1);
999 }
1000
1001 #[test]
1002 fn test_version_flag_uses_uppercase_v() {
1003 let args = &["-V"];
1004 let raw_opts = RawOpts::new(args, &[]);
1005 let ((), len) = <() as Opts>::parse(&raw_opts).unwrap();
1006 assert_eq!(len, 1);
1007
1008 // Test that -v is not recognized as version (it would be unknown)
1009 let args = &["-v"];
1010 let raw_opts = RawOpts::new(args, TestOpts::specs());
1011 let (opts, len) = TestOpts::parse(&raw_opts).unwrap();
1012 assert_eq!(
1013 opts,
1014 TestOpts {
1015 a: false,
1016 b: false,
1017 c: None
1018 }
1019 );
1020 assert_eq!(len, 1);
1021 }
1022}