diff options
Diffstat (limited to 'src/libstd/getopts.rs')
| -rw-r--r-- | src/libstd/getopts.rs | 412 |
1 files changed, 409 insertions, 3 deletions
diff --git a/src/libstd/getopts.rs b/src/libstd/getopts.rs index 6da51571e34..8d77b88aba2 100644 --- a/src/libstd/getopts.rs +++ b/src/libstd/getopts.rs @@ -82,7 +82,7 @@ pub type Opt = {name: Name, hasarg: HasArg, occur: Occur}; fn mkname(nm: &str) -> Name { let unm = str::from_slice(nm); - return if str::len(nm) == 1u { + return if nm.len() == 1u { Short(str::char_at(unm, 0u)) } else { Long(unm) }; } @@ -114,6 +114,22 @@ impl Occur : Eq { pure fn ne(other: &Occur) -> bool { !self.eq(other) } } +impl HasArg : Eq { + pure fn eq(other: &HasArg) -> bool { + (self as uint) == ((*other) as uint) + } + pure fn ne(other: &HasArg) -> bool { !self.eq(other) } +} + +impl Opt : Eq { + pure fn eq(other: &Opt) -> bool { + self.name == (*other).name && + self.hasarg == (*other).hasarg && + self.occur == (*other).occur + } + pure fn ne(other: &Opt) -> bool { !self.eq(other) } +} + /// Create an option that is required and takes an argument pub fn reqopt(name: &str) -> Opt { return {name: mkname(name), hasarg: Yes, occur: Req}; @@ -150,8 +166,29 @@ enum Optval { Val(~str), Given, } */ pub type Matches = {opts: ~[Opt], vals: ~[~[Optval]], free: ~[~str]}; +impl Optval : Eq { + pure fn eq(other: &Optval) -> bool { + match self { + Val(ref s) => match *other { Val (ref os) => s == os, + Given => false }, + Given => match *other { Val(_) => false, + Given => true } + } + } + pure fn ne(other: &Optval) -> bool { !self.eq(other) } +} + +impl Matches : Eq { + pure fn eq(other: &Matches) -> bool { + self.opts == (*other).opts && + self.vals == (*other).vals && + self.free == (*other).free + } + pure fn ne(other: &Matches) -> bool { !self.eq(other) } +} + fn is_arg(arg: &str) -> bool { - return str::len(arg) > 1u && arg[0] == '-' as u8; + return arg.len() > 1u && arg[0] == '-' as u8; } fn name_str(nm: &Name) -> ~str { @@ -177,6 +214,35 @@ pub enum Fail_ { UnexpectedArgument(~str), } +impl Fail_ : Eq { + // this whole thing should be easy to infer... + pure fn eq(other: &Fail_) -> bool { + match self { + ArgumentMissing(ref s) => { + match *other { ArgumentMissing(ref so) => s == so, + _ => false } + } + UnrecognizedOption(ref s) => { + match *other { UnrecognizedOption(ref so) => s == so, + _ => false } + } + OptionMissing(ref s) => { + match *other { OptionMissing(ref so) => s == so, + _ => false } + } + OptionDuplicated(ref s) => { + match *other { OptionDuplicated(ref so) => s == so, + _ => false } + } + UnexpectedArgument(ref s) => { + match *other { UnexpectedArgument(ref so) => s == so, + _ => false } + } + } + } + pure fn ne(other: &Fail_) -> bool { !self.eq(other) } +} + /// Convert a `fail_` enum into an error string pub fn fail_str(f: Fail_) -> ~str { return match f { @@ -220,7 +286,7 @@ pub fn getopts(args: &[~str], opts: &[Opt]) -> Result unsafe { let mut i = 0u; while i < l { let cur = args[i]; - let curlen = str::len(cur); + let curlen = cur.len(); if !is_arg(cur) { free.push(cur); } else if cur == ~"--" { @@ -444,6 +510,194 @@ impl FailType : Eq { pure fn ne(other: &FailType) -> bool { !self.eq(other) } } +/** A module which provides a way to specify descriptions and + * groups of short and long option names, together. + */ +pub mod groups { + + /** one group of options, e.g., both -h and --help, along with + * their shared description and properties + */ + pub type OptGroup = { + short_name: ~str, + long_name: ~str, + hint: ~str, + desc: ~str, + hasarg: HasArg, + occur: Occur + }; + + impl OptGroup : Eq { + pure fn eq(other: &OptGroup) -> bool { + self.short_name == (*other).short_name && + self.long_name == (*other).long_name && + self.hint == (*other).hint && + self.desc == (*other).desc && + self.hasarg == (*other).hasarg && + self.occur == (*other).occur + } + pure fn ne(other: &OptGroup) -> bool { !self.eq(other) } + } + + /// Create a long option that is required and takes an argument + pub fn reqopt(short_name: &str, long_name: &str, + desc: &str, hint: &str) -> OptGroup { + let len = short_name.len(); + assert len == 1 || len == 0; + return {short_name: str::from_slice(short_name), + long_name: str::from_slice(long_name), + hint: str::from_slice(hint), + desc: str::from_slice(desc), + hasarg: Yes, + occur: Req}; + } + + /// Create a long option that is optional and takes an argument + pub fn optopt(short_name: &str, long_name: &str, + desc: &str, hint: &str) -> OptGroup { + let len = short_name.len(); + assert len == 1 || len == 0; + return {short_name: str::from_slice(short_name), + long_name: str::from_slice(long_name), + hint: str::from_slice(hint), + desc: str::from_slice(desc), + hasarg: Yes, + occur: Optional}; + } + + /// Create a long option that is optional and does not take an argument + pub fn optflag(short_name: &str, long_name: &str, + desc: &str) -> OptGroup { + let len = short_name.len(); + assert len == 1 || len == 0; + return {short_name: str::from_slice(short_name), + long_name: str::from_slice(long_name), + hint: ~"", + desc: str::from_slice(desc), + hasarg: No, + occur: Optional}; + } + + /// Create a long option that is optional and takes an optional argument + pub fn optflagopt(short_name: &str, long_name: &str, + desc: &str, hint: &str) -> OptGroup { + let len = short_name.len(); + assert len == 1 || len == 0; + return {short_name: str::from_slice(short_name), + long_name: str::from_slice(long_name), + hint: str::from_slice(hint), + desc: str::from_slice(desc), + hasarg: Maybe, + occur: Optional}; + } + + /** + * Create a long option that is optional, takes an argument, and may occur + * multiple times + */ + pub fn optmulti(short_name: &str, long_name: &str, + desc: &str, hint: &str) -> OptGroup { + let len = short_name.len(); + assert len == 1 || len == 0; + return {short_name: str::from_slice(short_name), + long_name: str::from_slice(long_name), + hint: str::from_slice(hint), + desc: str::from_slice(desc), + hasarg: Yes, + occur: Multi}; + } + + // translate OptGroup into Opt + // (both short and long names correspond to different Opts) + pub fn long_to_short(lopt: &OptGroup) -> ~[Opt] { + match ((*lopt).short_name.len(), + (*lopt).long_name.len()) { + + (0,0) => fail ~"this long-format option was given no name", + + (0,_) => ~[{name: Long(((*lopt).long_name)), + hasarg: (*lopt).hasarg, + occur: (*lopt).occur}], + + (1,0) => ~[{name: Short(str::char_at((*lopt).short_name, 0)), + hasarg: (*lopt).hasarg, + occur: (*lopt).occur}], + + (1,_) => ~[{name: Short(str::char_at((*lopt).short_name, 0)), + hasarg: (*lopt).hasarg, + occur: (*lopt).occur}, + {name: Long(((*lopt).long_name)), + hasarg: (*lopt).hasarg, + occur: (*lopt).occur}], + + (_,_) => fail ~"something is wrong with the long-form opt" + } + } + + /* + * Parse command line args with the provided long format options + */ + pub fn getopts(args: &[~str], opts: &[OptGroup]) -> Result { + ::getopts::getopts(args, vec::flat_map(opts, long_to_short)) + } + + /** + * Derive a usage message from a set of long options + */ + pub fn usage(brief: &str, opts: &[OptGroup]) -> ~str { + + let desc_sep = ~"\n" + str::repeat(~" ", 24); + + let rows = vec::map(opts, |optref| { + let short_name = (*optref).short_name; + let long_name = (*optref).long_name; + let hint = (*optref).hint; + let desc = (*optref).desc; + let hasarg = (*optref).hasarg; + + let mut row = str::repeat(~" ", 4); + + // short option + row += match short_name.len() { + 0 => ~"", + 1 => ~"-" + short_name + " ", + _ => fail ~"the short name should only be 1 char long", + }; + + // long option + row += match long_name.len() { + 0 => ~"", + _ => ~"--" + long_name + " ", + }; + + // arg + row += match hasarg { + No => ~"", + Yes => hint, + Maybe => ~"[" + hint + ~"]", + }; + + // here we just need to indent the start of the description + let rowlen = row.len(); + row += if rowlen < 24 { + str::repeat(~" ", 24 - rowlen) + } else { + desc_sep + }; + + // wrapped description + row += str::connect(str::split_within(desc, 54), desc_sep); + + row + }); + + return str::from_slice(brief) + + ~"\n\nOptions:\n" + + str::connect(rows, ~"\n") + + ~"\n\n"; + } +} // end groups module + #[cfg(test)] mod tests { #[legacy_exports]; @@ -943,6 +1197,158 @@ mod tests { assert opts_present(matches, ~[~"L"]); assert opts_str(matches, ~[~"L"]) == ~"foo"; } + + #[test] + fn test_groups_reqopt() { + let opt = groups::reqopt(~"b", ~"banana", ~"some bananas", ~"VAL"); + assert opt == { short_name: ~"b", + long_name: ~"banana", + hint: ~"VAL", + desc: ~"some bananas", + hasarg: Yes, + occur: Req } + } + + #[test] + fn test_groups_optopt() { + let opt = groups::optopt(~"a", ~"apple", ~"some apples", ~"VAL"); + assert opt == { short_name: ~"a", + long_name: ~"apple", + hint: ~"VAL", + desc: ~"some apples", + hasarg: Yes, + occur: Optional } + } + + #[test] + fn test_groups_optflag() { + let opt = groups::optflag(~"k", ~"kiwi", ~"some kiwis"); + assert opt == { short_name: ~"k", + long_name: ~"kiwi", + hint: ~"", + desc: ~"some kiwis", + hasarg: No, + occur: Optional } + } + + #[test] + fn test_groups_optflagopt() { + let opt = groups::optflagopt(~"p", ~"pineapple", + ~"some pineapples", ~"VAL"); + assert opt == { short_name: ~"p", + long_name: ~"pineapple", + hint: ~"VAL", + desc: ~"some pineapples", + hasarg: Maybe, + occur: Optional } + } + + #[test] + fn test_groups_optmulti() { + let opt = groups::optmulti(~"l", ~"lime", + ~"some limes", ~"VAL"); + assert opt == { short_name: ~"l", + long_name: ~"lime", + hint: ~"VAL", + desc: ~"some limes", + hasarg: Yes, + occur: Multi } + } + + #[test] + fn test_groups_long_to_short() { + let short = ~[reqopt(~"b"), reqopt(~"banana")]; + let verbose = groups::reqopt(~"b", ~"banana", + ~"some bananas", ~"VAL"); + + assert groups::long_to_short(&verbose) == short; + } + + #[test] + fn test_groups_getopts() { + let short = ~[ + reqopt(~"b"), reqopt(~"banana"), + optopt(~"a"), optopt(~"apple"), + optflag(~"k"), optflagopt(~"kiwi"), + optflagopt(~"p"), + optmulti(~"l") + ]; + + let verbose = ~[ + groups::reqopt(~"b", ~"banana", ~"Desc", ~"VAL"), + groups::optopt(~"a", ~"apple", ~"Desc", ~"VAL"), + groups::optflag(~"k", ~"kiwi", ~"Desc"), + groups::optflagopt(~"p", ~"", ~"Desc", ~"VAL"), + groups::optmulti(~"l", ~"", ~"Desc", ~"VAL"), + ]; + + let sample_args = ~[~"-k", ~"15", ~"--apple", ~"1", ~"k", + ~"-p", ~"16", ~"l", ~"35"]; + + // NOTE: we should sort before comparing + assert getopts(sample_args, short) + == groups::getopts(sample_args, verbose); + } + + #[test] + fn test_groups_usage() { + let optgroups = ~[ + groups::reqopt(~"b", ~"banana", ~"Desc", ~"VAL"), + groups::optopt(~"a", ~"012345678901234567890123456789", + ~"Desc", ~"VAL"), + groups::optflag(~"k", ~"kiwi", ~"Desc"), + groups::optflagopt(~"p", ~"", ~"Desc", ~"VAL"), + groups::optmulti(~"l", ~"", ~"Desc", ~"VAL"), + ]; + + let expected = +~"Usage: fruits + +Options: + -b --banana VAL Desc + -a --012345678901234567890123456789 VAL + Desc + -k --kiwi Desc + -p [VAL] Desc + -l VAL Desc + +"; + + let generated_usage = groups::usage(~"Usage: fruits", optgroups); + + debug!("expected: <<%s>>", expected); + debug!("generated: <<%s>>", generated_usage); + assert generated_usage == expected; + } + + #[test] + fn test_groups_usage_description_wrapping() { + // indentation should be 24 spaces + // lines wrap after 78: or rather descriptions wrap after 54 + + let optgroups = ~[ + groups::optflag(~"k", ~"kiwi", + ~"This is a long description which won't be wrapped..+.."), // 54 + groups::optflag(~"a", ~"apple", + ~"This is a long description which _will_ be wrapped..+.."), // 55 + ]; + + let expected = +~"Usage: fruits + +Options: + -k --kiwi This is a long description which won't be wrapped..+.. + -a --apple This is a long description which _will_ be + wrapped..+.. + +"; + + let usage = groups::usage(~"Usage: fruits", optgroups); + + debug!("expected: <<%s>>", expected); + debug!("generated: <<%s>>", usage); + assert usage == expected + } } // Local Variables: |
