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