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