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