mirror of
https://github.com/clap-rs/clap
synced 2024-12-14 06:42:33 +00:00
feat(HELP): implements optional colored help messages
To enable, ensure `clap` is compiled with `color` cargo feature. Then in code use `AppSettings::ColoredHelp` Closes #483
This commit is contained in:
parent
c4ffd2a9ce
commit
770397bcb2
2 changed files with 110 additions and 66 deletions
111
src/app/help.rs
111
src/app/help.rs
|
@ -12,6 +12,7 @@ use errors::{Error, Result as ClapResult};
|
|||
use args::{AnyArg, ArgSettings, DispOrder};
|
||||
use app::{App, AppSettings};
|
||||
use app::parser::Parser;
|
||||
use fmt::Format;
|
||||
|
||||
use term;
|
||||
|
||||
|
@ -30,7 +31,6 @@ fn str_width(s: &str) -> usize {
|
|||
UnicodeWidthStr::width(s)
|
||||
}
|
||||
|
||||
|
||||
const TAB: &'static str = " ";
|
||||
|
||||
// These are just convenient traits to make the code easier to read.
|
||||
|
@ -58,6 +58,22 @@ impl<'b, 'c> DispOrder for App<'b, 'c> {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! color {
|
||||
($_self:ident, $nc:expr, $c:ident) => {
|
||||
if $_self.color {
|
||||
write!($_self.writer, "{}", Format::$c($nc))
|
||||
} else {
|
||||
write!($_self.writer, "{}", $nc)
|
||||
}
|
||||
};
|
||||
($_self:ident, $nc:expr, $i:expr, $c:ident) => {
|
||||
if $_self.color {
|
||||
write!($_self.writer, "{}", Format::$c(format!($nc, $i)))
|
||||
} else {
|
||||
write!($_self.writer, $nc, $i)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// CLAP Help Writer.
|
||||
///
|
||||
|
@ -67,17 +83,19 @@ pub struct Help<'a> {
|
|||
next_line_help: bool,
|
||||
hide_pv: bool,
|
||||
term_w: Option<usize>,
|
||||
color: bool,
|
||||
}
|
||||
|
||||
// Public Functions
|
||||
impl<'a> Help<'a> {
|
||||
/// Create a new Help instance.
|
||||
pub fn new(w: &'a mut Write, next_line_help: bool, hide_pv: bool) -> Self {
|
||||
pub fn new(w: &'a mut Write, next_line_help: bool, hide_pv: bool, color: bool) -> Self {
|
||||
Help {
|
||||
writer: w,
|
||||
next_line_help: next_line_help,
|
||||
hide_pv: hide_pv,
|
||||
term_w: term::dimensions().map(|(w, _)| w),
|
||||
color: color,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +110,8 @@ impl<'a> Help<'a> {
|
|||
pub fn write_parser_help(w: &'a mut Write, parser: &Parser) -> ClapResult<()> {
|
||||
let nlh = parser.is_set(AppSettings::NextLineHelp);
|
||||
let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp);
|
||||
Self::new(w, nlh, hide_v).write_help(&parser)
|
||||
let color = parser.is_set(AppSettings::ColoredHelp);
|
||||
Self::new(w, nlh, hide_v, color).write_help(&parser)
|
||||
}
|
||||
|
||||
/// Writes the parser help to the wrapped stream.
|
||||
|
@ -176,7 +195,7 @@ impl<'a> Help<'a> {
|
|||
debugln!("fn=short;");
|
||||
try!(write!(self.writer, "{}", TAB));
|
||||
if let Some(s) = arg.short() {
|
||||
write!(self.writer, "-{}", s)
|
||||
color!(self, "-{}", s, Good)
|
||||
} else if arg.has_switch() {
|
||||
write!(self.writer, "{}", TAB)
|
||||
} else {
|
||||
|
@ -192,26 +211,18 @@ impl<'a> Help<'a> {
|
|||
}
|
||||
if arg.takes_value() {
|
||||
if let Some(l) = arg.long() {
|
||||
try!(write!(self.writer,
|
||||
"{}--{}",
|
||||
if arg.short().is_some() {
|
||||
", "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
l));
|
||||
if arg.short().is_some() {
|
||||
try!(write!(self.writer, ", "));
|
||||
}
|
||||
try!(color!(self, "--{}", l, Good))
|
||||
}
|
||||
try!(write!(self.writer, " "));
|
||||
} else {
|
||||
if let Some(l) = arg.long() {
|
||||
try!(write!(self.writer,
|
||||
"{}--{}",
|
||||
if arg.short().is_some() {
|
||||
", "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
l));
|
||||
if arg.short().is_some() {
|
||||
try!(write!(self.writer, ", "));
|
||||
}
|
||||
try!(color!(self, "--{}", l, Good));
|
||||
if !self.next_line_help || !arg.is_set(ArgSettings::NextLineHelp) {
|
||||
write_nspaces!(self.writer, (longest + 4) - (l.len() + 2));
|
||||
}
|
||||
|
@ -234,27 +245,27 @@ impl<'a> Help<'a> {
|
|||
if let Some(ref vec) = arg.val_names() {
|
||||
let mut it = vec.iter().peekable();
|
||||
while let Some((_, val)) = it.next() {
|
||||
try!(write!(self.writer, "<{}>", val));
|
||||
try!(color!(self, "<{}>", val, Good));
|
||||
if it.peek().is_some() {
|
||||
try!(write!(self.writer, " "));
|
||||
}
|
||||
}
|
||||
let num = vec.len();
|
||||
if arg.is_set(ArgSettings::Multiple) && num == 1 {
|
||||
try!(write!(self.writer, "..."));
|
||||
try!(color!(self, "...", Good));
|
||||
}
|
||||
} else if let Some(num) = arg.num_vals() {
|
||||
let mut it = (0..num).peekable();
|
||||
while let Some(_) = it.next() {
|
||||
try!(write!(self.writer, "<{}>", arg.name()));
|
||||
try!(color!(self, "<{}>", arg.name(), Good));
|
||||
if it.peek().is_some() {
|
||||
try!(write!(self.writer, " "));
|
||||
}
|
||||
}
|
||||
} else if arg.has_switch() {
|
||||
try!(write!(self.writer, "<{}>", arg.name()));
|
||||
try!(color!(self, "<{}>", arg.name(), Good));
|
||||
} else {
|
||||
try!(write!(self.writer, "{}", arg));
|
||||
try!(color!(self, "{}", arg, Good));
|
||||
}
|
||||
if arg.has_switch() {
|
||||
if !(self.next_line_help || arg.is_set(ArgSettings::NextLineHelp)) {
|
||||
|
@ -394,21 +405,33 @@ impl<'a> Help<'a> {
|
|||
if let Some(ref pv) = a.default_val() {
|
||||
debugln!("Writing defaults");
|
||||
return format!(" [default: {}] {}",
|
||||
pv,
|
||||
if self.hide_pv {
|
||||
if self.color {
|
||||
format!("{}", Format::Good(pv))
|
||||
} else {
|
||||
format!("{}", pv)
|
||||
},
|
||||
if self.hide_pv {
|
||||
"".into()
|
||||
} else {
|
||||
if let Some(ref pv) = a.possible_vals() {
|
||||
format!(" [values: {}]", pv.join(", "))
|
||||
} else {
|
||||
} else {
|
||||
if let Some(ref pv) = a.possible_vals() {
|
||||
if self.color {
|
||||
format!(" [values: {}]", pv.iter().map(|v| format!("{}", Format::Good(v))).collect::<Vec<_>>().join(", "))
|
||||
} else {
|
||||
format!(" [values: {}]", pv.join(", "))
|
||||
}
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if !self.hide_pv {
|
||||
debugln!("Writing values");
|
||||
if let Some(ref pv) = a.possible_vals() {
|
||||
debugln!("Possible vals...{:?}", pv);
|
||||
return format!(" [values: {}]", pv.join(", "));
|
||||
return if self.color {
|
||||
format!(" [values: {}]", pv.iter().map(|v| format!("{}", Format::Good(v))).collect::<Vec<_>>().join(", "))
|
||||
} else {
|
||||
format!(" [values: {}]", pv.join(", "))
|
||||
};
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
|
@ -435,12 +458,12 @@ impl<'a> Help<'a> {
|
|||
let opts_flags = parser.iter_flags()
|
||||
.map(as_arg_trait)
|
||||
.chain(parser.iter_opts().map(as_arg_trait));
|
||||
try!(write!(self.writer, "OPTIONS:\n"));
|
||||
try!(color!(self, "OPTIONS:\n", Warning));
|
||||
try!(self.write_args(opts_flags));
|
||||
first = false;
|
||||
} else {
|
||||
if flags {
|
||||
try!(write!(self.writer, "FLAGS:\n"));
|
||||
try!(color!(self, "FLAGS:\n", Warning));
|
||||
try!(self.write_args(parser.iter_flags()
|
||||
.map(as_arg_trait)));
|
||||
first = false;
|
||||
|
@ -449,7 +472,7 @@ impl<'a> Help<'a> {
|
|||
if !first {
|
||||
try!(self.writer.write(b"\n"));
|
||||
}
|
||||
try!(write!(self.writer, "OPTIONS:\n"));
|
||||
try!(color!(self, "OPTIONS:\n", Warning));
|
||||
try!(self.write_args(parser.iter_opts().map(as_arg_trait)));
|
||||
first = false;
|
||||
}
|
||||
|
@ -459,7 +482,7 @@ impl<'a> Help<'a> {
|
|||
if !first {
|
||||
try!(self.writer.write(b"\n"));
|
||||
}
|
||||
try!(write!(self.writer, "ARGS:\n"));
|
||||
try!(color!(self, "ARGS:\n", Warning));
|
||||
try!(self.write_args_unsorted(parser.iter_positionals().map(as_arg_trait)));
|
||||
first = false;
|
||||
}
|
||||
|
@ -468,7 +491,7 @@ impl<'a> Help<'a> {
|
|||
if !first {
|
||||
try!(self.writer.write(b"\n"));
|
||||
}
|
||||
try!(write!(self.writer, "SUBCOMMANDS:\n"));
|
||||
try!(color!(self, "SUBCOMMANDS:\n", Warning));
|
||||
try!(self.write_subcommands(&parser));
|
||||
}
|
||||
|
||||
|
@ -505,12 +528,12 @@ impl<'a> Help<'a> {
|
|||
if let Some(bn) = parser.meta.bin_name.as_ref() {
|
||||
if bn.contains(' ') {
|
||||
// Incase we're dealing with subcommands i.e. git mv is translated to git-mv
|
||||
try!(write!(self.writer, "{}", bn.replace(" ", "-")))
|
||||
try!(color!(self, bn.replace(" ", "-"), Good))
|
||||
} else {
|
||||
try!(write!(self.writer, "{}", &parser.meta.name[..]))
|
||||
try!(color!(self, &parser.meta.name[..], Good))
|
||||
}
|
||||
} else {
|
||||
try!(write!(self.writer, "{}", &parser.meta.name[..]))
|
||||
try!(color!(self, &parser.meta.name[..], Good))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -530,8 +553,8 @@ impl<'a> Help<'a> {
|
|||
try!(write!(self.writer, "{}\n", about));
|
||||
}
|
||||
|
||||
try!(write!(self.writer,
|
||||
"\nUSAGE:\n{}{}\n\n",
|
||||
try!(color!(self, "\nUSAGE:", Warning));
|
||||
try!(write!(self.writer, "\n{}{}\n\n",
|
||||
TAB,
|
||||
parser.create_usage_no_title(&[])));
|
||||
|
||||
|
|
|
@ -3,28 +3,29 @@ use std::ascii::AsciiExt;
|
|||
|
||||
bitflags! {
|
||||
flags Flags: u32 {
|
||||
const SC_NEGATE_REQS = 0b0000000000000000000001,
|
||||
const SC_REQUIRED = 0b0000000000000000000010,
|
||||
const A_REQUIRED_ELSE_HELP = 0b0000000000000000000100,
|
||||
const GLOBAL_VERSION = 0b0000000000000000001000,
|
||||
const VERSIONLESS_SC = 0b0000000000000000010000,
|
||||
const UNIFIED_HELP = 0b0000000000000000100000,
|
||||
const WAIT_ON_ERROR = 0b0000000000000001000000,
|
||||
const SC_REQUIRED_ELSE_HELP= 0b0000000000000010000000,
|
||||
const NEEDS_LONG_HELP = 0b0000000000000100000000,
|
||||
const NEEDS_LONG_VERSION = 0b0000000000001000000000,
|
||||
const NEEDS_SC_HELP = 0b0000000000010000000000,
|
||||
const DISABLE_VERSION = 0b0000000000100000000000,
|
||||
const HIDDEN = 0b0000000001000000000000,
|
||||
const TRAILING_VARARG = 0b0000000010000000000000,
|
||||
const NO_BIN_NAME = 0b0000000100000000000000,
|
||||
const ALLOW_UNK_SC = 0b0000001000000000000000,
|
||||
const UTF8_STRICT = 0b0000010000000000000000,
|
||||
const UTF8_NONE = 0b0000100000000000000000,
|
||||
const LEADING_HYPHEN = 0b0001000000000000000000,
|
||||
const NO_POS_VALUES = 0b0010000000000000000000,
|
||||
const NEXT_LINE_HELP = 0b0100000000000000000000,
|
||||
const DERIVE_DISP_ORDER = 0b1000000000000000000000,
|
||||
const SC_NEGATE_REQS = 0b00000000000000000000001,
|
||||
const SC_REQUIRED = 0b00000000000000000000010,
|
||||
const A_REQUIRED_ELSE_HELP = 0b00000000000000000000100,
|
||||
const GLOBAL_VERSION = 0b00000000000000000001000,
|
||||
const VERSIONLESS_SC = 0b00000000000000000010000,
|
||||
const UNIFIED_HELP = 0b00000000000000000100000,
|
||||
const WAIT_ON_ERROR = 0b00000000000000001000000,
|
||||
const SC_REQUIRED_ELSE_HELP= 0b00000000000000010000000,
|
||||
const NEEDS_LONG_HELP = 0b00000000000000100000000,
|
||||
const NEEDS_LONG_VERSION = 0b00000000000001000000000,
|
||||
const NEEDS_SC_HELP = 0b00000000000010000000000,
|
||||
const DISABLE_VERSION = 0b00000000000100000000000,
|
||||
const HIDDEN = 0b00000000001000000000000,
|
||||
const TRAILING_VARARG = 0b00000000010000000000000,
|
||||
const NO_BIN_NAME = 0b00000000100000000000000,
|
||||
const ALLOW_UNK_SC = 0b00000001000000000000000,
|
||||
const UTF8_STRICT = 0b00000010000000000000000,
|
||||
const UTF8_NONE = 0b00000100000000000000000,
|
||||
const LEADING_HYPHEN = 0b00001000000000000000000,
|
||||
const NO_POS_VALUES = 0b00010000000000000000000,
|
||||
const NEXT_LINE_HELP = 0b00100000000000000000000,
|
||||
const DERIVE_DISP_ORDER = 0b01000000000000000000000,
|
||||
const COLORED_HELP = 0b10000000000000000000000,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,6 +72,7 @@ impl AppFlags {
|
|||
AllowLeadingHyphen => LEADING_HYPHEN,
|
||||
HidePossibleValuesInHelp => NO_POS_VALUES,
|
||||
NextLineHelp => NEXT_LINE_HELP,
|
||||
ColoredHelp => COLORED_HELP,
|
||||
DeriveDisplayOrder => DERIVE_DISP_ORDER
|
||||
}
|
||||
}
|
||||
|
@ -446,6 +448,23 @@ pub enum AppSettings {
|
|||
/// .get_matches();
|
||||
/// ```
|
||||
DeriveDisplayOrder,
|
||||
/// Uses colorized help messages.
|
||||
///
|
||||
/// **NOTE:** Must be compiled with the `color` cargo feature
|
||||
///
|
||||
/// # Platform Specific
|
||||
///
|
||||
/// This setting only applies to Unix, Linux, and OSX (i.e. non-Windows platforms)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use clap::{App, Arg, SubCommand, AppSettings};
|
||||
/// App::new("myprog")
|
||||
/// .setting(AppSettings::ColoredHelp)
|
||||
/// .get_matches();
|
||||
/// ```
|
||||
ColoredHelp,
|
||||
#[doc(hidden)]
|
||||
NeedsLongVersion,
|
||||
#[doc(hidden)]
|
||||
|
@ -478,6 +497,7 @@ impl FromStr for AppSettings {
|
|||
"hidepossiblevaluesinhelp" => Ok(AppSettings::HidePossibleValuesInHelp),
|
||||
"nextlinehelp" => Ok(AppSettings::NextLineHelp),
|
||||
"derivedisplayorder" => Ok(AppSettings::DeriveDisplayOrder),
|
||||
"coloredhelp" => Ok(AppSettings::ColoredHelp),
|
||||
_ => Err("unknown AppSetting, cannot convert from str".to_owned()),
|
||||
}
|
||||
}
|
||||
|
@ -504,6 +524,7 @@ mod test {
|
|||
assert_eq!("allowinvalidutf8".parse::<AppSettings>().unwrap(), AppSettings::AllowInvalidUtf8);
|
||||
assert_eq!("allowleadinghyphen".parse::<AppSettings>().unwrap(), AppSettings::AllowLeadingHyphen);
|
||||
assert_eq!("hidepossiblevaluesinhelp".parse::<AppSettings>().unwrap(), AppSettings::HidePossibleValuesInHelp);
|
||||
assert_eq!("coloredhelp".parse::<AppSettings>().unwrap(), AppSettings::ColoredHelp);
|
||||
assert_eq!("hidden".parse::<AppSettings>().unwrap(), AppSettings::Hidden);
|
||||
assert!("hahahaha".parse::<AppSettings>().is_err());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue