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