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