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