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