]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
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. | |
1a4d82fc JJ |
10 | |
11 | //! Simple getopt alternative. | |
12 | //! | |
13 | //! Construct a vector of options, either by using `reqopt`, `optopt`, and `optflag` | |
14 | //! or by building them from components yourself, and pass them to `getopts`, | |
15 | //! along with a vector of actual arguments (not including `argv[0]`). You'll | |
16 | //! either get a failure code back, or a match. You'll have to verify whether | |
17 | //! the amount of 'free' arguments in the match is what you expect. Use `opt_*` | |
18 | //! accessors to get argument values out of the matches object. | |
19 | //! | |
20 | //! Single-character options are expected to appear on the command line with a | |
21 | //! single preceding dash; multiple-character options are expected to be | |
22 | //! proceeded by two dashes. Options that expect an argument accept their | |
23 | //! argument following either a space or an equals sign. Single-character | |
24 | //! options don't require the space. | |
25 | //! | |
26 | //! # Example | |
27 | //! | |
28 | //! The following example shows simple command line parsing for an application | |
29 | //! that requires an input file to be specified, accepts an optional output | |
30 | //! file name following `-o`, and accepts both `-h` and `--help` as optional flags. | |
31 | //! | |
32 | //! ```{.rust} | |
33 | //! extern crate getopts; | |
34 | //! use getopts::{optopt,optflag,getopts,OptGroup,usage}; | |
35 | //! use std::os; | |
36 | //! | |
37 | //! fn do_work(inp: &str, out: Option<String>) { | |
38 | //! println!("{}", inp); | |
39 | //! match out { | |
40 | //! Some(x) => println!("{}", x), | |
41 | //! None => println!("No Output"), | |
42 | //! } | |
43 | //! } | |
44 | //! | |
45 | //! fn print_usage(program: &str, opts: &[OptGroup]) { | |
46 | //! let brief = format!("Usage: {} [options]", program); | |
c34b1796 | 47 | //! print!("{}", usage(brief, opts)); |
1a4d82fc JJ |
48 | //! } |
49 | //! | |
50 | //! fn main() { | |
51 | //! let args: Vec<String> = os::args(); | |
52 | //! | |
53 | //! let program = args[0].clone(); | |
54 | //! | |
55 | //! let opts = &[ | |
56 | //! optopt("o", "", "set output file name", "NAME"), | |
57 | //! optflag("h", "help", "print this help menu") | |
58 | //! ]; | |
59 | //! let matches = match getopts(args.tail(), opts) { | |
60 | //! Ok(m) => { m } | |
61 | //! Err(f) => { panic!(f.to_string()) } | |
62 | //! }; | |
63 | //! if matches.opt_present("h") { | |
c34b1796 | 64 | //! print_usage(program, opts); |
1a4d82fc JJ |
65 | //! return; |
66 | //! } | |
67 | //! let output = matches.opt_str("o"); | |
68 | //! let input = if !matches.free.is_empty() { | |
69 | //! matches.free[0].clone() | |
70 | //! } else { | |
c34b1796 | 71 | //! print_usage(program, opts); |
1a4d82fc JJ |
72 | //! return; |
73 | //! }; | |
c34b1796 | 74 | //! do_work(input, output); |
1a4d82fc JJ |
75 | //! } |
76 | //! ``` | |
77 | ||
c34b1796 AL |
78 | |
79 | // Do not remove on snapshot creation. Needed for bootstrap. (Issue #22364) | |
80 | #![cfg_attr(stage0, feature(custom_attribute))] | |
1a4d82fc | 81 | #![crate_name = "getopts"] |
85aaf69f SL |
82 | #![unstable(feature = "rustc_private", |
83 | reason = "use the crates.io `getopts` library instead")] | |
1a4d82fc JJ |
84 | #![staged_api] |
85 | #![crate_type = "rlib"] | |
86 | #![crate_type = "dylib"] | |
87 | #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", | |
88 | html_favicon_url = "http://www.rust-lang.org/favicon.ico", | |
89 | html_root_url = "http://doc.rust-lang.org/nightly/", | |
90 | html_playground_url = "http://play.rust-lang.org/")] | |
85aaf69f | 91 | |
1a4d82fc | 92 | #![deny(missing_docs)] |
85aaf69f | 93 | #![feature(staged_api)] |
c34b1796 | 94 | #![feature(str_char)] |
85aaf69f | 95 | #![cfg_attr(test, feature(rustc_private))] |
1a4d82fc JJ |
96 | |
97 | #[cfg(test)] #[macro_use] extern crate log; | |
98 | ||
99 | use self::Name::*; | |
100 | use self::HasArg::*; | |
101 | use self::Occur::*; | |
102 | use self::Fail::*; | |
103 | use self::Optval::*; | |
104 | use self::SplitWithinState::*; | |
105 | use self::Whitespace::*; | |
106 | use self::LengthLimit::*; | |
107 | ||
108 | use std::fmt; | |
109 | use std::iter::repeat; | |
110 | use std::result; | |
111 | ||
112 | /// Name of an option. Either a string or a single char. | |
85aaf69f | 113 | #[derive(Clone, PartialEq, Eq, Debug)] |
1a4d82fc JJ |
114 | pub enum Name { |
115 | /// A string representing the long name of an option. | |
116 | /// For example: "help" | |
117 | Long(String), | |
118 | /// A char representing the short name of an option. | |
119 | /// For example: 'h' | |
120 | Short(char), | |
121 | } | |
122 | ||
123 | /// Describes whether an option has an argument. | |
85aaf69f | 124 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
1a4d82fc JJ |
125 | pub enum HasArg { |
126 | /// The option requires an argument. | |
127 | Yes, | |
128 | /// The option takes no argument. | |
129 | No, | |
130 | /// The option argument is optional. | |
131 | Maybe, | |
132 | } | |
133 | ||
134 | /// Describes how often an option may occur. | |
85aaf69f | 135 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
1a4d82fc JJ |
136 | pub enum Occur { |
137 | /// The option occurs once. | |
138 | Req, | |
139 | /// The option occurs at most once. | |
140 | Optional, | |
141 | /// The option occurs zero or more times. | |
142 | Multi, | |
143 | } | |
144 | ||
145 | /// A description of a possible option. | |
85aaf69f | 146 | #[derive(Clone, PartialEq, Eq, Debug)] |
1a4d82fc JJ |
147 | pub struct Opt { |
148 | /// Name of the option | |
149 | pub name: Name, | |
150 | /// Whether it has an argument | |
151 | pub hasarg: HasArg, | |
152 | /// How often it can occur | |
153 | pub occur: Occur, | |
154 | /// Which options it aliases | |
155 | pub aliases: Vec<Opt>, | |
156 | } | |
157 | ||
158 | /// One group of options, e.g., both `-h` and `--help`, along with | |
159 | /// their shared description and properties. | |
85aaf69f | 160 | #[derive(Clone, PartialEq, Eq, Debug)] |
1a4d82fc JJ |
161 | pub struct OptGroup { |
162 | /// Short name of the option, e.g. `h` for a `-h` option | |
163 | pub short_name: String, | |
164 | /// Long name of the option, e.g. `help` for a `--help` option | |
165 | pub long_name: String, | |
166 | /// Hint for argument, e.g. `FILE` for a `-o FILE` option | |
167 | pub hint: String, | |
168 | /// Description for usage help text | |
169 | pub desc: String, | |
170 | /// Whether option has an argument | |
171 | pub hasarg: HasArg, | |
172 | /// How often it can occur | |
173 | pub occur: Occur | |
174 | } | |
175 | ||
176 | /// Describes whether an option is given at all or has a value. | |
85aaf69f | 177 | #[derive(Clone, PartialEq, Eq, Debug)] |
1a4d82fc JJ |
178 | enum Optval { |
179 | Val(String), | |
180 | Given, | |
181 | } | |
182 | ||
183 | /// The result of checking command line arguments. Contains a vector | |
184 | /// of matches and a vector of free strings. | |
85aaf69f | 185 | #[derive(Clone, PartialEq, Eq, Debug)] |
1a4d82fc JJ |
186 | pub struct Matches { |
187 | /// Options that matched | |
188 | opts: Vec<Opt>, | |
189 | /// Values of the Options that matched | |
190 | vals: Vec<Vec<Optval>>, | |
191 | /// Free string fragments | |
192 | pub free: Vec<String>, | |
193 | } | |
194 | ||
195 | /// The type returned when the command line does not conform to the | |
85aaf69f | 196 | /// expected format. Use the `Debug` implementation to output detailed |
1a4d82fc | 197 | /// information. |
85aaf69f | 198 | #[derive(Clone, PartialEq, Eq, Debug)] |
1a4d82fc JJ |
199 | pub enum Fail { |
200 | /// The option requires an argument but none was passed. | |
201 | ArgumentMissing(String), | |
202 | /// The passed option is not declared among the possible options. | |
203 | UnrecognizedOption(String), | |
204 | /// A required option is not present. | |
205 | OptionMissing(String), | |
206 | /// A single occurrence option is being used multiple times. | |
207 | OptionDuplicated(String), | |
208 | /// There's an argument being passed to a non-argument option. | |
209 | UnexpectedArgument(String), | |
210 | } | |
211 | ||
212 | /// The type of failure that occurred. | |
c34b1796 | 213 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
1a4d82fc JJ |
214 | #[allow(missing_docs)] |
215 | pub enum FailType { | |
216 | ArgumentMissing_, | |
217 | UnrecognizedOption_, | |
218 | OptionMissing_, | |
219 | OptionDuplicated_, | |
220 | UnexpectedArgument_, | |
221 | } | |
222 | ||
223 | /// The result of parsing a command line with a set of options. | |
224 | pub type Result = result::Result<Matches, Fail>; | |
225 | ||
226 | impl Name { | |
227 | fn from_str(nm: &str) -> Name { | |
85aaf69f SL |
228 | if nm.len() == 1 { |
229 | Short(nm.char_at(0)) | |
1a4d82fc JJ |
230 | } else { |
231 | Long(nm.to_string()) | |
232 | } | |
233 | } | |
234 | ||
235 | fn to_string(&self) -> String { | |
236 | match *self { | |
237 | Short(ch) => ch.to_string(), | |
238 | Long(ref s) => s.to_string() | |
239 | } | |
240 | } | |
241 | } | |
242 | ||
243 | impl OptGroup { | |
244 | /// Translate OptGroup into Opt. | |
245 | /// (Both short and long names correspond to different Opts). | |
246 | pub fn long_to_short(&self) -> Opt { | |
247 | let OptGroup { | |
248 | short_name, | |
249 | long_name, | |
250 | hasarg, | |
251 | occur, | |
252 | .. | |
253 | } = (*self).clone(); | |
254 | ||
255 | match (short_name.len(), long_name.len()) { | |
256 | (0,0) => panic!("this long-format option was given no name"), | |
257 | (0,_) => Opt { | |
258 | name: Long((long_name)), | |
259 | hasarg: hasarg, | |
260 | occur: occur, | |
261 | aliases: Vec::new() | |
262 | }, | |
263 | (1,0) => Opt { | |
264 | name: Short(short_name.char_at(0)), | |
265 | hasarg: hasarg, | |
266 | occur: occur, | |
267 | aliases: Vec::new() | |
268 | }, | |
269 | (1,_) => Opt { | |
270 | name: Long((long_name)), | |
271 | hasarg: hasarg, | |
272 | occur: occur, | |
273 | aliases: vec!( | |
274 | Opt { | |
275 | name: Short(short_name.char_at(0)), | |
276 | hasarg: hasarg, | |
277 | occur: occur, | |
278 | aliases: Vec::new() | |
279 | } | |
280 | ) | |
281 | }, | |
282 | (_,_) => panic!("something is wrong with the long-form opt") | |
283 | } | |
284 | } | |
285 | } | |
286 | ||
287 | impl Matches { | |
288 | fn opt_vals(&self, nm: &str) -> Vec<Optval> { | |
85aaf69f | 289 | match find_opt(&self.opts[..], Name::from_str(nm)) { |
1a4d82fc JJ |
290 | Some(id) => self.vals[id].clone(), |
291 | None => panic!("No option '{}' defined", nm) | |
292 | } | |
293 | } | |
294 | ||
295 | fn opt_val(&self, nm: &str) -> Option<Optval> { | |
296 | let vals = self.opt_vals(nm); | |
297 | if vals.is_empty() { | |
298 | None | |
299 | } else { | |
300 | Some(vals[0].clone()) | |
301 | } | |
302 | } | |
303 | ||
304 | /// Returns true if an option was matched. | |
305 | pub fn opt_present(&self, nm: &str) -> bool { | |
306 | !self.opt_vals(nm).is_empty() | |
307 | } | |
308 | ||
309 | /// Returns the number of times an option was matched. | |
c34b1796 | 310 | pub fn opt_count(&self, nm: &str) -> usize { |
1a4d82fc JJ |
311 | self.opt_vals(nm).len() |
312 | } | |
313 | ||
314 | /// Returns true if any of several options were matched. | |
315 | pub fn opts_present(&self, names: &[String]) -> bool { | |
85aaf69f SL |
316 | for nm in names { |
317 | match find_opt(&self.opts, Name::from_str(&**nm)) { | |
1a4d82fc JJ |
318 | Some(id) if !self.vals[id].is_empty() => return true, |
319 | _ => (), | |
320 | }; | |
321 | } | |
322 | false | |
323 | } | |
324 | ||
325 | /// Returns the string argument supplied to one of several matching options or `None`. | |
326 | pub fn opts_str(&self, names: &[String]) -> Option<String> { | |
85aaf69f SL |
327 | for nm in names { |
328 | match self.opt_val(&nm[..]) { | |
1a4d82fc JJ |
329 | Some(Val(ref s)) => return Some(s.clone()), |
330 | _ => () | |
331 | } | |
332 | } | |
333 | None | |
334 | } | |
335 | ||
336 | /// Returns a vector of the arguments provided to all matches of the given | |
337 | /// option. | |
338 | /// | |
339 | /// Used when an option accepts multiple values. | |
340 | pub fn opt_strs(&self, nm: &str) -> Vec<String> { | |
341 | let mut acc: Vec<String> = Vec::new(); | |
342 | let r = self.opt_vals(nm); | |
85aaf69f | 343 | for v in &r { |
1a4d82fc JJ |
344 | match *v { |
345 | Val(ref s) => acc.push((*s).clone()), | |
346 | _ => () | |
347 | } | |
348 | } | |
349 | acc | |
350 | } | |
351 | ||
352 | /// Returns the string argument supplied to a matching option or `None`. | |
353 | pub fn opt_str(&self, nm: &str) -> Option<String> { | |
354 | let vals = self.opt_vals(nm); | |
355 | if vals.is_empty() { | |
356 | return None::<String>; | |
357 | } | |
358 | match vals[0] { | |
359 | Val(ref s) => Some((*s).clone()), | |
360 | _ => None | |
361 | } | |
362 | } | |
363 | ||
364 | ||
365 | /// Returns the matching string, a default, or none. | |
366 | /// | |
367 | /// Returns none if the option was not present, `def` if the option was | |
368 | /// present but no argument was provided, and the argument if the option was | |
369 | /// present and an argument was provided. | |
370 | pub fn opt_default(&self, nm: &str, def: &str) -> Option<String> { | |
371 | let vals = self.opt_vals(nm); | |
372 | if vals.is_empty() { | |
373 | None | |
374 | } else { | |
375 | match vals[0] { | |
376 | Val(ref s) => Some((*s).clone()), | |
377 | _ => Some(def.to_string()) | |
378 | } | |
379 | } | |
380 | } | |
381 | ||
382 | } | |
383 | ||
384 | fn is_arg(arg: &str) -> bool { | |
385 | arg.len() > 1 && arg.as_bytes()[0] == b'-' | |
386 | } | |
387 | ||
c34b1796 | 388 | fn find_opt(opts: &[Opt], nm: Name) -> Option<usize> { |
1a4d82fc JJ |
389 | // Search main options. |
390 | let pos = opts.iter().position(|opt| opt.name == nm); | |
391 | if pos.is_some() { | |
392 | return pos | |
393 | } | |
394 | ||
395 | // Search in aliases. | |
85aaf69f | 396 | for candidate in opts { |
1a4d82fc JJ |
397 | if candidate.aliases.iter().position(|opt| opt.name == nm).is_some() { |
398 | return opts.iter().position(|opt| opt.name == candidate.name); | |
399 | } | |
400 | } | |
401 | ||
402 | None | |
403 | } | |
404 | ||
405 | /// Create a long option that is required and takes an argument. | |
406 | /// | |
407 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none | |
408 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none | |
409 | /// * `desc` - Description for usage help | |
410 | /// * `hint` - Hint that is used in place of the argument in the usage help, | |
411 | /// e.g. `"FILE"` for a `-o FILE` option | |
412 | pub fn reqopt(short_name: &str, long_name: &str, desc: &str, hint: &str) -> OptGroup { | |
413 | let len = short_name.len(); | |
414 | assert!(len == 1 || len == 0); | |
415 | OptGroup { | |
416 | short_name: short_name.to_string(), | |
417 | long_name: long_name.to_string(), | |
418 | hint: hint.to_string(), | |
419 | desc: desc.to_string(), | |
420 | hasarg: Yes, | |
421 | occur: Req | |
422 | } | |
423 | } | |
424 | ||
425 | /// Create a long option that is optional and takes an argument. | |
426 | /// | |
427 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none | |
428 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none | |
429 | /// * `desc` - Description for usage help | |
430 | /// * `hint` - Hint that is used in place of the argument in the usage help, | |
431 | /// e.g. `"FILE"` for a `-o FILE` option | |
432 | pub fn optopt(short_name: &str, long_name: &str, desc: &str, hint: &str) -> OptGroup { | |
433 | let len = short_name.len(); | |
434 | assert!(len == 1 || len == 0); | |
435 | OptGroup { | |
436 | short_name: short_name.to_string(), | |
437 | long_name: long_name.to_string(), | |
438 | hint: hint.to_string(), | |
439 | desc: desc.to_string(), | |
440 | hasarg: Yes, | |
441 | occur: Optional | |
442 | } | |
443 | } | |
444 | ||
445 | /// Create a long option that is optional and does not take an argument. | |
446 | /// | |
447 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none | |
448 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none | |
449 | /// * `desc` - Description for usage help | |
450 | pub fn optflag(short_name: &str, long_name: &str, desc: &str) -> OptGroup { | |
451 | let len = short_name.len(); | |
452 | assert!(len == 1 || len == 0); | |
453 | OptGroup { | |
454 | short_name: short_name.to_string(), | |
455 | long_name: long_name.to_string(), | |
456 | hint: "".to_string(), | |
457 | desc: desc.to_string(), | |
458 | hasarg: No, | |
459 | occur: Optional | |
460 | } | |
461 | } | |
462 | ||
463 | /// Create a long option that can occur more than once and does not | |
464 | /// take an argument. | |
465 | /// | |
466 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none | |
467 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none | |
468 | /// * `desc` - Description for usage help | |
469 | pub fn optflagmulti(short_name: &str, long_name: &str, desc: &str) -> OptGroup { | |
470 | let len = short_name.len(); | |
471 | assert!(len == 1 || len == 0); | |
472 | OptGroup { | |
473 | short_name: short_name.to_string(), | |
474 | long_name: long_name.to_string(), | |
475 | hint: "".to_string(), | |
476 | desc: desc.to_string(), | |
477 | hasarg: No, | |
478 | occur: Multi | |
479 | } | |
480 | } | |
481 | ||
482 | /// Create a long option that is optional and takes an optional argument. | |
483 | /// | |
484 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none | |
485 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none | |
486 | /// * `desc` - Description for usage help | |
487 | /// * `hint` - Hint that is used in place of the argument in the usage help, | |
488 | /// e.g. `"FILE"` for a `-o FILE` option | |
489 | pub fn optflagopt(short_name: &str, long_name: &str, desc: &str, hint: &str) -> OptGroup { | |
490 | let len = short_name.len(); | |
491 | assert!(len == 1 || len == 0); | |
492 | OptGroup { | |
493 | short_name: short_name.to_string(), | |
494 | long_name: long_name.to_string(), | |
495 | hint: hint.to_string(), | |
496 | desc: desc.to_string(), | |
497 | hasarg: Maybe, | |
498 | occur: Optional | |
499 | } | |
500 | } | |
501 | ||
502 | /// Create a long option that is optional, takes an argument, and may occur | |
503 | /// multiple times. | |
504 | /// | |
505 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none | |
506 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none | |
507 | /// * `desc` - Description for usage help | |
508 | /// * `hint` - Hint that is used in place of the argument in the usage help, | |
509 | /// e.g. `"FILE"` for a `-o FILE` option | |
510 | pub fn optmulti(short_name: &str, long_name: &str, desc: &str, hint: &str) -> OptGroup { | |
511 | let len = short_name.len(); | |
512 | assert!(len == 1 || len == 0); | |
513 | OptGroup { | |
514 | short_name: short_name.to_string(), | |
515 | long_name: long_name.to_string(), | |
516 | hint: hint.to_string(), | |
517 | desc: desc.to_string(), | |
518 | hasarg: Yes, | |
519 | occur: Multi | |
520 | } | |
521 | } | |
522 | ||
523 | /// Create a generic option group, stating all parameters explicitly | |
524 | pub fn opt(short_name: &str, | |
525 | long_name: &str, | |
526 | desc: &str, | |
527 | hint: &str, | |
528 | hasarg: HasArg, | |
529 | occur: Occur) -> OptGroup { | |
530 | let len = short_name.len(); | |
531 | assert!(len == 1 || len == 0); | |
532 | OptGroup { | |
533 | short_name: short_name.to_string(), | |
534 | long_name: long_name.to_string(), | |
535 | hint: hint.to_string(), | |
536 | desc: desc.to_string(), | |
537 | hasarg: hasarg, | |
538 | occur: occur | |
539 | } | |
540 | } | |
541 | ||
542 | impl Fail { | |
543 | /// Convert a `Fail` enum into an error string. | |
85aaf69f SL |
544 | #[unstable(feature = "rustc_private")] |
545 | #[deprecated(since = "1.0.0", | |
546 | reason = "use `fmt::Display` (`{}` format specifier)")] | |
1a4d82fc JJ |
547 | pub fn to_err_msg(self) -> String { |
548 | self.to_string() | |
549 | } | |
550 | } | |
551 | ||
85aaf69f | 552 | impl fmt::Display for Fail { |
1a4d82fc JJ |
553 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
554 | match *self { | |
555 | ArgumentMissing(ref nm) => { | |
556 | write!(f, "Argument to option '{}' missing.", *nm) | |
557 | } | |
558 | UnrecognizedOption(ref nm) => { | |
559 | write!(f, "Unrecognized option: '{}'.", *nm) | |
560 | } | |
561 | OptionMissing(ref nm) => { | |
562 | write!(f, "Required option '{}' missing.", *nm) | |
563 | } | |
564 | OptionDuplicated(ref nm) => { | |
565 | write!(f, "Option '{}' given more than once.", *nm) | |
566 | } | |
567 | UnexpectedArgument(ref nm) => { | |
568 | write!(f, "Option '{}' does not take an argument.", *nm) | |
569 | } | |
570 | } | |
571 | } | |
572 | } | |
573 | ||
574 | /// Parse command line arguments according to the provided options. | |
575 | /// | |
576 | /// On success returns `Ok(Matches)`. Use methods such as `opt_present` | |
577 | /// `opt_str`, etc. to interrogate results. | |
578 | /// # Panics | |
579 | /// | |
85aaf69f | 580 | /// Returns `Err(Fail)` on failure: use the `Debug` implementation of `Fail` to display |
1a4d82fc JJ |
581 | /// information about it. |
582 | pub fn getopts(args: &[String], optgrps: &[OptGroup]) -> Result { | |
583 | let opts: Vec<Opt> = optgrps.iter().map(|x| x.long_to_short()).collect(); | |
584 | let n_opts = opts.len(); | |
585 | ||
c34b1796 | 586 | fn f(_x: usize) -> Vec<Optval> { return Vec::new(); } |
1a4d82fc | 587 | |
85aaf69f | 588 | let mut vals: Vec<_> = (0..n_opts).map(f).collect(); |
1a4d82fc JJ |
589 | let mut free: Vec<String> = Vec::new(); |
590 | let l = args.len(); | |
591 | let mut i = 0; | |
592 | while i < l { | |
593 | let cur = args[i].clone(); | |
594 | let curlen = cur.len(); | |
85aaf69f | 595 | if !is_arg(&cur[..]) { |
1a4d82fc JJ |
596 | free.push(cur); |
597 | } else if cur == "--" { | |
598 | let mut j = i + 1; | |
599 | while j < l { free.push(args[j].clone()); j += 1; } | |
600 | break; | |
601 | } else { | |
602 | let mut names; | |
603 | let mut i_arg = None; | |
604 | if cur.as_bytes()[1] == b'-' { | |
605 | let tail = &cur[2..curlen]; | |
606 | let tail_eq: Vec<&str> = tail.split('=').collect(); | |
607 | if tail_eq.len() <= 1 { | |
608 | names = vec!(Long(tail.to_string())); | |
609 | } else { | |
610 | names = | |
611 | vec!(Long(tail_eq[0].to_string())); | |
612 | i_arg = Some(tail_eq[1].to_string()); | |
613 | } | |
614 | } else { | |
615 | let mut j = 1; | |
616 | names = Vec::new(); | |
617 | while j < curlen { | |
c34b1796 AL |
618 | let ch = cur.char_at(j); |
619 | let opt = Short(ch); | |
1a4d82fc JJ |
620 | |
621 | /* In a series of potential options (eg. -aheJ), if we | |
622 | see one which takes an argument, we assume all | |
623 | subsequent characters make up the argument. This | |
624 | allows options such as -L/usr/local/lib/foo to be | |
625 | interpreted correctly | |
626 | */ | |
627 | ||
85aaf69f | 628 | let opt_id = match find_opt(&opts, opt.clone()) { |
1a4d82fc JJ |
629 | Some(id) => id, |
630 | None => return Err(UnrecognizedOption(opt.to_string())) | |
631 | }; | |
632 | ||
633 | names.push(opt); | |
634 | ||
635 | let arg_follows = match opts[opt_id].hasarg { | |
636 | Yes | Maybe => true, | |
637 | No => false | |
638 | }; | |
639 | ||
c34b1796 AL |
640 | let next = j + ch.len_utf8(); |
641 | if arg_follows && next < curlen { | |
642 | i_arg = Some((&cur[next..curlen]).to_string()); | |
1a4d82fc JJ |
643 | break; |
644 | } | |
645 | ||
c34b1796 | 646 | j = next; |
1a4d82fc JJ |
647 | } |
648 | } | |
649 | let mut name_pos = 0; | |
85aaf69f | 650 | for nm in &names { |
1a4d82fc | 651 | name_pos += 1; |
85aaf69f | 652 | let optid = match find_opt(&opts, (*nm).clone()) { |
1a4d82fc JJ |
653 | Some(id) => id, |
654 | None => return Err(UnrecognizedOption(nm.to_string())) | |
655 | }; | |
656 | match opts[optid].hasarg { | |
657 | No => { | |
658 | if name_pos == names.len() && !i_arg.is_none() { | |
659 | return Err(UnexpectedArgument(nm.to_string())); | |
660 | } | |
661 | let v = &mut vals[optid]; | |
662 | v.push(Given); | |
663 | } | |
664 | Maybe => { | |
665 | if !i_arg.is_none() { | |
666 | let v = &mut vals[optid]; | |
667 | v.push(Val((i_arg.clone()) | |
668 | .unwrap())); | |
669 | } else if name_pos < names.len() || i + 1 == l || | |
85aaf69f | 670 | is_arg(&args[i + 1][..]) { |
1a4d82fc JJ |
671 | let v = &mut vals[optid]; |
672 | v.push(Given); | |
673 | } else { | |
674 | i += 1; | |
675 | let v = &mut vals[optid]; | |
676 | v.push(Val(args[i].clone())); | |
677 | } | |
678 | } | |
679 | Yes => { | |
680 | if !i_arg.is_none() { | |
681 | let v = &mut vals[optid]; | |
682 | v.push(Val(i_arg.clone().unwrap())); | |
683 | } else if i + 1 == l { | |
684 | return Err(ArgumentMissing(nm.to_string())); | |
685 | } else { | |
686 | i += 1; | |
687 | let v = &mut vals[optid]; | |
688 | v.push(Val(args[i].clone())); | |
689 | } | |
690 | } | |
691 | } | |
692 | } | |
693 | } | |
694 | i += 1; | |
695 | } | |
85aaf69f | 696 | for i in 0..n_opts { |
1a4d82fc JJ |
697 | let n = vals[i].len(); |
698 | let occ = opts[i].occur; | |
699 | if occ == Req && n == 0 { | |
700 | return Err(OptionMissing(opts[i].name.to_string())); | |
701 | } | |
702 | if occ != Multi && n > 1 { | |
703 | return Err(OptionDuplicated(opts[i].name.to_string())); | |
704 | } | |
705 | } | |
706 | Ok(Matches { | |
707 | opts: opts, | |
708 | vals: vals, | |
709 | free: free | |
710 | }) | |
711 | } | |
712 | ||
713 | /// Derive a usage message from a set of long options. | |
714 | pub fn usage(brief: &str, opts: &[OptGroup]) -> String { | |
715 | ||
716 | let desc_sep = format!("\n{}", repeat(" ").take(24).collect::<String>()); | |
717 | ||
718 | let rows = opts.iter().map(|optref| { | |
719 | let OptGroup{short_name, | |
720 | long_name, | |
721 | hint, | |
722 | desc, | |
723 | hasarg, | |
724 | ..} = (*optref).clone(); | |
725 | ||
726 | let mut row = repeat(" ").take(4).collect::<String>(); | |
727 | ||
728 | // short option | |
729 | match short_name.len() { | |
730 | 0 => {} | |
731 | 1 => { | |
732 | row.push('-'); | |
85aaf69f | 733 | row.push_str(&short_name[..]); |
1a4d82fc JJ |
734 | row.push(' '); |
735 | } | |
736 | _ => panic!("the short name should only be 1 ascii char long"), | |
737 | } | |
738 | ||
739 | // long option | |
740 | match long_name.len() { | |
741 | 0 => {} | |
742 | _ => { | |
743 | row.push_str("--"); | |
85aaf69f | 744 | row.push_str(&long_name[..]); |
1a4d82fc JJ |
745 | row.push(' '); |
746 | } | |
747 | } | |
748 | ||
749 | // arg | |
750 | match hasarg { | |
751 | No => {} | |
85aaf69f | 752 | Yes => row.push_str(&hint[..]), |
1a4d82fc JJ |
753 | Maybe => { |
754 | row.push('['); | |
85aaf69f | 755 | row.push_str(&hint[..]); |
1a4d82fc JJ |
756 | row.push(']'); |
757 | } | |
758 | } | |
759 | ||
760 | // FIXME: #5516 should be graphemes not codepoints | |
761 | // here we just need to indent the start of the description | |
762 | let rowlen = row.chars().count(); | |
763 | if rowlen < 24 { | |
85aaf69f | 764 | for _ in 0..24 - rowlen { |
1a4d82fc JJ |
765 | row.push(' '); |
766 | } | |
767 | } else { | |
85aaf69f | 768 | row.push_str(&desc_sep[..]); |
1a4d82fc JJ |
769 | } |
770 | ||
771 | // Normalize desc to contain words separated by one space character | |
772 | let mut desc_normalized_whitespace = String::new(); | |
d9579d0f | 773 | for word in desc.split_whitespace() { |
1a4d82fc JJ |
774 | desc_normalized_whitespace.push_str(word); |
775 | desc_normalized_whitespace.push(' '); | |
776 | } | |
777 | ||
778 | // FIXME: #5516 should be graphemes not codepoints | |
779 | let mut desc_rows = Vec::new(); | |
85aaf69f | 780 | each_split_within(&desc_normalized_whitespace[..], 54, |substr| { |
1a4d82fc JJ |
781 | desc_rows.push(substr.to_string()); |
782 | true | |
783 | }); | |
784 | ||
785 | // FIXME: #5516 should be graphemes not codepoints | |
786 | // wrapped description | |
c34b1796 | 787 | row.push_str(&desc_rows.connect(&desc_sep[..])); |
1a4d82fc JJ |
788 | |
789 | row | |
790 | }); | |
791 | ||
792 | format!("{}\n\nOptions:\n{}\n", brief, | |
793 | rows.collect::<Vec<String>>().connect("\n")) | |
794 | } | |
795 | ||
796 | fn format_option(opt: &OptGroup) -> String { | |
797 | let mut line = String::new(); | |
798 | ||
799 | if opt.occur != Req { | |
800 | line.push('['); | |
801 | } | |
802 | ||
803 | // Use short_name is possible, but fallback to long_name. | |
9346a6ac | 804 | if !opt.short_name.is_empty() { |
1a4d82fc | 805 | line.push('-'); |
85aaf69f | 806 | line.push_str(&opt.short_name[..]); |
1a4d82fc JJ |
807 | } else { |
808 | line.push_str("--"); | |
85aaf69f | 809 | line.push_str(&opt.long_name[..]); |
1a4d82fc JJ |
810 | } |
811 | ||
812 | if opt.hasarg != No { | |
813 | line.push(' '); | |
814 | if opt.hasarg == Maybe { | |
815 | line.push('['); | |
816 | } | |
85aaf69f | 817 | line.push_str(&opt.hint[..]); |
1a4d82fc JJ |
818 | if opt.hasarg == Maybe { |
819 | line.push(']'); | |
820 | } | |
821 | } | |
822 | ||
823 | if opt.occur != Req { | |
824 | line.push(']'); | |
825 | } | |
826 | if opt.occur == Multi { | |
827 | line.push_str(".."); | |
828 | } | |
829 | ||
830 | line | |
831 | } | |
832 | ||
833 | /// Derive a short one-line usage summary from a set of long options. | |
834 | pub fn short_usage(program_name: &str, opts: &[OptGroup]) -> String { | |
835 | let mut line = format!("Usage: {} ", program_name); | |
836 | line.push_str(&opts.iter() | |
837 | .map(format_option) | |
838 | .collect::<Vec<String>>() | |
85aaf69f | 839 | .connect(" ")[..]); |
1a4d82fc JJ |
840 | line |
841 | } | |
842 | ||
c34b1796 | 843 | #[derive(Copy, Clone)] |
1a4d82fc JJ |
844 | enum SplitWithinState { |
845 | A, // leading whitespace, initial state | |
846 | B, // words | |
847 | C, // internal and trailing whitespace | |
848 | } | |
c34b1796 | 849 | #[derive(Copy, Clone)] |
1a4d82fc JJ |
850 | enum Whitespace { |
851 | Ws, // current char is whitespace | |
852 | Cr // current char is not whitespace | |
853 | } | |
c34b1796 | 854 | #[derive(Copy, Clone)] |
1a4d82fc JJ |
855 | enum LengthLimit { |
856 | UnderLim, // current char makes current substring still fit in limit | |
857 | OverLim // current char makes current substring no longer fit in limit | |
858 | } | |
859 | ||
860 | ||
861 | /// Splits a string into substrings with possibly internal whitespace, | |
862 | /// each of them at most `lim` bytes long. The substrings have leading and trailing | |
863 | /// whitespace removed, and are only cut at whitespace boundaries. | |
864 | /// | |
865 | /// Note: Function was moved here from `std::str` because this module is the only place that | |
866 | /// uses it, and because it was too specific for a general string function. | |
867 | /// | |
868 | /// # Panics | |
869 | /// | |
870 | /// Panics during iteration if the string contains a non-whitespace | |
871 | /// sequence longer than the limit. | |
c34b1796 | 872 | fn each_split_within<F>(ss: &str, lim: usize, mut it: F) -> bool where |
1a4d82fc JJ |
873 | F: FnMut(&str) -> bool |
874 | { | |
875 | // Just for fun, let's write this as a state machine: | |
876 | ||
877 | let mut slice_start = 0; | |
878 | let mut last_start = 0; | |
879 | let mut last_end = 0; | |
880 | let mut state = A; | |
881 | let mut fake_i = ss.len(); | |
882 | let mut lim = lim; | |
883 | ||
884 | let mut cont = true; | |
885 | ||
886 | // if the limit is larger than the string, lower it to save cycles | |
887 | if lim >= fake_i { | |
888 | lim = fake_i; | |
889 | } | |
890 | ||
c34b1796 | 891 | let mut machine = |cont: &mut bool, (i, c): (usize, char)| -> bool { |
1a4d82fc JJ |
892 | let whitespace = if c.is_whitespace() { Ws } else { Cr }; |
893 | let limit = if (i - slice_start + 1) <= lim { UnderLim } else { OverLim }; | |
894 | ||
895 | state = match (state, whitespace, limit) { | |
896 | (A, Ws, _) => { A } | |
897 | (A, Cr, _) => { slice_start = i; last_start = i; B } | |
898 | ||
899 | (B, Cr, UnderLim) => { B } | |
900 | (B, Cr, OverLim) if (i - last_start + 1) > lim | |
901 | => panic!("word starting with {} longer than limit!", | |
85aaf69f | 902 | &ss[last_start..i + 1]), |
1a4d82fc JJ |
903 | (B, Cr, OverLim) => { |
904 | *cont = it(&ss[slice_start..last_end]); | |
905 | slice_start = last_start; | |
906 | B | |
907 | } | |
908 | (B, Ws, UnderLim) => { | |
909 | last_end = i; | |
910 | C | |
911 | } | |
912 | (B, Ws, OverLim) => { | |
913 | last_end = i; | |
914 | *cont = it(&ss[slice_start..last_end]); | |
915 | A | |
916 | } | |
917 | ||
918 | (C, Cr, UnderLim) => { | |
919 | last_start = i; | |
920 | B | |
921 | } | |
922 | (C, Cr, OverLim) => { | |
923 | *cont = it(&ss[slice_start..last_end]); | |
924 | slice_start = i; | |
925 | last_start = i; | |
926 | last_end = i; | |
927 | B | |
928 | } | |
929 | (C, Ws, OverLim) => { | |
930 | *cont = it(&ss[slice_start..last_end]); | |
931 | A | |
932 | } | |
933 | (C, Ws, UnderLim) => { | |
934 | C | |
935 | } | |
936 | }; | |
937 | ||
938 | *cont | |
939 | }; | |
940 | ||
941 | ss.char_indices().all(|x| machine(&mut cont, x)); | |
942 | ||
943 | // Let the automaton 'run out' by supplying trailing whitespace | |
944 | while cont && match state { B | C => true, A => false } { | |
945 | machine(&mut cont, (fake_i, ' ')); | |
946 | fake_i += 1; | |
947 | } | |
948 | return cont; | |
949 | } | |
950 | ||
951 | #[test] | |
952 | fn test_split_within() { | |
c34b1796 | 953 | fn t(s: &str, i: usize, u: &[String]) { |
1a4d82fc JJ |
954 | let mut v = Vec::new(); |
955 | each_split_within(s, i, |s| { v.push(s.to_string()); true }); | |
956 | assert!(v.iter().zip(u.iter()).all(|(a,b)| a == b)); | |
957 | } | |
958 | t("", 0, &[]); | |
959 | t("", 15, &[]); | |
960 | t("hello", 15, &["hello".to_string()]); | |
961 | t("\nMary had a little lamb\nLittle lamb\n", 15, &[ | |
962 | "Mary had a".to_string(), | |
963 | "little lamb".to_string(), | |
964 | "Little lamb".to_string() | |
965 | ]); | |
c34b1796 | 966 | t("\nMary had a little lamb\nLittle lamb\n", ::std::usize::MAX, |
1a4d82fc JJ |
967 | &["Mary had a little lamb\nLittle lamb".to_string()]); |
968 | } | |
969 | ||
970 | #[cfg(test)] | |
971 | mod tests { | |
972 | use super::*; | |
973 | use super::Fail::*; | |
974 | ||
975 | use std::result::Result::{Err, Ok}; | |
976 | use std::result; | |
977 | ||
978 | // Tests for reqopt | |
979 | #[test] | |
980 | fn test_reqopt() { | |
981 | let long_args = vec!("--test=20".to_string()); | |
982 | let opts = vec!(reqopt("t", "test", "testing", "TEST")); | |
85aaf69f | 983 | let rs = getopts(&long_args, &opts); |
1a4d82fc JJ |
984 | match rs { |
985 | Ok(ref m) => { | |
986 | assert!(m.opt_present("test")); | |
987 | assert_eq!(m.opt_str("test").unwrap(), "20"); | |
988 | assert!(m.opt_present("t")); | |
989 | assert_eq!(m.opt_str("t").unwrap(), "20"); | |
990 | } | |
991 | _ => { panic!("test_reqopt failed (long arg)"); } | |
992 | } | |
993 | let short_args = vec!("-t".to_string(), "20".to_string()); | |
85aaf69f | 994 | match getopts(&short_args, &opts) { |
1a4d82fc JJ |
995 | Ok(ref m) => { |
996 | assert!((m.opt_present("test"))); | |
997 | assert_eq!(m.opt_str("test").unwrap(), "20"); | |
998 | assert!((m.opt_present("t"))); | |
999 | assert_eq!(m.opt_str("t").unwrap(), "20"); | |
1000 | } | |
1001 | _ => { panic!("test_reqopt failed (short arg)"); } | |
1002 | } | |
1003 | } | |
1004 | ||
1005 | #[test] | |
1006 | fn test_reqopt_missing() { | |
1007 | let args = vec!("blah".to_string()); | |
1008 | let opts = vec!(reqopt("t", "test", "testing", "TEST")); | |
85aaf69f | 1009 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1010 | match rs { |
1011 | Err(OptionMissing(_)) => {}, | |
1012 | _ => panic!() | |
1013 | } | |
1014 | } | |
1015 | ||
1016 | #[test] | |
1017 | fn test_reqopt_no_arg() { | |
1018 | let long_args = vec!("--test".to_string()); | |
1019 | let opts = vec!(reqopt("t", "test", "testing", "TEST")); | |
85aaf69f | 1020 | let rs = getopts(&long_args, &opts); |
1a4d82fc JJ |
1021 | match rs { |
1022 | Err(ArgumentMissing(_)) => {}, | |
1023 | _ => panic!() | |
1024 | } | |
1025 | let short_args = vec!("-t".to_string()); | |
85aaf69f | 1026 | match getopts(&short_args, &opts) { |
1a4d82fc JJ |
1027 | Err(ArgumentMissing(_)) => {}, |
1028 | _ => panic!() | |
1029 | } | |
1030 | } | |
1031 | ||
1032 | #[test] | |
1033 | fn test_reqopt_multi() { | |
1034 | let args = vec!("--test=20".to_string(), "-t".to_string(), "30".to_string()); | |
1035 | let opts = vec!(reqopt("t", "test", "testing", "TEST")); | |
85aaf69f | 1036 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1037 | match rs { |
1038 | Err(OptionDuplicated(_)) => {}, | |
1039 | _ => panic!() | |
1040 | } | |
1041 | } | |
1042 | ||
1043 | // Tests for optopt | |
1044 | #[test] | |
1045 | fn test_optopt() { | |
1046 | let long_args = vec!("--test=20".to_string()); | |
1047 | let opts = vec!(optopt("t", "test", "testing", "TEST")); | |
85aaf69f | 1048 | let rs = getopts(&long_args, &opts); |
1a4d82fc JJ |
1049 | match rs { |
1050 | Ok(ref m) => { | |
1051 | assert!(m.opt_present("test")); | |
1052 | assert_eq!(m.opt_str("test").unwrap(), "20"); | |
1053 | assert!((m.opt_present("t"))); | |
1054 | assert_eq!(m.opt_str("t").unwrap(), "20"); | |
1055 | } | |
1056 | _ => panic!() | |
1057 | } | |
1058 | let short_args = vec!("-t".to_string(), "20".to_string()); | |
85aaf69f | 1059 | match getopts(&short_args, &opts) { |
1a4d82fc JJ |
1060 | Ok(ref m) => { |
1061 | assert!((m.opt_present("test"))); | |
1062 | assert_eq!(m.opt_str("test").unwrap(), "20"); | |
1063 | assert!((m.opt_present("t"))); | |
1064 | assert_eq!(m.opt_str("t").unwrap(), "20"); | |
1065 | } | |
1066 | _ => panic!() | |
1067 | } | |
1068 | } | |
1069 | ||
1070 | #[test] | |
1071 | fn test_optopt_missing() { | |
1072 | let args = vec!("blah".to_string()); | |
1073 | let opts = vec!(optopt("t", "test", "testing", "TEST")); | |
85aaf69f | 1074 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1075 | match rs { |
1076 | Ok(ref m) => { | |
1077 | assert!(!m.opt_present("test")); | |
1078 | assert!(!m.opt_present("t")); | |
1079 | } | |
1080 | _ => panic!() | |
1081 | } | |
1082 | } | |
1083 | ||
1084 | #[test] | |
1085 | fn test_optopt_no_arg() { | |
1086 | let long_args = vec!("--test".to_string()); | |
1087 | let opts = vec!(optopt("t", "test", "testing", "TEST")); | |
85aaf69f | 1088 | let rs = getopts(&long_args, &opts); |
1a4d82fc JJ |
1089 | match rs { |
1090 | Err(ArgumentMissing(_)) => {}, | |
1091 | _ => panic!() | |
1092 | } | |
1093 | let short_args = vec!("-t".to_string()); | |
85aaf69f | 1094 | match getopts(&short_args, &opts) { |
1a4d82fc JJ |
1095 | Err(ArgumentMissing(_)) => {}, |
1096 | _ => panic!() | |
1097 | } | |
1098 | } | |
1099 | ||
1100 | #[test] | |
1101 | fn test_optopt_multi() { | |
1102 | let args = vec!("--test=20".to_string(), "-t".to_string(), "30".to_string()); | |
1103 | let opts = vec!(optopt("t", "test", "testing", "TEST")); | |
85aaf69f | 1104 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1105 | match rs { |
1106 | Err(OptionDuplicated(_)) => {}, | |
1107 | _ => panic!() | |
1108 | } | |
1109 | } | |
1110 | ||
1111 | // Tests for optflag | |
1112 | #[test] | |
1113 | fn test_optflag() { | |
1114 | let long_args = vec!("--test".to_string()); | |
1115 | let opts = vec!(optflag("t", "test", "testing")); | |
85aaf69f | 1116 | let rs = getopts(&long_args, &opts); |
1a4d82fc JJ |
1117 | match rs { |
1118 | Ok(ref m) => { | |
1119 | assert!(m.opt_present("test")); | |
1120 | assert!(m.opt_present("t")); | |
1121 | } | |
1122 | _ => panic!() | |
1123 | } | |
1124 | let short_args = vec!("-t".to_string()); | |
85aaf69f | 1125 | match getopts(&short_args, &opts) { |
1a4d82fc JJ |
1126 | Ok(ref m) => { |
1127 | assert!(m.opt_present("test")); | |
1128 | assert!(m.opt_present("t")); | |
1129 | } | |
1130 | _ => panic!() | |
1131 | } | |
1132 | } | |
1133 | ||
1134 | #[test] | |
1135 | fn test_optflag_missing() { | |
1136 | let args = vec!("blah".to_string()); | |
1137 | let opts = vec!(optflag("t", "test", "testing")); | |
85aaf69f | 1138 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1139 | match rs { |
1140 | Ok(ref m) => { | |
1141 | assert!(!m.opt_present("test")); | |
1142 | assert!(!m.opt_present("t")); | |
1143 | } | |
1144 | _ => panic!() | |
1145 | } | |
1146 | } | |
1147 | ||
1148 | #[test] | |
1149 | fn test_optflag_long_arg() { | |
1150 | let args = vec!("--test=20".to_string()); | |
1151 | let opts = vec!(optflag("t", "test", "testing")); | |
85aaf69f | 1152 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1153 | match rs { |
1154 | Err(UnexpectedArgument(_)) => {}, | |
1155 | _ => panic!() | |
1156 | } | |
1157 | } | |
1158 | ||
1159 | #[test] | |
1160 | fn test_optflag_multi() { | |
1161 | let args = vec!("--test".to_string(), "-t".to_string()); | |
1162 | let opts = vec!(optflag("t", "test", "testing")); | |
85aaf69f | 1163 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1164 | match rs { |
1165 | Err(OptionDuplicated(_)) => {}, | |
1166 | _ => panic!() | |
1167 | } | |
1168 | } | |
1169 | ||
1170 | #[test] | |
1171 | fn test_optflag_short_arg() { | |
1172 | let args = vec!("-t".to_string(), "20".to_string()); | |
1173 | let opts = vec!(optflag("t", "test", "testing")); | |
85aaf69f | 1174 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1175 | match rs { |
1176 | Ok(ref m) => { | |
1177 | // The next variable after the flag is just a free argument | |
1178 | ||
1179 | assert!(m.free[0] == "20"); | |
1180 | } | |
1181 | _ => panic!() | |
1182 | } | |
1183 | } | |
1184 | ||
1185 | // Tests for optflagmulti | |
1186 | #[test] | |
1187 | fn test_optflagmulti_short1() { | |
1188 | let args = vec!("-v".to_string()); | |
1189 | let opts = vec!(optflagmulti("v", "verbose", "verbosity")); | |
85aaf69f | 1190 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1191 | match rs { |
1192 | Ok(ref m) => { | |
1193 | assert_eq!(m.opt_count("v"), 1); | |
1194 | } | |
1195 | _ => panic!() | |
1196 | } | |
1197 | } | |
1198 | ||
1199 | #[test] | |
1200 | fn test_optflagmulti_short2a() { | |
1201 | let args = vec!("-v".to_string(), "-v".to_string()); | |
1202 | let opts = vec!(optflagmulti("v", "verbose", "verbosity")); | |
85aaf69f | 1203 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1204 | match rs { |
1205 | Ok(ref m) => { | |
1206 | assert_eq!(m.opt_count("v"), 2); | |
1207 | } | |
1208 | _ => panic!() | |
1209 | } | |
1210 | } | |
1211 | ||
1212 | #[test] | |
1213 | fn test_optflagmulti_short2b() { | |
1214 | let args = vec!("-vv".to_string()); | |
1215 | let opts = vec!(optflagmulti("v", "verbose", "verbosity")); | |
85aaf69f | 1216 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1217 | match rs { |
1218 | Ok(ref m) => { | |
1219 | assert_eq!(m.opt_count("v"), 2); | |
1220 | } | |
1221 | _ => panic!() | |
1222 | } | |
1223 | } | |
1224 | ||
1225 | #[test] | |
1226 | fn test_optflagmulti_long1() { | |
1227 | let args = vec!("--verbose".to_string()); | |
1228 | let opts = vec!(optflagmulti("v", "verbose", "verbosity")); | |
85aaf69f | 1229 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1230 | match rs { |
1231 | Ok(ref m) => { | |
1232 | assert_eq!(m.opt_count("verbose"), 1); | |
1233 | } | |
1234 | _ => panic!() | |
1235 | } | |
1236 | } | |
1237 | ||
1238 | #[test] | |
1239 | fn test_optflagmulti_long2() { | |
1240 | let args = vec!("--verbose".to_string(), "--verbose".to_string()); | |
1241 | let opts = vec!(optflagmulti("v", "verbose", "verbosity")); | |
85aaf69f | 1242 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1243 | match rs { |
1244 | Ok(ref m) => { | |
1245 | assert_eq!(m.opt_count("verbose"), 2); | |
1246 | } | |
1247 | _ => panic!() | |
1248 | } | |
1249 | } | |
1250 | ||
1251 | #[test] | |
1252 | fn test_optflagmulti_mix() { | |
1253 | let args = vec!("--verbose".to_string(), "-v".to_string(), | |
1254 | "-vv".to_string(), "verbose".to_string()); | |
1255 | let opts = vec!(optflagmulti("v", "verbose", "verbosity")); | |
85aaf69f | 1256 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1257 | match rs { |
1258 | Ok(ref m) => { | |
1259 | assert_eq!(m.opt_count("verbose"), 4); | |
1260 | assert_eq!(m.opt_count("v"), 4); | |
1261 | } | |
1262 | _ => panic!() | |
1263 | } | |
1264 | } | |
1265 | ||
1266 | // Tests for optmulti | |
1267 | #[test] | |
1268 | fn test_optmulti() { | |
1269 | let long_args = vec!("--test=20".to_string()); | |
1270 | let opts = vec!(optmulti("t", "test", "testing", "TEST")); | |
85aaf69f | 1271 | let rs = getopts(&long_args, &opts); |
1a4d82fc JJ |
1272 | match rs { |
1273 | Ok(ref m) => { | |
1274 | assert!((m.opt_present("test"))); | |
1275 | assert_eq!(m.opt_str("test").unwrap(), "20"); | |
1276 | assert!((m.opt_present("t"))); | |
1277 | assert_eq!(m.opt_str("t").unwrap(), "20"); | |
1278 | } | |
1279 | _ => panic!() | |
1280 | } | |
1281 | let short_args = vec!("-t".to_string(), "20".to_string()); | |
85aaf69f | 1282 | match getopts(&short_args, &opts) { |
1a4d82fc JJ |
1283 | Ok(ref m) => { |
1284 | assert!((m.opt_present("test"))); | |
1285 | assert_eq!(m.opt_str("test").unwrap(), "20"); | |
1286 | assert!((m.opt_present("t"))); | |
1287 | assert_eq!(m.opt_str("t").unwrap(), "20"); | |
1288 | } | |
1289 | _ => panic!() | |
1290 | } | |
1291 | } | |
1292 | ||
1293 | #[test] | |
1294 | fn test_optmulti_missing() { | |
1295 | let args = vec!("blah".to_string()); | |
1296 | let opts = vec!(optmulti("t", "test", "testing", "TEST")); | |
85aaf69f | 1297 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1298 | match rs { |
1299 | Ok(ref m) => { | |
1300 | assert!(!m.opt_present("test")); | |
1301 | assert!(!m.opt_present("t")); | |
1302 | } | |
1303 | _ => panic!() | |
1304 | } | |
1305 | } | |
1306 | ||
1307 | #[test] | |
1308 | fn test_optmulti_no_arg() { | |
1309 | let long_args = vec!("--test".to_string()); | |
1310 | let opts = vec!(optmulti("t", "test", "testing", "TEST")); | |
85aaf69f | 1311 | let rs = getopts(&long_args, &opts); |
1a4d82fc JJ |
1312 | match rs { |
1313 | Err(ArgumentMissing(_)) => {}, | |
1314 | _ => panic!() | |
1315 | } | |
1316 | let short_args = vec!("-t".to_string()); | |
85aaf69f | 1317 | match getopts(&short_args, &opts) { |
1a4d82fc JJ |
1318 | Err(ArgumentMissing(_)) => {}, |
1319 | _ => panic!() | |
1320 | } | |
1321 | } | |
1322 | ||
1323 | #[test] | |
1324 | fn test_optmulti_multi() { | |
1325 | let args = vec!("--test=20".to_string(), "-t".to_string(), "30".to_string()); | |
1326 | let opts = vec!(optmulti("t", "test", "testing", "TEST")); | |
85aaf69f | 1327 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1328 | match rs { |
1329 | Ok(ref m) => { | |
1330 | assert!(m.opt_present("test")); | |
1331 | assert_eq!(m.opt_str("test").unwrap(), "20"); | |
1332 | assert!(m.opt_present("t")); | |
1333 | assert_eq!(m.opt_str("t").unwrap(), "20"); | |
1334 | let pair = m.opt_strs("test"); | |
1335 | assert!(pair[0] == "20"); | |
1336 | assert!(pair[1] == "30"); | |
1337 | } | |
1338 | _ => panic!() | |
1339 | } | |
1340 | } | |
1341 | ||
1342 | #[test] | |
1343 | fn test_unrecognized_option() { | |
1344 | let long_args = vec!("--untest".to_string()); | |
1345 | let opts = vec!(optmulti("t", "test", "testing", "TEST")); | |
85aaf69f | 1346 | let rs = getopts(&long_args, &opts); |
1a4d82fc JJ |
1347 | match rs { |
1348 | Err(UnrecognizedOption(_)) => {}, | |
1349 | _ => panic!() | |
1350 | } | |
1351 | let short_args = vec!("-u".to_string()); | |
85aaf69f | 1352 | match getopts(&short_args, &opts) { |
1a4d82fc JJ |
1353 | Err(UnrecognizedOption(_)) => {}, |
1354 | _ => panic!() | |
1355 | } | |
1356 | } | |
1357 | ||
1358 | #[test] | |
1359 | fn test_combined() { | |
1360 | let args = | |
1361 | vec!("prog".to_string(), | |
1362 | "free1".to_string(), | |
1363 | "-s".to_string(), | |
1364 | "20".to_string(), | |
1365 | "free2".to_string(), | |
1366 | "--flag".to_string(), | |
1367 | "--long=30".to_string(), | |
1368 | "-f".to_string(), | |
1369 | "-m".to_string(), | |
1370 | "40".to_string(), | |
1371 | "-m".to_string(), | |
1372 | "50".to_string(), | |
1373 | "-n".to_string(), | |
1374 | "-A B".to_string(), | |
1375 | "-n".to_string(), | |
1376 | "-60 70".to_string()); | |
1377 | let opts = | |
1378 | vec!(optopt("s", "something", "something", "SOMETHING"), | |
1379 | optflag("", "flag", "a flag"), | |
1380 | reqopt("", "long", "hi", "LONG"), | |
1381 | optflag("f", "", "another flag"), | |
1382 | optmulti("m", "", "mmmmmm", "YUM"), | |
1383 | optmulti("n", "", "nothing", "NOTHING"), | |
1384 | optopt("", "notpresent", "nothing to see here", "NOPE")); | |
85aaf69f | 1385 | let rs = getopts(&args, &opts); |
1a4d82fc JJ |
1386 | match rs { |
1387 | Ok(ref m) => { | |
1388 | assert!(m.free[0] == "prog"); | |
1389 | assert!(m.free[1] == "free1"); | |
1390 | assert_eq!(m.opt_str("s").unwrap(), "20"); | |
1391 | assert!(m.free[2] == "free2"); | |
1392 | assert!((m.opt_present("flag"))); | |
1393 | assert_eq!(m.opt_str("long").unwrap(), "30"); | |
1394 | assert!((m.opt_present("f"))); | |
1395 | let pair = m.opt_strs("m"); | |
1396 | assert!(pair[0] == "40"); | |
1397 | assert!(pair[1] == "50"); | |
1398 | let pair = m.opt_strs("n"); | |
1399 | assert!(pair[0] == "-A B"); | |
1400 | assert!(pair[1] == "-60 70"); | |
1401 | assert!((!m.opt_present("notpresent"))); | |
1402 | } | |
1403 | _ => panic!() | |
1404 | } | |
1405 | } | |
1406 | ||
1407 | #[test] | |
1408 | fn test_multi() { | |
1409 | let opts = vec!(optopt("e", "", "encrypt", "ENCRYPT"), | |
1410 | optopt("", "encrypt", "encrypt", "ENCRYPT"), | |
1411 | optopt("f", "", "flag", "FLAG")); | |
1412 | ||
1413 | let args_single = vec!("-e".to_string(), "foo".to_string()); | |
85aaf69f | 1414 | let matches_single = &match getopts(&args_single, &opts) { |
1a4d82fc JJ |
1415 | result::Result::Ok(m) => m, |
1416 | result::Result::Err(_) => panic!() | |
1417 | }; | |
1418 | assert!(matches_single.opts_present(&["e".to_string()])); | |
1419 | assert!(matches_single.opts_present(&["encrypt".to_string(), "e".to_string()])); | |
1420 | assert!(matches_single.opts_present(&["e".to_string(), "encrypt".to_string()])); | |
1421 | assert!(!matches_single.opts_present(&["encrypt".to_string()])); | |
1422 | assert!(!matches_single.opts_present(&["thing".to_string()])); | |
1423 | assert!(!matches_single.opts_present(&[])); | |
1424 | ||
1425 | assert_eq!(matches_single.opts_str(&["e".to_string()]).unwrap(), "foo"); | |
1426 | assert_eq!(matches_single.opts_str(&["e".to_string(), "encrypt".to_string()]).unwrap(), | |
1427 | "foo"); | |
1428 | assert_eq!(matches_single.opts_str(&["encrypt".to_string(), "e".to_string()]).unwrap(), | |
1429 | "foo"); | |
1430 | ||
1431 | let args_both = vec!("-e".to_string(), "foo".to_string(), "--encrypt".to_string(), | |
1432 | "foo".to_string()); | |
85aaf69f | 1433 | let matches_both = &match getopts(&args_both, &opts) { |
1a4d82fc JJ |
1434 | result::Result::Ok(m) => m, |
1435 | result::Result::Err(_) => panic!() | |
1436 | }; | |
1437 | assert!(matches_both.opts_present(&["e".to_string()])); | |
1438 | assert!(matches_both.opts_present(&["encrypt".to_string()])); | |
1439 | assert!(matches_both.opts_present(&["encrypt".to_string(), "e".to_string()])); | |
1440 | assert!(matches_both.opts_present(&["e".to_string(), "encrypt".to_string()])); | |
1441 | assert!(!matches_both.opts_present(&["f".to_string()])); | |
1442 | assert!(!matches_both.opts_present(&["thing".to_string()])); | |
1443 | assert!(!matches_both.opts_present(&[])); | |
1444 | ||
1445 | assert_eq!(matches_both.opts_str(&["e".to_string()]).unwrap(), "foo"); | |
1446 | assert_eq!(matches_both.opts_str(&["encrypt".to_string()]).unwrap(), "foo"); | |
1447 | assert_eq!(matches_both.opts_str(&["e".to_string(), "encrypt".to_string()]).unwrap(), | |
1448 | "foo"); | |
1449 | assert_eq!(matches_both.opts_str(&["encrypt".to_string(), "e".to_string()]).unwrap(), | |
1450 | "foo"); | |
1451 | } | |
1452 | ||
1453 | #[test] | |
1454 | fn test_nospace() { | |
1455 | let args = vec!("-Lfoo".to_string(), "-M.".to_string()); | |
1456 | let opts = vec!(optmulti("L", "", "library directory", "LIB"), | |
1457 | optmulti("M", "", "something", "MMMM")); | |
85aaf69f | 1458 | let matches = &match getopts(&args, &opts) { |
1a4d82fc JJ |
1459 | result::Result::Ok(m) => m, |
1460 | result::Result::Err(_) => panic!() | |
1461 | }; | |
1462 | assert!(matches.opts_present(&["L".to_string()])); | |
1463 | assert_eq!(matches.opts_str(&["L".to_string()]).unwrap(), "foo"); | |
1464 | assert!(matches.opts_present(&["M".to_string()])); | |
1465 | assert_eq!(matches.opts_str(&["M".to_string()]).unwrap(), "."); | |
1466 | ||
1467 | } | |
1468 | ||
1469 | #[test] | |
1470 | fn test_nospace_conflict() { | |
1471 | let args = vec!("-vvLverbose".to_string(), "-v".to_string() ); | |
1472 | let opts = vec!(optmulti("L", "", "library directory", "LIB"), | |
1473 | optflagmulti("v", "verbose", "Verbose")); | |
85aaf69f | 1474 | let matches = &match getopts(&args, &opts) { |
1a4d82fc JJ |
1475 | result::Result::Ok(m) => m, |
1476 | result::Result::Err(e) => panic!( "{}", e ) | |
1477 | }; | |
1478 | assert!(matches.opts_present(&["L".to_string()])); | |
1479 | assert_eq!(matches.opts_str(&["L".to_string()]).unwrap(), "verbose"); | |
1480 | assert!(matches.opts_present(&["v".to_string()])); | |
1481 | assert_eq!(3, matches.opt_count("v")); | |
1482 | } | |
1483 | ||
1484 | #[test] | |
1485 | fn test_long_to_short() { | |
1486 | let mut short = Opt { | |
1487 | name: Name::Long("banana".to_string()), | |
1488 | hasarg: HasArg::Yes, | |
1489 | occur: Occur::Req, | |
1490 | aliases: Vec::new(), | |
1491 | }; | |
1492 | short.aliases = vec!(Opt { name: Name::Short('b'), | |
1493 | hasarg: HasArg::Yes, | |
1494 | occur: Occur::Req, | |
1495 | aliases: Vec::new() }); | |
1496 | let verbose = reqopt("b", "banana", "some bananas", "VAL"); | |
1497 | ||
1498 | assert!(verbose.long_to_short() == short); | |
1499 | } | |
1500 | ||
1501 | #[test] | |
1502 | fn test_aliases_long_and_short() { | |
1503 | let opts = vec!( | |
1504 | optflagmulti("a", "apple", "Desc")); | |
1505 | ||
1506 | let args = vec!("-a".to_string(), "--apple".to_string(), "-a".to_string()); | |
1507 | ||
85aaf69f | 1508 | let matches = getopts(&args, &opts).unwrap(); |
1a4d82fc JJ |
1509 | assert_eq!(3, matches.opt_count("a")); |
1510 | assert_eq!(3, matches.opt_count("apple")); | |
1511 | } | |
1512 | ||
1513 | #[test] | |
1514 | fn test_usage() { | |
1515 | let optgroups = vec!( | |
1516 | reqopt("b", "banana", "Desc", "VAL"), | |
1517 | optopt("a", "012345678901234567890123456789", | |
1518 | "Desc", "VAL"), | |
1519 | optflag("k", "kiwi", "Desc"), | |
1520 | optflagopt("p", "", "Desc", "VAL"), | |
1521 | optmulti("l", "", "Desc", "VAL")); | |
1522 | ||
1523 | let expected = | |
1524 | "Usage: fruits | |
1525 | ||
1526 | Options: | |
1527 | -b --banana VAL Desc | |
1528 | -a --012345678901234567890123456789 VAL | |
1529 | Desc | |
1530 | -k --kiwi Desc | |
1531 | -p [VAL] Desc | |
1532 | -l VAL Desc | |
1533 | "; | |
1534 | ||
85aaf69f | 1535 | let generated_usage = usage("Usage: fruits", &optgroups); |
1a4d82fc JJ |
1536 | |
1537 | debug!("expected: <<{}>>", expected); | |
1538 | debug!("generated: <<{}>>", generated_usage); | |
1539 | assert_eq!(generated_usage, expected); | |
1540 | } | |
1541 | ||
1542 | #[test] | |
1543 | fn test_usage_description_wrapping() { | |
1544 | // indentation should be 24 spaces | |
1545 | // lines wrap after 78: or rather descriptions wrap after 54 | |
1546 | ||
1547 | let optgroups = vec!( | |
1548 | optflag("k", "kiwi", | |
1549 | "This is a long description which won't be wrapped..+.."), // 54 | |
1550 | optflag("a", "apple", | |
1551 | "This is a long description which _will_ be wrapped..+..")); | |
1552 | ||
1553 | let expected = | |
1554 | "Usage: fruits | |
1555 | ||
1556 | Options: | |
1557 | -k --kiwi This is a long description which won't be wrapped..+.. | |
1558 | -a --apple This is a long description which _will_ be | |
1559 | wrapped..+.. | |
1560 | "; | |
1561 | ||
85aaf69f | 1562 | let usage = usage("Usage: fruits", &optgroups); |
1a4d82fc JJ |
1563 | |
1564 | debug!("expected: <<{}>>", expected); | |
1565 | debug!("generated: <<{}>>", usage); | |
1566 | assert!(usage == expected) | |
1567 | } | |
1568 | ||
1569 | #[test] | |
1570 | fn test_usage_description_multibyte_handling() { | |
1571 | let optgroups = vec!( | |
1572 | optflag("k", "k\u{2013}w\u{2013}", | |
1573 | "The word kiwi is normally spelled with two i's"), | |
1574 | optflag("a", "apple", | |
1575 | "This \u{201C}description\u{201D} has some characters that could \ | |
1576 | confuse the line wrapping; an apple costs 0.51€ in some parts of Europe.")); | |
1577 | ||
1578 | let expected = | |
1579 | "Usage: fruits | |
1580 | ||
1581 | Options: | |
1582 | -k --k–w– The word kiwi is normally spelled with two i's | |
1583 | -a --apple This “description” has some characters that could | |
1584 | confuse the line wrapping; an apple costs 0.51€ in | |
1585 | some parts of Europe. | |
1586 | "; | |
1587 | ||
85aaf69f | 1588 | let usage = usage("Usage: fruits", &optgroups); |
1a4d82fc JJ |
1589 | |
1590 | debug!("expected: <<{}>>", expected); | |
1591 | debug!("generated: <<{}>>", usage); | |
1592 | assert!(usage == expected) | |
1593 | } | |
1594 | ||
1595 | #[test] | |
1596 | fn test_short_usage() { | |
1597 | let optgroups = vec!( | |
1598 | reqopt("b", "banana", "Desc", "VAL"), | |
1599 | optopt("a", "012345678901234567890123456789", | |
1600 | "Desc", "VAL"), | |
1601 | optflag("k", "kiwi", "Desc"), | |
1602 | optflagopt("p", "", "Desc", "VAL"), | |
1603 | optmulti("l", "", "Desc", "VAL")); | |
1604 | ||
1605 | let expected = "Usage: fruits -b VAL [-a VAL] [-k] [-p [VAL]] [-l VAL]..".to_string(); | |
85aaf69f | 1606 | let generated_usage = short_usage("fruits", &optgroups); |
1a4d82fc JJ |
1607 | |
1608 | debug!("expected: <<{}>>", expected); | |
1609 | debug!("generated: <<{}>>", generated_usage); | |
1610 | assert_eq!(generated_usage, expected); | |
1611 | } | |
1612 | } |