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