Merge pull request #160 from kbknapp/feature-rollup

perf: improves help message printing drastically
This commit is contained in:
Kevin K. 2015-07-16 01:43:41 -04:00
commit 023981cf05
4 changed files with 256 additions and 154 deletions

View file

@ -8,24 +8,14 @@ It is a simple to use, efficient, and full featured library for parsing command
## What's New
If you're already familiar with `clap` but just want to see some new highlights as of **1.0.3**
If you're already familiar with `clap` but just want to see some new highlights as of **1.1.0**
* **Errors are written to stderr** - In order to follow good standards, errors are now written to stderr
* **Deprecated Functions Removed** - In an effort to start a 1.x all deprecated functions have been removed, see the deprecations sections below to update your code (very minimal)
* **Can fully override help** - This allows you fully override the auto-generated help if you so choose
* **Can wait for user input on error** - This is helpful mainly on Windows if a user mistakenly opens your application via double-click, or you'd like to provide a GUI shortcut to run your application
* **Args can now be parsed from arbitrary locations** This makes testing immensly easier. Thanks to [cristicbz](https://github.com/cristicbz) for the idea!
Example...
```rust
let v = vec!["my_prog", "some_arg", "-f"];
let m = App::new("my_prog")
// Normal configuration goes here...
.get_matches_from(v);
// Use matches like normal...
```
* **Newlines properly aligned in help strings!** - Allows one to specify a newline in long help strings. **Note:** Specify the newlines in help strings via `{n}` and *not* `\n` due to how `clap` handles help parsing.
* **Unified Help Format** - This is cosmetic only, but allows a help message formated similiar to `docopt` or `getopts` where what `clap` calls "options" and "flags" are combined into a single group (and still properly aligned and formatted)
* **Can propogate versions through subcommands auto-matically** - This allows all subcommands to handle `--version` or `-V` with the same version as the parent application
* **Can specify that subcommands disable version** - Can now say `--version` and `-V` won't be valid flags for subcommands
* **Big performacne improvement when printing help messages** - While printing help messages wasn't slow before, it's now super fast ;)
* **PSA: Deprecated Functions Removed as of 1.0** - In an effort to start a 1.x all deprecated functions have been removed, see the deprecations sections below to update your code (very minimal)
For full details see the [changelog](https://github.com/kbknapp/clap-rs/blob/master/CHANGELOG.md)

View file

@ -12,7 +12,7 @@ Kevin K. <kbknapp@gmail.com>
tests clap library
USAGE:
claptests [FLAGS] [OPTIONS] [ARGS] [SUBCOMMAND]
\tclaptests [FLAGS] [OPTIONS] [ARGS] [SUBCOMMAND]
FLAGS:
-f, --flag tests flags
@ -27,12 +27,12 @@ OPTIONS:
--multvalsmo <one> <two> Tests mutliple values, not mult occs
-o, --option <opt>... tests options
--long-option-2 <option2> tests long options with exclusions
-O, --Option <option3> tests options with specific value sets [values: fast, slow]
-O, --Option <option3> tests options with specific value sets [values: fast slow]
ARGS:
positional tests positionals
positional2 tests positionals with exclusions
positional3... tests positionals with specific values [values: emacs, vi]
positional tests positionals
positional2 tests positionals with exclusions
positional3... tests positionals with specific values [values: emacs vi]
SUBCOMMANDS:
help Prints this message
@ -138,7 +138,7 @@ Kevin K. <kbknapp@gmail.com>
tests subcommands
USAGE:
claptests subcmd [FLAGS] [OPTIONS] [ARGS]
\tclaptests subcmd [FLAGS] [OPTIONS] [ARGS]
FLAGS:
-f, --flag tests flags
@ -146,10 +146,10 @@ FLAGS:
-V, --version Prints version information
OPTIONS:
-o, --option <scoption>... tests options
-o, --option <scoption>... tests options
ARGS:
scpositional tests positionals'''
scpositional tests positionals'''
_scfop = '''flag NOT present
option NOT present

View file

@ -99,12 +99,8 @@ pub struct App<'a, 'v, 'ab, 'u, 'h, 'ar> {
positionals_name: HashMap<&'ar str, u8>,
// A list of subcommands
subcommands: BTreeMap<String, App<'a, 'v, 'ab, 'u, 'h, 'ar>>,
needs_long_help: bool,
needs_long_version: bool,
help_short: Option<char>,
version_short: Option<char>,
needs_subcmd_help: bool,
subcmds_neg_reqs: bool,
required: HashSet<&'ar str>,
short_list: HashSet<char>,
long_list: HashSet<&'ar str>,
@ -114,11 +110,19 @@ pub struct App<'a, 'v, 'ab, 'u, 'h, 'ar> {
usage: Option<String>,
groups: HashMap<&'ar str, ArgGroup<'ar, 'ar>>,
global_args: Vec<Arg<'ar, 'ar, 'ar, 'ar, 'ar, 'ar>>,
help_str: Option<&'u str>,
no_sc_error: bool,
wait_on_error: bool,
help_on_no_args: bool,
help_str: Option<&'u str>,
help_on_no_sc: bool
needs_long_help: bool,
needs_long_version: bool,
needs_subcmd_help: bool,
subcmds_neg_reqs: bool,
help_on_no_sc: bool,
global_ver: bool,
// None = not set, Some(true) set for all children, Some(false) = disable version
versionless_scs: Option<bool>,
unified_help: bool
}
impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
@ -166,7 +170,10 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
help_str: None,
wait_on_error: false,
help_on_no_args: false,
help_on_no_sc: false
help_on_no_sc: false,
global_ver: false,
versionless_scs: None,
unified_help: false
}
}
@ -407,7 +414,6 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
///
/// **NOTE:** Subcommands count as arguments
///
///
/// # Example
///
/// ```no_run
@ -421,6 +427,72 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
self
}
/// Uses version of the current command for all subcommands. (Defaults to false; subcommands
/// have independant version strings)
///
/// **NOTE:** The version for the current command and this setting must be set **prior** to
/// adding any subcommands
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg, SubCommand};
/// App::new("myprog")
/// .version("v1.1")
/// .global_version(true)
/// .subcommand(SubCommand::with_name("test"))
/// .get_matches();
/// // running `myprog test --version` will display
/// // "myprog-test v1.1"
/// ```
pub fn global_version(mut self, gv: bool) -> App<'a, 'v, 'ab, 'u, 'h, 'ar> {
self.global_ver = gv;
self
}
/// Disables `-V` and `--version` for all subcommands (Defaults to false; subcommands have
/// version flags)
///
/// **NOTE:** This setting must be set **prior** adding any subcommands
///
/// **NOTE:** Do not set this value to false, it will have undesired results!
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg, SubCommand};
/// App::new("myprog")
/// .version("v1.1")
/// .versionless_subcommands(true)
/// .subcommand(SubCommand::with_name("test"))
/// .get_matches();
/// // running `myprog test --version` will display unknown argument error
/// ```
pub fn versionless_subcommands(mut self, vers: bool) -> App<'a, 'v, 'ab, 'u, 'h, 'ar> {
self.versionless_scs = Some(vers);
self
}
/// By default the auto-generated help message groups flags, options, and positional arguments
/// separately. This setting disable that and groups flags and options together presenting a
/// more unified help message (a la getopts or docopt style).
///
/// **NOTE:** This setting is cosmetic only and does not affect any functionality.
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg, SubCommand};
/// App::new("myprog")
/// .unified_help_message(true)
/// .get_matches();
/// // running `myprog --help` will display a unified "docopt" or "getopts" style help message
/// ```
pub fn unified_help_message(mut self, uni_help: bool) -> App<'a, 'v, 'ab, 'u, 'h, 'ar> {
self.unified_help = uni_help;
self
}
/// Will display a message "Press [ENTER]/[RETURN] to continue..." and wait user before
/// exiting
///
@ -927,9 +999,15 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
/// // Additional subcommand configuration goes here, such as other arguments...
/// # ;
/// ```
pub fn subcommand(mut self, subcmd: App<'a, 'v, 'ab, 'u, 'h, 'ar>)
pub fn subcommand(mut self, mut subcmd: App<'a, 'v, 'ab, 'u, 'h, 'ar>)
-> App<'a, 'v, 'ab, 'u, 'h, 'ar> {
if subcmd.name == "help" { self.needs_subcmd_help = false; }
if self.versionless_scs.is_some() && self.versionless_scs.unwrap() {
subcmd.versionless_scs = Some(false);
}
if self.global_ver && subcmd.version.is_none() && self.version.is_some() {
subcmd.version = Some(self.version.unwrap());
}
self.subcommands.insert(subcmd.name.clone(), subcmd);
self
}
@ -1175,10 +1253,13 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
});
if !self.flags.is_empty() {
if !self.flags.is_empty() && !self.unified_help {
usage.push_str(" [FLAGS]");
} else {
usage.push_str(" [OPTIONS]");
}
if !self.opts.is_empty() && self.opts.values().any(|a| !a.required) {
if !self.unified_help
&& !self.opts.is_empty() && self.opts.values().any(|a| !a.required) {
usage.push_str(" [OPTIONS]");
}
if !self.positionals_idx.is_empty() && self.positionals_idx.values()
@ -1233,24 +1314,24 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
let mut longest_opt= 0;
for ol in self.opts
.values()
.filter(|ref o| o.long.is_some())
// .filter(|ref o| o.long.is_some())
.map(|ref a|
a.to_string().len() + if a.short.is_some() { 4 } else { 0 }
a.to_string().len() // + if a.short.is_some() { 4 } else { 0 }
) {
if ol > longest_opt {
longest_opt = ol;
}
}
if longest_opt == 0 {
for ol in self.opts
.values()
.filter(|ref o| o.short.is_some())
// 3='...'
// 4='- <>'
.map(|ref a| a.to_string().len() + if a.long.is_some() { 4 } else { 0 }) {
if ol > longest_opt {longest_opt = ol;}
}
}
// if longest_opt == 0 {
// for ol in self.opts
// .values()
// .filter(|ref o| o.short.is_some())
// // 3='...'
// // 4='- <>'
// .map(|ref a| a.to_string().len() + if a.long.is_some() { 4 } else { 0 }) {
// if ol > longest_opt {longest_opt = ol;}
// }
// }
let mut longest_pos = 0;
for pl in self.positionals_idx
.values()
@ -1270,9 +1351,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
if let Some(about) = self.about {
print!("{}\n", about);
}
print!("\n");
print!("{}", self.create_usage(None));
print!("\n{}", self.create_usage(None));
if flags || opts || pos || subcmds {
print!("\n");
@ -1280,78 +1360,132 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
let tab = " ";
if flags {
print!("\nFLAGS:\n");
if !self.unified_help {
print!("\nFLAGS:\n");
} else {
print!("\nOPTIONS:\n")
}
for v in self.flags.values() {
print!("{}{}{}{}\n",tab,
if let Some(s) = v.short{format!("-{}",s)}else{tab.to_owned()},
if let Some(l) = v.long {
format!("{}--{}{}",
if v.short.is_some() { ", " } else {""},
l,
self.get_spaces((longest_flag + 4) - (v.long.unwrap().len() + 2)))
print!("{}", tab);
if let Some(s) = v.short {
print!("-{}",s);
} else {
print!("{}", tab);
}
if let Some(l) = v.long {
print!("{}--{}",
if v.short.is_some() { ", " } else {""},
l
);
self.print_spaces(
if !self.unified_help {
(longest_flag + 4)
} else {
(longest_opt + 4)
} - (l.len() + 2)
);
} else {
// 6 is tab (4) + -- (2)
self.print_spaces(
if !self.unified_help {
(longest_flag + 6)
} else {
(longest_opt + 6)
}
);
}
if let Some(h) = v.help {
if h.contains("{n}") {
let mut hel = h.split("{n}");
while let Some(part) = hel.next() {
print!("{}\n", part);
self.print_spaces(
if !self.unified_help {
longest_flag
} else {
longest_opt
} + 12);
print!("{}", hel.next().unwrap_or(""));
}
} else {
// 6 is tab (4) + -- (2)
self.get_spaces(longest_flag + 6).to_owned()
},
v.help.unwrap_or(tab)
);
print!("{}", h);
}
}
print!("\n");
}
}
if opts {
print!("\nOPTIONS:\n");
if !self.unified_help {
print!("\nOPTIONS:\n");
} else {
// maybe erase
}
for v in self.opts.values() {
// if it supports multiple we add '...' i.e. 3 to the name length
print!("{}{}{}{}{}{}\n",tab,
if let Some(s) = v.short{format!("-{}",s)}else{tab.to_owned()},
if let Some(l) = v.long {
format!("{}--{}",
if v.short.is_some() {", "} else {""},l)
} else {
"".to_owned()
},
format!("{}",
if let Some(ref vec) = v.val_names {
vec.iter().fold(String::new(), |acc, s| {
acc + &format!(" <{}>", s)[..]
})
} else if let Some(num) = v.num_vals {
(0..num).fold(String::new(), |acc, _| {
acc + &format!(" <{}>", v.name)[..]
})
} else {
format!(" <{}>{}", v.name, if v.multiple{"..."} else {""})
}),
if v.long.is_some() {
self.get_spaces(
(longest_opt + 4) - (v.to_string().len())
)
} else {
// 8 = tab + '-a, '.len()
self.get_spaces((longest_opt + 9) - (v.to_string().len()))
},
get_help!(v)
);
print!("{}", tab);
if let Some(s) = v.short {
print!("-{}",s);
} else {
print!("{}", tab);
}
if let Some(l) = v.long {
print!("{}--{}", if v.short.is_some() {", "} else {""}, l);
}
if let Some(ref vec) = v.val_names {
for val in vec.iter() {
print!(" <{}>", val);
}
} else if let Some(num) = v.num_vals {
for _ in (0..num) {
print!(" <{}>", v.name);
}
} else {
print!(" <{}>{}", v.name, if v.multiple{"..."} else {""});
}
if v.long.is_some() {
self.print_spaces(
(longest_opt + 4) - (v.to_string().len())
);
} else {
// 8 = tab + '-a, '.len()
self.print_spaces((longest_opt + 8) - (v.to_string().len()));
};
print_opt_help!(self, v, longest_opt + 12);
print!("\n");
}
}
if pos {
print!("\nARGS:\n");
for v in self.positionals_idx.values() {
let mult = if v.multiple { 3 } else { 0 };
print!("{}{}{}{}\n",tab,
if v.multiple {format!("{}...",v.name)} else {v.name.to_owned()},
self.get_spaces((longest_pos + 4) - (v.name.len() + mult)),
get_help!(v)
);
// let mult = if v.multiple { 3 } else { 0 };
print!("{}", tab);
print!("{}", v.name);
if v.multiple {
print!("...");
}
self.print_spaces((longest_pos + 4) - (v.to_string().len()));
print_opt_help!(self, v, longest_pos + 12);
print!("\n");
}
}
if subcmds {
print!("\nSUBCOMMANDS:\n");
for sc in self.subcommands.values() {
print!("{}{}{}{}\n",tab,
sc.name,
self.get_spaces((longest_sc + 4) - (sc.name.len())),
if let Some(a) = sc.about {a} else {tab}
);
print!("{}{}", tab, sc.name);
self.print_spaces((longest_sc + 4) - (sc.name.len()));
if let Some(a) = sc.about {
if a.contains("{n}") {
let mut ab = a.split("{n}");
while let Some(part) = ab.next() {
print!("{}\n", part);
self.print_spaces(longest_sc + 8);
print!("{}", ab.next().unwrap_or(""));
}
} else {
print!("{}", a);
}
}
print!("\n");
}
}
@ -1359,45 +1493,16 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
print!("\n{}", h);
}
// flush the buffer
println!("");
self.exit(0);
}
// Used when spacing arguments and their help message when displaying help information
fn get_spaces(&self, num: usize) -> &'static str {
match num {
0 => "",
1 => " ",
2 => " ",
3 => " ",
4 => " ",
5 => " ",
6 => " ",
7 => " ",
8 => " ",
9 => " ",
10=> " ",
11=> " ",
12=> " ",
13=> " ",
14=> " ",
15=> " ",
16=> " ",
17=> " ",
18=> " ",
19=> " ",
20=> " ",
21=> " ",
22=> " ",
23=> " ",
24=> " ",
25=> " ",
26=> " ",
27=> " ",
28=> " ",
29=> " ",
30|_=> " "
fn print_spaces(&self, num: usize) {
for _ in (0..num) {
print!(" ");
}
}
@ -1998,7 +2103,9 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
self.long_list.insert("help");
self.flags.insert("hclap_help", arg);
}
if self.needs_long_version {
if self.needs_long_version
&& self.versionless_scs.is_none()
|| (self.versionless_scs.unwrap()) {
if self.version_short.is_none() && !self.short_list.contains(&'V') {
self.version_short = Some('V');
}

View file

@ -37,22 +37,27 @@ macro_rules! debug {
}
// De-duplication macro used in src/app.rs
macro_rules! get_help {
($opt:ident) => {
macro_rules! print_opt_help {
($me:ident, $opt:ident, $spc:expr) => {
if let Some(h) = $opt.help {
format!("{}{}", h,
if let Some(ref pv) = $opt.possible_vals {
let mut pv_s = pv.iter().fold(String::with_capacity(50), |acc, name| {
acc + &format!(" {},",name)[..]
});
pv_s.shrink_to_fit();
// pv_s = one, two, three, four,
// Needs to remove trailing comma (',')
format!(" [values:{}]", &pv_s[..pv_s.len()-1])
}else{"".to_owned()})
} else {
" ".to_owned()
}
if h.contains("{n}") {
let mut hel = h.split("{n}");
while let Some(part) = hel.next() {
print!("{}\n", part);
$me.print_spaces($spc);
print!("{}", hel.next().unwrap_or(""));
}
} else {
print!("{}", h);
}
if let Some(ref pv) = $opt.possible_vals {
print!(" [values:");
for pv_s in pv.iter() {
print!(" {}", pv_s);
}
print!("]");
}
}
};
}