feat: allows specifying a short help vs a long help (i.e. varying levels of detail depending on if -h or --help was used)

One can now use `Arg::long_help` which will be displayed when the user
runs `--help`. This is typically longer form content and will be
displayed using the NextLineHelp methodology.

If one specifies the standard `Arg::help` it will be displayed when the
user runs `-h` (by default, unless the help short has been overridden).
The help text will be displayed in the typical clap fashion.

The help format requested (-h/short or --help/long) will be the
*default* displayed. Meaning, if one runs `--help` (long) but only an
`Arg::help` has been provided, the message from `Arg::help` will be
used. Likewise, if one requests `-h` (short) but only
`Arg::long_help` was provided, `Arg::long_help` will be displayed appropriately
wrapped and aligned.

Completion script generation *only* uses `Arg::help` in order to be
concise.

cc @BurntSushi thanks for the idea!
This commit is contained in:
Kevin K 2017-04-04 23:36:18 -04:00
parent b48a5bb930
commit ef1b24c3a0
10 changed files with 168 additions and 93 deletions

View file

@ -89,6 +89,7 @@ pub struct Help<'a> {
cizer: Colorizer,
longest: usize,
force_next_line: bool,
use_long: bool,
}
// Public Functions
@ -100,7 +101,8 @@ impl<'a> Help<'a> {
color: bool,
cizer: Colorizer,
term_w: Option<usize>,
max_w: Option<usize>)
max_w: Option<usize>,
use_long: bool)
-> Self {
debugln!("Help::new;");
Help {
@ -121,21 +123,22 @@ impl<'a> Help<'a> {
cizer: cizer,
longest: 0,
force_next_line: false,
use_long: use_long,
}
}
/// Reads help settings from an App
/// and write its help to the wrapped stream.
pub fn write_app_help(w: &'a mut Write, app: &App) -> ClapResult<()> {
pub fn write_app_help(w: &'a mut Write, app: &App, use_long: bool) -> ClapResult<()> {
debugln!("Help::write_app_help;");
Self::write_parser_help(w, &app.p)
Self::write_parser_help(w, &app.p, use_long)
}
/// Reads help settings from a Parser
/// and write its help to the wrapped stream.
pub fn write_parser_help(w: &'a mut Write, parser: &Parser) -> ClapResult<()> {
pub fn write_parser_help(w: &'a mut Write, parser: &Parser, use_long: bool) -> ClapResult<()> {
debugln!("Help::write_parser_help;");
Self::_write_parser_help(w, parser, false)
Self::_write_parser_help(w, parser, false, use_long)
}
/// Reads help settings from a Parser
@ -143,11 +146,11 @@ impl<'a> Help<'a> {
/// formatting when required.
pub fn write_parser_help_to_stderr(w: &'a mut Write, parser: &Parser) -> ClapResult<()> {
debugln!("Help::write_parser_help;");
Self::_write_parser_help(w, parser, true)
Self::_write_parser_help(w, parser, true, false)
}
#[doc(hidden)]
pub fn _write_parser_help(w: &'a mut Write, parser: &Parser, stderr: bool) -> ClapResult<()> {
pub fn _write_parser_help(w: &'a mut Write, parser: &Parser, stderr: bool, use_long: bool) -> ClapResult<()> {
debugln!("Help::write_parser_help;");
let nlh = parser.is_set(AppSettings::NextLineHelp);
let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp);
@ -162,8 +165,9 @@ impl<'a> Help<'a> {
color,
cizer,
parser.meta.term_w,
parser.meta.max_w)
.write_help(parser)
parser.meta.max_w,
use_long)
.write_help(parser)
}
/// Writes the parser help to the wrapped stream.
@ -191,8 +195,9 @@ impl<'a> Help<'a> {
self.longest = 2;
let mut arg_v = Vec::with_capacity(10);
for arg in args.filter(|arg| {
!(arg.is_set(ArgSettings::Hidden)) || arg.is_set(ArgSettings::NextLineHelp)
}) {
!(arg.is_set(ArgSettings::Hidden)) ||
arg.is_set(ArgSettings::NextLineHelp)
}) {
if arg.longest_filter() {
self.longest = cmp::max(self.longest, arg.to_string().len());
}
@ -432,8 +437,12 @@ impl<'a> Help<'a> {
fn help<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, spec_vals: &str) -> io::Result<()> {
debugln!("Help::help;");
let mut help = String::new();
let h = arg.help().unwrap_or("");
let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp);
let h = if self.use_long {
arg.long_help().unwrap_or(arg.help().unwrap_or(""))
} else {
arg.help().unwrap_or(arg.long_help().unwrap_or(""))
};
let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp) || self.use_long;
debugln!("Help::help: Next Line...{:?}", nlh);
let spcs = if nlh || self.force_next_line {
@ -513,7 +522,8 @@ impl<'a> Help<'a> {
debugln!("Help::spec_vals: Found aliases...{:?}", aliases);
spec_vals.push(format!(" [aliases: {}]",
if self.color {
aliases.iter()
aliases
.iter()
.map(|v| format!("{}", self.cizer.good(v)))
.collect::<Vec<_>>()
.join(", ")
@ -525,14 +535,14 @@ impl<'a> Help<'a> {
if let Some(pv) = a.possible_vals() {
debugln!("Help::spec_vals: Found possible vals...{:?}", pv);
spec_vals.push(if self.color {
format!(" [values: {}]",
pv.iter()
.map(|v| format!("{}", self.cizer.good(v)))
.collect::<Vec<_>>()
.join(", "))
} else {
format!(" [values: {}]", pv.join(", "))
});
format!(" [values: {}]",
pv.iter()
.map(|v| format!("{}", self.cizer.good(v)))
.collect::<Vec<_>>()
.join(", "))
} else {
format!(" [values: {}]", pv.join(", "))
});
}
}
spec_vals.join(" ")
@ -548,7 +558,10 @@ impl<'a> Help<'a> {
pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> {
debugln!("Help::write_all_args;");
let flags = parser.has_flags();
let pos = parser.positionals().filter(|arg| !arg.is_set(ArgSettings::Hidden)).count() > 0;
let pos = parser
.positionals()
.filter(|arg| !arg.is_set(ArgSettings::Hidden))
.count() > 0;
let opts = parser.has_opts();
let subcmds = parser.has_subcommands();
@ -557,7 +570,8 @@ impl<'a> Help<'a> {
let mut first = true;
if unified_help && (flags || opts) {
let opts_flags = parser.flags()
let opts_flags = parser
.flags()
.map(as_arg_trait)
.chain(parser.opts().map(as_arg_trait));
try!(color!(self, "OPTIONS:\n", warning));
@ -566,8 +580,7 @@ impl<'a> Help<'a> {
} else {
if flags {
try!(color!(self, "FLAGS:\n", warning));
try!(self.write_args(parser.flags()
.map(as_arg_trait)));
try!(self.write_args(parser.flags().map(as_arg_trait)));
first = false;
}
if opts {
@ -606,8 +619,13 @@ impl<'a> Help<'a> {
// The shortest an arg can legally be is 2 (i.e. '-x')
self.longest = 2;
let mut ord_m = VecMap::new();
for sc in parser.subcommands.iter().filter(|s| !s.p.is_set(AppSettings::Hidden)) {
let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new());
for sc in parser
.subcommands
.iter()
.filter(|s| !s.p.is_set(AppSettings::Hidden)) {
let btm = ord_m
.entry(sc.p.meta.disp_ord)
.or_insert(BTreeMap::new());
self.longest = cmp::max(self.longest, sc.p.meta.name.len());
btm.insert(sc.p.meta.name.clone(), sc.clone());
}
@ -861,9 +879,9 @@ impl<'a> Help<'a> {
debugln!("Help::write_template_help:iter: tag_buf={};", unsafe {
String::from_utf8_unchecked(tag_buf.get_ref()[0..tag_length]
.iter()
.map(|&i| i)
.collect::<Vec<_>>())
.iter()
.map(|&i| i)
.collect::<Vec<_>>())
});
match &tag_buf.get_ref()[0..tag_length] {
b"?" => {
@ -894,22 +912,20 @@ impl<'a> Help<'a> {
try!(self.write_all_args(&parser));
}
b"unified" => {
let opts_flags = parser.flags()
let opts_flags = parser
.flags()
.map(as_arg_trait)
.chain(parser.opts().map(as_arg_trait));
try!(self.write_args(opts_flags));
}
b"flags" => {
try!(self.write_args(parser.flags()
.map(as_arg_trait)));
try!(self.write_args(parser.flags().map(as_arg_trait)));
}
b"options" => {
try!(self.write_args(parser.opts()
.map(as_arg_trait)));
try!(self.write_args(parser.opts().map(as_arg_trait)));
}
b"positionals" => {
try!(self.write_args(parser.positionals()
.map(as_arg_trait)));
try!(self.write_args(parser.positionals().map(as_arg_trait)));
}
b"subcommands" => {
try!(self.write_subcommands(&parser));

View file

@ -1,11 +1,13 @@
#[doc(hidden)]
#[allow(missing_debug_implementations)]
#[derive(Default, Clone)]
pub struct AppMeta<'b> {
pub name: String,
pub bin_name: Option<String>,
pub author: Option<&'b str>,
pub version: Option<&'b str>,
pub about: Option<&'b str>,
pub long_about: Option<&'b str>,
pub more_help: Option<&'b str>,
pub pre_help: Option<&'b str>,
pub aliases: Option<Vec<(&'b str, bool)>>, // (name, visible)
@ -18,51 +20,7 @@ pub struct AppMeta<'b> {
pub template: Option<&'b str>,
}
impl<'b> Default for AppMeta<'b> {
fn default() -> Self {
AppMeta {
name: String::new(),
author: None,
about: None,
more_help: None,
pre_help: None,
version: None,
usage_str: None,
usage: None,
bin_name: None,
help_str: None,
disp_ord: 999,
template: None,
aliases: None,
term_w: None,
max_w: None,
}
}
}
impl<'b> AppMeta<'b> {
pub fn new() -> Self { Default::default() }
pub fn with_name(s: String) -> Self { AppMeta { name: s, ..Default::default() } }
}
impl<'b> Clone for AppMeta<'b> {
fn clone(&self) -> Self {
AppMeta {
name: self.name.clone(),
author: self.author,
about: self.about,
more_help: self.more_help,
pre_help: self.pre_help,
version: self.version,
usage_str: self.usage_str,
usage: self.usage.clone(),
bin_name: self.bin_name.clone(),
help_str: self.help_str,
disp_ord: self.disp_ord,
template: self.template,
aliases: self.aliases.clone(),
term_w: self.term_w,
max_w: self.max_w,
}
}
pub fn with_name(s: String) -> Self { AppMeta { name: s, disp_ord: 999, ..Default::default() } }
}

View file

@ -1108,7 +1108,7 @@ impl<'a, 'b> App<'a, 'b> {
// self.p.derive_display_order();
// self.p.create_help_and_version();
Help::write_app_help(w, self)
Help::write_app_help(w, self, false)
}
/// Writes the version message to the user to a [`io::Write`] object
@ -1622,6 +1622,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> {
fn val_delim(&self) -> Option<char> { None }
fn takes_value(&self) -> bool { true }
fn help(&self) -> Option<&'e str> { self.p.meta.about }
fn long_help(&self) -> Option<&'e str> { self.p.meta.long_about }
fn default_val(&self) -> Option<&'e OsStr> { None }
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
None

View file

@ -680,7 +680,7 @@ impl<'a, 'b> Parser<'a, 'b>
if sc.meta.bin_name != self.meta.bin_name {
sc.meta.bin_name = Some(format!("{} {}", bin_name, sc.meta.name));
}
sc._help()
sc._help(false)
}
// allow wrong self convention due to self.valid_neg_num = true and it's a private method
@ -1246,7 +1246,7 @@ impl<'a, 'b> Parser<'a, 'b>
arg.to_str().unwrap());
if arg == "help" && self.is_set(AS::NeedsLongHelp) {
sdebugln!("Help");
try!(self._help());
try!(self._help(true));
}
if arg == "version" && self.is_set(AS::NeedsLongVersion) {
sdebugln!("Version");
@ -1264,7 +1264,7 @@ impl<'a, 'b> Parser<'a, 'b>
if let Some(h) = self.help_short {
if arg == h && self.is_set(AS::NeedsLongHelp) {
sdebugln!("Help");
try!(self._help());
try!(self._help(false));
}
}
if let Some(v) = self.version_short {
@ -1277,9 +1277,20 @@ impl<'a, 'b> Parser<'a, 'b>
Ok(())
}
fn _help(&self) -> ClapResult<()> {
fn use_long_help(&self) -> bool {
let ul = self.flags.iter().any(|f| f.b.long_help.is_some()) ||
self.opts.iter().any(|o| o.b.long_help.is_some()) ||
self.positionals.values().any(|p| p.b.long_help.is_some()) ||
self.subcommands.iter().any(|s| s.p.meta.long_about.is_some());
debugln!("Parser::use_long_help: ret={:?}", ul);
ul
}
fn _help(&self, mut use_long: bool) -> ClapResult<()> {
debugln!("Parser::_help: use_long={:?}", use_long);
use_long = use_long && self.use_long_help();
let mut buf = vec![];
try!(Help::write_parser_help(&mut buf, self));
try!(Help::write_parser_help(&mut buf, self, use_long));
Err(Error {
message: unsafe { String::from_utf8_unchecked(buf) },
kind: ErrorKind::HelpDisplayed,
@ -1288,6 +1299,7 @@ impl<'a, 'b> Parser<'a, 'b>
}
fn _version(&self) -> ClapResult<()> {
debugln!("Parser::_version: ");
let out = io::stdout();
let mut buf_w = BufWriter::new(out.lock());
try!(self.print_version(&mut buf_w));
@ -1630,7 +1642,11 @@ impl<'a, 'b> Parser<'a, 'b>
}
pub fn write_help<W: Write>(&self, w: &mut W) -> ClapResult<()> {
Help::write_parser_help(w, self)
Help::write_parser_help(w, self, false)
}
pub fn write_long_help<W: Write>(&self, w: &mut W) -> ClapResult<()> {
Help::write_parser_help(w, self, true)
}
pub fn write_help_err<W: Write>(&self, w: &mut W) -> ClapResult<()> {

View file

@ -32,6 +32,7 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display {
fn takes_value(&self) -> bool;
fn val_names(&self) -> Option<&VecMap<&'e str>>;
fn help(&self) -> Option<&'e str>;
fn long_help(&self) -> Option<&'e str>;
fn default_val(&self) -> Option<&'e OsStr>;
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>>;
fn longest_filter(&self) -> bool;

View file

@ -103,6 +103,7 @@ impl<'a, 'b> Arg<'a, 'b> {
"long" => yaml_to_str!(a, v, long),
"aliases" => yaml_vec_or_str!(v, a, alias),
"help" => yaml_to_str!(a, v, help),
"long_help" => yaml_to_str!(a, v, long_help),
"required" => yaml_to_bool!(a, v, required),
"required_if" => yaml_tuple2!(a, v, required_if),
"required_ifs" => yaml_tuple2!(a, v, required_if),
@ -489,8 +490,14 @@ impl<'a, 'b> Arg<'a, 'b> {
self
}
/// Sets the help text of the argument that will be displayed to the user when they print the
/// usage/help information.
/// Sets the short help text of the argument that will be displayed to the user when they print
/// the help information with `-h`. Typically, this is a short (one line) description of the
/// arg.
///
/// **NOTE:** If only `Arg::help` is provided, and not [`Arg::long_help`] but the user requests
/// `--help` clap will still display the contents of `help` appropriately
///
/// **NOTE:** Only `Arg::help` is used in completion script generation in order to be concise
///
/// # Examples
///
@ -532,11 +539,83 @@ impl<'a, 'b> Arg<'a, 'b> {
/// -h, --help Prints help information
/// -V, --version Prints version information
/// ```
/// [`Arg::long_help`]: ./struct.Arg.html#method.long_help
pub fn help(mut self, h: &'b str) -> Self {
self.b.help = Some(h);
self
}
/// Sets the long help text of the argument that will be displayed to the user when they print
/// the help information with `--help`. Typically this a more detailed (multi-line) message
/// that describes the arg.
///
/// **NOTE:** If only `long_help` is provided, and not [`Arg::help`] but the user requests `-h`
/// clap will still display the contents of `long_help` appropriately
///
/// **NOTE:** Only [`Arg::help`] is used in completion script generation in order to be concise
///
/// # Examples
///
/// Any valid UTF-8 is allowed in the help text. The one exception is when one wishes to
/// include a newline in the help text and have the following text be properly aligned with all
/// the other help text.
///
/// ```rust
/// # use clap::{App, Arg};
/// Arg::with_name("config")
/// .long_help(
/// "The config file used by the myprog must be in JSON format
/// with only valid keys and may not contain other nonsense
/// that cannot be read by this program. Obviously I'm going on
/// and on, so I'll stop now.")
/// # ;
/// ```
///
/// Setting `help` displays a short message to the side of the argument when the user passes
/// `-h` or `--help` (by default).
///
/// ```rust
/// # use clap::{App, Arg};
/// let m = App::new("prog")
/// .arg(Arg::with_name("cfg")
/// .long("config")
/// .long_help(
/// "The config file used by the myprog must be in JSON format
/// with only valid keys and may not contain other nonsense
/// that cannot be read by this program. Obviously I'm going on
/// and on, so I'll stop now."))
/// .get_matches_from(vec![
/// "prog", "--help"
/// ]);
/// ```
///
/// The above example displays
///
/// ```notrust
/// helptest
///
/// USAGE:
/// helptest [FLAGS]
///
/// FLAGS:
/// --config
/// The config file used by the myprog must be in JSON format
/// with only valid keys and may not contain other nonsense
/// that cannot be read by this program. Obviously I'm going on
/// and on, so I'll stop now.
///
/// -h, --help
/// Prints help information
///
/// -V, --version
/// Prints version information
/// ```
/// [`Arg::help`]: ./struct.Arg.html#method.help
pub fn long_help(mut self, h: &'b str) -> Self {
self.b.long_help = Some(h);
self
}
/// Specifies that this arg is the last, or final, positional argument (i.e. has the highest
/// index) and is *only* able to be accessed via the `--` syntax (i.e. `$ prog args --
/// last_arg`). Even, if no other arguments are left to parse, if the user omits the `--` syntax

View file

@ -7,6 +7,7 @@ pub struct Base<'a, 'b>
{
pub name: &'a str,
pub help: Option<&'b str>,
pub long_help: Option<&'b str>,
pub blacklist: Option<Vec<&'a str>>,
pub settings: ArgFlags,
pub r_unless: Option<Vec<&'a str>>,

View file

@ -79,6 +79,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
fn long(&self) -> Option<&'e str> { self.s.long }
fn val_delim(&self) -> Option<char> { None }
fn help(&self) -> Option<&'e str> { self.b.help }
fn long_help(&self) -> Option<&'e str> { self.b.long_help }
fn val_terminator(&self) -> Option<&'e str> { None }
fn default_val(&self) -> Option<&'e OsStr> { None }
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {

View file

@ -129,6 +129,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
fn val_delim(&self) -> Option<char> { self.v.val_delim }
fn takes_value(&self) -> bool { true }
fn help(&self) -> Option<&'e str> { self.b.help }
fn long_help(&self) -> Option<&'e str> { self.b.long_help }
fn default_val(&self) -> Option<&'e OsStr> { self.v.default_val }
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
self.v.default_vals_ifs.as_ref().map(|vm| vm.values())

View file

@ -126,6 +126,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> {
fn val_delim(&self) -> Option<char> { self.v.val_delim }
fn takes_value(&self) -> bool { true }
fn help(&self) -> Option<&'e str> { self.b.help }
fn long_help(&self) -> Option<&'e str> { self.b.long_help }
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
self.v.default_vals_ifs.as_ref().map(|vm| vm.values())
}