Skip to content

Commit

Permalink
imp(Help): adds setting for next line help by arg
Browse files Browse the repository at this point in the history
Adds a setting for both `AppSettings` and an `Arg` method which allows
placing the help text for a particular argument (or all arguments via
the `AppSettings`) on the line after the argument itself and indented
once.

This is useful for when a single argument may have a very long
invocation flag, or many value names which throws off the alignment of
al other arguments. On a small terminal this can be terrible. Placing
the text on the line below the argument solves this issue. This is also
how many of the GNU utilities display their help strings for individual
arguments.

The final caes where this can be hepful is if the argument has a very
long or detailed and complex help string that will span multiple lines.
It can be visually more appealing and easier to read when those lines
don't wrap as frequently since there is more space on the next line.

Closes #427
  • Loading branch information
kbknapp committed Feb 18, 2016
1 parent e08fdfb commit 48b5792
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 167 deletions.
17 changes: 9 additions & 8 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1400,15 +1400,15 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {

let mut longest_flag = 0;
for fl in self.flags.iter()
.filter(|f| f.long.is_some() && !f.settings.is_set(ArgSettings::Hidden))
.filter(|f| f.long.is_some() && !(f.settings.is_set(ArgSettings::Hidden) || f.settings.is_set(ArgSettings::NextLineHelp)))
.map(|a| a.to_string().len()) {
if fl > longest_flag {
longest_flag = fl;
}
}
let mut longest_opt = 0;
for ol in self.opts.iter()
.filter(|o| !o.settings.is_set(ArgSettings::Hidden))
.filter(|o| !(o.settings.is_set(ArgSettings::Hidden) || o.settings.is_set(ArgSettings::NextLineHelp)))
.map(|a| a.to_string().len()) {
if ol > longest_opt {
longest_opt = ol;
Expand All @@ -1417,7 +1417,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
let mut longest_pos = 0;
for pl in self.positionals
.values()
.filter(|p| !p.settings.is_set(ArgSettings::Hidden))
.filter(|p| !(p.settings.is_set(ArgSettings::Hidden) || p.settings.is_set(ArgSettings::NextLineHelp)))
.map(|f| f.to_string().len()) {
if pl > longest_pos {
longest_pos = pl;
Expand All @@ -1443,17 +1443,18 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
} else {
longest_opt
};
let nlh = self.settings.is_set(AppSettings::NextLineHelp);
if unified_help && (flags || opts) {
try!(write!(w, "\nOPTIONS:\n"));
let mut combined = BTreeMap::new();
for f in self.flags.iter().filter(|f| !f.settings.is_set(ArgSettings::Hidden)) {
let mut v = vec![];
try!(f.write_help(&mut v, tab, longest));
try!(f.write_help(&mut v, tab, longest, nlh));
combined.insert(f.name, v);
}
for o in self.opts.iter().filter(|o| !o.settings.is_set(ArgSettings::Hidden)) {
let mut v = vec![];
try!(o.write_help(&mut v, tab, longest, self.is_set(AppSettings::HidePossibleValuesInHelp)));
try!(o.write_help(&mut v, tab, longest, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
combined.insert(o.name, v);
}
for (_, a) in combined {
Expand All @@ -1468,7 +1469,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
.map(|f| (f.name, f))
.collect::<BTreeMap<_, _>>()
.values() {
try!(f.write_help(w, tab, longest));
try!(f.write_help(w, tab, longest, nlh));
}
}
if opts {
Expand All @@ -1478,15 +1479,15 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
.map(|o| (o.name, o))
.collect::<BTreeMap<_, _>>()
.values() {
try!(o.write_help(w, tab, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp)));
try!(o.write_help(w, tab, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
}
}
}
if pos {
try!(write!(w, "\nARGS:\n"));
for v in self.positionals.values()
.filter(|p| !p.settings.is_set(ArgSettings::Hidden)) {
try!(v.write_help(w, tab, longest_pos, self.is_set(AppSettings::HidePossibleValuesInHelp)));
try!(v.write_help(w, tab, longest_pos, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
}
}
if subcmds {
Expand Down
60 changes: 38 additions & 22 deletions src/app/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,27 @@ use std::ascii::AsciiExt;

bitflags! {
flags Flags: u32 {
const SC_NEGATE_REQS = 0b00000000000000000001,
const SC_REQUIRED = 0b00000000000000000010,
const A_REQUIRED_ELSE_HELP = 0b00000000000000000100,
const GLOBAL_VERSION = 0b00000000000000001000,
const VERSIONLESS_SC = 0b00000000000000010000,
const UNIFIED_HELP = 0b00000000000000100000,
const WAIT_ON_ERROR = 0b00000000000001000000,
const SC_REQUIRED_ELSE_HELP= 0b00000000000010000000,
const NEEDS_LONG_HELP = 0b00000000000100000000,
const NEEDS_LONG_VERSION = 0b00000000001000000000,
const NEEDS_SC_HELP = 0b00000000010000000000,
const DISABLE_VERSION = 0b00000000100000000000,
const HIDDEN = 0b00000001000000000000,
const TRAILING_VARARG = 0b00000010000000000000,
const NO_BIN_NAME = 0b00000100000000000000,
const ALLOW_UNK_SC = 0b00001000000000000000,
const UTF8_STRICT = 0b00010000000000000000,
const UTF8_NONE = 0b00100000000000000000,
const LEADING_HYPHEN = 0b01000000000000000000,
const NO_POS_VALUES = 0b10000000000000000000,
const SC_NEGATE_REQS = 0b000000000000000000001,
const SC_REQUIRED = 0b000000000000000000010,
const A_REQUIRED_ELSE_HELP = 0b000000000000000000100,
const GLOBAL_VERSION = 0b000000000000000001000,
const VERSIONLESS_SC = 0b000000000000000010000,
const UNIFIED_HELP = 0b000000000000000100000,
const WAIT_ON_ERROR = 0b000000000000001000000,
const SC_REQUIRED_ELSE_HELP= 0b000000000000010000000,
const NEEDS_LONG_HELP = 0b000000000000100000000,
const NEEDS_LONG_VERSION = 0b000000000001000000000,
const NEEDS_SC_HELP = 0b000000000010000000000,
const DISABLE_VERSION = 0b000000000100000000000,
const HIDDEN = 0b000000001000000000000,
const TRAILING_VARARG = 0b000000010000000000000,
const NO_BIN_NAME = 0b000000100000000000000,
const ALLOW_UNK_SC = 0b000001000000000000000,
const UTF8_STRICT = 0b000010000000000000000,
const UTF8_NONE = 0b000100000000000000000,
const LEADING_HYPHEN = 0b001000000000000000000,
const NO_POS_VALUES = 0b010000000000000000000,
const NEXT_LINE_HELP = 0b100000000000000000000,
}
}

Expand Down Expand Up @@ -55,7 +56,8 @@ impl AppFlags {
StrictUtf8 => UTF8_STRICT,
AllowInvalidUtf8 => UTF8_NONE,
AllowLeadingHyphen => LEADING_HYPHEN,
HidePossibleValuesInHelp => NO_POS_VALUES
HidePossibleValuesInHelp => NO_POS_VALUES,
NextLineHelp => NEXT_LINE_HELP
}
}

Expand Down Expand Up @@ -398,9 +400,22 @@ pub enum AppSettings {
/// # ;
/// ```
AllowLeadingHyphen,
/// Tells `clap` *not* to print possible values when displaying help information. This can be
/// Tells `clap` *not* to print possible values when displaying help information. This can be
/// useful if there are many values, or they are explained elsewhere.
HidePossibleValuesInHelp,
/// Places the help string for all arguments on the line after the argument
///
/// **NOTE:** This setting is cosmetic only and does not affect any functionality.
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, SubCommand, AppSettings};
/// App::new("myprog")
/// .setting(AppSettings::NextLineHelp)
/// .get_matches();
/// ```
NextLineHelp,
#[doc(hidden)]
NeedsLongVersion,
#[doc(hidden)]
Expand Down Expand Up @@ -431,6 +446,7 @@ impl FromStr for AppSettings {
"allowinvalidutf8" => Ok(AppSettings::AllowInvalidUtf8),
"allowleadinghyphen" => Ok(AppSettings::AllowLeadingHyphen),
"hidepossiblevaluesinhelp" => Ok(AppSettings::HidePossibleValuesInHelp),
"nextlinehelp" => Ok(AppSettings::NextLineHelp),
_ => Err("unknown AppSetting, cannot convert from str".to_owned()),
}
}
Expand Down
52 changes: 52 additions & 0 deletions src/args/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,58 @@ impl<'a, 'b> Arg<'a, 'b> {
self
}

/// When set to `true` the help string will be displayed on the line after the argument and
/// indented once. This can be helpful for arguments with very long or complex help messages.
/// This can also be helpful for arguments with very long flag names, or many/long value names.
///
/// **NOTE:** To apply this setting to all arguments consider using `AppSettings::NextLineHelp`
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg};
/// let m = App::new("nlh")
/// .arg(Arg::with_name("opt")
/// .long("long-option-flag")
/// .short("o")
/// .takes_value(true)
/// .value_names(&["value1", "value2"])
/// .help("Some really long help and complex{n}\
/// help that makes more sense to be{n}\
/// on a line after the option")
/// .next_line_help(true))
/// .get_matches_from(vec![
/// "nlh", "--help"
/// ]);
/// ```
///
/// The above example displays the following help message
///
/// ```ignore
/// nlh
///
/// USAGE:
/// nlh [FLAGS] [OPTIONS]
///
/// FLAGS:
/// -h, --help Prints help information
/// -V, --version Prints version information
///
/// OPTIONS:
/// -o, --long-option-flag <value1> <value2>
/// Some really long help and complex
/// help that makes more sense to be
/// on a line after the option
/// ```
pub fn next_line_help(mut self, nlh: bool) -> Self {
if nlh {
self.setb(ArgSettings::NextLineHelp);
} else {
self.unsetb(ArgSettings::NextLineHelp);
}
self
}

/// Checks if one of the `ArgSettings` settings is set for the argument
pub fn is_set(&self, s: ArgSettings) -> bool {
self.settings.is_set(s)
Expand Down
35 changes: 2 additions & 33 deletions src/args/arg_builder/flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,39 +45,8 @@ impl<'n, 'e> FlagBuilder<'n, 'e> {
}
}

pub fn write_help<W: io::Write>(&self, w: &mut W, tab: &str, longest: usize) -> io::Result<()> {
try!(write!(w, "{}", tab));
if let Some(s) = self.short {
try!(write!(w, "-{}", s));
} else {
try!(write!(w, "{}", tab));
}
if let Some(l) = self.long {
try!(write!(w,
"{}--{}",
if self.short.is_some() {
", "
} else {
""
},
l));
write_spaces!((longest + 4) - (l.len() + 2), w);
} else {
// 6 is tab (4) + -- (2)
write_spaces!((longest + 6), w);
}
if let Some(h) = self.help {
if h.contains("{n}") {
let mut hel = h.split("{n}");
while let Some(part) = hel.next() {
try!(write!(w, "{}\n", part));
write_spaces!((longest + 12), w);
try!(write!(w, "{}", hel.next().unwrap_or("")));
}
} else {
try!(write!(w, "{}", h));
}
}
pub fn write_help<W: io::Write>(&self, w: &mut W, tab: &str, longest: usize, nlh: bool) -> io::Result<()> {
write_arg_help!(@flag self, w, tab, longest, nlh);
write!(w, "\n")
}
}
Expand Down
123 changes: 123 additions & 0 deletions src/args/arg_builder/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
macro_rules! write_arg_help {
(@opt $_self:ident, $w:ident, $tab:ident, $longest:ident, $skip_pv:ident, $nlh:ident) => {
write_arg_help!(@short $_self, $w, $tab);
write_arg_help!(@opt_long $_self, $w, $nlh, $longest);
write_arg_help!(@val $_self, $w);
if !($nlh || $_self.settings.is_set(ArgSettings::NextLineHelp)) {
write_spaces!(if $_self.long.is_some() { $longest + 4 } else { $longest + 8 } - ($_self.to_string().len()), $w);
}
if let Some(h) = $_self.help {
write_arg_help!(@help $_self, $w, h, $tab, $longest, $nlh);
write_arg_help!(@spec_vals $_self, $w, $skip_pv);
}
};
(@flag $_self:ident, $w:ident, $tab:ident, $longest:ident, $nlh:ident) => {
write_arg_help!(@short $_self, $w, $tab);
write_arg_help!(@flag_long $_self, $w, $longest, $nlh);
if let Some(h) = $_self.help {
write_arg_help!(@help $_self, $w, h, $tab, $longest, $nlh);
}
};
(@pos $_self:ident, $w:ident, $tab:ident, $longest:ident, $skip_pv:ident, $nlh:ident) => {
try!(write!($w, "{}", $tab));
write_arg_help!(@val $_self, $w);
if !($nlh || $_self.settings.is_set(ArgSettings::NextLineHelp)) {
write_spaces!($longest + 4 - ($_self.to_string().len()), $w);
}
if let Some(h) = $_self.help {
write_arg_help!(@help $_self, $w, h, $tab, $longest, $nlh);
write_arg_help!(@spec_vals $_self, $w, $skip_pv);
}
};
(@short $_self:ident, $w:ident, $tab:ident) => {
try!(write!($w, "{}", $tab));
if let Some(s) = $_self.short {
try!(write!($w, "-{}", s));
} else {
try!(write!($w, "{}", $tab));
}
};
(@flag_long $_self:ident, $w:ident, $longest:ident, $nlh:ident) => {
if let Some(l) = $_self.long {
write_arg_help!(@long $_self, $w, l);
if !$nlh || !$_self.settings.is_set(ArgSettings::NextLineHelp) {
write_spaces!(($longest + 4) - (l.len() + 2), $w);
}
} else {
if !$nlh || !$_self.settings.is_set(ArgSettings::NextLineHelp) {
// 6 is tab (4) + -- (2)
write_spaces!(($longest + 6), $w);
}
}
};
(@opt_long $_self:ident, $w:ident, $nlh:ident, $longest:ident) => {
if let Some(l) = $_self.long {
write_arg_help!(@long $_self, $w, l);
}
};
(@long $_self:ident, $w:ident, $l:ident) => {
try!(write!($w,
"{}--{}",
if $_self.short.is_some() {
", "
} else {
""
},
$l));
};
(@val $_self:ident, $w:ident) => {
if let Some(ref vec) = $_self.val_names {
for (_, val) in vec {
try!(write!($w, " <{}>", val));
}
let num = vec.len();
if $_self.settings.is_set(ArgSettings::Multiple) && num == 1 {
try!(write!($w, "..."));
}
} else if let Some(num) = $_self.num_vals {
for _ in 0..num {
try!(write!($w, " <{}>", $_self.name));
}
} else {
try!(write!($w,
" <{}>{}",
$_self.name,
if $_self.settings.is_set(ArgSettings::Multiple) {
"..."
} else {
""
}));
}
};
(@spec_vals $_self:ident, $w:ident, $skip_pv:ident) => {
if let Some(ref pv) = $_self.default_val {
try!(write!($w, " [default: {}]", pv));
}
if !$skip_pv {
if let Some(ref pv) = $_self.possible_vals {
try!(write!($w, " [values: {}]", pv.join(", ")));
}
}
};
(@help $_self:ident, $w:ident, $h:ident, $tab:ident, $longest:expr, $nlh:ident) => {
if $nlh || $_self.settings.is_set(ArgSettings::NextLineHelp) {
try!(write!($w, "\n{}{}", $tab, $tab));
}
if $h.contains("{n}") {
if let Some(part) = $h.split("{n}").next() {
try!(write!($w, "{}", part));
}
for part in $h.split("{n}").skip(1) {
try!(write!($w, "\n"));
if $nlh || $_self.settings.is_set(ArgSettings::NextLineHelp) {
try!(write!($w, "{}{}", $tab, $tab));
} else {
write_spaces!($longest + 12, $w);
}
try!(write!($w, "{}", part));
}
} else {
try!(write!($w, "{}", $h));
}
};
}
2 changes: 2 additions & 0 deletions src/args/arg_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ pub use self::flag::FlagBuilder;
pub use self::option::OptBuilder;
pub use self::positional::PosBuilder;

#[macro_use]
mod macros;
#[allow(dead_code)]
mod flag;
#[allow(dead_code)]
Expand Down
Loading

0 comments on commit 48b5792

Please sign in to comment.