Skip to content

Commit 066df74

Browse files
committed
imp(Help): adds setting for next line help by arg
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
1 parent e08fdfb commit 066df74

File tree

9 files changed

+246
-167
lines changed

9 files changed

+246
-167
lines changed

src/app/parser.rs

+9-8
Original file line numberDiff line numberDiff line change
@@ -1400,15 +1400,15 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
14001400

14011401
let mut longest_flag = 0;
14021402
for fl in self.flags.iter()
1403-
.filter(|f| f.long.is_some() && !f.settings.is_set(ArgSettings::Hidden))
1403+
.filter(|f| f.long.is_some() && !(f.settings.is_set(ArgSettings::Hidden) || f.settings.is_set(ArgSettings::NextLineHelp)))
14041404
.map(|a| a.to_string().len()) {
14051405
if fl > longest_flag {
14061406
longest_flag = fl;
14071407
}
14081408
}
14091409
let mut longest_opt = 0;
14101410
for ol in self.opts.iter()
1411-
.filter(|o| !o.settings.is_set(ArgSettings::Hidden))
1411+
.filter(|o| !(o.settings.is_set(ArgSettings::Hidden) || o.settings.is_set(ArgSettings::NextLineHelp)))
14121412
.map(|a| a.to_string().len()) {
14131413
if ol > longest_opt {
14141414
longest_opt = ol;
@@ -1417,7 +1417,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
14171417
let mut longest_pos = 0;
14181418
for pl in self.positionals
14191419
.values()
1420-
.filter(|p| !p.settings.is_set(ArgSettings::Hidden))
1420+
.filter(|p| !(p.settings.is_set(ArgSettings::Hidden) || p.settings.is_set(ArgSettings::NextLineHelp)))
14211421
.map(|f| f.to_string().len()) {
14221422
if pl > longest_pos {
14231423
longest_pos = pl;
@@ -1443,17 +1443,18 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
14431443
} else {
14441444
longest_opt
14451445
};
1446+
let nlh = self.settings.is_set(AppSettings::NextLineHelp);
14461447
if unified_help && (flags || opts) {
14471448
try!(write!(w, "\nOPTIONS:\n"));
14481449
let mut combined = BTreeMap::new();
14491450
for f in self.flags.iter().filter(|f| !f.settings.is_set(ArgSettings::Hidden)) {
14501451
let mut v = vec![];
1451-
try!(f.write_help(&mut v, tab, longest));
1452+
try!(f.write_help(&mut v, tab, longest, nlh));
14521453
combined.insert(f.name, v);
14531454
}
14541455
for o in self.opts.iter().filter(|o| !o.settings.is_set(ArgSettings::Hidden)) {
14551456
let mut v = vec![];
1456-
try!(o.write_help(&mut v, tab, longest, self.is_set(AppSettings::HidePossibleValuesInHelp)));
1457+
try!(o.write_help(&mut v, tab, longest, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
14571458
combined.insert(o.name, v);
14581459
}
14591460
for (_, a) in combined {
@@ -1468,7 +1469,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
14681469
.map(|f| (f.name, f))
14691470
.collect::<BTreeMap<_, _>>()
14701471
.values() {
1471-
try!(f.write_help(w, tab, longest));
1472+
try!(f.write_help(w, tab, longest, nlh));
14721473
}
14731474
}
14741475
if opts {
@@ -1478,15 +1479,15 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
14781479
.map(|o| (o.name, o))
14791480
.collect::<BTreeMap<_, _>>()
14801481
.values() {
1481-
try!(o.write_help(w, tab, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp)));
1482+
try!(o.write_help(w, tab, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
14821483
}
14831484
}
14841485
}
14851486
if pos {
14861487
try!(write!(w, "\nARGS:\n"));
14871488
for v in self.positionals.values()
14881489
.filter(|p| !p.settings.is_set(ArgSettings::Hidden)) {
1489-
try!(v.write_help(w, tab, longest_pos, self.is_set(AppSettings::HidePossibleValuesInHelp)));
1490+
try!(v.write_help(w, tab, longest_pos, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
14901491
}
14911492
}
14921493
if subcmds {

src/app/settings.rs

+38-22
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,27 @@ use std::ascii::AsciiExt;
33

44
bitflags! {
55
flags Flags: u32 {
6-
const SC_NEGATE_REQS = 0b00000000000000000001,
7-
const SC_REQUIRED = 0b00000000000000000010,
8-
const A_REQUIRED_ELSE_HELP = 0b00000000000000000100,
9-
const GLOBAL_VERSION = 0b00000000000000001000,
10-
const VERSIONLESS_SC = 0b00000000000000010000,
11-
const UNIFIED_HELP = 0b00000000000000100000,
12-
const WAIT_ON_ERROR = 0b00000000000001000000,
13-
const SC_REQUIRED_ELSE_HELP= 0b00000000000010000000,
14-
const NEEDS_LONG_HELP = 0b00000000000100000000,
15-
const NEEDS_LONG_VERSION = 0b00000000001000000000,
16-
const NEEDS_SC_HELP = 0b00000000010000000000,
17-
const DISABLE_VERSION = 0b00000000100000000000,
18-
const HIDDEN = 0b00000001000000000000,
19-
const TRAILING_VARARG = 0b00000010000000000000,
20-
const NO_BIN_NAME = 0b00000100000000000000,
21-
const ALLOW_UNK_SC = 0b00001000000000000000,
22-
const UTF8_STRICT = 0b00010000000000000000,
23-
const UTF8_NONE = 0b00100000000000000000,
24-
const LEADING_HYPHEN = 0b01000000000000000000,
25-
const NO_POS_VALUES = 0b10000000000000000000,
6+
const SC_NEGATE_REQS = 0b000000000000000000001,
7+
const SC_REQUIRED = 0b000000000000000000010,
8+
const A_REQUIRED_ELSE_HELP = 0b000000000000000000100,
9+
const GLOBAL_VERSION = 0b000000000000000001000,
10+
const VERSIONLESS_SC = 0b000000000000000010000,
11+
const UNIFIED_HELP = 0b000000000000000100000,
12+
const WAIT_ON_ERROR = 0b000000000000001000000,
13+
const SC_REQUIRED_ELSE_HELP= 0b000000000000010000000,
14+
const NEEDS_LONG_HELP = 0b000000000000100000000,
15+
const NEEDS_LONG_VERSION = 0b000000000001000000000,
16+
const NEEDS_SC_HELP = 0b000000000010000000000,
17+
const DISABLE_VERSION = 0b000000000100000000000,
18+
const HIDDEN = 0b000000001000000000000,
19+
const TRAILING_VARARG = 0b000000010000000000000,
20+
const NO_BIN_NAME = 0b000000100000000000000,
21+
const ALLOW_UNK_SC = 0b000001000000000000000,
22+
const UTF8_STRICT = 0b000010000000000000000,
23+
const UTF8_NONE = 0b000100000000000000000,
24+
const LEADING_HYPHEN = 0b001000000000000000000,
25+
const NO_POS_VALUES = 0b010000000000000000000,
26+
const NEXT_LINE_HELP = 0b100000000000000000000,
2627
}
2728
}
2829

@@ -55,7 +56,8 @@ impl AppFlags {
5556
StrictUtf8 => UTF8_STRICT,
5657
AllowInvalidUtf8 => UTF8_NONE,
5758
AllowLeadingHyphen => LEADING_HYPHEN,
58-
HidePossibleValuesInHelp => NO_POS_VALUES
59+
HidePossibleValuesInHelp => NO_POS_VALUES,
60+
NextLineHelp => NEXT_LINE_HELP
5961
}
6062
}
6163

@@ -398,9 +400,22 @@ pub enum AppSettings {
398400
/// # ;
399401
/// ```
400402
AllowLeadingHyphen,
401-
/// Tells `clap` *not* to print possible values when displaying help information. This can be
403+
/// Tells `clap` *not* to print possible values when displaying help information. This can be
402404
/// useful if there are many values, or they are explained elsewhere.
403405
HidePossibleValuesInHelp,
406+
/// Places the help string for all arguments on the line after the argument
407+
///
408+
/// **NOTE:** This setting is cosmetic only and does not affect any functionality.
409+
///
410+
/// # Examples
411+
///
412+
/// ```no_run
413+
/// # use clap::{App, Arg, SubCommand, AppSettings};
414+
/// App::new("myprog")
415+
/// .setting(AppSettings::NextLineHelp)
416+
/// .get_matches();
417+
/// ```
418+
NextLineHelp,
404419
#[doc(hidden)]
405420
NeedsLongVersion,
406421
#[doc(hidden)]
@@ -431,6 +446,7 @@ impl FromStr for AppSettings {
431446
"allowinvalidutf8" => Ok(AppSettings::AllowInvalidUtf8),
432447
"allowleadinghyphen" => Ok(AppSettings::AllowLeadingHyphen),
433448
"hidepossiblevaluesinhelp" => Ok(AppSettings::HidePossibleValuesInHelp),
449+
"nextlinehelp" => Ok(AppSettings::NextLineHelp),
434450
_ => Err("unknown AppSetting, cannot convert from str".to_owned()),
435451
}
436452
}

src/args/arg.rs

+52
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,58 @@ impl<'a, 'b> Arg<'a, 'b> {
11241124
self
11251125
}
11261126

1127+
/// When set to `true` the help string will be displayed on the line after the argument and
1128+
/// indented once. This can be helpful for arguments with very long or complex help messages.
1129+
/// This can also be helpful for arguments with very long flag names, or many/long value names.
1130+
///
1131+
/// **NOTE:** To apply this setting to all arguments consider using `AppSettings::NextLineHelp`
1132+
///
1133+
/// # Examples
1134+
///
1135+
/// ```rust
1136+
/// # use clap::{App, Arg};
1137+
/// let m = App::new("nlh")
1138+
/// .arg(Arg::with_name("opt")
1139+
/// .long("long-option-flag")
1140+
/// .short("o")
1141+
/// .takes_value(true)
1142+
/// .value_names(&["value1", "value2"])
1143+
/// .help("Some really long help and complex{n}\
1144+
/// help that makes more sense to be{n}\
1145+
/// on a line after the option")
1146+
/// .next_line_help(true))
1147+
/// .get_matches_from(vec![
1148+
/// "nlh", "--help"
1149+
/// ]);
1150+
/// ```
1151+
///
1152+
/// The above example displays the following help message
1153+
///
1154+
/// ```ignore
1155+
/// nlh
1156+
///
1157+
/// USAGE:
1158+
/// nlh [FLAGS] [OPTIONS]
1159+
///
1160+
/// FLAGS:
1161+
/// -h, --help Prints help information
1162+
/// -V, --version Prints version information
1163+
///
1164+
/// OPTIONS:
1165+
/// -o, --long-option-flag <value1> <value2>
1166+
/// Some really long help and complex
1167+
/// help that makes more sense to be
1168+
/// on a line after the option
1169+
/// ```
1170+
pub fn next_line_help(mut self, nlh: bool) -> Self {
1171+
if nlh {
1172+
self.setb(ArgSettings::NextLineHelp);
1173+
} else {
1174+
self.unsetb(ArgSettings::NextLineHelp);
1175+
}
1176+
self
1177+
}
1178+
11271179
/// Checks if one of the `ArgSettings` settings is set for the argument
11281180
pub fn is_set(&self, s: ArgSettings) -> bool {
11291181
self.settings.is_set(s)

src/args/arg_builder/flag.rs

+2-33
Original file line numberDiff line numberDiff line change
@@ -45,39 +45,8 @@ impl<'n, 'e> FlagBuilder<'n, 'e> {
4545
}
4646
}
4747

48-
pub fn write_help<W: io::Write>(&self, w: &mut W, tab: &str, longest: usize) -> io::Result<()> {
49-
try!(write!(w, "{}", tab));
50-
if let Some(s) = self.short {
51-
try!(write!(w, "-{}", s));
52-
} else {
53-
try!(write!(w, "{}", tab));
54-
}
55-
if let Some(l) = self.long {
56-
try!(write!(w,
57-
"{}--{}",
58-
if self.short.is_some() {
59-
", "
60-
} else {
61-
""
62-
},
63-
l));
64-
write_spaces!((longest + 4) - (l.len() + 2), w);
65-
} else {
66-
// 6 is tab (4) + -- (2)
67-
write_spaces!((longest + 6), w);
68-
}
69-
if let Some(h) = self.help {
70-
if h.contains("{n}") {
71-
let mut hel = h.split("{n}");
72-
while let Some(part) = hel.next() {
73-
try!(write!(w, "{}\n", part));
74-
write_spaces!((longest + 12), w);
75-
try!(write!(w, "{}", hel.next().unwrap_or("")));
76-
}
77-
} else {
78-
try!(write!(w, "{}", h));
79-
}
80-
}
48+
pub fn write_help<W: io::Write>(&self, w: &mut W, tab: &str, longest: usize, nlh: bool) -> io::Result<()> {
49+
write_arg_help!(@flag self, w, tab, longest, nlh);
8150
write!(w, "\n")
8251
}
8352
}

src/args/arg_builder/macros.rs

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
macro_rules! write_arg_help {
2+
(@opt $_self:ident, $w:ident, $tab:ident, $longest:ident, $skip_pv:ident, $nlh:ident) => {
3+
write_arg_help!(@short $_self, $w, $tab);
4+
write_arg_help!(@opt_long $_self, $w, $nlh, $longest);
5+
write_arg_help!(@val $_self, $w);
6+
if !($nlh || $_self.settings.is_set(ArgSettings::NextLineHelp)) {
7+
write_spaces!(if $_self.long.is_some() { $longest + 4 } else { $longest + 8 } - ($_self.to_string().len()), $w);
8+
}
9+
if let Some(h) = $_self.help {
10+
write_arg_help!(@help $_self, $w, h, $tab, $longest, $nlh);
11+
write_arg_help!(@spec_vals $_self, $w, $skip_pv);
12+
}
13+
};
14+
(@flag $_self:ident, $w:ident, $tab:ident, $longest:ident, $nlh:ident) => {
15+
write_arg_help!(@short $_self, $w, $tab);
16+
write_arg_help!(@flag_long $_self, $w, $longest, $nlh);
17+
if let Some(h) = $_self.help {
18+
write_arg_help!(@help $_self, $w, h, $tab, $longest, $nlh);
19+
}
20+
};
21+
(@pos $_self:ident, $w:ident, $tab:ident, $longest:ident, $skip_pv:ident, $nlh:ident) => {
22+
try!(write!($w, "{}", $tab));
23+
write_arg_help!(@val $_self, $w);
24+
if !($nlh || $_self.settings.is_set(ArgSettings::NextLineHelp)) {
25+
write_spaces!($longest + 4 - ($_self.to_string().len()), $w);
26+
}
27+
if let Some(h) = $_self.help {
28+
write_arg_help!(@help $_self, $w, h, $tab, $longest, $nlh);
29+
write_arg_help!(@spec_vals $_self, $w, $skip_pv);
30+
}
31+
};
32+
(@short $_self:ident, $w:ident, $tab:ident) => {
33+
try!(write!($w, "{}", $tab));
34+
if let Some(s) = $_self.short {
35+
try!(write!($w, "-{}", s));
36+
} else {
37+
try!(write!($w, "{}", $tab));
38+
}
39+
};
40+
(@flag_long $_self:ident, $w:ident, $longest:ident, $nlh:ident) => {
41+
if let Some(l) = $_self.long {
42+
write_arg_help!(@long $_self, $w, l);
43+
if !$nlh || !$_self.settings.is_set(ArgSettings::NextLineHelp) {
44+
write_spaces!(($longest + 4) - (l.len() + 2), $w);
45+
}
46+
} else {
47+
if !$nlh || !$_self.settings.is_set(ArgSettings::NextLineHelp) {
48+
// 6 is tab (4) + -- (2)
49+
write_spaces!(($longest + 6), $w);
50+
}
51+
}
52+
};
53+
(@opt_long $_self:ident, $w:ident, $nlh:ident, $longest:ident) => {
54+
if let Some(l) = $_self.long {
55+
write_arg_help!(@long $_self, $w, l);
56+
}
57+
try!(write!($w, " "));
58+
};
59+
(@long $_self:ident, $w:ident, $l:ident) => {
60+
try!(write!($w,
61+
"{}--{}",
62+
if $_self.short.is_some() {
63+
", "
64+
} else {
65+
""
66+
},
67+
$l));
68+
};
69+
(@val $_self:ident, $w:ident) => {
70+
if let Some(ref vec) = $_self.val_names {
71+
let mut it = vec.iter().peekable();
72+
while let Some((_, val)) = it.next() {
73+
try!(write!($w, "<{}>", val));
74+
if it.peek().is_some() { try!(write!($w, " ")); }
75+
}
76+
let num = vec.len();
77+
if $_self.settings.is_set(ArgSettings::Multiple) && num == 1 {
78+
try!(write!($w, "..."));
79+
}
80+
} else if let Some(num) = $_self.num_vals {
81+
for _ in 0..num {
82+
try!(write!($w, "<{}>", $_self.name));
83+
}
84+
} else {
85+
try!(write!($w,
86+
"<{}>{}",
87+
$_self.name,
88+
if $_self.settings.is_set(ArgSettings::Multiple) {
89+
"..."
90+
} else {
91+
""
92+
}));
93+
}
94+
};
95+
(@spec_vals $_self:ident, $w:ident, $skip_pv:ident) => {
96+
if let Some(ref pv) = $_self.default_val {
97+
try!(write!($w, " [default: {}]", pv));
98+
}
99+
if !$skip_pv {
100+
if let Some(ref pv) = $_self.possible_vals {
101+
try!(write!($w, " [values: {}]", pv.join(", ")));
102+
}
103+
}
104+
};
105+
(@help $_self:ident, $w:ident, $h:ident, $tab:ident, $longest:expr, $nlh:ident) => {
106+
if $nlh || $_self.settings.is_set(ArgSettings::NextLineHelp) {
107+
try!(write!($w, "\n{}{}", $tab, $tab));
108+
}
109+
if $h.contains("{n}") {
110+
if let Some(part) = $h.split("{n}").next() {
111+
try!(write!($w, "{}", part));
112+
}
113+
for part in $h.split("{n}").skip(1) {
114+
try!(write!($w, "\n"));
115+
if $nlh || $_self.settings.is_set(ArgSettings::NextLineHelp) {
116+
try!(write!($w, "{}{}", $tab, $tab));
117+
} else {
118+
write_spaces!($longest + 12, $w);
119+
}
120+
try!(write!($w, "{}", part));
121+
}
122+
} else {
123+
try!(write!($w, "{}", $h));
124+
}
125+
};
126+
}

0 commit comments

Comments
 (0)