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