just_getopt/
lib.rs

1#![warn(missing_docs)]
2
3//! # Introduction
4//!
5//! This library crate implements a Posix `getopt`-like command-line
6//! option parser with simple programming interface. More specifically
7//! the parser is like `getopt`'s GNU extension called `getopt_long`
8//! which is familiar command-line option format for users of
9//! Linux-based operating systems.
10//!
11//! The name is `just_getopt` because this is *just a getopt parser* and
12//! (almost) nothing more. The intent is to provide the parsed output
13//! and basic methods for examining the output. There will not be
14//! anything for interpreting the output or for printing messages to
15//! program's user. The responsibility of interpretation is left to your
16//! program.
17//!
18//! In getopt logic there are two types of command-line options:
19//!
20//!  1. short options with a single letter name (`-f`)
21//!  2. long options with more than one letter as their name (`--file`).
22//!
23//! Both option types may accept an optional value or they may require a
24//! value. Values are given after the option. See the section **Parsing
25//! Rules** below for more information.
26//!
27//! Programming examples are in the **Examples** section below and in
28//! the source code repository's "examples" directory.
29//!
30//! # Parsing Rules
31//!
32//! By default, all options are expected to come first in the command
33//! line. Other arguments (non-options) come after options. Therefore
34//! the first argument that does not look like an option stops option
35//! parsing and the rest of the command line is parsed as non-options.
36//! This default can be changed, so that options and non-options can be
37//! mixed in their order in the command line. See [`OptSpecs::flag`]
38//! method for more information.
39//!
40//! In command line the "pseudo option" `--` (two dashes) always stops
41//! the option parser. Then the rest of the command line is parsed as
42//! regular arguments (non-options).
43//!
44//! ## Short Options
45//!
46//! Short options in the command line start with the `-` character which
47//! is followed by option's name character (`-c`), usually a letter.
48//!
49//! If option requires a value the value must be entered either directly
50//! after the option character (`-cVALUE`) or as the next command-line
51//! argument (`-c VALUE`). In the latter case anything that follows `-c`
52//! will be parsed as option's value.
53//!
54//! If option accepts an optional value the value must always be entered
55//! directly after the option character (`-cVALUE`). Otherwise there is
56//! no value for this option.
57//!
58//! Several short options can be entered together after one `-`
59//! character (`-abc`) but then only the last option in the series may
60//! have required or optional value.
61//!
62//! ## Long Options
63//!
64//! Long options start with `--` characters and the option name comes
65//! directly after it (`--foo`). The name must be at least two
66//! characters long.
67//!
68//! If option requires a value the value must be entered either directly
69//! after the option name and `=` character (`--foo=VALUE`) or as the
70//! next command-line argument (`--foo VALUE`). In the latter case
71//! anything that follows `--foo` will be parsed as option's value.
72//!
73//! If option accepts an optional value the value must always be entered
74//! directly after the option name and `=` character (`--foo=VALUE`).
75//! Otherwise there is no value for this option.
76//!
77//! Option `--foo=` is valid format when the option requires a value or
78//! accepts an optional value. It means that the value is empty string.
79//! It is not valid format when the option does not accept a value.
80//!
81//! # Examples
82//!
83//! Following examples will guide through a typical use of this library
84//! crate and command-line parsing.
85//!
86//! ## Prepare
87//!
88//! First we bring some important paths into the scope of our program.
89//!
90//! ```
91//! use just_getopt::{OptFlags, OptSpecs, OptValueType};
92//! ```
93//!
94//! Then we define which command-line options are valid for the program.
95//! We do this by creating an instance of [`OptSpecs`] struct by calling
96//! function [`OptSpecs::new`]. Then we modify the struct instance with
97//! [`option`](OptSpecs::option) and [`flag`](OptSpecs::flag) methods.
98//!
99//! ```
100//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
101//! let specs = OptSpecs::new()
102//!     .option("help", "h", OptValueType::None) // Arguments: (id, name, value_type)
103//!     .option("help", "help", OptValueType::None)
104//!     .option("file", "f", OptValueType::Required)
105//!     .option("file", "file", OptValueType::Required)
106//!     .option("verbose", "v", OptValueType::Optional)
107//!     .option("verbose", "verbose", OptValueType::Optional)
108//!     .flag(OptFlags::OptionsEverywhere);
109//! ```
110//!
111//! Each [`option`](OptSpecs::option) method above adds a single option
112//! information to the option specification. Method's arguments are:
113//!
114//!  1. `id`: Programmer's identifier string for the option. The same
115//!     identifier is used later to check if this particular option was
116//!     present in the command line. Several options may have the same
117//!     `id`. This makes sense when short option and long option have
118//!     the same meaning, like `-h` and `--help` for printing help.
119//!
120//!  2. `name`: Option's name string in the command line, without
121//!     prefix. A single-character name (like `h`) defines a short
122//!     option which is entered like `-h` in the command line. Longer
123//!     name defines a long option which is entered like `--help` in the
124//!     command line.
125//!
126//!  3. `value_type`: Whether or not this option accepts a value and if
127//!     the value is optional or required. The argument is a variant of
128//!     enum [`OptValueType`].
129//!
130//! The [`flag`](OptSpecs::flag) method above adds a configuration flag
131//! for the command-line parser. It is a variant of enum [`OptFlags`].
132//! This variant [`OptionsEverywhere`](OptFlags::OptionsEverywhere)
133//! changes the command-line parser to accept options and other
134//! arguments in mixed order in the command line. That is, options can
135//! come after non-option arguments.
136//!
137//! For better explanation see the documentation of [`OptSpecs`] struct
138//! and its methods [`option`](OptSpecs::option) and
139//! [`flag`](OptSpecs::flag). Also see methods
140//! [`limit_options`](OptSpecs::limit_options),
141//! [`limit_other_args`](OptSpecs::limit_other_args) and
142//! [`limit_unknown_options`](OptSpecs::limit_unknown_options).
143//!
144//! ## Parse the Command Line
145//!
146//! We are ready to parse program's command-line arguments. We do this
147//! with [`OptSpecs::getopt`] method. Arguments we get from
148//! [`std::env::args`] function which returns an iterator.
149//!
150//! ```
151//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
152//! # let specs = OptSpecs::new();
153//! // Get arguments iterator from operating system and skip the first item
154//! let args = std::env::args().skip(1); // which is this program's file path.
155//! let parsed = specs.getopt(args); // Getopt! Use the "specs" variable defined above.
156//! ```
157//!
158//! If you want to try [`getopt`](OptSpecs::getopt) method without
159//! program's real command-line arguments you can also run it with other
160//! iterator argument or with a vector or an array as an argument. Like
161//! this:
162//!
163//! ```
164//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
165//! # let specs = OptSpecs::new();
166//! let parsed = specs.getopt(["--file=123", "-f456", "foo", "-av", "bar"]);
167//! ```
168//!
169//! ## Examine the Parsed Output
170//!
171//! The command line is now parsed and the variable `parsed` (see above)
172//! owns an [`Args`] struct which represents the parsed output in
173//! organized form. It is a public struct and it can be examined
174//! manually. There are some methods for convenience, and some of them
175//! are shown in the following examples.
176//!
177//! At this stage it is useful to see the returned [`Args`] struct. One
178//! of its fields may contain some [`Opt`] structs too if the parser
179//! found valid command-line options. Let's print it:
180//!
181//! ```
182//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
183//! # let specs = OptSpecs::new();
184//! # let parsed = specs.getopt(["--file=123", "-f456", "foo", "-av", "bar"]);
185//! eprintln!("{:#?}", parsed);
186//! ```
187//!
188//! That could print something like this:
189//!
190//! ```text
191//! Args {
192//!     options: [
193//!         Opt {
194//!             id: "file",
195//!             name: "file",
196//!             value_required: true,
197//!             value: Some(
198//!                 "123",
199//!             ),
200//!         },
201//!         Opt {
202//!             id: "file",
203//!             name: "f",
204//!             value_required: true,
205//!             value: Some(
206//!                 "456",
207//!             ),
208//!         },
209//!         Opt {
210//!             id: "verbose",
211//!             name: "v",
212//!             value_required: false,
213//!             value: None,
214//!         },
215//!     ],
216//!     other: [
217//!         "foo",
218//!         "bar",
219//!     ],
220//!     unknown: [
221//!         "a",
222//!     ],
223//! }
224//! ```
225//!
226//! ### Unknown Options
227//!
228//! We probably want to tell program's user if there were unknown
229//! options. An error message to [`std::io::stderr`] stream is usually
230//! enough. No need to panic.
231//!
232//! ```
233//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
234//! # let specs = OptSpecs::new();
235//! # let parsed = specs.getopt(["--file=123", "-f456", "foo", "-av", "bar"]);
236//! for u in &parsed.unknown {
237//!     eprintln!("Unknown option: {}", u);
238//! }
239//! ```
240//!
241//! ### Required Value Missing
242//!
243//! More serious error is a missing value to an option which requires a
244//! value (like `file` option in our example, see above). That can be a
245//! good reason to exit the program.
246//!
247//! ```no_run
248//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
249//! # let specs = OptSpecs::new();
250//! # let parsed = specs.getopt(["--file=123", "-f456", "foo", "-av", "bar"]);
251//! for o in &parsed.required_value_missing() {
252//!     eprintln!("Value is required for option '{}'.", o.name);
253//!     std::process::exit(1);
254//! }
255//! ```
256//!
257//! ### Print Help Message
258//!
259//! Command-line programs always have `-h` or `--help` option for
260//! printing a friendly help message. The following example shows how to
261//! detect that option.
262//!
263//! ```no_run
264//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
265//! # let specs = OptSpecs::new();
266//! # let parsed = specs.getopt(["--file=123", "-f456", "foo", "-av", "bar"]);
267//! if parsed.option_exists("help") {
268//!     println!("Print friendly help about program's usage.");
269//!     std::process::exit(2);
270//! }
271//! ```
272//!
273//! The `"help"` string in the first line above is the identifier string
274//! (`id`) for the option. It was defined with [`OptSpecs::option`]
275//! method in the example code earlier. Identifier strings are used to
276//! find if a specific option was given in the command line.
277//!
278//! ### Collect Values and Other Arguments
279//!
280//! The rest depends very much on individual program's needs. Probably
281//! often we would collect what values were given to options. In our
282//! example program there are `-f` and `--file` options that require a
283//! value. We could collect all those values next.
284//!
285//! ```
286//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
287//! # let specs = OptSpecs::new();
288//! # let parsed = specs.getopt(["--file=123", "-f456", "foo", "-av", "bar"]);
289//! for f in &parsed.options_value_all("file") {
290//!     println!("File name: {:?}", f);
291//! }
292//! ```
293//!
294//! Notice if `-v` or `--verbose` was given, even without a value. Then
295//! collect all (optional) values for the option.
296//!
297//! ```
298//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
299//! # let specs = OptSpecs::new();
300//! # let parsed = specs.getopt(["--file=123", "-f456", "foo", "-av", "bar"]);
301//! if parsed.option_exists("verbose") {
302//!     println!("Option 'verbose' was given.");
303//!
304//!     for v in &parsed.options_value_all("verbose") {
305//!         println!("Verbose level: {:?}", v);
306//!     }
307//! }
308//! ```
309//!
310//! Finally, our example program will handle all other arguments, that
311//! is, non-option arguments.
312//!
313//! ```
314//! # use just_getopt::{OptFlags, OptSpecs, OptValueType};
315//! # let specs = OptSpecs::new();
316//! # let parsed = specs.getopt(["--file=123", "-f456", "foo", "-av", "bar"]);
317//! for o in &parsed.other {
318//!     println!("Other argument: {:?}", o);
319//! }
320//! ```
321//!
322//! # More Help
323//!
324//! A complete working example code -- very similar to previous examples
325//! -- is in the source code repository's "examples" directory. It can
326//! be run with command `cargo run --example basic -- your arguments`.
327//! Try it with different command-line arguments.
328//!
329//! Further reading:
330//!
331//!   - [`OptSpecs`] struct and its methods.
332//!   - [`Args`] struct and its methods.
333
334mod parser;
335
336/// Specification for program's valid command-line options.
337///
338/// An instance of this struct is needed before command-line options can
339/// be parsed. Instances are created with function [`OptSpecs::new`] and
340/// they are modified with [`option`](OptSpecs::option) and other
341/// methods
342///
343/// The struct instance is used when parsing the command line given by
344/// program's user. The parser methods is [`getopt`](OptSpecs::getopt).
345
346#[derive(Debug, PartialEq)]
347pub struct OptSpecs {
348    options: Vec<OptSpec>,
349    flags: Vec<OptFlags>,
350    option_limit: u32,
351    other_limit: u32,
352    unknown_limit: u32,
353}
354
355const COUNTER_LIMIT: u32 = u32::MAX;
356
357#[derive(Debug, PartialEq)]
358struct OptSpec {
359    id: String,
360    name: String,
361    value_type: OptValueType,
362}
363
364/// Option's value type.
365///
366/// See [`OptSpecs::option`] method for more information.
367
368#[derive(Debug, PartialEq)]
369pub enum OptValueType {
370    /// Option does not accept a value.
371    None,
372    /// Option accepts an optional value.
373    Optional,
374    /// Option requires a value.
375    Required,
376}
377
378/// Flags for changing command-line parser's behavior.
379///
380/// See [`OptSpecs::flag`] method for more information.
381
382#[derive(Debug, PartialEq)]
383#[non_exhaustive]
384pub enum OptFlags {
385    /// Accept command-line options and other arguments in mixed order
386    /// in the command line. That is, options can come after non-option
387    /// arguments.
388    ///
389    /// This is not the default behavior. By default the first
390    /// non-option argument in the command line stops option parsing and
391    /// the rest of the command line is parsed as non-options (other
392    /// arguments), even if they look like options.
393    OptionsEverywhere,
394
395    /// Long options don't need to be written in full in the command
396    /// line. They can be shortened as long as there are enough
397    /// characters to find a unique prefix match. If there are more than
398    /// one match the option given in the command line is classified as
399    /// unknown.
400    PrefixMatchLongOptions,
401}
402
403impl OptSpecs {
404    /// Create and return a new instance of [`OptSpecs`] struct.
405    ///
406    /// The created instance is "empty" and does not contain any
407    /// specifications for command-line options. Apply
408    /// [`option`](OptSpecs::option) or other methods to make it useful
409    /// for parsing command-line.
410    pub fn new() -> Self {
411        Self {
412            options: Vec::new(),
413            flags: Vec::new(),
414            option_limit: COUNTER_LIMIT,
415            other_limit: COUNTER_LIMIT,
416            unknown_limit: COUNTER_LIMIT,
417        }
418    }
419
420    /// Add an option specification for [`OptSpecs`].
421    ///
422    /// The method requires three arguments:
423    ///
424    ///  1. `id`: Programmer's identifier string for the option. Later,
425    ///     after parsing the command line, the identifier is used to
426    ///     match if this particular option was present in the
427    ///     command-line.
428    ///
429    ///     Several options may have the same identifier string. This
430    ///     makes sense when different option names in the command line
431    ///     represent the same meaning, like `-h` and `--help` for
432    ///     printing program's help message.
433    ///
434    ///  2. `name`: Option's name string in the command line (without
435    ///     prefix). If the string is a single character (like `h`) it
436    ///     defines a short option which is entered as `-h` in the
437    ///     command line. If there are more than one character in the
438    ///     string it defines a long option name (like `help`) which is
439    ///     entered as `--help` in the command line.
440    ///
441    ///     All options must have a unique `name` string. This method
442    ///     will panic if the same `name` is added twice. The method
443    ///     will also panic if the `name` string contains illegal
444    ///     characters. Space characters are not accepted. A short
445    ///     option name can't be `-` and long option names can't have
446    ///     any `=` characters nor `-` as their first character.
447    ///
448    ///  3. `value_type`: A variant of enum [`OptValueType`] which
449    ///     defines if this option accepts a value. If not, use
450    ///     [`OptValueType::None`] as method's argument. If an optional
451    ///     value is accepted, use [`OptValueType::Optional`]. If the
452    ///     option requires a value, use [`OptValueType::Required`].
453    ///
454    /// The return value is the same struct instance which was modified.
455    pub fn option(mut self, id: &str, name: &str, value_type: OptValueType) -> Self {
456        assert!(
457            id.chars().count() > 0,
458            "Option's \"id\" must be at least 1 character long."
459        );
460
461        let name_count = name.chars().count();
462
463        if name_count == 1 {
464            assert!(
465                parser::is_valid_short_option_name(name),
466                "Not a valid short option name."
467            );
468        } else if name_count >= 2 {
469            assert!(
470                parser::is_valid_long_option_name(name),
471                "Not a valid long option name."
472            );
473        } else {
474            panic!("Option's \"name\" must be at least 1 character long.");
475        }
476
477        for e in &self.options {
478            assert!(
479                e.name != name,
480                "No duplicates allowed for option's \"name\"."
481            );
482        }
483
484        self.options.push(OptSpec {
485            id: id.to_string(),
486            name: name.to_string(),
487            value_type,
488        });
489        self
490    }
491
492    /// Add a flag that changes parser's behavior.
493    ///
494    /// Method's only argument `flag` is a variant of enum [`OptFlags`].
495    /// Their names and meanings are:
496    ///
497    ///   - [`OptFlags::OptionsEverywhere`]: Accept command-line options
498    ///     and other arguments in mixed order in the command line. That
499    ///     is, options can come after non-option arguments.
500    ///
501    ///     This is not the default behavior. By default the first
502    ///     non-option argument in the command line stops option parsing
503    ///     and the rest of the command line is parsed as non-options
504    ///     (other arguments), even if they look like options.
505    ///
506    ///   - [`OptFlags::PrefixMatchLongOptions`]: With this flag long
507    ///     options don't need to be written in full in the command
508    ///     line. They can be shortened as long as there are enough
509    ///     characters to find a unique prefix match. If there are more
510    ///     than one match the option which was given in the command
511    ///     line is classified as unknown.
512    ///
513    /// The return value is the same struct instance which was modified.
514    pub fn flag(mut self, flag: OptFlags) -> Self {
515        if !self.flags.contains(&flag) {
516            self.flags.push(flag);
517        }
518        self
519    }
520
521    fn is_flag(&self, flag: OptFlags) -> bool {
522        self.flags.contains(&flag)
523    }
524
525    /// Maximum number of valid options.
526    ///
527    /// Method's argument `limit` sets the maximum number of valid
528    /// options to collect from the command line. The rest is ignored.
529    /// This doesn't include unknown options (see
530    /// [`limit_unknown_options`](OptSpecs::limit_unknown_options)).
531    ///
532    /// The return value is the same struct instance which was modified.
533    pub fn limit_options(mut self, limit: u32) -> Self {
534        self.option_limit = limit;
535        self
536    }
537
538    /// Maximum number of other command-line arguments.
539    ///
540    /// Method's argument `limit` sets the maximum number of other
541    /// (non-option) arguments to collect from the command line. The
542    /// rest is ignored.
543    ///
544    /// Note: If your program accepts *n* number of command-line
545    /// argument (apart from options) you could set this limit to *n +
546    /// 1*. This way you know if there were more arguments than needed
547    /// and can inform program's user about that. There is no need to
548    /// collect more arguments.
549    ///
550    /// The return value is the same struct instance which was modified.
551    pub fn limit_other_args(mut self, limit: u32) -> Self {
552        self.other_limit = limit;
553        self
554    }
555
556    /// Maximum number of unknown options.
557    ///
558    /// Method's argument `limit` sets the maximum number of unique
559    /// unknown options to collect from the command line. Duplicates are
560    /// not collected.
561    ///
562    /// Note: If you want to stop your program if it notices just one
563    /// unknown option you can set this limit to 1. There is probably no
564    /// need to collect more of them.
565    ///
566    /// The return value is the same struct instance which was modified.
567    pub fn limit_unknown_options(mut self, limit: u32) -> Self {
568        self.unknown_limit = limit;
569        self
570    }
571
572    /// Getopt-parse an iterable item as command line arguments.
573    ///
574    /// This method's argument `args` is of any type that implements
575    /// trait [`IntoIterator`] and that has items of type that
576    /// implements trait [`ToString`]. For example, argument `args` can
577    /// be a vector or an iterator such as command-line arguments
578    /// returned by [`std::env::args`].
579    ///
580    /// The return value is an [`Args`] struct which represents the
581    /// command-line information in organized form.
582    pub fn getopt<I, S>(&self, args: I) -> Args
583    where
584        I: IntoIterator<Item = S>,
585        S: ToString,
586    {
587        parser::parse(self, args.into_iter().map(|i| i.to_string()))
588    }
589
590    fn get_short_option_match(&self, name: &str) -> Option<&OptSpec> {
591        if name.chars().count() != 1 {
592            return None;
593        }
594        self.options.iter().find(|e| e.name == name)
595    }
596
597    fn get_long_option_match(&self, name: &str) -> Option<&OptSpec> {
598        if name.chars().count() < 2 {
599            return None;
600        }
601        self.options.iter().find(|e| e.name == name)
602    }
603
604    fn get_long_option_prefix_match(&self, name: &str) -> Option<&OptSpec> {
605        if name.chars().count() < 2 {
606            return None;
607        }
608
609        let mut result = None;
610
611        for e in &self.options {
612            if e.name.starts_with(name) {
613                if result.is_none() {
614                    result = Some(e);
615                } else {
616                    return None;
617                }
618            }
619        }
620        result
621    }
622}
623
624impl Default for OptSpecs {
625    fn default() -> Self {
626        Self::new()
627    }
628}
629
630/// Parsed command line in organized form.
631///
632/// Instances of this struct are usually created with
633/// [`OptSpecs::getopt`] method and an instance represents the parsed
634/// output in organized form. See each field's documentation for more
635/// information.
636///
637/// Programmers can use the parsed output ([`Args`] struct) any way they
638/// like. There are some methods for convenience.
639
640#[derive(Debug, PartialEq)]
641pub struct Args {
642    /// A vector of valid command-line options.
643    ///
644    /// Elements of this vector are [`Opt`] structs which each
645    /// represents a single command-line option. Elements are in the
646    /// same order as given (by program's user) in the command line. The
647    /// vector is empty if the parser didn't find any valid command-line
648    /// options.
649    pub options: Vec<Opt>,
650
651    /// A vector of other arguments (non-options).
652    ///
653    /// Each element of the vector is a single non-option argument
654    /// string in the same order as given (by program's user) in the
655    /// command line. The vector is empty if the parser didn't find any
656    /// non-option arguments.
657    pub other: Vec<String>,
658
659    /// Unknown options.
660    ///
661    /// Command-line arguments that look like options but were not part
662    /// of [`OptSpecs`] specification are classified as unknown. They
663    /// are listed in this vector. Possible duplicate unknown options
664    /// have been filtered out.
665    ///
666    /// Each element is the name string for the option (without `-` or
667    /// `--` prefix). For unknown short options the element is a
668    /// single-character string. For unknown long options the string has
669    /// more than one character. The whole vector is empty if there were
670    /// no unknown options.
671    ///
672    /// If a long option does not accept a value (that is, its value
673    /// type is [`OptValueType::None`]) but user gives it a value with
674    /// equal sign notation (`--foo=`), that option is classified as
675    /// unknown and it will be in this field's vector with name `foo=`.
676    pub unknown: Vec<String>,
677}
678
679impl Args {
680    fn new() -> Self {
681        Args {
682            options: Vec::new(),
683            other: Vec::new(),
684            unknown: Vec::new(),
685        }
686    }
687
688    /// Find options with missing required value.
689    ///
690    /// This method finds all (otherwise valid) options which require a
691    /// value but the value is missing. That is, [`OptSpecs`] struct
692    /// specification defined that an option requires a value but
693    /// program's user didn't give one in the command line. Such thing
694    /// can happen if an option like `--file` is the last argument in
695    /// the command line and that option requires a value. Empty string
696    /// `""` is not classified as missing value because it can be valid
697    /// user input in many situations.
698    ///
699    /// This method returns a vector (possibly empty) and each element
700    /// is a reference to an [`Opt`] struct in the original
701    /// [`Args::options`] field contents.
702    pub fn required_value_missing(&self) -> Vec<&Opt> {
703        self.options
704            .iter()
705            .filter(|opt| opt.value_required && opt.value.is_none())
706            .collect()
707    }
708
709    /// Return boolean whether option with the given `id` exists.
710    ///
711    /// This is functionally the same as
712    /// [`options_first`](Args::options_first)`(id).is_some()`.
713    pub fn option_exists(&self, id: &str) -> bool {
714        self.options.iter().any(|opt| opt.id == id)
715    }
716
717    /// Find all options with the given `id`.
718    ///
719    /// Find all options which have the identifier `id`. (Option
720    /// identifiers have been defined in [`OptSpecs`] struct before
721    /// parsing.) The return value is a vector (possibly empty, if no
722    /// matches) and each element is a reference to [`Opt`] struct in
723    /// the original [`Args`] struct. Elements in the vector are in the
724    /// same order as in the parsed command line.
725    pub fn options_all(&self, id: &str) -> Vec<&Opt> {
726        self.options.iter().filter(|opt| opt.id == id).collect()
727    }
728
729    /// Find the first option with the given `id`.
730    ///
731    /// Find and return the first match for option `id` in command-line
732    /// arguments' order. (Options' identifiers have been defined in
733    /// [`OptSpecs`] struct before parsing.)
734    ///
735    /// The return value is a variant of enum [`Option`]. Their
736    /// meanings:
737    ///
738    ///   - `None`: No options found with the given `id`.
739    ///
740    ///   - `Some(&Opt)`: An option was found with the given `id` and a
741    ///     reference is provided to its [`Opt`] struct in the original
742    ///     [`Args::options`] field.
743    pub fn options_first(&self, id: &str) -> Option<&Opt> {
744        self.options.iter().find(|opt| opt.id == id)
745    }
746
747    /// Find the last option with the given `id`.
748    ///
749    /// This is similar to [`options_first`](Args::options_first) method
750    /// but this returns the last match in command-line arguments'
751    /// order.
752    pub fn options_last(&self, id: &str) -> Option<&Opt> {
753        self.options.iter().rev().find(|opt| opt.id == id)
754    }
755
756    /// Find and return all values for options with the given `id`.
757    ///
758    /// Find all options which match the identifier `id` and which also
759    /// have a value assigned. (Options' identifiers have been defined
760    /// in [`OptSpecs`] struct before parsing.) Collect options' values
761    /// into a new vector in the same order as they were given in the
762    /// command line. Vector's elements are references to the value
763    /// strings in the original [`Args`] struct. The returned vector is
764    /// empty if there were no matches.
765    pub fn options_value_all(&self, id: &str) -> Vec<&String> {
766        self.options
767            .iter()
768            .filter_map(|opt| {
769                if opt.id == id {
770                    opt.value.as_ref()
771                } else {
772                    None
773                }
774            })
775            .collect()
776    }
777
778    /// Find the first option with a value for given option `id`.
779    ///
780    /// Find the first option with the identifier `id` and which has a
781    /// value assigned. (Options' identifiers have been defined in
782    /// [`OptSpecs`] struct before parsing.) Method's return value is a
783    /// variant of enum [`Option`] which are:
784    ///
785    ///   - `None`: No options found with the given `id` and a value
786    ///     assigned. Note that there could be options for the same `id`
787    ///     but they don't have a value.
788    ///
789    ///   - `Some(&String)`: An option was found with the given `id` and
790    ///     the option has a value assigned. A reference is provided to
791    ///     the string value in the [`Opt::value`] field in the original
792    ///     [`Args::options`] field.
793    pub fn options_value_first(&self, id: &str) -> Option<&String> {
794        match self
795            .options
796            .iter()
797            .find(|opt| opt.id == id && opt.value.is_some())
798        {
799            Some(o) => o.value.as_ref(),
800            None => None,
801        }
802    }
803
804    /// Find the last option with a value for given option `id`.
805    ///
806    /// This is similar to
807    /// [`options_value_first`](Args::options_value_first) method but
808    /// this method finds and returns the last option's value.
809    ///
810    /// Note: Program's user may give the same option several times in
811    /// the command line. If the option accepts a value it may be
812    /// suitable to consider only the last value relevant. (Or the
813    /// first, or maybe print an error message for providing several,
814    /// possibly conflicting, values.)
815    pub fn options_value_last(&self, id: &str) -> Option<&String> {
816        match self
817            .options
818            .iter()
819            .rev()
820            .find(|opt| opt.id == id && opt.value.is_some())
821        {
822            Some(o) => o.value.as_ref(),
823            None => None,
824        }
825    }
826}
827
828/// Structured option information.
829///
830/// This [`Opt`] struct represents organized information about single
831/// command-line option. Instances of this struct are usually created by
832/// [`OptSpecs::getopt`] method which returns an [`Args`] struct which
833/// have these [`Opt`] structs inside.
834///
835/// A programmer may need these when examining parsed command-line
836/// options. See the documentation of individual fields for more
837/// information. Also see [`Args`] struct and its methods.
838
839#[derive(Debug, PartialEq)]
840pub struct Opt {
841    /// Identifier for the option.
842    ///
843    /// Identifiers are defined with [`OptSpecs::option`] method before
844    /// parsing command-line arguments. After [`OptSpecs::getopt`]
845    /// parsing the same identifier is copied here and it confirms that
846    /// the option was indeed given in the command line.
847    pub id: String,
848
849    /// Option's name in the parsed command line.
850    ///
851    /// Option's name that was used in the command line. For short
852    /// options this is a single-character string. For long options the
853    /// name has more than one characters.
854    pub name: String,
855
856    /// The option requires a value.
857    ///
858    /// `true` means that the option was defined with value type
859    /// [`OptValueType::Required`]. See [`OptSpecs::flag`] method for
860    /// more information. This field does not guarantee that there
861    /// actually was a value for the option in the command line.
862    pub value_required: bool,
863
864    /// Option's value.
865    ///
866    /// The value is a variant of enum [`Option`]. Value `None` means
867    /// that there is no value for the option. Value `Some(String)`
868    /// provides a value.
869    pub value: Option<String>,
870}
871
872#[cfg(test)]
873mod tests {
874    use super::*;
875
876    #[test]
877    fn t_create_optspecs_01() {
878        let mut spec;
879        let mut expect;
880
881        spec = OptSpecs::new().option("help", "help", OptValueType::None);
882        expect = OptSpec {
883            id: String::from("help"),
884            name: String::from("help"),
885            value_type: OptValueType::None,
886        };
887        assert_eq!(1, spec.options.len());
888        assert_eq!(&expect, &spec.options[0]);
889        assert_eq!(COUNTER_LIMIT, spec.option_limit);
890        assert_eq!(COUNTER_LIMIT, spec.other_limit);
891        assert_eq!(COUNTER_LIMIT, spec.unknown_limit);
892
893        spec = spec.option("file", "f", OptValueType::Optional);
894        expect = OptSpec {
895            id: String::from("file"),
896            name: String::from("f"),
897            value_type: OptValueType::Optional,
898        };
899        assert_eq!(2, spec.options.len());
900        assert_eq!(&expect, &spec.options[1]);
901
902        spec = spec.option("file", "file", OptValueType::Required);
903        expect = OptSpec {
904            id: String::from("file"),
905            name: String::from("file"),
906            value_type: OptValueType::Required,
907        };
908        assert_eq!(3, spec.options.len());
909        assert_eq!(&expect, &spec.options[2]);
910
911        spec = spec.flag(OptFlags::OptionsEverywhere);
912        assert_eq!(1, spec.flags.len()); // Length 1
913        assert_eq!(true, spec.is_flag(OptFlags::OptionsEverywhere));
914        spec = spec.flag(OptFlags::PrefixMatchLongOptions);
915        assert_eq!(2, spec.flags.len()); // Length 2
916        assert_eq!(true, spec.is_flag(OptFlags::PrefixMatchLongOptions));
917        spec = spec.flag(OptFlags::OptionsEverywhere);
918        spec = spec.flag(OptFlags::PrefixMatchLongOptions);
919        assert_eq!(2, spec.flags.len()); // Length still 2
920
921        spec = spec.limit_options(9);
922        spec = spec.limit_other_args(10);
923        spec = spec.limit_unknown_options(3);
924        assert_eq!(9, spec.option_limit);
925        assert_eq!(10, spec.other_limit);
926        assert_eq!(3, spec.unknown_limit);
927    }
928
929    #[test]
930    #[should_panic]
931    fn t_create_optspecs_02() {
932        let _spec = OptSpecs::new().option("", "h", OptValueType::None);
933    }
934
935    #[test]
936    #[should_panic]
937    fn t_create_optspecs_03() {
938        let _spec = OptSpecs::new().option("h", "h", OptValueType::None).option(
939            "h",
940            "h",
941            OptValueType::None,
942        );
943    }
944
945    #[test]
946    #[should_panic]
947    fn t_create_optspecs_04() {
948        let _spec = OptSpecs::new().option("h", "", OptValueType::None);
949    }
950
951    #[test]
952    fn t_is_flag() {
953        let mut spec = OptSpecs::new().flag(OptFlags::OptionsEverywhere);
954        assert_eq!(true, spec.is_flag(OptFlags::OptionsEverywhere));
955
956        spec = spec.flag(OptFlags::PrefixMatchLongOptions);
957        assert_eq!(true, spec.is_flag(OptFlags::PrefixMatchLongOptions));
958    }
959
960    #[test]
961    fn t_parsed_output_010() {
962        let parsed = OptSpecs::new()
963            .option("help", "h", OptValueType::None)
964            .option("help", "help", OptValueType::None)
965            .option("file", "f", OptValueType::Required)
966            .option("file", "file", OptValueType::Required)
967            .getopt(["-h", "--help", "-f123", "-f", "456", "foo", "bar"]);
968
969        assert_eq!(true, parsed.option_exists("help"));
970        assert_eq!(true, parsed.option_exists("file"));
971        assert_eq!(false, parsed.option_exists("x"));
972
973        assert_eq!("h", parsed.options_first("help").unwrap().name);
974        assert_eq!("help", parsed.options_last("help").unwrap().name);
975        assert_eq!("help", parsed.options_first("help").unwrap().id);
976        assert_eq!("help", parsed.options_last("help").unwrap().id);
977        assert_eq!(false, parsed.options_first("help").unwrap().value_required);
978        assert_eq!(false, parsed.options_last("help").unwrap().value_required);
979
980        assert_eq!("f", parsed.options_first("file").unwrap().name);
981        assert_eq!(
982            "123",
983            parsed.options_first("file").unwrap().value.clone().unwrap()
984        );
985        assert_eq!(
986            "456",
987            parsed.options_last("file").unwrap().value.clone().unwrap()
988        );
989        assert_eq!(true, parsed.options_first("file").unwrap().value_required);
990
991        assert_eq!("foo", parsed.other[0]);
992        assert_eq!("bar", parsed.other[1]);
993    }
994
995    #[test]
996    fn t_parsed_output_020() {
997        let parsed = OptSpecs::new()
998            .limit_options(1)
999            .limit_other_args(2)
1000            .option("help", "h", OptValueType::None)
1001            .getopt(["-h", "foo", "-h"]);
1002
1003        assert_eq!("h", parsed.options_first("help").unwrap().name);
1004        assert_eq!(2, parsed.other.len());
1005        assert_eq!("foo", parsed.other[0]);
1006        assert_eq!("-h", parsed.other[1]);
1007    }
1008
1009    #[test]
1010    fn t_parsed_output_030() {
1011        let parsed = OptSpecs::new()
1012            .flag(OptFlags::OptionsEverywhere)
1013            .option("help", "h", OptValueType::None)
1014            .option("help", "help", OptValueType::None)
1015            .option("file", "f", OptValueType::Required)
1016            .option("file", "file", OptValueType::Required)
1017            .getopt(["-h", "foo", "--help", "--file=123", "bar", "--file", "456"]);
1018
1019        assert_eq!("h", parsed.options_first("help").unwrap().name);
1020        assert_eq!("help", parsed.options_last("help").unwrap().name);
1021        assert_eq!(
1022            "123",
1023            parsed.options_first("file").unwrap().value.clone().unwrap()
1024        );
1025        assert_eq!(
1026            "456",
1027            parsed.options_last("file").unwrap().value.clone().unwrap()
1028        );
1029        assert_eq!("foo", parsed.other[0]);
1030        assert_eq!("bar", parsed.other[1]);
1031    }
1032
1033    #[test]
1034    fn t_parsed_output_040() {
1035        let parsed = OptSpecs::new()
1036            .option("debug", "d", OptValueType::Optional)
1037            .option("verbose", "verbose", OptValueType::Optional)
1038            .getopt(["-d1", "-d", "--verbose", "--verbose=123"]);
1039
1040        assert_eq!(
1041            "1",
1042            parsed
1043                .options_first("debug")
1044                .unwrap()
1045                .value
1046                .clone()
1047                .unwrap()
1048        );
1049        assert_eq!(None, parsed.options_last("debug").unwrap().value);
1050        assert_eq!(false, parsed.options_last("debug").unwrap().value_required);
1051
1052        assert_eq!(None, parsed.options_first("verbose").unwrap().value);
1053        assert_eq!(
1054            "123",
1055            parsed
1056                .options_last("verbose")
1057                .unwrap()
1058                .value
1059                .clone()
1060                .unwrap()
1061        );
1062        assert_eq!(
1063            false,
1064            parsed.options_last("verbose").unwrap().value_required
1065        );
1066    }
1067
1068    #[test]
1069    fn t_parsed_output_050() {
1070        let parsed = OptSpecs::new()
1071            .option("debug", "d", OptValueType::Optional)
1072            .getopt(["-abcd", "-adbc"]);
1073
1074        assert_eq!(None, parsed.options_first("debug").unwrap().value);
1075        assert_eq!(
1076            "bc",
1077            parsed.options_last("debug").unwrap().value.clone().unwrap()
1078        );
1079
1080        assert_eq!(3, parsed.unknown.len());
1081        assert_eq!("a", parsed.unknown[0]);
1082        assert_eq!("b", parsed.unknown[1]);
1083        assert_eq!("c", parsed.unknown[2]);
1084    }
1085
1086    #[test]
1087    fn t_parsed_output_060() {
1088        let parsed = OptSpecs::new()
1089            .option("aaa", "bbb", OptValueType::None)
1090            .option("aaa", "c", OptValueType::None)
1091            .option("aaa", "d", OptValueType::None)
1092            .option("aaa", "eee", OptValueType::None)
1093            .getopt(["--bbb", "-cd", "--eee"]);
1094
1095        let m = parsed.options_all("aaa");
1096        assert_eq!("bbb", m[0].name);
1097        assert_eq!("c", m[1].name);
1098        assert_eq!("d", m[2].name);
1099        assert_eq!("eee", m[3].name);
1100    }
1101
1102    #[test]
1103    fn t_parsed_output_070() {
1104        let parsed = OptSpecs::new()
1105            .flag(OptFlags::PrefixMatchLongOptions)
1106            .option("version", "version", OptValueType::None)
1107            .option("verbose", "verbose", OptValueType::None)
1108            .getopt(["--ver", "--verb", "--versi", "--verbose"]);
1109
1110        assert_eq!("ver", parsed.unknown[0]);
1111        assert_eq!("verb", parsed.options_first("verbose").unwrap().name);
1112        assert_eq!("verbose", parsed.options_last("verbose").unwrap().name);
1113        assert_eq!("version", parsed.options_first("version").unwrap().id);
1114        assert_eq!("versi", parsed.options_first("version").unwrap().name);
1115    }
1116
1117    #[test]
1118    fn t_parsed_output_080() {
1119        let parsed = OptSpecs::new()
1120            // .flag(OptFlags::PrefixMatchLongOptions) Must be commented!
1121            .option("version", "version", OptValueType::None)
1122            .option("verbose", "verbose", OptValueType::None)
1123            .getopt(["--version", "--ver", "--verb", "--versi", "--verbose"]);
1124
1125        assert_eq!("ver", parsed.unknown[0]);
1126        assert_eq!("verb", parsed.unknown[1]);
1127        assert_eq!("versi", parsed.unknown[2]);
1128        assert_eq!("version", parsed.options_first("version").unwrap().name);
1129        assert_eq!("verbose", parsed.options_first("verbose").unwrap().name);
1130    }
1131
1132    #[test]
1133    fn t_parsed_output_090() {
1134        let parsed = OptSpecs::new()
1135            .flag(OptFlags::OptionsEverywhere)
1136            .option("help", "h", OptValueType::None)
1137            .option("file", "file", OptValueType::Required)
1138            .getopt(["-h", "foo", "--file=123", "--", "bar", "--file", "456"]);
1139
1140        assert_eq!("h", parsed.options_first("help").unwrap().name);
1141        assert_eq!("file", parsed.options_first("file").unwrap().name);
1142        assert_eq!(
1143            "123",
1144            parsed.options_first("file").unwrap().value.clone().unwrap()
1145        );
1146
1147        assert_eq!(4, parsed.other.len());
1148        assert_eq!("foo", parsed.other[0]);
1149        assert_eq!("bar", parsed.other[1]);
1150        assert_eq!("--file", parsed.other[2]);
1151        assert_eq!("456", parsed.other[3]);
1152    }
1153
1154    #[test]
1155    fn t_parsed_output_100() {
1156        let parsed = OptSpecs::new()
1157            .option("file", "file", OptValueType::Required)
1158            .getopt(["--file=", "--file"]);
1159
1160        assert_eq!(true, parsed.options_first("file").unwrap().value_required);
1161        assert_eq!(
1162            "",
1163            parsed.options_first("file").unwrap().value.clone().unwrap()
1164        );
1165        assert_eq!(None, parsed.options_last("file").unwrap().value);
1166    }
1167
1168    #[test]
1169    fn t_parsed_output_110() {
1170        let parsed = OptSpecs::new()
1171            .option("file", "f", OptValueType::Required)
1172            .option("debug", "d", OptValueType::Required)
1173            .getopt(["-fx", "-d", "", "-f"]);
1174
1175        assert_eq!(true, parsed.options_first("file").unwrap().value_required);
1176        assert_eq!(
1177            "x",
1178            parsed.options_first("file").unwrap().value.clone().unwrap()
1179        );
1180        assert_eq!(None, parsed.options_last("file").unwrap().value);
1181        assert_eq!(
1182            "",
1183            parsed
1184                .options_first("debug")
1185                .unwrap()
1186                .value
1187                .clone()
1188                .unwrap()
1189        );
1190    }
1191
1192    #[test]
1193    fn t_parsed_output_120() {
1194        let parsed = OptSpecs::new()
1195            .option("file", "f", OptValueType::Required)
1196            .option("debug", "d", OptValueType::Required)
1197            .getopt(["-f123", "-d", "", "-f", "456", "-f"]);
1198
1199        let f = parsed.options_value_all("file");
1200        let d = parsed.options_value_all("debug");
1201
1202        assert_eq!(2, f.len());
1203        assert_eq!("123", f[0]);
1204        assert_eq!("456", f[1]);
1205
1206        assert_eq!(1, d.len());
1207        assert_eq!("", d[0]);
1208
1209        assert_eq!(None, parsed.options_last("file").unwrap().value);
1210        let m = parsed.required_value_missing();
1211        assert_eq!(1, m.len());
1212        assert_eq!("f", m[0].name);
1213    }
1214
1215    #[test]
1216    fn t_parsed_output_130() {
1217        let parsed = OptSpecs::new()
1218            .option("file", "file", OptValueType::Required)
1219            .option("debug", "debug", OptValueType::Required)
1220            .getopt(["--file=123", "--debug", "", "--file", "456", "--file"]);
1221
1222        let f = parsed.options_value_all("file");
1223        let d = parsed.options_value_all("debug");
1224
1225        assert_eq!(2, f.len());
1226        assert_eq!("123", f[0]);
1227        assert_eq!("456", f[1]);
1228
1229        assert_eq!(1, d.len());
1230        assert_eq!("", d[0]);
1231
1232        assert_eq!(None, parsed.options_last("file").unwrap().value);
1233        let m = parsed.required_value_missing();
1234        assert_eq!(1, m.len());
1235        assert_eq!("file", m[0].name);
1236    }
1237
1238    #[test]
1239    fn t_parsed_output_140() {
1240        let parsed = OptSpecs::new()
1241            .flag(OptFlags::OptionsEverywhere)
1242            .option("debug", "d", OptValueType::Optional)
1243            .option("debug", "debug", OptValueType::Optional)
1244            .getopt([
1245                "-d",
1246                "-d123",
1247                "-d",
1248                "--debug",
1249                "--debug=",
1250                "foo",
1251                "--debug=456",
1252                "-d",
1253            ]);
1254
1255        let d = parsed.options_all("debug");
1256        assert_eq!(7, d.len());
1257
1258        let d = parsed.options_value_all("debug");
1259        assert_eq!(3, d.len());
1260        assert_eq!("123", d[0]);
1261        assert_eq!("", d[1]);
1262        assert_eq!("456", d[2]);
1263        assert_eq!("123", parsed.options_value_first("debug").unwrap());
1264        assert_eq!("456", parsed.options_value_last("debug").unwrap());
1265
1266        assert_eq!(None, parsed.options_value_first("not-at-all"));
1267        assert_eq!(None, parsed.options_value_last("not-at-all"));
1268
1269        assert_eq!("foo", parsed.other[0]);
1270    }
1271
1272    #[test]
1273    fn t_parsed_output_150() {
1274        let parsed = OptSpecs::new().limit_unknown_options(6).getopt([
1275            "-abcd",
1276            "-e",
1277            "--debug",
1278            "-x", // Won't be listed in unknown because of limit.
1279            "--",
1280            "--debug=",
1281            "foo",
1282            "--debug=456",
1283        ]);
1284
1285        assert_eq!(0, parsed.options.len());
1286        assert_eq!(3, parsed.other.len());
1287        assert_eq!(6, parsed.unknown.len());
1288        assert_eq!(vec!["a", "b", "c", "d", "e", "debug"], parsed.unknown);
1289    }
1290
1291    #[test]
1292    fn t_parsed_output_160() {
1293        let parsed = OptSpecs::new()
1294            .option("file", "file", OptValueType::Required)
1295            .getopt(["--file", "--", "--", "--"]);
1296
1297        assert_eq!(
1298            "--",
1299            parsed.options_first("file").unwrap().value.clone().unwrap()
1300        );
1301        assert_eq!(1, parsed.other.len());
1302        assert_eq!("--", parsed.other[0]);
1303
1304        assert_eq!(0, parsed.required_value_missing().len());
1305    }
1306
1307    #[test]
1308    fn t_parsed_output_170() {
1309        let parsed = OptSpecs::new().getopt(["foo", "bar"]);
1310
1311        assert_eq!(None, parsed.options_first("not-at-all"));
1312        assert_eq!(None, parsed.options_last("not-at-all"));
1313    }
1314
1315    #[test]
1316    fn t_parsed_output_180() {
1317        let parsed = OptSpecs::new()
1318            .limit_unknown_options(3)
1319            .option("bar", "bar", OptValueType::None)
1320            .getopt(["-aaa", "--foo", "--foo", "--bar=", "--bar=", "-x"]);
1321
1322        assert_eq!(3, parsed.unknown.len());
1323        assert_eq!("a", parsed.unknown[0]);
1324        assert_eq!("foo", parsed.unknown[1]);
1325        assert_eq!("bar=", parsed.unknown[2]);
1326    }
1327
1328    #[test]
1329    fn t_parsed_output_190() {
1330        let parsed = OptSpecs::new()
1331            .option("äiti", "äiti", OptValueType::Required)
1332            .option("€uro", "€uro", OptValueType::Required)
1333            .getopt(["--äiti=ööö", "--€uro", "€€€", "--äiti", "ää", "--äiti"]);
1334
1335        let a = parsed.options_value_all("äiti");
1336        let e = parsed.options_value_all("€uro");
1337
1338        assert_eq!(2, a.len());
1339        assert_eq!("ööö", a[0]);
1340        assert_eq!("ää", a[1]);
1341        assert_eq!("ööö", parsed.options_value_first("äiti").unwrap());
1342        assert_eq!("ää", parsed.options_value_last("äiti").unwrap());
1343
1344        assert_eq!(1, e.len());
1345        assert_eq!("€€€", e[0]);
1346        assert_eq!("€€€", parsed.options_value_first("€uro").unwrap());
1347        assert_eq!("€€€", parsed.options_value_last("€uro").unwrap());
1348
1349        assert_eq!(None, parsed.options_last("äiti").unwrap().value);
1350
1351        let m = parsed.required_value_missing();
1352        assert_eq!(1, m.len());
1353        assert_eq!("äiti", m[0].name);
1354        assert_eq!(None, m[0].value);
1355    }
1356
1357    #[test]
1358    fn t_parsed_output_195() {
1359        let parsed = OptSpecs::new().getopt(["-ä€", "--€uro", "äää", "€€€"]);
1360
1361        assert_eq!(2, parsed.other.len());
1362        assert_eq!("äää", parsed.other[0]);
1363        assert_eq!("€€€", parsed.other[1]);
1364
1365        assert_eq!(3, parsed.unknown.len());
1366        assert_eq!("ä", parsed.unknown[0]);
1367        assert_eq!("€", parsed.unknown[1]);
1368        assert_eq!("€uro", parsed.unknown[2]);
1369    }
1370
1371    #[test]
1372    fn t_parsed_output_200() {
1373        let parsed = OptSpecs::new().limit_other_args(5).getopt(1..10);
1374        assert_eq!(5, parsed.other.len());
1375        assert_eq!(vec!["1", "2", "3", "4", "5"], parsed.other);
1376    }
1377
1378    #[test]
1379    fn t_parsed_output_210() {
1380        let parsed = OptSpecs::new().limit_other_args(0).getopt(1..10);
1381        assert_eq!(0, parsed.other.len());
1382    }
1383
1384    #[test]
1385    fn t_parsed_output_220() {
1386        let parsed = OptSpecs::new()
1387            .option("file", "f", OptValueType::Required)
1388            .option("file", "file", OptValueType::Required)
1389            .option("help", "help", OptValueType::None)
1390            .limit_options(3)
1391            .limit_other_args(1)
1392            .limit_unknown_options(3)
1393            .getopt([
1394                "--unknown",
1395                "--help=",
1396                "-ab",
1397                "-f",
1398                "one",
1399                "-ftwo",
1400                "--file",
1401                "three",
1402                "--file",
1403                "four",
1404                "other1",
1405                "other2",
1406            ]);
1407
1408        assert_eq!(3, parsed.options.len());
1409        assert_eq!(
1410            vec!["one", "two", "three"],
1411            parsed.options_value_all("file")
1412        );
1413
1414        assert_eq!(1, parsed.other.len());
1415        assert_eq!("other1", parsed.other[0]);
1416
1417        assert_eq!(3, parsed.unknown.len());
1418        assert_eq!(vec!["unknown", "help=", "a"], parsed.unknown);
1419    }
1420
1421    #[test]
1422    fn t_parsed_output_230() {
1423        let parsed = OptSpecs::new()
1424            .option("file", "f", OptValueType::Required)
1425            .option("file", "file", OptValueType::Required)
1426            .limit_options(3)
1427            .getopt(["-f", "one", "-ftwo", "--file=three", "--unknown"]);
1428
1429        assert_eq!(
1430            vec!["one", "two", "three"],
1431            parsed.options_value_all("file")
1432        );
1433        assert_eq!(1, parsed.unknown.len());
1434        assert_eq!("unknown", parsed.unknown[0]);
1435    }
1436
1437    #[test]
1438    fn t_parsed_output_240() {
1439        let parsed = OptSpecs::new()
1440            .option("help", "h", OptValueType::None)
1441            .limit_options(3)
1442            .getopt(["-xhhhh"]);
1443
1444        assert_eq!(3, parsed.options.len());
1445        assert_eq!(true, parsed.options_first("help").is_some());
1446        assert_eq!(1, parsed.unknown.len());
1447        assert_eq!("x", parsed.unknown[0]);
1448    }
1449
1450    #[test]
1451    fn t_parsed_output_250() {
1452        let parsed = OptSpecs::new()
1453            .option("help", "h", OptValueType::None)
1454            .limit_options(3)
1455            .getopt(["-x", "-h", "-h", "-h", "-h"]);
1456
1457        assert_eq!(3, parsed.options.len());
1458        assert_eq!(true, parsed.options_first("help").is_some());
1459        assert_eq!(1, parsed.unknown.len());
1460        assert_eq!("x", parsed.unknown[0]);
1461    }
1462
1463    #[test]
1464    fn t_parsed_output_260() {
1465        let parsed = OptSpecs::new()
1466            .option("help", "h", OptValueType::None)
1467            .limit_options(3)
1468            .getopt(["-x", "-h", "-h", "--", "-h", "-h"]);
1469
1470        assert_eq!(2, parsed.options.len());
1471        assert_eq!(true, parsed.options_first("help").is_some());
1472        assert_eq!(2, parsed.other.len());
1473        assert_eq!(vec!["-h", "-h"], parsed.other);
1474        assert_eq!(1, parsed.unknown.len());
1475        assert_eq!("x", parsed.unknown[0]);
1476    }
1477
1478    #[test]
1479    fn t_parsed_output_270() {
1480        let parsed = OptSpecs::new()
1481            .flag(OptFlags::OptionsEverywhere)
1482            .option("help", "h", OptValueType::None)
1483            .option("file", "f", OptValueType::Required)
1484            .limit_options(1)
1485            .limit_other_args(2)
1486            .limit_unknown_options(1)
1487            .getopt(["bar", "-habf", "123", "foo"]);
1488
1489        // "123" must be parsed as "f" option's value even though it is
1490        // beyond limit_options.
1491        assert_eq!(true, parsed.options_first("help").is_some());
1492        assert_eq!(false, parsed.options_first("file").is_some());
1493        assert_eq!(2, parsed.other.len());
1494        assert_eq!("bar", parsed.other[0]);
1495        assert_eq!("foo", parsed.other[1]);
1496        assert_eq!(1, parsed.unknown.len());
1497        assert_eq!("a", parsed.unknown[0]);
1498    }
1499
1500    #[test]
1501    fn t_parsed_output_280() {
1502        let parsed = OptSpecs::new()
1503            .flag(OptFlags::OptionsEverywhere)
1504            .option("help", "help", OptValueType::None)
1505            .option("file", "file", OptValueType::Required)
1506            .limit_options(1)
1507            .limit_other_args(2)
1508            .limit_unknown_options(1)
1509            .getopt(["bar", "--help", "-ab", "--file", "123", "foo"]);
1510
1511        // "123" must be parsed as "--file" option's value even though
1512        // it is beyond limit_options.
1513        assert_eq!(true, parsed.options_first("help").is_some());
1514        assert_eq!(false, parsed.options_first("file").is_some());
1515        assert_eq!(2, parsed.other.len());
1516        assert_eq!("bar", parsed.other[0]);
1517        assert_eq!("foo", parsed.other[1]);
1518        assert_eq!(1, parsed.unknown.len());
1519        assert_eq!("a", parsed.unknown[0]);
1520    }
1521}