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