feat(Help Message): wraps and aligns the help message of subcommands

Subcommand's help strings are now automatically wrapped and aligned just
like other arguments.

Closes #452
This commit is contained in:
Kevin K 2016-03-16 10:17:00 -04:00
parent 1d73b03552
commit 813d75d06f
5 changed files with 78 additions and 32 deletions

View file

@ -13,11 +13,15 @@ use std::path::Path;
use std::process;
use std::ffi::OsString;
use std::borrow::Borrow;
use std::result::Result as StdResult;
use std::rc::Rc;
use std::fmt;
#[cfg(feature = "yaml")]
use yaml_rust::Yaml;
use vec_map::VecMap;
use args::{Arg, AnyArg, ArgGroup, ArgMatches, ArgMatcher};
use args::{Arg, HelpWriter, ArgSettings, AnyArg, ArgGroup, ArgMatches, ArgMatcher};
use app::parser::Parser;
use errors::Error;
use errors::Result as ClapResult;
@ -798,6 +802,30 @@ impl<'a, 'b> App<'a, 'b> {
e.exit()
}
#[doc(hidden)]
pub fn write_self_help<W>(&self, w: &mut W, longest: usize, nlh: bool) -> io::Result<()>
where W: Write
{
let hw = HelpWriter::new(self, longest, nlh);
hw.write_to(w)
// try!(write!(w, " {}", self.p.meta.name));
// write_spaces!((longest_sc + 4) - (self.p.meta.name.len()), w);
// if let Some(a) = self.p.meta.about {
// if a.contains("{n}") {
// let mut ab = a.split("{n}");
// while let Some(part) = ab.next() {
// try!(write!(w, "{}\n", part));
// write_spaces!(longest_sc + 8, w);
// try!(write!(w, "{}", ab.next().unwrap_or("")));
// }
// } else {
// try!(write!(w, "{}", a));
// }
// }
// write!(w, "\n")
}
}
#[cfg(feature = "yaml")]
@ -883,3 +911,35 @@ impl<'a, 'b> Clone for App<'a, 'b> {
}
}
}
impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> {
fn name(&self) -> &'n str {
unreachable!("App struct does not support AnyArg::name, this is a bug!")
}
fn overrides(&self) -> Option<&[&'e str]> { None }
fn requires(&self) -> Option<&[&'e str]> { None }
fn blacklist(&self) -> Option<&[&'e str]> { None }
fn val_names(&self) -> Option<&VecMap<&'e str>> { None }
fn is_set(&self, _: ArgSettings) -> bool { false }
fn set(&mut self, _: ArgSettings) {
unreachable!("App struct does not support AnyArg::set, this is a bug!")
}
fn has_switch(&self) -> bool { false }
fn max_vals(&self) -> Option<u64> { None }
fn num_vals(&self) -> Option<u64> { None }
fn possible_vals(&self) -> Option<&[&'e str]> { None }
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> { None }
fn min_vals(&self) -> Option<u64> { None }
fn short(&self) -> Option<char> { None }
fn long(&self) -> Option<&'e str> { None }
fn val_delim(&self) -> Option<char> { None }
fn takes_value(&self) -> bool { true }
fn help(&self) -> Option<&'e str> { self.p.meta.about }
fn default_val(&self) -> Option<&'n str> { None }
}
impl<'n, 'e> fmt::Display for App<'n, 'e> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.p.meta.name)
}
}

View file

@ -814,7 +814,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
.iter()
.any(|s| &s.p.meta.name[..] == "help") {
debugln!("Building help");
self.subcommands.push(App::new("help").about("With no arguments it prints this message, otherwise it prints help information about other subcommands"));
self.subcommands.push(App::new("help").about("Prints this message or the help message of the given subcommand(s)"));
}
}
@ -1034,7 +1034,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
}
fn add_single_val_to_arg<A>(&self, arg: &A, v: &OsStr, matcher: &mut ArgMatcher<'a>) -> ClapResult<Option<&'a str>>
where A: AnyArg<'a, 'b>
where A: AnyArg<'a, 'b> + Display
{
debugln!("adding val: {:?}", v);
matcher.add_val_to(arg.name(), v);
@ -1052,7 +1052,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
}
fn validate_value<A>(&self, arg: &A, val: &OsStr, matcher: &ArgMatcher<'a>) -> ClapResult<Option<&'a str>>
where A: AnyArg<'a, 'b> {
where A: AnyArg<'a, 'b> + Display {
debugln!("fn=validate_value; val={:?}", val);
if self.is_set(AppSettings::StrictUtf8) && val.to_str().is_none() {
return Err(Error::invalid_utf8(&*self.create_current_usage(matcher)));
@ -1165,7 +1165,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
}
fn _validate_num_vals<A>(&self, a: &A, ma: &MatchedArg, matcher: &ArgMatcher) -> ClapResult<()>
where A: AnyArg<'a, 'b>
where A: AnyArg<'a, 'b> + Display
{
debugln!("fn=_validate_num_vals;");
if let Some(num) = a.num_vals() {
@ -1532,22 +1532,8 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
btm.insert(sc.p.meta.name.clone(), sc);
}
for (_, btm) in ord_m.into_iter() {
for (name, sc) in btm.into_iter() {
try!(write!(w, " {}", name));
write_spaces!((longest_sc + 4) - (name.len()), w);
if let Some(a) = sc.p.meta.about {
if a.contains("{n}") {
let mut ab = a.split("{n}");
while let Some(part) = ab.next() {
try!(write!(w, "{}\n", part));
write_spaces!(longest_sc + 8, w);
try!(write!(w, "{}", ab.next().unwrap_or("")));
}
} else {
try!(write!(w, "{}", a));
}
}
try!(write!(w, "\n"));
for (_, sc) in btm.into_iter() {
try!(sc.write_self_help(w, longest_sc, nlh));
}
}
}

View file

@ -1,12 +1,11 @@
use std::rc::Rc;
use std::fmt::Display;
use vec_map::VecMap;
use args::settings::ArgSettings;
#[doc(hidden)]
pub trait AnyArg<'n, 'e>: Display {
pub trait AnyArg<'n, 'e> {
fn name(&self) -> &'n str;
fn overrides(&self) -> Option<&[&'e str]>;
fn requires(&self) -> Option<&[&'e str]>;

View file

@ -1,4 +1,5 @@
use std::io;
use std::fmt::Display;
use args::AnyArg;
use args::settings::ArgSettings;
@ -14,7 +15,7 @@ pub struct HelpWriter<'a, A> where A: 'a {
term_w: Option<usize>,
}
impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> {
impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> + Display {
pub fn new(a: &'a A, l: usize, nlh: bool) -> Self {
HelpWriter {
a: a,
@ -99,7 +100,7 @@ impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> {
if it.peek().is_some() { try!(write!(w, " ")); }
}
} else {
try!(write!(w, "<{}>{}", self.a.name(), if self.a.is_set(ArgSettings::Multiple) { "..." } else { "" }));
try!(write!(w, "{}", self.a));
}
if self.a.has_switch() {
if !(self.nlh || self.a.is_set(ArgSettings::NextLineHelp)) {

View file

@ -355,7 +355,7 @@ impl Error {
#[doc(hidden)]
pub fn argument_conflict<'a, 'b, A, O, U>(arg: &A, other: Option<O>, usage: U) -> Self
where A: AnyArg<'a, 'b>,
where A: AnyArg<'a, 'b> + Display,
O: Into<String>,
U: Display
{
@ -383,7 +383,7 @@ impl Error {
#[doc(hidden)]
pub fn empty_value<'a, 'b, A, U>(arg: &A, usage: U) -> Self
where A: AnyArg<'a, 'b>,
where A: AnyArg<'a, 'b> + Display,
U: Display
{
Error {
@ -404,7 +404,7 @@ impl Error {
pub fn invalid_value<'a, 'b, B, G, A, U>(bad_val: B, good_vals: &[G], arg: &A, usage: U) -> Self
where B: AsRef<str>,
G: AsRef<str> + Display,
A: AnyArg<'a, 'b>,
A: AnyArg<'a, 'b> + Display,
U: Display
{
let suffix = suggestions::did_you_mean_suffix(bad_val.as_ref(),
@ -539,7 +539,7 @@ impl Error {
#[doc(hidden)]
pub fn too_many_values<'a, 'b, V, A, U>(val: V, arg: &A, usage: U) -> Self
where V: AsRef<str> + Display + ToOwned,
A: AnyArg<'a, 'b>,
A: AnyArg<'a, 'b> + Display,
U: Display
{
let v = val.as_ref();
@ -560,7 +560,7 @@ impl Error {
#[doc(hidden)]
pub fn too_few_values<'a, 'b, A, U>(arg: &A, min_vals: u64, curr_vals: usize, usage: U) -> Self
where A: AnyArg<'a, 'b>,
where A: AnyArg<'a, 'b> + Display,
U: Display
{
Error {
@ -595,7 +595,7 @@ impl Error {
#[doc(hidden)]
pub fn wrong_number_of_values<'a, 'b, A, S, U>(arg: &A, num_vals: u64, curr_vals: usize, suffix: S, usage: U) -> Self
where A: AnyArg<'a, 'b>,
where A: AnyArg<'a, 'b> + Display,
S: Display,
U: Display
{
@ -618,7 +618,7 @@ impl Error {
#[doc(hidden)]
pub fn unexpected_multiple_usage<'a, 'b, A, U>(arg: &A, usage: U) -> Self
where A: AnyArg<'a, 'b>,
where A: AnyArg<'a, 'b> + Display,
U: Display
{
Error {