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