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