[go: up one dir, main page]

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