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