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