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