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