getopts/
lib.rs

1// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10//
11// ignore-lexer-test FIXME #15677
12
13//! Simple getopt alternative.
14//!
15//! Construct a vector of options, either by using `reqopt`, `optopt`, and
16//! `optflag` or by building them from components yourself, and pass them to
17//! `getopts`, along with a vector of actual arguments (not including
18//! `argv[0]`). You'll either get a failure code back, or a match. You'll have
19//! to verify whether the amount of 'free' arguments in the match is what you
20//! expect. Use `opt_*` accessors to get argument values out of the matches
21//! object.
22//!
23//! Single-character options are expected to appear on the command line with a
24//! single preceding dash; multiple-character options are expected to be
25//! proceeded by two dashes. Options that expect an argument accept their
26//! argument following either a space or an equals sign. Single-character
27//! options don't require the space.
28//!
29//! # Usage
30//!
31//! This crate is [on crates.io](https://crates.io/crates/getopts) and can be
32//! used by adding `getopts` to the dependencies in your project's `Cargo.toml`.
33//!
34//! ```toml
35//! [dependencies]
36//! getopts = "0.2"
37//! ```
38//!
39//! and this to your crate root:
40//!
41//! ```rust
42//! extern crate getopts;
43//! ```
44//!
45//! # Example
46//!
47//! The following example shows simple command line parsing for an application
48//! that requires an input file to be specified, accepts an optional output file
49//! name following `-o`, and accepts both `-h` and `--help` as optional flags.
50//!
51//! ```{.rust}
52//! extern crate getopts;
53//! use getopts::Options;
54//! use std::env;
55//!
56//! fn do_work(inp: &str, out: Option<String>) {
57//!     println!("{}", inp);
58//!     match out {
59//!         Some(x) => println!("{}", x),
60//!         None => println!("No Output"),
61//!     }
62//! }
63//!
64//! fn print_usage(program: &str, opts: Options) {
65//!     let brief = format!("Usage: {} FILE [options]", program);
66//!     print!("{}", opts.usage(&brief));
67//! }
68//!
69//! fn main() {
70//!     let args: Vec<String> = env::args().collect();
71//!     let program = args[0].clone();
72//!
73//!     let mut opts = Options::new();
74//!     opts.optopt("o", "", "set output file name", "NAME");
75//!     opts.optflag("h", "help", "print this help menu");
76//!     let matches = match opts.parse(&args[1..]) {
77//!         Ok(m) => { m }
78//!         Err(f) => { panic!(f.to_string()) }
79//!     };
80//!     if matches.opt_present("h") {
81//!         print_usage(&program, opts);
82//!         return;
83//!     }
84//!     let output = matches.opt_str("o");
85//!     let input = if !matches.free.is_empty() {
86//!         matches.free[0].clone()
87//!     } else {
88//!         print_usage(&program, opts);
89//!         return;
90//!     };
91//!     do_work(&input, output);
92//! }
93//! ```
94
95#![doc(
96    html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
97    html_favicon_url = "https://www.rust-lang.org/favicon.ico",
98    html_root_url = "https://docs.rs/getopts/0.2.20"
99)]
100#![deny(missing_docs)]
101#![cfg_attr(test, deny(warnings))]
102
103#[cfg(test)]
104#[macro_use]
105extern crate log;
106extern crate unicode_width;
107
108use self::Fail::*;
109use self::HasArg::*;
110use self::Name::*;
111use self::Occur::*;
112use self::Optval::*;
113
114use std::error::Error;
115use std::ffi::OsStr;
116use std::fmt;
117use std::iter::{repeat, IntoIterator};
118use std::result;
119use std::str::FromStr;
120
121use unicode_width::UnicodeWidthStr;
122
123#[cfg(test)]
124mod tests;
125
126/// A description of the options that a program can handle.
127pub struct Options {
128    grps: Vec<OptGroup>,
129    parsing_style: ParsingStyle,
130    long_only: bool,
131}
132
133impl Default for Options {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139impl Options {
140    /// Create a blank set of options.
141    pub fn new() -> Options {
142        Options {
143            grps: Vec::new(),
144            parsing_style: ParsingStyle::FloatingFrees,
145            long_only: false,
146        }
147    }
148
149    /// Set the parsing style.
150    pub fn parsing_style(&mut self, style: ParsingStyle) -> &mut Options {
151        self.parsing_style = style;
152        self
153    }
154
155    /// Set or clear "long options only" mode.
156    ///
157    /// In "long options only" mode, short options cannot be clustered
158    /// together, and long options can be given with either a single
159    /// "-" or the customary "--".  This mode also changes the meaning
160    /// of "-a=b"; in the ordinary mode this will parse a short option
161    /// "-a" with argument "=b"; whereas in long-options-only mode the
162    /// argument will be simply "b".
163    pub fn long_only(&mut self, long_only: bool) -> &mut Options {
164        self.long_only = long_only;
165        self
166    }
167
168    /// Create a generic option group, stating all parameters explicitly.
169    pub fn opt(
170        &mut self,
171        short_name: &str,
172        long_name: &str,
173        desc: &str,
174        hint: &str,
175        hasarg: HasArg,
176        occur: Occur,
177    ) -> &mut Options {
178        validate_names(short_name, long_name);
179        self.grps.push(OptGroup {
180            short_name: short_name.to_string(),
181            long_name: long_name.to_string(),
182            hint: hint.to_string(),
183            desc: desc.to_string(),
184            hasarg,
185            occur,
186        });
187        self
188    }
189
190    /// Create a long option that is optional and does not take an argument.
191    ///
192    /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none
193    /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none
194    /// * `desc` - Description for usage help
195    pub fn optflag(&mut self, short_name: &str, long_name: &str, desc: &str) -> &mut Options {
196        validate_names(short_name, long_name);
197        self.grps.push(OptGroup {
198            short_name: short_name.to_string(),
199            long_name: long_name.to_string(),
200            hint: "".to_string(),
201            desc: desc.to_string(),
202            hasarg: No,
203            occur: Optional,
204        });
205        self
206    }
207
208    /// Create a long option that can occur more than once and does not
209    /// take an argument.
210    ///
211    /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none
212    /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none
213    /// * `desc` - Description for usage help
214    pub fn optflagmulti(&mut self, short_name: &str, long_name: &str, desc: &str) -> &mut Options {
215        validate_names(short_name, long_name);
216        self.grps.push(OptGroup {
217            short_name: short_name.to_string(),
218            long_name: long_name.to_string(),
219            hint: "".to_string(),
220            desc: desc.to_string(),
221            hasarg: No,
222            occur: Multi,
223        });
224        self
225    }
226
227    /// Create a long option that is optional and takes an optional argument.
228    ///
229    /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none
230    /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none
231    /// * `desc` - Description for usage help
232    /// * `hint` - Hint that is used in place of the argument in the usage help,
233    ///   e.g. `"FILE"` for a `-o FILE` option
234    pub fn optflagopt(
235        &mut self,
236        short_name: &str,
237        long_name: &str,
238        desc: &str,
239        hint: &str,
240    ) -> &mut Options {
241        validate_names(short_name, long_name);
242        self.grps.push(OptGroup {
243            short_name: short_name.to_string(),
244            long_name: long_name.to_string(),
245            hint: hint.to_string(),
246            desc: desc.to_string(),
247            hasarg: Maybe,
248            occur: Optional,
249        });
250        self
251    }
252
253    /// Create a long option that is optional, takes an argument, and may occur
254    /// multiple times.
255    ///
256    /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none
257    /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none
258    /// * `desc` - Description for usage help
259    /// * `hint` - Hint that is used in place of the argument in the usage help,
260    ///   e.g. `"FILE"` for a `-o FILE` option
261    pub fn optmulti(
262        &mut self,
263        short_name: &str,
264        long_name: &str,
265        desc: &str,
266        hint: &str,
267    ) -> &mut Options {
268        validate_names(short_name, long_name);
269        self.grps.push(OptGroup {
270            short_name: short_name.to_string(),
271            long_name: long_name.to_string(),
272            hint: hint.to_string(),
273            desc: desc.to_string(),
274            hasarg: Yes,
275            occur: Multi,
276        });
277        self
278    }
279
280    /// Create a long option that is optional and takes an argument.
281    ///
282    /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none
283    /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none
284    /// * `desc` - Description for usage help
285    /// * `hint` - Hint that is used in place of the argument in the usage help,
286    ///   e.g. `"FILE"` for a `-o FILE` option
287    pub fn optopt(
288        &mut self,
289        short_name: &str,
290        long_name: &str,
291        desc: &str,
292        hint: &str,
293    ) -> &mut Options {
294        validate_names(short_name, long_name);
295        self.grps.push(OptGroup {
296            short_name: short_name.to_string(),
297            long_name: long_name.to_string(),
298            hint: hint.to_string(),
299            desc: desc.to_string(),
300            hasarg: Yes,
301            occur: Optional,
302        });
303        self
304    }
305
306    /// Create a long option that is required and takes an argument.
307    ///
308    /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none
309    /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none
310    /// * `desc` - Description for usage help
311    /// * `hint` - Hint that is used in place of the argument in the usage help,
312    ///   e.g. `"FILE"` for a `-o FILE` option
313    pub fn reqopt(
314        &mut self,
315        short_name: &str,
316        long_name: &str,
317        desc: &str,
318        hint: &str,
319    ) -> &mut Options {
320        validate_names(short_name, long_name);
321        self.grps.push(OptGroup {
322            short_name: short_name.to_string(),
323            long_name: long_name.to_string(),
324            hint: hint.to_string(),
325            desc: desc.to_string(),
326            hasarg: Yes,
327            occur: Req,
328        });
329        self
330    }
331
332    /// Parse command line arguments according to the provided options.
333    ///
334    /// On success returns `Ok(Matches)`. Use methods such as `opt_present`
335    /// `opt_str`, etc. to interrogate results.
336    /// # Panics
337    ///
338    /// Returns `Err(Fail)` on failure: use the `Debug` implementation of `Fail`
339    /// to display information about it.
340    pub fn parse<C: IntoIterator>(&self, args: C) -> Result
341    where
342        C::Item: AsRef<OsStr>,
343    {
344        let opts: Vec<Opt> = self.grps.iter().map(|x| x.long_to_short()).collect();
345
346        let mut vals = (0..opts.len())
347            .map(|_| Vec::new())
348            .collect::<Vec<Vec<(usize, Optval)>>>();
349        let mut free: Vec<String> = Vec::new();
350        let args = args
351            .into_iter()
352            .map(|i| {
353                i.as_ref()
354                    .to_str()
355                    .ok_or_else(|| Fail::UnrecognizedOption(format!("{:?}", i.as_ref())))
356                    .map(|s| s.to_owned())
357            })
358            .collect::<::std::result::Result<Vec<_>, _>>()?;
359        let mut args = args.into_iter().peekable();
360        let mut arg_pos = 0;
361        while let Some(cur) = args.next() {
362            if !is_arg(&cur) {
363                free.push(cur);
364                match self.parsing_style {
365                    ParsingStyle::FloatingFrees => {}
366                    ParsingStyle::StopAtFirstFree => {
367                        free.extend(args);
368                        break;
369                    }
370                }
371            } else if cur == "--" {
372                free.extend(args);
373                break;
374            } else {
375                let mut names;
376                let mut i_arg = None;
377                let mut was_long = true;
378                if cur.as_bytes()[1] == b'-' || self.long_only {
379                    let tail = if cur.as_bytes()[1] == b'-' {
380                        &cur[2..]
381                    } else {
382                        assert!(self.long_only);
383                        &cur[1..]
384                    };
385                    let mut parts = tail.splitn(2, '=');
386                    names = vec![Name::from_str(parts.next().unwrap())];
387                    if let Some(rest) = parts.next() {
388                        i_arg = Some(rest.to_string());
389                    }
390                } else {
391                    was_long = false;
392                    names = Vec::new();
393                    for (j, ch) in cur.char_indices().skip(1) {
394                        let opt = Short(ch);
395
396                        /* In a series of potential options (eg. -aheJ), if we
397                           see one which takes an argument, we assume all
398                           subsequent characters make up the argument. This
399                           allows options such as -L/usr/local/lib/foo to be
400                           interpreted correctly
401                        */
402
403                        let opt_id = match find_opt(&opts, &opt) {
404                            Some(id) => id,
405                            None => return Err(UnrecognizedOption(opt.to_string())),
406                        };
407
408                        names.push(opt);
409
410                        let arg_follows = match opts[opt_id].hasarg {
411                            Yes | Maybe => true,
412                            No => false,
413                        };
414
415                        if arg_follows {
416                            let next = j + ch.len_utf8();
417                            if next < cur.len() {
418                                i_arg = Some(cur[next..].to_string());
419                                break;
420                            }
421                        }
422                    }
423                }
424                let mut name_pos = 0;
425                for nm in names.iter() {
426                    name_pos += 1;
427                    let optid = match find_opt(&opts, &nm) {
428                        Some(id) => id,
429                        None => return Err(UnrecognizedOption(nm.to_string())),
430                    };
431                    match opts[optid].hasarg {
432                        No => {
433                            if name_pos == names.len() && i_arg.is_some() {
434                                return Err(UnexpectedArgument(nm.to_string()));
435                            }
436                            vals[optid].push((arg_pos, Given));
437                        }
438                        Maybe => {
439                            // Note that here we do not handle `--arg value`.
440                            // This matches GNU getopt behavior; but also
441                            // makes sense, because if this were accepted,
442                            // then users could only write a "Maybe" long
443                            // option at the end of the arguments when
444                            // FloatingFrees is in use.
445                            if let Some(i_arg) = i_arg.take() {
446                                vals[optid].push((arg_pos, Val(i_arg)));
447                            } else if was_long
448                                || name_pos < names.len()
449                                || args.peek().map_or(true, |n| is_arg(&n))
450                            {
451                                vals[optid].push((arg_pos, Given));
452                            } else {
453                                vals[optid].push((arg_pos, Val(args.next().unwrap())));
454                            }
455                        }
456                        Yes => {
457                            if let Some(i_arg) = i_arg.take() {
458                                vals[optid].push((arg_pos, Val(i_arg)));
459                            } else if let Some(n) = args.next() {
460                                vals[optid].push((arg_pos, Val(n)));
461                            } else {
462                                return Err(ArgumentMissing(nm.to_string()));
463                            }
464                        }
465                    }
466                }
467            }
468            arg_pos += 1;
469        }
470        debug_assert_eq!(vals.len(), opts.len());
471        for (vals, opt) in vals.iter().zip(opts.iter()) {
472            if opt.occur == Req && vals.is_empty() {
473                return Err(OptionMissing(opt.name.to_string()));
474            }
475            if opt.occur != Multi && vals.len() > 1 {
476                return Err(OptionDuplicated(opt.name.to_string()));
477            }
478        }
479        Ok(Matches { opts, vals, free })
480    }
481
482    /// Derive a short one-line usage summary from a set of long options.
483    pub fn short_usage(&self, program_name: &str) -> String {
484        let mut line = format!("Usage: {} ", program_name);
485        line.push_str(
486            &self
487                .grps
488                .iter()
489                .map(format_option)
490                .collect::<Vec<String>>()
491                .join(" "),
492        );
493        line
494    }
495
496    /// Derive a formatted message from a set of options.
497    pub fn usage(&self, brief: &str) -> String {
498        self.usage_with_format(|opts| {
499            format!(
500                "{}\n\nOptions:\n{}\n",
501                brief,
502                opts.collect::<Vec<String>>().join("\n")
503            )
504        })
505    }
506
507    /// Derive a custom formatted message from a set of options. The formatted options provided to
508    /// a closure as an iterator.
509    pub fn usage_with_format<F: FnMut(&mut dyn Iterator<Item = String>) -> String>(
510        &self,
511        mut formatter: F,
512    ) -> String {
513        formatter(&mut self.usage_items())
514    }
515
516    /// Derive usage items from a set of options.
517    fn usage_items<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a> {
518        let desc_sep = format!("\n{}", repeat(" ").take(24).collect::<String>());
519
520        let any_short = self.grps.iter().any(|optref| !optref.short_name.is_empty());
521
522        let rows = self.grps.iter().map(move |optref| {
523            let OptGroup {
524                short_name,
525                long_name,
526                hint,
527                desc,
528                hasarg,
529                ..
530            } = (*optref).clone();
531
532            let mut row = "    ".to_string();
533
534            // short option
535            match short_name.width() {
536                0 => {
537                    if any_short {
538                        row.push_str("    ");
539                    }
540                }
541                1 => {
542                    row.push('-');
543                    row.push_str(&short_name);
544                    if long_name.width() > 0 {
545                        row.push_str(", ");
546                    } else {
547                        // Only a single space here, so that any
548                        // argument is printed in the correct spot.
549                        row.push(' ');
550                    }
551                }
552                // FIXME: refer issue #7.
553                _ => panic!("the short name should only be 1 ascii char long"),
554            }
555
556            // long option
557            match long_name.width() {
558                0 => {}
559                _ => {
560                    row.push_str(if self.long_only { "-" } else { "--" });
561                    row.push_str(&long_name);
562                    row.push(' ');
563                }
564            }
565
566            // arg
567            match hasarg {
568                No => {}
569                Yes => row.push_str(&hint),
570                Maybe => {
571                    row.push('[');
572                    row.push_str(&hint);
573                    row.push(']');
574                }
575            }
576
577            let rowlen = row.width();
578            if rowlen < 24 {
579                for _ in 0..24 - rowlen {
580                    row.push(' ');
581                }
582            } else {
583                row.push_str(&desc_sep)
584            }
585
586            let desc_rows = each_split_within(&desc, 54);
587            row.push_str(&desc_rows.join(&desc_sep));
588
589            row
590        });
591
592        Box::new(rows)
593    }
594}
595
596fn validate_names(short_name: &str, long_name: &str) {
597    let len = short_name.len();
598    assert!(
599        len == 1 || len == 0,
600        "the short_name (first argument) should be a single character, \
601         or an empty string for none"
602    );
603    let len = long_name.len();
604    assert!(
605        len == 0 || len > 1,
606        "the long_name (second argument) should be longer than a single \
607         character, or an empty string for none"
608    );
609}
610
611/// What parsing style to use when parsing arguments.
612#[derive(Clone, Copy, PartialEq, Eq)]
613pub enum ParsingStyle {
614    /// Flags and "free" arguments can be freely inter-mixed.
615    FloatingFrees,
616    /// As soon as a "free" argument (i.e. non-flag) is encountered, stop
617    /// considering any remaining arguments as flags.
618    StopAtFirstFree,
619}
620
621/// Name of an option. Either a string or a single char.
622#[derive(Clone, Debug, PartialEq, Eq)]
623enum Name {
624    /// A string representing the long name of an option.
625    /// For example: "help"
626    Long(String),
627    /// A char representing the short name of an option.
628    /// For example: 'h'
629    Short(char),
630}
631
632/// Describes whether an option has an argument.
633#[derive(Clone, Debug, Copy, PartialEq, Eq)]
634pub enum HasArg {
635    /// The option requires an argument.
636    Yes,
637    /// The option takes no argument.
638    No,
639    /// The option argument is optional.
640    Maybe,
641}
642
643/// Describes how often an option may occur.
644#[derive(Clone, Debug, Copy, PartialEq, Eq)]
645pub enum Occur {
646    /// The option occurs once.
647    Req,
648    /// The option occurs at most once.
649    Optional,
650    /// The option occurs zero or more times.
651    Multi,
652}
653
654/// A description of a possible option.
655#[derive(Clone, Debug, PartialEq, Eq)]
656struct Opt {
657    /// Name of the option
658    name: Name,
659    /// Whether it has an argument
660    hasarg: HasArg,
661    /// How often it can occur
662    occur: Occur,
663    /// Which options it aliases
664    aliases: Vec<Opt>,
665}
666
667/// One group of options, e.g., both `-h` and `--help`, along with
668/// their shared description and properties.
669#[derive(Clone, PartialEq, Eq)]
670struct OptGroup {
671    /// Short name of the option, e.g. `h` for a `-h` option
672    short_name: String,
673    /// Long name of the option, e.g. `help` for a `--help` option
674    long_name: String,
675    /// Hint for argument, e.g. `FILE` for a `-o FILE` option
676    hint: String,
677    /// Description for usage help text
678    desc: String,
679    /// Whether option has an argument
680    hasarg: HasArg,
681    /// How often it can occur
682    occur: Occur,
683}
684
685/// Describes whether an option is given at all or has a value.
686#[derive(Clone, Debug, PartialEq, Eq)]
687enum Optval {
688    Val(String),
689    Given,
690}
691
692/// The result of checking command line arguments. Contains a vector
693/// of matches and a vector of free strings.
694#[derive(Clone, Debug, PartialEq, Eq)]
695pub struct Matches {
696    /// Options that matched
697    opts: Vec<Opt>,
698    /// Values of the Options that matched and their positions
699    vals: Vec<Vec<(usize, Optval)>>,
700    /// Free string fragments
701    pub free: Vec<String>,
702}
703
704/// The type returned when the command line does not conform to the
705/// expected format. Use the `Debug` implementation to output detailed
706/// information.
707#[derive(Clone, Debug, PartialEq, Eq)]
708pub enum Fail {
709    /// The option requires an argument but none was passed.
710    ArgumentMissing(String),
711    /// The passed option is not declared among the possible options.
712    UnrecognizedOption(String),
713    /// A required option is not present.
714    OptionMissing(String),
715    /// A single occurrence option is being used multiple times.
716    OptionDuplicated(String),
717    /// There's an argument being passed to a non-argument option.
718    UnexpectedArgument(String),
719}
720
721impl Error for Fail {
722    fn description(&self) -> &str {
723        match *self {
724            ArgumentMissing(_) => "missing argument",
725            UnrecognizedOption(_) => "unrecognized option",
726            OptionMissing(_) => "missing option",
727            OptionDuplicated(_) => "duplicated option",
728            UnexpectedArgument(_) => "unexpected argument",
729        }
730    }
731}
732
733/// The result of parsing a command line with a set of options.
734pub type Result = result::Result<Matches, Fail>;
735
736impl Name {
737    fn from_str(nm: &str) -> Name {
738        if nm.len() == 1 {
739            Short(nm.as_bytes()[0] as char)
740        } else {
741            Long(nm.to_string())
742        }
743    }
744
745    fn to_string(&self) -> String {
746        match *self {
747            Short(ch) => ch.to_string(),
748            Long(ref s) => s.to_string(),
749        }
750    }
751}
752
753impl OptGroup {
754    /// Translate OptGroup into Opt.
755    /// (Both short and long names correspond to different Opts).
756    fn long_to_short(&self) -> Opt {
757        let OptGroup {
758            short_name,
759            long_name,
760            hasarg,
761            occur,
762            ..
763        } = (*self).clone();
764
765        match (short_name.len(), long_name.len()) {
766            (0, 0) => panic!("this long-format option was given no name"),
767            (0, _) => Opt {
768                name: Long(long_name),
769                hasarg,
770                occur,
771                aliases: Vec::new(),
772            },
773            (1, 0) => Opt {
774                name: Short(short_name.as_bytes()[0] as char),
775                hasarg,
776                occur,
777                aliases: Vec::new(),
778            },
779            (1, _) => Opt {
780                name: Long(long_name),
781                hasarg,
782                occur,
783                aliases: vec![Opt {
784                    name: Short(short_name.as_bytes()[0] as char),
785                    hasarg: hasarg,
786                    occur: occur,
787                    aliases: Vec::new(),
788                }],
789            },
790            (_, _) => panic!("something is wrong with the long-form opt"),
791        }
792    }
793}
794
795impl Matches {
796    fn opt_vals(&self, nm: &str) -> Vec<(usize, Optval)> {
797        match find_opt(&self.opts, &Name::from_str(nm)) {
798            Some(id) => self.vals[id].clone(),
799            None => panic!("No option '{}' defined", nm),
800        }
801    }
802
803    fn opt_val(&self, nm: &str) -> Option<Optval> {
804        self.opt_vals(nm).into_iter().map(|(_, o)| o).next()
805    }
806    /// Returns true if an option was defined
807    pub fn opt_defined(&self, nm: &str) -> bool {
808        find_opt(&self.opts, &Name::from_str(nm)).is_some()
809    }
810
811    /// Returns true if an option was matched.
812    pub fn opt_present(&self, nm: &str) -> bool {
813        !self.opt_vals(nm).is_empty()
814    }
815
816    /// Returns the number of times an option was matched.
817    pub fn opt_count(&self, nm: &str) -> usize {
818        self.opt_vals(nm).len()
819    }
820
821    /// Returns a vector of all the positions in which an option was matched.
822    pub fn opt_positions(&self, nm: &str) -> Vec<usize> {
823        self.opt_vals(nm).into_iter().map(|(pos, _)| pos).collect()
824    }
825
826    /// Returns true if any of several options were matched.
827    pub fn opts_present(&self, names: &[String]) -> bool {
828        names
829            .iter()
830            .any(|nm| match find_opt(&self.opts, &Name::from_str(&nm)) {
831                Some(id) if !self.vals[id].is_empty() => true,
832                _ => false,
833            })
834    }
835
836    /// Returns the string argument supplied to one of several matching options or `None`.
837    pub fn opts_str(&self, names: &[String]) -> Option<String> {
838        names
839            .iter()
840            .filter_map(|nm| match self.opt_val(&nm) {
841                Some(Val(s)) => Some(s),
842                _ => None,
843            })
844            .next()
845    }
846
847    /// Returns a vector of the arguments provided to all matches of the given
848    /// option.
849    ///
850    /// Used when an option accepts multiple values.
851    pub fn opt_strs(&self, nm: &str) -> Vec<String> {
852        self.opt_vals(nm)
853            .into_iter()
854            .filter_map(|(_, v)| match v {
855                Val(s) => Some(s),
856                _ => None,
857            })
858            .collect()
859    }
860
861    /// Returns a vector of the arguments provided to all matches of the given
862    /// option, together with their positions.
863    ///
864    /// Used when an option accepts multiple values.
865    pub fn opt_strs_pos(&self, nm: &str) -> Vec<(usize, String)> {
866        self.opt_vals(nm)
867            .into_iter()
868            .filter_map(|(p, v)| match v {
869                Val(s) => Some((p, s)),
870                _ => None,
871            })
872            .collect()
873    }
874
875    /// Returns the string argument supplied to a matching option or `None`.
876    pub fn opt_str(&self, nm: &str) -> Option<String> {
877        match self.opt_val(nm) {
878            Some(Val(s)) => Some(s),
879            _ => None,
880        }
881    }
882
883    /// Returns the matching string, a default, or `None`.
884    ///
885    /// Returns `None` if the option was not present, `def` if the option was
886    /// present but no argument was provided, and the argument if the option was
887    /// present and an argument was provided.
888    pub fn opt_default(&self, nm: &str, def: &str) -> Option<String> {
889        match self.opt_val(nm) {
890            Some(Val(s)) => Some(s),
891            Some(_) => Some(def.to_string()),
892            None => None,
893        }
894    }
895
896    /// Returns some matching value or `None`.
897    ///
898    /// Similar to opt_str, also converts matching argument using FromStr.
899    pub fn opt_get<T>(&self, nm: &str) -> result::Result<Option<T>, T::Err>
900    where
901        T: FromStr,
902    {
903        match self.opt_val(nm) {
904            Some(Val(s)) => Ok(Some(s.parse()?)),
905            Some(Given) => Ok(None),
906            None => Ok(None),
907        }
908    }
909
910    /// Returns a matching value or default.
911    ///
912    /// Similar to opt_default, except the two differences.
913    /// Instead of returning None when argument was not present, return `def`.
914    /// Instead of returning &str return type T, parsed using str::parse().
915    pub fn opt_get_default<T>(&self, nm: &str, def: T) -> result::Result<T, T::Err>
916    where
917        T: FromStr,
918    {
919        match self.opt_val(nm) {
920            Some(Val(s)) => s.parse(),
921            Some(Given) => Ok(def),
922            None => Ok(def),
923        }
924    }
925}
926
927fn is_arg(arg: &str) -> bool {
928    arg.as_bytes().get(0) == Some(&b'-') && arg.len() > 1
929}
930
931fn find_opt(opts: &[Opt], nm: &Name) -> Option<usize> {
932    // Search main options.
933    let pos = opts.iter().position(|opt| &opt.name == nm);
934    if pos.is_some() {
935        return pos;
936    }
937
938    // Search in aliases.
939    for candidate in opts.iter() {
940        if candidate.aliases.iter().any(|opt| &opt.name == nm) {
941            return opts.iter().position(|opt| opt.name == candidate.name);
942        }
943    }
944
945    None
946}
947
948impl fmt::Display for Fail {
949    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
950        match *self {
951            ArgumentMissing(ref nm) => write!(f, "Argument to option '{}' missing", *nm),
952            UnrecognizedOption(ref nm) => write!(f, "Unrecognized option: '{}'", *nm),
953            OptionMissing(ref nm) => write!(f, "Required option '{}' missing", *nm),
954            OptionDuplicated(ref nm) => write!(f, "Option '{}' given more than once", *nm),
955            UnexpectedArgument(ref nm) => write!(f, "Option '{}' does not take an argument", *nm),
956        }
957    }
958}
959
960fn format_option(opt: &OptGroup) -> String {
961    let mut line = String::new();
962
963    if opt.occur != Req {
964        line.push('[');
965    }
966
967    // Use short_name if possible, but fall back to long_name.
968    if !opt.short_name.is_empty() {
969        line.push('-');
970        line.push_str(&opt.short_name);
971    } else {
972        line.push_str("--");
973        line.push_str(&opt.long_name);
974    }
975
976    if opt.hasarg != No {
977        line.push(' ');
978        if opt.hasarg == Maybe {
979            line.push('[');
980        }
981        line.push_str(&opt.hint);
982        if opt.hasarg == Maybe {
983            line.push(']');
984        }
985    }
986
987    if opt.occur != Req {
988        line.push(']');
989    }
990    if opt.occur == Multi {
991        line.push_str("..");
992    }
993
994    line
995}
996
997/// Splits a string into substrings with possibly internal whitespace,
998/// each of them at most `lim` bytes long, if possible. The substrings
999/// have leading and trailing whitespace removed, and are only cut at
1000/// whitespace boundaries.
1001fn each_split_within(desc: &str, lim: usize) -> Vec<String> {
1002    let mut rows = Vec::new();
1003    for line in desc.trim().lines() {
1004        let line_chars = line.chars().chain(Some(' '));
1005        let words = line_chars
1006            .fold((Vec::new(), 0, 0), |(mut words, a, z), c| {
1007                let idx = z + c.len_utf8(); // Get the current byte offset
1008
1009                // If the char is whitespace, advance the word start and maybe push a word
1010                if c.is_whitespace() {
1011                    if a != z {
1012                        words.push(&line[a..z]);
1013                    }
1014                    (words, idx, idx)
1015                }
1016                // If the char is not whitespace, continue, retaining the current
1017                else {
1018                    (words, a, idx)
1019                }
1020            })
1021            .0;
1022
1023        let mut row = String::new();
1024        for word in words.iter() {
1025            let sep = if !row.is_empty() { Some(" ") } else { None };
1026            let width = row.width() + word.width() + sep.map(UnicodeWidthStr::width).unwrap_or(0);
1027
1028            if width <= lim {
1029                if let Some(sep) = sep {
1030                    row.push_str(sep)
1031                }
1032                row.push_str(word);
1033                continue;
1034            }
1035            if !row.is_empty() {
1036                rows.push(row.clone());
1037                row.clear();
1038            }
1039            row.push_str(word);
1040        }
1041        if !row.is_empty() {
1042            rows.push(row);
1043        }
1044    }
1045    rows
1046}