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