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        if let Some(exact) = self.get_long_option_match(name) {
610            return Some(exact);
611        }
612
613        let mut result = None;
614
615        for e in &self.options {
616            if e.name.starts_with(name) {
617                if result.is_none() {
618                    result = Some(e);
619                } else {
620                    return None;
621                }
622            }
623        }
624        result
625    }
626}
627
628impl Default for OptSpecs {
629    fn default() -> Self {
630        Self::new()
631    }
632}
633
634/// Parsed command line in organized form.
635///
636/// Instances of this struct are usually created with
637/// [`OptSpecs::getopt`] method and an instance represents the parsed
638/// output in organized form. See each field's documentation for more
639/// information.
640///
641/// Programmers can use the parsed output ([`Args`] struct) any way they
642/// like. There are some methods for convenience.
643
644#[derive(Debug, PartialEq)]
645pub struct Args {
646    /// A vector of valid command-line options.
647    ///
648    /// Elements of this vector are [`Opt`] structs which each
649    /// represents a single command-line option. Elements are in the
650    /// same order as given (by program's user) in the command line. The
651    /// vector is empty if the parser didn't find any valid command-line
652    /// options.
653    pub options: Vec<Opt>,
654
655    /// A vector of other arguments (non-options).
656    ///
657    /// Each element of the vector is a single non-option argument
658    /// string in the same order as given (by program's user) in the
659    /// command line. The vector is empty if the parser didn't find any
660    /// non-option arguments.
661    pub other: Vec<String>,
662
663    /// Unknown options.
664    ///
665    /// Command-line arguments that look like options but were not part
666    /// of [`OptSpecs`] specification are classified as unknown. They
667    /// are listed in this vector. Possible duplicate unknown options
668    /// have been filtered out.
669    ///
670    /// Each element is the name string for the option (without `-` or
671    /// `--` prefix). For unknown short options the element is a
672    /// single-character string. For unknown long options the string has
673    /// more than one character. The whole vector is empty if there were
674    /// no unknown options.
675    ///
676    /// If a long option does not accept a value (that is, its value
677    /// type is [`OptValueType::None`]) but user gives it a value with
678    /// equal sign notation (`--foo=`), that option is classified as
679    /// unknown and it will be in this field's vector with name `foo=`.
680    pub unknown: Vec<String>,
681}
682
683impl Args {
684    fn new() -> Self {
685        Args {
686            options: Vec::new(),
687            other: Vec::new(),
688            unknown: Vec::new(),
689        }
690    }
691
692    /// Find options with missing required value.
693    ///
694    /// This method finds all (otherwise valid) options which require a
695    /// value but the value is missing. That is, [`OptSpecs`] struct
696    /// specification defined that an option requires a value but
697    /// program's user didn't give one in the command line. Such thing
698    /// can happen if an option like `--file` is the last argument in
699    /// the command line and that option requires a value. Empty string
700    /// `""` is not classified as missing value because it can be valid
701    /// user input in many situations.
702    ///
703    /// This method returns a vector (possibly empty) and each element
704    /// is a reference to an [`Opt`] struct in the original
705    /// [`Args::options`] field contents.
706    pub fn required_value_missing(&self) -> Vec<&Opt> {
707        self.options
708            .iter()
709            .filter(|opt| opt.value_required && opt.value.is_none())
710            .collect()
711    }
712
713    /// Return boolean whether option with the given `id` exists.
714    ///
715    /// This is functionally the same as
716    /// [`options_first`](Args::options_first)`(id).is_some()`.
717    pub fn option_exists(&self, id: &str) -> bool {
718        self.options.iter().any(|opt| opt.id == id)
719    }
720
721    /// Find all options with the given `id`.
722    ///
723    /// Find all options which have the identifier `id`. (Option
724    /// identifiers have been defined in [`OptSpecs`] struct before
725    /// parsing.) The return value is a vector (possibly empty, if no
726    /// matches) and each element is a reference to [`Opt`] struct in
727    /// the original [`Args`] struct. Elements in the vector are in the
728    /// same order as in the parsed command line.
729    pub fn options_all(&self, id: &str) -> Vec<&Opt> {
730        self.options.iter().filter(|opt| opt.id == id).collect()
731    }
732
733    /// Find the first option with the given `id`.
734    ///
735    /// Find and return the first match for option `id` in command-line
736    /// arguments' order. (Options' identifiers have been defined in
737    /// [`OptSpecs`] struct before parsing.)
738    ///
739    /// The return value is a variant of enum [`Option`]. Their
740    /// meanings:
741    ///
742    ///   - `None`: No options found with the given `id`.
743    ///
744    ///   - `Some(&Opt)`: An option was found with the given `id` and a
745    ///     reference is provided to its [`Opt`] struct in the original
746    ///     [`Args::options`] field.
747    pub fn options_first(&self, id: &str) -> Option<&Opt> {
748        self.options.iter().find(|opt| opt.id == id)
749    }
750
751    /// Find the last option with the given `id`.
752    ///
753    /// This is similar to [`options_first`](Args::options_first) method
754    /// but this returns the last match in command-line arguments'
755    /// order.
756    pub fn options_last(&self, id: &str) -> Option<&Opt> {
757        self.options.iter().rev().find(|opt| opt.id == id)
758    }
759
760    /// Find and return all values for options with the given `id`.
761    ///
762    /// Find all options which match the identifier `id` and which also
763    /// have a value assigned. (Options' identifiers have been defined
764    /// in [`OptSpecs`] struct before parsing.) Collect options' values
765    /// into a new vector in the same order as they were given in the
766    /// command line. Vector's elements are references to the value
767    /// strings in the original [`Args`] struct. The returned vector is
768    /// empty if there were no matches.
769    pub fn options_value_all(&self, id: &str) -> Vec<&String> {
770        self.options
771            .iter()
772            .filter_map(|opt| {
773                if opt.id == id {
774                    opt.value.as_ref()
775                } else {
776                    None
777                }
778            })
779            .collect()
780    }
781
782    /// Find the first option with a value for given option `id`.
783    ///
784    /// Find the first option with the identifier `id` and which has a
785    /// value assigned. (Options' identifiers have been defined in
786    /// [`OptSpecs`] struct before parsing.) Method's return value is a
787    /// variant of enum [`Option`] which are:
788    ///
789    ///   - `None`: No options found with the given `id` and a value
790    ///     assigned. Note that there could be options for the same `id`
791    ///     but they don't have a value.
792    ///
793    ///   - `Some(&String)`: An option was found with the given `id` and
794    ///     the option has a value assigned. A reference is provided to
795    ///     the string value in the [`Opt::value`] field in the original
796    ///     [`Args::options`] field.
797    pub fn options_value_first(&self, id: &str) -> Option<&String> {
798        match self
799            .options
800            .iter()
801            .find(|opt| opt.id == id && opt.value.is_some())
802        {
803            Some(o) => o.value.as_ref(),
804            None => None,
805        }
806    }
807
808    /// Find the last option with a value for given option `id`.
809    ///
810    /// This is similar to
811    /// [`options_value_first`](Args::options_value_first) method but
812    /// this method finds and returns the last option's value.
813    ///
814    /// Note: Program's user may give the same option several times in
815    /// the command line. If the option accepts a value it may be
816    /// suitable to consider only the last value relevant. (Or the
817    /// first, or maybe print an error message for providing several,
818    /// possibly conflicting, values.)
819    pub fn options_value_last(&self, id: &str) -> Option<&String> {
820        match self
821            .options
822            .iter()
823            .rev()
824            .find(|opt| opt.id == id && opt.value.is_some())
825        {
826            Some(o) => o.value.as_ref(),
827            None => None,
828        }
829    }
830}
831
832/// Structured option information.
833///
834/// This [`Opt`] struct represents organized information about single
835/// command-line option. Instances of this struct are usually created by
836/// [`OptSpecs::getopt`] method which returns an [`Args`] struct which
837/// have these [`Opt`] structs inside.
838///
839/// A programmer may need these when examining parsed command-line
840/// options. See the documentation of individual fields for more
841/// information. Also see [`Args`] struct and its methods.
842
843#[derive(Debug, PartialEq)]
844pub struct Opt {
845    /// Identifier for the option.
846    ///
847    /// Identifiers are defined with [`OptSpecs::option`] method before
848    /// parsing command-line arguments. After [`OptSpecs::getopt`]
849    /// parsing the same identifier is copied here and it confirms that
850    /// the option was indeed given in the command line.
851    pub id: String,
852
853    /// Option's name in the parsed command line.
854    ///
855    /// Option's name that was used in the command line. For short
856    /// options this is a single-character string. For long options the
857    /// name has more than one characters.
858    pub name: String,
859
860    /// The option requires a value.
861    ///
862    /// `true` means that the option was defined with value type
863    /// [`OptValueType::Required`]. See [`OptSpecs::flag`] method for
864    /// more information. This field does not guarantee that there
865    /// actually was a value for the option in the command line.
866    pub value_required: bool,
867
868    /// Option's value.
869    ///
870    /// The value is a variant of enum [`Option`]. Value `None` means
871    /// that there is no value for the option. Value `Some(String)`
872    /// provides a value.
873    pub value: Option<String>,
874}
875
876#[cfg(test)]
877mod tests {
878    use super::*;
879
880    #[test]
881    fn t_create_optspecs_01() {
882        let mut spec;
883        let mut expect;
884
885        spec = OptSpecs::new().option("help", "help", OptValueType::None);
886        expect = OptSpec {
887            id: String::from("help"),
888            name: String::from("help"),
889            value_type: OptValueType::None,
890        };
891        assert_eq!(1, spec.options.len());
892        assert_eq!(&expect, &spec.options[0]);
893        assert_eq!(COUNTER_LIMIT, spec.option_limit);
894        assert_eq!(COUNTER_LIMIT, spec.other_limit);
895        assert_eq!(COUNTER_LIMIT, spec.unknown_limit);
896
897        spec = spec.option("file", "f", OptValueType::Optional);
898        expect = OptSpec {
899            id: String::from("file"),
900            name: String::from("f"),
901            value_type: OptValueType::Optional,
902        };
903        assert_eq!(2, spec.options.len());
904        assert_eq!(&expect, &spec.options[1]);
905
906        spec = spec.option("file", "file", OptValueType::Required);
907        expect = OptSpec {
908            id: String::from("file"),
909            name: String::from("file"),
910            value_type: OptValueType::Required,
911        };
912        assert_eq!(3, spec.options.len());
913        assert_eq!(&expect, &spec.options[2]);
914
915        spec = spec.flag(OptFlags::OptionsEverywhere);
916        assert_eq!(1, spec.flags.len()); // Length 1
917        assert_eq!(true, spec.is_flag(OptFlags::OptionsEverywhere));
918        spec = spec.flag(OptFlags::PrefixMatchLongOptions);
919        assert_eq!(2, spec.flags.len()); // Length 2
920        assert_eq!(true, spec.is_flag(OptFlags::PrefixMatchLongOptions));
921        spec = spec.flag(OptFlags::OptionsEverywhere);
922        spec = spec.flag(OptFlags::PrefixMatchLongOptions);
923        assert_eq!(2, spec.flags.len()); // Length still 2
924
925        spec = spec.limit_options(9);
926        spec = spec.limit_other_args(10);
927        spec = spec.limit_unknown_options(3);
928        assert_eq!(9, spec.option_limit);
929        assert_eq!(10, spec.other_limit);
930        assert_eq!(3, spec.unknown_limit);
931    }
932
933    #[test]
934    #[should_panic]
935    fn t_create_optspecs_02() {
936        let _spec = OptSpecs::new().option("", "h", OptValueType::None);
937    }
938
939    #[test]
940    #[should_panic]
941    fn t_create_optspecs_03() {
942        let _spec = OptSpecs::new().option("h", "h", OptValueType::None).option(
943            "h",
944            "h",
945            OptValueType::None,
946        );
947    }
948
949    #[test]
950    #[should_panic]
951    fn t_create_optspecs_04() {
952        let _spec = OptSpecs::new().option("h", "", OptValueType::None);
953    }
954
955    #[test]
956    fn t_is_flag() {
957        let mut spec = OptSpecs::new().flag(OptFlags::OptionsEverywhere);
958        assert_eq!(true, spec.is_flag(OptFlags::OptionsEverywhere));
959
960        spec = spec.flag(OptFlags::PrefixMatchLongOptions);
961        assert_eq!(true, spec.is_flag(OptFlags::PrefixMatchLongOptions));
962    }
963
964    #[test]
965    fn t_parsed_output_010() {
966        let parsed = OptSpecs::new()
967            .option("help", "h", OptValueType::None)
968            .option("help", "help", OptValueType::None)
969            .option("file", "f", OptValueType::Required)
970            .option("file", "file", OptValueType::Required)
971            .getopt(["-h", "--help", "-f123", "-f", "456", "foo", "bar"]);
972
973        assert_eq!(true, parsed.option_exists("help"));
974        assert_eq!(true, parsed.option_exists("file"));
975        assert_eq!(false, parsed.option_exists("x"));
976
977        assert_eq!("h", parsed.options_first("help").unwrap().name);
978        assert_eq!("help", parsed.options_last("help").unwrap().name);
979        assert_eq!("help", parsed.options_first("help").unwrap().id);
980        assert_eq!("help", parsed.options_last("help").unwrap().id);
981        assert_eq!(false, parsed.options_first("help").unwrap().value_required);
982        assert_eq!(false, parsed.options_last("help").unwrap().value_required);
983
984        assert_eq!("f", parsed.options_first("file").unwrap().name);
985        assert_eq!(
986            "123",
987            parsed.options_first("file").unwrap().value.clone().unwrap()
988        );
989        assert_eq!(
990            "456",
991            parsed.options_last("file").unwrap().value.clone().unwrap()
992        );
993        assert_eq!(true, parsed.options_first("file").unwrap().value_required);
994
995        assert_eq!("foo", parsed.other[0]);
996        assert_eq!("bar", parsed.other[1]);
997    }
998
999    #[test]
1000    fn t_parsed_output_020() {
1001        let parsed = OptSpecs::new()
1002            .limit_options(1)
1003            .limit_other_args(2)
1004            .option("help", "h", OptValueType::None)
1005            .getopt(["-h", "foo", "-h"]);
1006
1007        assert_eq!("h", parsed.options_first("help").unwrap().name);
1008        assert_eq!(2, parsed.other.len());
1009        assert_eq!("foo", parsed.other[0]);
1010        assert_eq!("-h", parsed.other[1]);
1011    }
1012
1013    #[test]
1014    fn t_parsed_output_030() {
1015        let parsed = OptSpecs::new()
1016            .flag(OptFlags::OptionsEverywhere)
1017            .option("help", "h", OptValueType::None)
1018            .option("help", "help", OptValueType::None)
1019            .option("file", "f", OptValueType::Required)
1020            .option("file", "file", OptValueType::Required)
1021            .getopt(["-h", "foo", "--help", "--file=123", "bar", "--file", "456"]);
1022
1023        assert_eq!("h", parsed.options_first("help").unwrap().name);
1024        assert_eq!("help", parsed.options_last("help").unwrap().name);
1025        assert_eq!(
1026            "123",
1027            parsed.options_first("file").unwrap().value.clone().unwrap()
1028        );
1029        assert_eq!(
1030            "456",
1031            parsed.options_last("file").unwrap().value.clone().unwrap()
1032        );
1033        assert_eq!("foo", parsed.other[0]);
1034        assert_eq!("bar", parsed.other[1]);
1035    }
1036
1037    #[test]
1038    fn t_parsed_output_040() {
1039        let parsed = OptSpecs::new()
1040            .option("debug", "d", OptValueType::Optional)
1041            .option("verbose", "verbose", OptValueType::Optional)
1042            .getopt(["-d1", "-d", "--verbose", "--verbose=123"]);
1043
1044        assert_eq!(
1045            "1",
1046            parsed
1047                .options_first("debug")
1048                .unwrap()
1049                .value
1050                .clone()
1051                .unwrap()
1052        );
1053        assert_eq!(None, parsed.options_last("debug").unwrap().value);
1054        assert_eq!(false, parsed.options_last("debug").unwrap().value_required);
1055
1056        assert_eq!(None, parsed.options_first("verbose").unwrap().value);
1057        assert_eq!(
1058            "123",
1059            parsed
1060                .options_last("verbose")
1061                .unwrap()
1062                .value
1063                .clone()
1064                .unwrap()
1065        );
1066        assert_eq!(
1067            false,
1068            parsed.options_last("verbose").unwrap().value_required
1069        );
1070    }
1071
1072    #[test]
1073    fn t_parsed_output_050() {
1074        let parsed = OptSpecs::new()
1075            .option("debug", "d", OptValueType::Optional)
1076            .getopt(["-abcd", "-adbc"]);
1077
1078        assert_eq!(None, parsed.options_first("debug").unwrap().value);
1079        assert_eq!(
1080            "bc",
1081            parsed.options_last("debug").unwrap().value.clone().unwrap()
1082        );
1083
1084        assert_eq!(3, parsed.unknown.len());
1085        assert_eq!("a", parsed.unknown[0]);
1086        assert_eq!("b", parsed.unknown[1]);
1087        assert_eq!("c", parsed.unknown[2]);
1088    }
1089
1090    #[test]
1091    fn t_parsed_output_060() {
1092        let parsed = OptSpecs::new()
1093            .option("aaa", "bbb", OptValueType::None)
1094            .option("aaa", "c", OptValueType::None)
1095            .option("aaa", "d", OptValueType::None)
1096            .option("aaa", "eee", OptValueType::None)
1097            .getopt(["--bbb", "-cd", "--eee"]);
1098
1099        let m = parsed.options_all("aaa");
1100        assert_eq!("bbb", m[0].name);
1101        assert_eq!("c", m[1].name);
1102        assert_eq!("d", m[2].name);
1103        assert_eq!("eee", m[3].name);
1104    }
1105
1106    #[test]
1107    fn t_parsed_output_070() {
1108        let parsed = OptSpecs::new()
1109            .flag(OptFlags::PrefixMatchLongOptions)
1110            .option("version", "version", OptValueType::None)
1111            .option("verbose", "verbose", OptValueType::None)
1112            .getopt(["--ver", "--verb", "--versi", "--verbose"]);
1113
1114        assert_eq!("ver", parsed.unknown[0]);
1115        assert_eq!("verb", parsed.options_first("verbose").unwrap().name);
1116        assert_eq!("verbose", parsed.options_last("verbose").unwrap().name);
1117        assert_eq!("version", parsed.options_first("version").unwrap().id);
1118        assert_eq!("versi", parsed.options_first("version").unwrap().name);
1119    }
1120
1121    #[test]
1122    fn t_parsed_output_080() {
1123        let parsed = OptSpecs::new()
1124            // .flag(OptFlags::PrefixMatchLongOptions) Must be commented!
1125            .option("version", "version", OptValueType::None)
1126            .option("verbose", "verbose", OptValueType::None)
1127            .getopt(["--version", "--ver", "--verb", "--versi", "--verbose"]);
1128
1129        assert_eq!("ver", parsed.unknown[0]);
1130        assert_eq!("verb", parsed.unknown[1]);
1131        assert_eq!("versi", parsed.unknown[2]);
1132        assert_eq!("version", parsed.options_first("version").unwrap().name);
1133        assert_eq!("verbose", parsed.options_first("verbose").unwrap().name);
1134    }
1135
1136    #[test]
1137    fn t_parsed_output_085() {
1138        let parsed = OptSpecs::new()
1139            .flag(OptFlags::PrefixMatchLongOptions)
1140            .option("foo1", "foo", OptValueType::None)
1141            .option("foo2", "foo-longer", OptValueType::None)
1142            .getopt(["--fo", "--foo", "--foo-", "--foo-longer"]);
1143
1144        assert_eq!("fo", parsed.unknown[0]);
1145        assert_eq!("foo", parsed.options_first("foo1").unwrap().name);
1146        assert_eq!("foo", parsed.options_last("foo1").unwrap().name);
1147        assert_eq!("foo-", parsed.options_first("foo2").unwrap().name);
1148        assert_eq!("foo-longer", parsed.options_last("foo2").unwrap().name);
1149    }
1150
1151    #[test]
1152    fn t_parsed_output_090() {
1153        let parsed = OptSpecs::new()
1154            .flag(OptFlags::OptionsEverywhere)
1155            .option("help", "h", OptValueType::None)
1156            .option("file", "file", OptValueType::Required)
1157            .getopt(["-h", "foo", "--file=123", "--", "bar", "--file", "456"]);
1158
1159        assert_eq!("h", parsed.options_first("help").unwrap().name);
1160        assert_eq!("file", parsed.options_first("file").unwrap().name);
1161        assert_eq!(
1162            "123",
1163            parsed.options_first("file").unwrap().value.clone().unwrap()
1164        );
1165
1166        assert_eq!(4, parsed.other.len());
1167        assert_eq!("foo", parsed.other[0]);
1168        assert_eq!("bar", parsed.other[1]);
1169        assert_eq!("--file", parsed.other[2]);
1170        assert_eq!("456", parsed.other[3]);
1171    }
1172
1173    #[test]
1174    fn t_parsed_output_100() {
1175        let parsed = OptSpecs::new()
1176            .option("file", "file", OptValueType::Required)
1177            .getopt(["--file=", "--file"]);
1178
1179        assert_eq!(true, parsed.options_first("file").unwrap().value_required);
1180        assert_eq!(
1181            "",
1182            parsed.options_first("file").unwrap().value.clone().unwrap()
1183        );
1184        assert_eq!(None, parsed.options_last("file").unwrap().value);
1185    }
1186
1187    #[test]
1188    fn t_parsed_output_110() {
1189        let parsed = OptSpecs::new()
1190            .option("file", "f", OptValueType::Required)
1191            .option("debug", "d", OptValueType::Required)
1192            .getopt(["-fx", "-d", "", "-f"]);
1193
1194        assert_eq!(true, parsed.options_first("file").unwrap().value_required);
1195        assert_eq!(
1196            "x",
1197            parsed.options_first("file").unwrap().value.clone().unwrap()
1198        );
1199        assert_eq!(None, parsed.options_last("file").unwrap().value);
1200        assert_eq!(
1201            "",
1202            parsed
1203                .options_first("debug")
1204                .unwrap()
1205                .value
1206                .clone()
1207                .unwrap()
1208        );
1209    }
1210
1211    #[test]
1212    fn t_parsed_output_120() {
1213        let parsed = OptSpecs::new()
1214            .option("file", "f", OptValueType::Required)
1215            .option("debug", "d", OptValueType::Required)
1216            .getopt(["-f123", "-d", "", "-f", "456", "-f"]);
1217
1218        let f = parsed.options_value_all("file");
1219        let d = parsed.options_value_all("debug");
1220
1221        assert_eq!(2, f.len());
1222        assert_eq!("123", f[0]);
1223        assert_eq!("456", f[1]);
1224
1225        assert_eq!(1, d.len());
1226        assert_eq!("", d[0]);
1227
1228        assert_eq!(None, parsed.options_last("file").unwrap().value);
1229        let m = parsed.required_value_missing();
1230        assert_eq!(1, m.len());
1231        assert_eq!("f", m[0].name);
1232    }
1233
1234    #[test]
1235    fn t_parsed_output_130() {
1236        let parsed = OptSpecs::new()
1237            .option("file", "file", OptValueType::Required)
1238            .option("debug", "debug", OptValueType::Required)
1239            .getopt(["--file=123", "--debug", "", "--file", "456", "--file"]);
1240
1241        let f = parsed.options_value_all("file");
1242        let d = parsed.options_value_all("debug");
1243
1244        assert_eq!(2, f.len());
1245        assert_eq!("123", f[0]);
1246        assert_eq!("456", f[1]);
1247
1248        assert_eq!(1, d.len());
1249        assert_eq!("", d[0]);
1250
1251        assert_eq!(None, parsed.options_last("file").unwrap().value);
1252        let m = parsed.required_value_missing();
1253        assert_eq!(1, m.len());
1254        assert_eq!("file", m[0].name);
1255    }
1256
1257    #[test]
1258    fn t_parsed_output_140() {
1259        let parsed = OptSpecs::new()
1260            .flag(OptFlags::OptionsEverywhere)
1261            .option("debug", "d", OptValueType::Optional)
1262            .option("debug", "debug", OptValueType::Optional)
1263            .getopt([
1264                "-d",
1265                "-d123",
1266                "-d",
1267                "--debug",
1268                "--debug=",
1269                "foo",
1270                "--debug=456",
1271                "-d",
1272            ]);
1273
1274        let d = parsed.options_all("debug");
1275        assert_eq!(7, d.len());
1276
1277        let d = parsed.options_value_all("debug");
1278        assert_eq!(3, d.len());
1279        assert_eq!("123", d[0]);
1280        assert_eq!("", d[1]);
1281        assert_eq!("456", d[2]);
1282        assert_eq!("123", parsed.options_value_first("debug").unwrap());
1283        assert_eq!("456", parsed.options_value_last("debug").unwrap());
1284
1285        assert_eq!(None, parsed.options_value_first("not-at-all"));
1286        assert_eq!(None, parsed.options_value_last("not-at-all"));
1287
1288        assert_eq!("foo", parsed.other[0]);
1289    }
1290
1291    #[test]
1292    fn t_parsed_output_150() {
1293        let parsed = OptSpecs::new().limit_unknown_options(6).getopt([
1294            "-abcd",
1295            "-e",
1296            "--debug",
1297            "-x", // Won't be listed in unknown because of limit.
1298            "--",
1299            "--debug=",
1300            "foo",
1301            "--debug=456",
1302        ]);
1303
1304        assert_eq!(0, parsed.options.len());
1305        assert_eq!(3, parsed.other.len());
1306        assert_eq!(6, parsed.unknown.len());
1307        assert_eq!(vec!["a", "b", "c", "d", "e", "debug"], parsed.unknown);
1308    }
1309
1310    #[test]
1311    fn t_parsed_output_160() {
1312        let parsed = OptSpecs::new()
1313            .option("file", "file", OptValueType::Required)
1314            .getopt(["--file", "--", "--", "--"]);
1315
1316        assert_eq!(
1317            "--",
1318            parsed.options_first("file").unwrap().value.clone().unwrap()
1319        );
1320        assert_eq!(1, parsed.other.len());
1321        assert_eq!("--", parsed.other[0]);
1322
1323        assert_eq!(0, parsed.required_value_missing().len());
1324    }
1325
1326    #[test]
1327    fn t_parsed_output_170() {
1328        let parsed = OptSpecs::new().getopt(["foo", "bar"]);
1329
1330        assert_eq!(None, parsed.options_first("not-at-all"));
1331        assert_eq!(None, parsed.options_last("not-at-all"));
1332    }
1333
1334    #[test]
1335    fn t_parsed_output_180() {
1336        let parsed = OptSpecs::new()
1337            .limit_unknown_options(3)
1338            .option("bar", "bar", OptValueType::None)
1339            .getopt(["-aaa", "--foo", "--foo", "--bar=", "--bar=", "-x"]);
1340
1341        assert_eq!(3, parsed.unknown.len());
1342        assert_eq!("a", parsed.unknown[0]);
1343        assert_eq!("foo", parsed.unknown[1]);
1344        assert_eq!("bar=", parsed.unknown[2]);
1345    }
1346
1347    #[test]
1348    fn t_parsed_output_190() {
1349        let parsed = OptSpecs::new()
1350            .option("äiti", "äiti", OptValueType::Required)
1351            .option("€uro", "€uro", OptValueType::Required)
1352            .getopt(["--äiti=ööö", "--€uro", "€€€", "--äiti", "ää", "--äiti"]);
1353
1354        let a = parsed.options_value_all("äiti");
1355        let e = parsed.options_value_all("€uro");
1356
1357        assert_eq!(2, a.len());
1358        assert_eq!("ööö", a[0]);
1359        assert_eq!("ää", a[1]);
1360        assert_eq!("ööö", parsed.options_value_first("äiti").unwrap());
1361        assert_eq!("ää", parsed.options_value_last("äiti").unwrap());
1362
1363        assert_eq!(1, e.len());
1364        assert_eq!("€€€", e[0]);
1365        assert_eq!("€€€", parsed.options_value_first("€uro").unwrap());
1366        assert_eq!("€€€", parsed.options_value_last("€uro").unwrap());
1367
1368        assert_eq!(None, parsed.options_last("äiti").unwrap().value);
1369
1370        let m = parsed.required_value_missing();
1371        assert_eq!(1, m.len());
1372        assert_eq!("äiti", m[0].name);
1373        assert_eq!(None, m[0].value);
1374    }
1375
1376    #[test]
1377    fn t_parsed_output_195() {
1378        let parsed = OptSpecs::new().getopt(["-ä€", "--€uro", "äää", "€€€"]);
1379
1380        assert_eq!(2, parsed.other.len());
1381        assert_eq!("äää", parsed.other[0]);
1382        assert_eq!("€€€", parsed.other[1]);
1383
1384        assert_eq!(3, parsed.unknown.len());
1385        assert_eq!("ä", parsed.unknown[0]);
1386        assert_eq!("€", parsed.unknown[1]);
1387        assert_eq!("€uro", parsed.unknown[2]);
1388    }
1389
1390    #[test]
1391    fn t_parsed_output_200() {
1392        let parsed = OptSpecs::new().limit_other_args(5).getopt(1..10);
1393        assert_eq!(5, parsed.other.len());
1394        assert_eq!(vec!["1", "2", "3", "4", "5"], parsed.other);
1395    }
1396
1397    #[test]
1398    fn t_parsed_output_210() {
1399        let parsed = OptSpecs::new().limit_other_args(0).getopt(1..10);
1400        assert_eq!(0, parsed.other.len());
1401    }
1402
1403    #[test]
1404    fn t_parsed_output_220() {
1405        let parsed = OptSpecs::new()
1406            .option("file", "f", OptValueType::Required)
1407            .option("file", "file", OptValueType::Required)
1408            .option("help", "help", OptValueType::None)
1409            .limit_options(3)
1410            .limit_other_args(1)
1411            .limit_unknown_options(3)
1412            .getopt([
1413                "--unknown",
1414                "--help=",
1415                "-ab",
1416                "-f",
1417                "one",
1418                "-ftwo",
1419                "--file",
1420                "three",
1421                "--file",
1422                "four",
1423                "other1",
1424                "other2",
1425            ]);
1426
1427        assert_eq!(3, parsed.options.len());
1428        assert_eq!(
1429            vec!["one", "two", "three"],
1430            parsed.options_value_all("file")
1431        );
1432
1433        assert_eq!(1, parsed.other.len());
1434        assert_eq!("other1", parsed.other[0]);
1435
1436        assert_eq!(3, parsed.unknown.len());
1437        assert_eq!(vec!["unknown", "help=", "a"], parsed.unknown);
1438    }
1439
1440    #[test]
1441    fn t_parsed_output_230() {
1442        let parsed = OptSpecs::new()
1443            .option("file", "f", OptValueType::Required)
1444            .option("file", "file", OptValueType::Required)
1445            .limit_options(3)
1446            .getopt(["-f", "one", "-ftwo", "--file=three", "--unknown"]);
1447
1448        assert_eq!(
1449            vec!["one", "two", "three"],
1450            parsed.options_value_all("file")
1451        );
1452        assert_eq!(1, parsed.unknown.len());
1453        assert_eq!("unknown", parsed.unknown[0]);
1454    }
1455
1456    #[test]
1457    fn t_parsed_output_240() {
1458        let parsed = OptSpecs::new()
1459            .option("help", "h", OptValueType::None)
1460            .limit_options(3)
1461            .getopt(["-xhhhh"]);
1462
1463        assert_eq!(3, parsed.options.len());
1464        assert_eq!(true, parsed.options_first("help").is_some());
1465        assert_eq!(1, parsed.unknown.len());
1466        assert_eq!("x", parsed.unknown[0]);
1467    }
1468
1469    #[test]
1470    fn t_parsed_output_250() {
1471        let parsed = OptSpecs::new()
1472            .option("help", "h", OptValueType::None)
1473            .limit_options(3)
1474            .getopt(["-x", "-h", "-h", "-h", "-h"]);
1475
1476        assert_eq!(3, parsed.options.len());
1477        assert_eq!(true, parsed.options_first("help").is_some());
1478        assert_eq!(1, parsed.unknown.len());
1479        assert_eq!("x", parsed.unknown[0]);
1480    }
1481
1482    #[test]
1483    fn t_parsed_output_260() {
1484        let parsed = OptSpecs::new()
1485            .option("help", "h", OptValueType::None)
1486            .limit_options(3)
1487            .getopt(["-x", "-h", "-h", "--", "-h", "-h"]);
1488
1489        assert_eq!(2, parsed.options.len());
1490        assert_eq!(true, parsed.options_first("help").is_some());
1491        assert_eq!(2, parsed.other.len());
1492        assert_eq!(vec!["-h", "-h"], parsed.other);
1493        assert_eq!(1, parsed.unknown.len());
1494        assert_eq!("x", parsed.unknown[0]);
1495    }
1496
1497    #[test]
1498    fn t_parsed_output_270() {
1499        let parsed = OptSpecs::new()
1500            .flag(OptFlags::OptionsEverywhere)
1501            .option("help", "h", OptValueType::None)
1502            .option("file", "f", OptValueType::Required)
1503            .limit_options(1)
1504            .limit_other_args(2)
1505            .limit_unknown_options(1)
1506            .getopt(["bar", "-habf", "123", "foo"]);
1507
1508        // "123" must be parsed as "f" option's value even though it is
1509        // beyond limit_options.
1510        assert_eq!(true, parsed.options_first("help").is_some());
1511        assert_eq!(false, parsed.options_first("file").is_some());
1512        assert_eq!(2, parsed.other.len());
1513        assert_eq!("bar", parsed.other[0]);
1514        assert_eq!("foo", parsed.other[1]);
1515        assert_eq!(1, parsed.unknown.len());
1516        assert_eq!("a", parsed.unknown[0]);
1517    }
1518
1519    #[test]
1520    fn t_parsed_output_280() {
1521        let parsed = OptSpecs::new()
1522            .flag(OptFlags::OptionsEverywhere)
1523            .option("help", "help", OptValueType::None)
1524            .option("file", "file", OptValueType::Required)
1525            .limit_options(1)
1526            .limit_other_args(2)
1527            .limit_unknown_options(1)
1528            .getopt(["bar", "--help", "-ab", "--file", "123", "foo"]);
1529
1530        // "123" must be parsed as "--file" option's value even though
1531        // it is beyond limit_options.
1532        assert_eq!(true, parsed.options_first("help").is_some());
1533        assert_eq!(false, parsed.options_first("file").is_some());
1534        assert_eq!(2, parsed.other.len());
1535        assert_eq!("bar", parsed.other[0]);
1536        assert_eq!("foo", parsed.other[1]);
1537        assert_eq!(1, parsed.unknown.len());
1538        assert_eq!("a", parsed.unknown[0]);
1539    }
1540}