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}