Auto merge of #454 - kbknapp:issue-453,452, r=kbknapp

Issue 453,452
This commit is contained in:
Homu 2016-03-17 22:32:31 +09:00
commit afa6b5d66e
11 changed files with 131 additions and 51 deletions

View file

@ -1,3 +1,16 @@
<a name="2.2.1"></a>
### 2.2.1 (2016-03-16)
#### Features
* **Help Message:** wraps and aligns the help message of subcommands ([813d75d0](https://github.com/kbknapp/clap-rs/commit/813d75d06fbf077c65762608c0fa5e941cfc393c), closes [#452](https://github.com/kbknapp/clap-rs/issues/452))
#### Bug Fixes
* **Help Message:** fixes a bug where small terminal sizes causing a loop ([1d73b035](https://github.com/kbknapp/clap-rs/commit/1d73b0355236923aeaf6799abc759762ded7e1d0), closes [#453](https://github.com/kbknapp/clap-rs/issues/453))
<a name="v2.2.0"></a>
## v2.2.0 (2016-03-15)

View file

@ -1,7 +1,7 @@
[package]
name = "clap"
version = "2.2.0"
version = "2.2.1"
authors = ["Kevin K. <kbknapp@gmail.com>"]
exclude = ["examples/*", "clap-tests/*", "tests/*", "benches/*", "*.png", "clap-perf/*"]
description = "A simple to use, efficient, and full featured Command Line Argument Parser"

View file

@ -38,6 +38,11 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
## What's New
Here's the highlights from v2.2.1
* **Help text auto wraps and aligns at for subcommands too!** - Long help strings of subcommands will now properly wrap and align to term width on Linux and OSX. This can be turned off as well.
* Bug fixes
Here's the highlights from v2.2.0
#### Features

View file

@ -31,12 +31,12 @@ OPTIONS:
-o, --option <opt>... tests options
ARGS:
<positional> tests positionals
<positional2> tests positionals with exclusions
<positional3>... tests positionals with specific values [values: vi, emacs]
[positional] tests positionals
[positional2] tests positionals with exclusions
[positional3]... tests positionals with specific values [values: vi, emacs]
SUBCOMMANDS:
help With no arguments it prints this message, otherwise it prints help information about other subcommands
help Prints this message or the help message of the given subcommand(s)
subcmd tests subcommands'''
_version = "claptests v1.4.8"
@ -152,7 +152,7 @@ OPTIONS:
-o, --option <scoption>... tests options
ARGS:
<scpositional> tests positionals'''
[scpositional] tests positionals'''
_scfop = '''flag NOT present
option NOT present

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)) {
@ -138,7 +139,10 @@ impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> {
self.l + 12
};
// determine if our help fits or needs to wrap
let too_long = self.term_w.is_some() && (spcs + h.len() + spec_vals.len() >= self.term_w.unwrap_or(0));
let width = self.term_w.unwrap_or(0);
debugln!("Term width...{}", width);
let too_long = self.term_w.is_some() && (spcs + h.len() + spec_vals.len() >= width);
debugln!("Too long...{:?}", too_long);
// Is help on next line, if so newline + 2x tab
if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) {
@ -148,15 +152,26 @@ impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> {
debug!("Too long...");
if too_long {
sdebugln!("Yes");
if let Some(width) = self.term_w {
help.push_str(h);
help.push_str(&*spec_vals);
debugln!("term width: {}", width);
debugln!("help: {}", help);
debugln!("help len: {}", help.len());
// Determine how many newlines we need to insert
let avail_chars = width - spcs;
debugln!("Usable space: {}", avail_chars);
help.push_str(h);
help.push_str(&*spec_vals);
debugln!("help: {}", help);
debugln!("help len: {}", help.len());
// Determine how many newlines we need to insert
let avail_chars = width - spcs;
debugln!("Usable space: {}", avail_chars);
let longest_w = {
let mut lw = 0;
for l in help.split(' ').map(|s| s.len()) {
if l > lw {
lw = l;
}
}
lw
};
debugln!("Longest word...{}", longest_w);
debug!("Enough space...");
if longest_w < avail_chars {
sdebugln!("Yes");
let mut indices = vec![];
let mut idx = 0;
loop {
@ -183,6 +198,8 @@ impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> {
help.insert(j + 1 , 'n');
help.insert(j + 2, '}');
}
} else {
sdebugln!("No");
}
} else { sdebugln!("No"); }
let help = if !help.is_empty() {

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 {

View file

@ -102,7 +102,7 @@ OPTIONS:
-V, --version Prints version information
ARGS:
<arg1> some pos arg\n"));
[arg1] some pos arg\n"));
}
#[test]
@ -135,5 +135,5 @@ OPTIONS:
-o, --opt <opt> some option
ARGS:
<arg1> some pos arg\n"));
[arg1] some pos arg\n"));
}

View file

@ -112,5 +112,5 @@ OPTIONS:
-o, --opt <opt> some option [values: one, two]
ARGS:
<arg1> some pos arg [values: three, four]\n"));
[arg1] some pos arg [values: three, four]\n"));
}