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