about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTim Chevalier <catamorphism@gmail.com>2012-10-17 13:05:04 -0700
committerTim Chevalier <catamorphism@gmail.com>2012-10-17 13:05:04 -0700
commit33adb7a82464a75d3bb33ce6112e01041b66bbd2 (patch)
tree5742e15946f42d3e3b1d58e005e546bf611ca379
parentbbc90b6bf6082f32cccef1011ac6b862a02957c4 (diff)
parent32baf1c54c4214f5a50da53979008ef9bcdad359 (diff)
downloadrust-33adb7a82464a75d3bb33ce6112e01041b66bbd2.tar.gz
rust-33adb7a82464a75d3bb33ce6112e01041b66bbd2.zip
Merge pull request #3739 from killerswan/usagemsg
Add a module to getopts for verbose option group declaration (and use it in rustc)
-rw-r--r--src/libcore/str.rs62
-rw-r--r--src/libstd/getopts.rs412
-rw-r--r--src/rustc/driver/driver.rs92
-rw-r--r--src/rustc/driver/rustc.rs48
4 files changed, 546 insertions, 68 deletions
diff --git a/src/libcore/str.rs b/src/libcore/str.rs
index 18a8f3d3702..da1defc38b1 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,
@@ -2480,6 +2521,18 @@ mod tests {
     }
 
     #[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
         assert find_str(~"banana", ~"apple pie").is_none();
@@ -2555,6 +2608,15 @@ mod tests {
     }
 
     #[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
         // are culturally insensitive: they only work for ASCII
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:
diff --git a/src/rustc/driver/driver.rs b/src/rustc/driver/driver.rs
index 5da8f5475ed..934a02d6dd3 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;
@@ -623,27 +625,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>", ~"FILENAME"),
+  optopt(~"", ~"opt-level",
+                        ~"Optimize with possible levels 0-3", ~"LEVEL"),
+  optopt( ~"",  ~"out-dir",
+                        ~"Write output to compiler-chosen filename
+                          in <dir>", ~"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};
@@ -741,7 +785,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)
@@ -758,7 +802,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 59d4e0dfdb8..e52f34f4645 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] <input>\n", argv0) +
-                 ~"
-Options:
-
-    --bin              Compile an executable crate (default)
-    -c                 Compile and assemble, but do not link
-    --cfg <cfgspec>    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 <path>          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 <filename>      Write output to <filename>
-    --opt-level <lvl>  Optimize with possible levels 0-3
-    --out-dir <dir>    Write output to compiler-chosen filename in <dir>
-    --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 <path>   Override the system root
-    --test             Build a test harness
-    --target <triple>  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))