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}