Skip to main content

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