Skip to content

Commit

Permalink
feat: allows specifying a short help vs a long help (i.e. varying lev…
Browse files Browse the repository at this point in the history
…els 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!
  • Loading branch information
kbknapp committed Apr 5, 2017
1 parent b48a5bb commit ef1b24c
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 93 deletions.
92 changes: 54 additions & 38 deletions src/app/help.rs
Expand Up @@ -89,6 +89,7 @@ pub struct Help<'a> {
cizer: Colorizer,
longest: usize,
force_next_line: bool,
use_long: bool,
}

// Public Functions
Expand All @@ -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 {
Expand All @@ -121,33 +123,34 @@ 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
/// and write its help to the wrapped stream which will be stderr. This method prevents
/// 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);
Expand All @@ -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.
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(", ")
Expand All @@ -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(" ")
Expand All @@ -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();

Expand All @@ -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));
Expand All @@ -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 {
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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"?" => {
Expand Down Expand Up @@ -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));
Expand Down
50 changes: 4 additions & 46 deletions src/app/meta.rs
@@ -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)
Expand All @@ -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() } }
}
3 changes: 2 additions & 1 deletion src/app/mod.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
28 changes: 22 additions & 6 deletions src/app/parser.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand All @@ -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 {
Expand All @@ -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,
Expand All @@ -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));
Expand Down Expand Up @@ -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<()> {
Expand Down
1 change: 1 addition & 0 deletions src/args/any_arg.rs
Expand Up @@ -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;
Expand Down

0 comments on commit ef1b24c

Please sign in to comment.