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