Merge pull request #3739 from killerswan/usagemsg

Add a module to getopts for verbose option group declaration (and use it in rustc)
This commit is contained in:
Tim Chevalier 2012-10-17 13:05:04 -07:00
commit 33adb7a824
4 changed files with 546 additions and 68 deletions

View File

@ -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,
@ -2479,6 +2520,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
@ -2554,6 +2607,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

View File

@ -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:

View File

@ -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: " +

View File

@ -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)
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
-v --version Print version info and exit
");
}
@ -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))