diff --git a/src/app/parser.rs b/src/app/parser.rs index a4a5e9e672e..4a9deb2d469 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1400,7 +1400,7 @@ 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; @@ -1408,7 +1408,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { } 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; @@ -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; @@ -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 { @@ -1468,7 +1469,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { .map(|f| (f.name, f)) .collect::>() .values() { - try!(f.write_help(w, tab, longest)); + try!(f.write_help(w, tab, longest, nlh)); } } if opts { @@ -1478,7 +1479,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { .map(|o| (o.name, o)) .collect::>() .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)); } } } @@ -1486,7 +1487,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { 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 { diff --git a/src/app/settings.rs b/src/app/settings.rs index ec30b80ed67..352272ac027 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -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, } } @@ -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 } } @@ -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)] @@ -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()), } } diff --git a/src/args/arg.rs b/src/args/arg.rs index e8d24b8b738..c8385464615 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -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 + /// 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) diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index 7fce3e7923e..e22d34b18fe 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -45,39 +45,8 @@ impl<'n, 'e> FlagBuilder<'n, 'e> { } } - pub fn write_help(&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(&self, w: &mut W, tab: &str, longest: usize, nlh: bool) -> io::Result<()> { + write_arg_help!(@flag self, w, tab, longest, nlh); write!(w, "\n") } } diff --git a/src/args/arg_builder/macros.rs b/src/args/arg_builder/macros.rs new file mode 100644 index 00000000000..d641930814e --- /dev/null +++ b/src/args/arg_builder/macros.rs @@ -0,0 +1,126 @@ +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); + } + try!(write!($w, " ")); + }; + (@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 { + let mut it = vec.iter().peekable(); + while let Some((_, val)) = it.next() { + try!(write!($w, "<{}>", val)); + if it.peek().is_some() { try!(write!($w, " ")); } + } + 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)); + } + }; +} diff --git a/src/args/arg_builder/mod.rs b/src/args/arg_builder/mod.rs index 2f96a5c7818..eafca0289b6 100644 --- a/src/args/arg_builder/mod.rs +++ b/src/args/arg_builder/mod.rs @@ -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)] diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 5ad1aedaf25..a2bf0c7558f 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -101,76 +101,9 @@ impl<'n, 'e> OptBuilder<'n, 'e> { ob } - pub fn write_help(&self, w: &mut W, tab: &str, longest: usize, skip_pv: bool) -> io::Result<()> { + pub fn write_help(&self, w: &mut W, tab: &str, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> { debugln!("fn=write_help"); - // if it supports multiple we add '...' i.e. 3 to the name length - 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)); - } - 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 { - "" - })); - } - if self.long.is_some() { - write_spaces!((longest + 4) - (self.to_string().len()), w); - } else { - // 8 = tab + '-a, '.len() - write_spaces!((longest + 8) - (self.to_string().len()), w); - } - if let Some(h) = self.help { - if h.contains("{n}") { - let mut hel = h.split("{n}"); - if let Some(part) = hel.next() { - try!(write!(w, "{}", part)); - } - for part in hel { - try!(write!(w, "\n")); - write_spaces!(longest + 12, w); - try!(write!(w, "{}", part)); - } - } else { - try!(write!(w, "{}", h)); - } - 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(", "))); - } - } - } + write_arg_help!(@opt self, w, tab, longest, skip_pv, nlh); write!(w, "\n") } } diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 1e901bb142e..b3ff352b35a 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -102,33 +102,8 @@ impl<'n, 'e> PosBuilder<'n, 'e> { pb } - pub fn write_help(&self, w: &mut W, tab: &str, longest: usize, skip_pv: bool) -> io::Result<()> { - try!(write!(w, "{}", tab)); - try!(write!(w, "{}", self.name)); - if self.settings.is_set(ArgSettings::Multiple) { - try!(write!(w, "...")); - } - write_spaces!((longest + 4) - (self.to_string().len()), 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 + 6, w); - try!(write!(w, "{}", hel.next().unwrap_or(""))); - } - } else { - try!(write!(w, "{}", h)); - } - 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(", "))); - } - } - } + pub fn write_help(&self, w: &mut W, tab: &str, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> { + write_arg_help!(@pos self, w, tab, longest, skip_pv, nlh); write!(w, "\n") } } diff --git a/src/args/settings.rs b/src/args/settings.rs index 76c58850897..b5f95eb058f 100644 --- a/src/args/settings.rs +++ b/src/args/settings.rs @@ -3,13 +3,14 @@ use std::ascii::AsciiExt; bitflags! { flags Flags: u8 { - const REQUIRED = 0b0000001, - const MULTIPLE = 0b0000010, - const EMPTY_VALS = 0b0000100, - const GLOBAL = 0b0001000, - const HIDDEN = 0b0010000, - const TAKES_VAL = 0b0100000, - const USE_DELIM = 0b1000000, + const REQUIRED = 0b00000001, + const MULTIPLE = 0b00000010, + const EMPTY_VALS = 0b00000100, + const GLOBAL = 0b00001000, + const HIDDEN = 0b00010000, + const TAKES_VAL = 0b00100000, + const USE_DELIM = 0b01000000, + const NEXT_LINE_HELP = 0b10000000, } } @@ -29,7 +30,8 @@ impl ArgFlags { Global => GLOBAL, Hidden => HIDDEN, TakesValue => TAKES_VAL, - UseValueDelimiter => USE_DELIM + UseValueDelimiter => USE_DELIM, + NextLineHelp => NEXT_LINE_HELP } } @@ -57,6 +59,8 @@ pub enum ArgSettings { TakesValue, /// Determines if the argument allows values to be grouped via a delimter UseValueDelimiter, + /// Prints the help text on the line after the argument + NextLineHelp, } impl FromStr for ArgSettings { @@ -70,6 +74,7 @@ impl FromStr for ArgSettings { "hidden" => Ok(ArgSettings::Hidden), "takesvalue" => Ok(ArgSettings::TakesValue), "usevaluedelimiter" => Ok(ArgSettings::UseValueDelimiter), + "nextlinehelp" => Ok(ArgSettings::NextLineHelp), _ => Err("unknown ArgSetting, cannot convert from str".to_owned()), } }