diff --git a/src/libcore/str.rs b/src/libcore/str.rs index aff4c50cfd2d4..c32858fc1e325 100644 --- a/src/libcore/str.rs +++ b/src/libcore/str.rs @@ -203,6 +203,13 @@ pub pure fn connect(v: &[~str], sep: &str) -> ~str { move s } +/// Given a string, make a new string with repeated copies of it +pub fn repeat(ss: &str, nn: uint) -> ~str { + let mut acc = ~""; + for nn.times { acc += ss; } + return acc; +} + /* Section: Adding to and removing from a string */ @@ -573,6 +580,40 @@ pub pure fn words(s: &str) -> ~[~str] { split_nonempty(s, |c| char::is_whitespace(c)) } +/** Split a string into a vector of substrings, + * each of which is less than a limit + */ +pub fn split_within(ss: &str, lim: uint) -> ~[~str] { + let words = str::words(ss); + + // empty? + if words == ~[] { return ~[]; } + + let mut rows : ~[~str] = ~[]; + let mut row : ~str = ~""; + + for words.each |wptr| { + let word = *wptr; + + // if adding this word to the row would go over the limit, + // then start a new row + if str::len(row) + str::len(word) + 1 > lim { + rows += [row]; // save previous row + row = word; // start a new one + } else { + if str::len(row) > 0 { row += ~" " } // separate words + row += word; // append to this row + } + } + + // save the last row + if row != ~"" { rows += [row]; } + + return rows; +} + + + /// Convert a string to lowercase. ASCII only pub pure fn to_lower(s: &str) -> ~str { map(s, @@ -2465,6 +2506,18 @@ mod tests { assert ~[] == words(~""); } + #[test] + fn test_split_within() { + assert split_within(~"", 0) == ~[]; + assert split_within(~"", 15) == ~[]; + assert split_within(~"hello", 15) == ~[~"hello"]; + + let data = ~"\nMary had a little lamb\nLittle lamb\n"; + assert split_within(data, 15) == ~[~"Mary had a little", + ~"lamb Little", + ~"lamb"]; + } + #[test] fn test_find_str() { // byte positions @@ -2540,6 +2593,15 @@ mod tests { t(~[~"hi"], ~" ", ~"hi"); } + #[test] + fn test_repeat() { + assert repeat(~"x", 4) == ~"xxxx"; + assert repeat(~"hi", 4) == ~"hihihihi"; + assert repeat(~"ไท华", 3) == ~"ไท华ไท华ไท华"; + assert repeat(~"", 4) == ~""; + assert repeat(~"hi", 0) == ~""; + } + #[test] fn test_to_upper() { // libc::toupper, and hence str::to_upper diff --git a/src/libstd/getopts.rs b/src/libstd/getopts.rs index 6da51571e34a2..8d77b88aba230 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: diff --git a/src/rustc/driver/driver.rs b/src/rustc/driver/driver.rs index e389f3a4bdf7d..1c79f91cf24aa 100644 --- a/src/rustc/driver/driver.rs +++ b/src/rustc/driver/driver.rs @@ -10,8 +10,10 @@ use util::ppaux; use back::link; use result::{Ok, Err}; use std::getopts; +use std::getopts::{opt_present}; +use std::getopts::groups; +use std::getopts::groups::{optopt, optmulti, optflag, optflagopt, getopts}; use io::WriterUtil; -use getopts::{optopt, optmulti, optflag, optflagopt, opt_present}; use back::{x86, x86_64}; use std::map::HashMap; use lib::llvm::llvm; @@ -624,27 +626,69 @@ fn parse_pretty(sess: session, &&name: ~str) -> pp_mode { } } -fn opts() -> ~[getopts::Opt] { - return ~[optflag(~"h"), optflag(~"help"), - optflag(~"v"), optflag(~"version"), - optflag(~"emit-llvm"), optflagopt(~"pretty"), - optflag(~"ls"), optflag(~"parse-only"), optflag(~"no-trans"), - optflag(~"O"), optopt(~"opt-level"), optmulti(~"L"), optflag(~"S"), - optopt(~"o"), optopt(~"out-dir"), optflag(~"xg"), - optflag(~"c"), optflag(~"g"), optflag(~"save-temps"), - optopt(~"sysroot"), optopt(~"target"), - optflag(~"jit"), - - optmulti(~"W"), optmulti(~"warn"), - optmulti(~"A"), optmulti(~"allow"), - optmulti(~"D"), optmulti(~"deny"), - optmulti(~"F"), optmulti(~"forbid"), - - optmulti(~"Z"), - - optmulti(~"cfg"), optflag(~"test"), - optflag(~"lib"), optflag(~"bin"), - optflag(~"static"), optflag(~"gc")]; +// rustc command line options +fn optgroups() -> ~[getopts::groups::OptGroup] { + ~[ + optflag(~"", ~"bin", ~"Compile an executable crate (default)"), + optflag(~"c", ~"", ~"Compile and assemble, but do not link"), + optmulti(~"", ~"cfg", ~"Configure the compilation + environment", ~"SPEC"), + optflag(~"", ~"emit-llvm", + ~"Produce an LLVM bitcode file"), + optflag(~"g", ~"", ~"Produce debug info (experimental)"), + optflag(~"", ~"gc", ~"Garbage collect shared data (experimental)"), + optflag(~"h", ~"help",~"Display this message"), + optmulti(~"L", ~"", ~"Add a directory to the library search path", + ~"PATH"), + optflag(~"", ~"lib", ~"Compile a library crate"), + optflag(~"", ~"ls", ~"List the symbols defined by a library crate"), + optflag(~"", ~"jit", ~"Execute using JIT (experimental)"), + optflag(~"", ~"no-trans", + ~"Run all passes except translation; no output"), + optflag(~"O", ~"", ~"Equivalent to --opt-level=2"), + optopt(~"o", ~"", ~"Write output to ", ~"FILENAME"), + optopt(~"", ~"opt-level", + ~"Optimize with possible levels 0-3", ~"LEVEL"), + optopt( ~"", ~"out-dir", + ~"Write output to compiler-chosen filename + in ", ~"DIR"), + optflag(~"", ~"parse-only", + ~"Parse only; do not compile, assemble, or link"), + optflagopt(~"", ~"pretty", + ~"Pretty-print the input instead of compiling; + valid types are: normal (un-annotated source), + expanded (crates expanded), + typed (crates expanded, with type annotations), + or identified (fully parenthesized, + AST nodes and blocks with IDs)", ~"TYPE"), + optflag(~"S", ~"", ~"Compile only; do not assemble or link"), + optflag(~"", ~"xg", ~"Extra debugging info (experimental)"), + optflag(~"", ~"save-temps", + ~"Write intermediate files (.bc, .opt.bc, .o) + in addition to normal output"), + optflag(~"", ~"static", + ~"Use or produce static libraries or binaries + (experimental)"), + optopt(~"", ~"sysroot", + ~"Override the system root", ~"PATH"), + optflag(~"", ~"test", ~"Build a test harness"), + optopt(~"", ~"target", + ~"Target triple cpu-manufacturer-kernel[-os] + to compile for (see + http://sources.redhat.com/autobook/autobook/autobook_17.html + for detail)", ~"TRIPLE"), + optmulti(~"W", ~"warn", + ~"Set lint warnings", ~"OPT"), + optmulti(~"A", ~"allow", + ~"Set lint allowed", ~"OPT"), + optmulti(~"D", ~"deny", + ~"Set lint denied", ~"OPT"), + optmulti(~"F", ~"forbid", + ~"Set lint forbidden", ~"OPT"), + optmulti(~"Z", ~"", ~"Set internal debugging options", "FLAG"), + optflag( ~"v", ~"version", + ~"Print version info and exit"), + ] } type output_filenames = @{out_filename:Path, obj_filename:Path}; @@ -742,7 +786,7 @@ mod test { #[test] fn test_switch_implies_cfg_test() { let matches = - match getopts::getopts(~[~"--test"], opts()) { + match getopts(~[~"--test"], optgroups()) { Ok(m) => m, Err(f) => fail ~"test_switch_implies_cfg_test: " + getopts::fail_str(f) @@ -759,7 +803,7 @@ mod test { #[test] fn test_switch_implies_cfg_test_unless_cfg_test() { let matches = - match getopts::getopts(~[~"--test", ~"--cfg=test"], opts()) { + match getopts(~[~"--test", ~"--cfg=test"], optgroups()) { Ok(m) => m, Err(f) => { fail ~"test_switch_implies_cfg_test_unless_cfg_test: " + diff --git a/src/rustc/driver/rustc.rs b/src/rustc/driver/rustc.rs index 5833723ec101b..b7783307cc318 100644 --- a/src/rustc/driver/rustc.rs +++ b/src/rustc/driver/rustc.rs @@ -16,6 +16,7 @@ use io::ReaderUtil; use std::getopts; use std::map::HashMap; use getopts::{opt_present}; +use getopts::groups; use rustc::driver::driver::*; use syntax::codemap; use syntax::diagnostic; @@ -31,46 +32,11 @@ fn version(argv0: &str) { } fn usage(argv0: &str) { - io::println(fmt!("Usage: %s [options] \n", argv0) + - ~" -Options: - - --bin Compile an executable crate (default) - -c Compile and assemble, but do not link - --cfg Configure the compilation environment - --emit-llvm Produce an LLVM bitcode file - -g Produce debug info (experimental) - --gc Garbage collect shared data (experimental/temporary) - -h --help Display this message - -L Add a directory to the library search path - --lib Compile a library crate - --ls List the symbols defined by a compiled library crate - --jit Execute using JIT (experimental) - --no-trans Run all passes except translation; no output - -O Equivalent to --opt-level=2 - -o Write output to - --opt-level Optimize with possible levels 0-3 - --out-dir Write output to compiler-chosen filename in - --parse-only Parse only; do not compile, assemble, or link - --pretty [type] Pretty-print the input instead of compiling; - valid types are: normal (un-annotated source), - expanded (crates expanded), typed (crates expanded, - with type annotations), or identified (fully - parenthesized, AST nodes and blocks with IDs) - -S Compile only; do not assemble or link - --save-temps Write intermediate files (.bc, .opt.bc, .o) - in addition to normal output - --static Use or produce static libraries or binaries - (experimental) - --sysroot Override the system root - --test Build a test harness - --target Target cpu-manufacturer-kernel[-os] to compile for - (default: host triple) - (see http://sources.redhat.com/autobook/autobook/ - autobook_17.html for detail) - -W help Print 'lint' options and default settings - -Z help Print internal options for debugging rustc - -v --version Print version info and exit + let message = fmt!("Usage: %s [OPTIONS] INPUT", argv0); + io::println(groups::usage(message, optgroups()) + + ~"Additional help: + -W help Print 'lint' options and default settings + -Z help Print internal options for debugging rustc "); } @@ -127,7 +93,7 @@ fn run_compiler(args: &~[~str], demitter: diagnostic::emitter) { if args.is_empty() { usage(binary); return; } let matches = - match getopts::getopts(args, opts()) { + match getopts::groups::getopts(args, optgroups()) { Ok(m) => m, Err(f) => { early_error(demitter, getopts::fail_str(f))