| // Std |
| use std::borrow::Cow; |
| use std::cmp; |
| use std::collections::BTreeMap; |
| use std::fmt::Display; |
| use std::io::{self, Cursor, Read, Write}; |
| use std::usize; |
| |
| // Internal |
| use app::parser::Parser; |
| use app::usage; |
| use app::{App, AppSettings}; |
| use args::{AnyArg, ArgSettings, DispOrder}; |
| use errors::{Error, Result as ClapResult}; |
| use fmt::{Colorizer, ColorizerOption, Format}; |
| use map::VecMap; |
| use INTERNAL_ERROR_MSG; |
| |
| // Third Party |
| #[cfg(feature = "wrap_help")] |
| use term_size; |
| use textwrap; |
| |
| #[cfg(not(feature = "wrap_help"))] |
| mod term_size { |
| pub fn dimensions() -> Option<(usize, usize)> { |
| None |
| } |
| } |
| |
| fn str_width(s: &str) -> usize { |
| #[cfg(not(feature = "unicode_help"))] |
| return s.len(); |
| |
| #[cfg(feature = "unicode_help")] |
| UnicodeWidthStr::width(s) |
| } |
| |
| const TAB: &'static str = " "; |
| |
| // These are just convenient traits to make the code easier to read. |
| trait ArgWithDisplay<'b, 'c>: AnyArg<'b, 'c> + Display {} |
| impl<'b, 'c, T> ArgWithDisplay<'b, 'c> for T where T: AnyArg<'b, 'c> + Display {} |
| |
| trait ArgWithOrder<'b, 'c>: ArgWithDisplay<'b, 'c> + DispOrder { |
| fn as_base(&self) -> &ArgWithDisplay<'b, 'c>; |
| } |
| impl<'b, 'c, T> ArgWithOrder<'b, 'c> for T |
| where |
| T: ArgWithDisplay<'b, 'c> + DispOrder, |
| { |
| fn as_base(&self) -> &ArgWithDisplay<'b, 'c> { |
| self |
| } |
| } |
| |
| fn as_arg_trait<'a, 'b, T: ArgWithOrder<'a, 'b>>(x: &T) -> &ArgWithOrder<'a, 'b> { |
| x |
| } |
| |
| impl<'b, 'c> DispOrder for App<'b, 'c> { |
| fn disp_ord(&self) -> usize { |
| 999 |
| } |
| } |
| |
| macro_rules! color { |
| ($_self:ident, $s:expr, $c:ident) => { |
| if $_self.color { |
| write!($_self.writer, "{}", $_self.cizer.$c($s)) |
| } else { |
| write!($_self.writer, "{}", $s) |
| } |
| }; |
| ($_self:ident, $fmt_s:expr, $v:expr, $c:ident) => { |
| if $_self.color { |
| write!($_self.writer, "{}", $_self.cizer.$c(format!($fmt_s, $v))) |
| } else { |
| write!($_self.writer, $fmt_s, $v) |
| } |
| }; |
| } |
| |
| /// `clap` Help Writer. |
| /// |
| /// Wraps a writer stream providing different methods to generate help for `clap` objects. |
| pub struct Help<'a> { |
| writer: &'a mut Write, |
| next_line_help: bool, |
| hide_pv: bool, |
| term_w: usize, |
| color: bool, |
| cizer: Colorizer, |
| longest: usize, |
| force_next_line: bool, |
| use_long: bool, |
| } |
| |
| // Public Functions |
| impl<'a> Help<'a> { |
| /// Create a new `Help` instance. |
| #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] |
| pub fn new( |
| w: &'a mut Write, |
| next_line_help: bool, |
| hide_pv: bool, |
| color: bool, |
| cizer: Colorizer, |
| term_w: Option<usize>, |
| max_w: Option<usize>, |
| use_long: bool, |
| ) -> Self { |
| debugln!("Help::new;"); |
| Help { |
| writer: w, |
| next_line_help: next_line_help, |
| hide_pv: hide_pv, |
| term_w: match term_w { |
| Some(width) => { |
| if width == 0 { |
| usize::MAX |
| } else { |
| width |
| } |
| } |
| None => cmp::min( |
| term_size::dimensions().map_or(120, |(w, _)| w), |
| match max_w { |
| None | Some(0) => usize::MAX, |
| Some(mw) => mw, |
| }, |
| ), |
| }, |
| color: color, |
| cizer: cizer, |
| longest: 0, |
| force_next_line: false, |
| use_long: use_long, |
| } |
| } |
| |
| /// Reads help settings from an App |
| /// and write its help to the wrapped stream. |
| pub fn write_app_help(w: &'a mut Write, app: &App, use_long: bool) -> ClapResult<()> { |
| debugln!("Help::write_app_help;"); |
| Self::write_parser_help(w, &app.p, use_long) |
| } |
| |
| /// Reads help settings from a Parser |
| /// and write its help to the wrapped stream. |
| pub fn write_parser_help(w: &'a mut Write, parser: &Parser, use_long: bool) -> ClapResult<()> { |
| debugln!("Help::write_parser_help;"); |
| Self::_write_parser_help(w, parser, false, use_long) |
| } |
| |
| /// Reads help settings from a Parser |
| /// and write its help to the wrapped stream which will be stderr. This method prevents |
| /// formatting when required. |
| pub fn write_parser_help_to_stderr(w: &'a mut Write, parser: &Parser) -> ClapResult<()> { |
| debugln!("Help::write_parser_help;"); |
| Self::_write_parser_help(w, parser, true, false) |
| } |
| |
| #[doc(hidden)] |
| pub fn _write_parser_help( |
| w: &'a mut Write, |
| parser: &Parser, |
| stderr: bool, |
| use_long: bool, |
| ) -> ClapResult<()> { |
| debugln!("Help::write_parser_help;"); |
| let nlh = parser.is_set(AppSettings::NextLineHelp); |
| let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp); |
| let color = parser.is_set(AppSettings::ColoredHelp); |
| let cizer = Colorizer::new(ColorizerOption { |
| use_stderr: stderr, |
| when: parser.color(), |
| }); |
| Self::new( |
| w, |
| nlh, |
| hide_v, |
| color, |
| cizer, |
| parser.meta.term_w, |
| parser.meta.max_w, |
| use_long, |
| ) |
| .write_help(parser) |
| } |
| |
| /// Writes the parser help to the wrapped stream. |
| pub fn write_help(&mut self, parser: &Parser) -> ClapResult<()> { |
| debugln!("Help::write_help;"); |
| if let Some(h) = parser.meta.help_str { |
| write!(self.writer, "{}", h).map_err(Error::from)?; |
| } else if let Some(tmpl) = parser.meta.template { |
| self.write_templated_help(parser, tmpl)?; |
| } else { |
| self.write_default_help(parser)?; |
| } |
| Ok(()) |
| } |
| } |
| |
| // Methods to write AnyArg help. |
| impl<'a> Help<'a> { |
| /// Writes help for each argument in the order they were declared to the wrapped stream. |
| fn write_args_unsorted<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> |
| where |
| I: Iterator<Item = &'d ArgWithOrder<'b, 'c>>, |
| { |
| debugln!("Help::write_args_unsorted;"); |
| // The shortest an arg can legally be is 2 (i.e. '-x') |
| self.longest = 2; |
| let mut arg_v = Vec::with_capacity(10); |
| let use_long = self.use_long; |
| for arg in args.filter(|arg| should_show_arg(use_long, *arg)) { |
| if arg.longest_filter() { |
| self.longest = cmp::max(self.longest, str_width(arg.to_string().as_str())); |
| } |
| arg_v.push(arg) |
| } |
| let mut first = true; |
| for arg in arg_v { |
| if first { |
| first = false; |
| } else { |
| self.writer.write_all(b"\n")?; |
| } |
| self.write_arg(arg.as_base())?; |
| } |
| Ok(()) |
| } |
| |
| /// Sorts arguments by length and display order and write their help to the wrapped stream. |
| fn write_args<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> |
| where |
| I: Iterator<Item = &'d ArgWithOrder<'b, 'c>>, |
| { |
| debugln!("Help::write_args;"); |
| // The shortest an arg can legally be is 2 (i.e. '-x') |
| self.longest = 2; |
| let mut ord_m = VecMap::new(); |
| let use_long = self.use_long; |
| // Determine the longest |
| for arg in args.filter(|arg| { |
| // If it's NextLineHelp, but we don't care to compute how long because it may be |
| // NextLineHelp on purpose *because* it's so long and would throw off all other |
| // args alignment |
| should_show_arg(use_long, *arg) |
| }) { |
| if arg.longest_filter() { |
| debugln!("Help::write_args: Current Longest...{}", self.longest); |
| self.longest = cmp::max(self.longest, str_width(arg.to_string().as_str())); |
| debugln!("Help::write_args: New Longest...{}", self.longest); |
| } |
| let btm = ord_m.entry(arg.disp_ord()).or_insert(BTreeMap::new()); |
| btm.insert(arg.name(), arg); |
| } |
| let mut first = true; |
| for btm in ord_m.values() { |
| for arg in btm.values() { |
| if first { |
| first = false; |
| } else { |
| self.writer.write_all(b"\n")?; |
| } |
| self.write_arg(arg.as_base())?; |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Writes help for an argument to the wrapped stream. |
| fn write_arg<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { |
| debugln!("Help::write_arg;"); |
| self.short(arg)?; |
| self.long(arg)?; |
| let spec_vals = self.val(arg)?; |
| self.help(arg, &*spec_vals)?; |
| Ok(()) |
| } |
| |
| /// Writes argument's short command to the wrapped stream. |
| fn short<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { |
| debugln!("Help::short;"); |
| write!(self.writer, "{}", TAB)?; |
| if let Some(s) = arg.short() { |
| color!(self, "-{}", s, good) |
| } else if arg.has_switch() { |
| write!(self.writer, "{}", TAB) |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Writes argument's long command to the wrapped stream. |
| fn long<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { |
| debugln!("Help::long;"); |
| if !arg.has_switch() { |
| return Ok(()); |
| } |
| if arg.takes_value() { |
| if let Some(l) = arg.long() { |
| if arg.short().is_some() { |
| write!(self.writer, ", ")?; |
| } |
| color!(self, "--{}", l, good)? |
| } |
| |
| let sep = if arg.is_set(ArgSettings::RequireEquals) { |
| "=" |
| } else { |
| " " |
| }; |
| write!(self.writer, "{}", sep)?; |
| } else if let Some(l) = arg.long() { |
| if arg.short().is_some() { |
| write!(self.writer, ", ")?; |
| } |
| color!(self, "--{}", l, good)?; |
| } |
| Ok(()) |
| } |
| |
| /// Writes argument's possible values to the wrapped stream. |
| fn val<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> Result<String, io::Error> { |
| debugln!("Help::val: arg={}", arg); |
| if arg.takes_value() { |
| let delim = if arg.is_set(ArgSettings::RequireDelimiter) { |
| arg.val_delim().expect(INTERNAL_ERROR_MSG) |
| } else { |
| ' ' |
| }; |
| if let Some(vec) = arg.val_names() { |
| let mut it = vec.iter().peekable(); |
| while let Some((_, val)) = it.next() { |
| color!(self, "<{}>", val, good)?; |
| if it.peek().is_some() { |
| write!(self.writer, "{}", delim)?; |
| } |
| } |
| let num = vec.len(); |
| if arg.is_set(ArgSettings::Multiple) && num == 1 { |
| color!(self, "...", good)?; |
| } |
| } else if let Some(num) = arg.num_vals() { |
| let mut it = (0..num).peekable(); |
| while let Some(_) = it.next() { |
| color!(self, "<{}>", arg.name(), good)?; |
| if it.peek().is_some() { |
| write!(self.writer, "{}", delim)?; |
| } |
| } |
| if arg.is_set(ArgSettings::Multiple) && num == 1 { |
| color!(self, "...", good)?; |
| } |
| } else if arg.has_switch() { |
| color!(self, "<{}>", arg.name(), good)?; |
| if arg.is_set(ArgSettings::Multiple) { |
| color!(self, "...", good)?; |
| } |
| } else { |
| color!(self, "{}", arg, good)?; |
| } |
| } |
| |
| let spec_vals = self.spec_vals(arg); |
| let h = arg.help().unwrap_or(""); |
| let h_w = str_width(h) + str_width(&*spec_vals); |
| let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp); |
| let taken = self.longest + 12; |
| self.force_next_line = !nlh |
| && self.term_w >= taken |
| && (taken as f32 / self.term_w as f32) > 0.40 |
| && h_w > (self.term_w - taken); |
| |
| debug!("Help::val: Has switch..."); |
| if arg.has_switch() { |
| sdebugln!("Yes"); |
| debugln!("Help::val: force_next_line...{:?}", self.force_next_line); |
| debugln!("Help::val: nlh...{:?}", nlh); |
| debugln!("Help::val: taken...{}", taken); |
| debugln!( |
| "Help::val: help_width > (width - taken)...{} > ({} - {})", |
| h_w, |
| self.term_w, |
| taken |
| ); |
| debugln!("Help::val: longest...{}", self.longest); |
| debug!("Help::val: next_line..."); |
| if !(nlh || self.force_next_line) { |
| sdebugln!("No"); |
| let self_len = str_width(arg.to_string().as_str()); |
| // subtract ourself |
| let mut spcs = self.longest - self_len; |
| // Since we're writing spaces from the tab point we first need to know if we |
| // had a long and short, or just short |
| if arg.long().is_some() { |
| // Only account 4 after the val |
| spcs += 4; |
| } else { |
| // Only account for ', --' + 4 after the val |
| spcs += 8; |
| } |
| |
| write_nspaces!(self.writer, spcs); |
| } else { |
| sdebugln!("Yes"); |
| } |
| } else if !(nlh || self.force_next_line) { |
| sdebugln!("No, and not next_line"); |
| write_nspaces!( |
| self.writer, |
| self.longest + 4 - (str_width(arg.to_string().as_str())) |
| ); |
| } else { |
| sdebugln!("No"); |
| } |
| Ok(spec_vals) |
| } |
| |
| fn write_before_after_help(&mut self, h: &str) -> io::Result<()> { |
| debugln!("Help::write_before_after_help;"); |
| let mut help = String::from(h); |
| // determine if our help fits or needs to wrap |
| debugln!( |
| "Help::write_before_after_help: Term width...{}", |
| self.term_w |
| ); |
| let too_long = str_width(h) >= self.term_w; |
| |
| debug!("Help::write_before_after_help: Too long..."); |
| if too_long || h.contains("{n}") { |
| sdebugln!("Yes"); |
| debugln!("Help::write_before_after_help: help: {}", help); |
| debugln!( |
| "Help::write_before_after_help: help width: {}", |
| str_width(&*help) |
| ); |
| // Determine how many newlines we need to insert |
| debugln!( |
| "Help::write_before_after_help: Usable space: {}", |
| self.term_w |
| ); |
| help = wrap_help(&help.replace("{n}", "\n"), self.term_w); |
| } else { |
| sdebugln!("No"); |
| } |
| write!(self.writer, "{}", help)?; |
| Ok(()) |
| } |
| |
| /// Writes argument's help to the wrapped stream. |
| fn help<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, spec_vals: &str) -> io::Result<()> { |
| debugln!("Help::help;"); |
| let h = if self.use_long && arg.name() != "" { |
| arg.long_help().unwrap_or_else(|| arg.help().unwrap_or("")) |
| } else { |
| arg.help().unwrap_or_else(|| arg.long_help().unwrap_or("")) |
| }; |
| let mut help = String::from(h) + spec_vals; |
| let nlh = self.next_line_help |
| || arg.is_set(ArgSettings::NextLineHelp) |
| || (self.use_long && arg.name() != ""); |
| debugln!("Help::help: Next Line...{:?}", nlh); |
| |
| let spcs = if nlh || self.force_next_line { |
| 12 // "tab" * 3 |
| } else { |
| self.longest + 12 |
| }; |
| |
| let too_long = spcs + str_width(h) + str_width(&*spec_vals) >= self.term_w; |
| |
| // Is help on next line, if so then indent |
| if nlh || self.force_next_line { |
| write!(self.writer, "\n{}{}{}", TAB, TAB, TAB)?; |
| } |
| |
| debug!("Help::help: Too long..."); |
| if too_long && spcs <= self.term_w || h.contains("{n}") { |
| sdebugln!("Yes"); |
| debugln!("Help::help: help...{}", help); |
| debugln!("Help::help: help width...{}", str_width(&*help)); |
| // Determine how many newlines we need to insert |
| let avail_chars = self.term_w - spcs; |
| debugln!("Help::help: Usable space...{}", avail_chars); |
| help = wrap_help(&help.replace("{n}", "\n"), avail_chars); |
| } else { |
| sdebugln!("No"); |
| } |
| if let Some(part) = help.lines().next() { |
| write!(self.writer, "{}", part)?; |
| } |
| for part in help.lines().skip(1) { |
| write!(self.writer, "\n")?; |
| if nlh || self.force_next_line { |
| write!(self.writer, "{}{}{}", TAB, TAB, TAB)?; |
| } else if arg.has_switch() { |
| write_nspaces!(self.writer, self.longest + 12); |
| } else { |
| write_nspaces!(self.writer, self.longest + 8); |
| } |
| write!(self.writer, "{}", part)?; |
| } |
| if !help.contains('\n') && (nlh || self.force_next_line) { |
| write!(self.writer, "\n")?; |
| } |
| Ok(()) |
| } |
| |
| fn spec_vals(&self, a: &ArgWithDisplay) -> String { |
| debugln!("Help::spec_vals: a={}", a); |
| let mut spec_vals = vec![]; |
| if let Some(ref env) = a.env() { |
| debugln!( |
| "Help::spec_vals: Found environment variable...[{:?}:{:?}]", |
| env.0, |
| env.1 |
| ); |
| let env_val = if !a.is_set(ArgSettings::HideEnvValues) { |
| format!( |
| "={}", |
| env.1.map_or(Cow::Borrowed(""), |val| val.to_string_lossy()) |
| ) |
| } else { |
| String::new() |
| }; |
| let env_info = format!(" [env: {}{}]", env.0.to_string_lossy(), env_val); |
| spec_vals.push(env_info); |
| } |
| if !a.is_set(ArgSettings::HideDefaultValue) { |
| if let Some(pv) = a.default_val() { |
| debugln!("Help::spec_vals: Found default value...[{:?}]", pv); |
| spec_vals.push(format!( |
| " [default: {}]", |
| if self.color { |
| self.cizer.good(pv.to_string_lossy()) |
| } else { |
| Format::None(pv.to_string_lossy()) |
| } |
| )); |
| } |
| } |
| if let Some(ref aliases) = a.aliases() { |
| debugln!("Help::spec_vals: Found aliases...{:?}", aliases); |
| spec_vals.push(format!( |
| " [aliases: {}]", |
| if self.color { |
| aliases |
| .iter() |
| .map(|v| format!("{}", self.cizer.good(v))) |
| .collect::<Vec<_>>() |
| .join(", ") |
| } else { |
| aliases.join(", ") |
| } |
| )); |
| } |
| if !self.hide_pv && !a.is_set(ArgSettings::HidePossibleValues) { |
| if let Some(pv) = a.possible_vals() { |
| debugln!("Help::spec_vals: Found possible vals...{:?}", pv); |
| spec_vals.push(if self.color { |
| format!( |
| " [possible values: {}]", |
| pv.iter() |
| .map(|v| format!("{}", self.cizer.good(v))) |
| .collect::<Vec<_>>() |
| .join(", ") |
| ) |
| } else { |
| format!(" [possible values: {}]", pv.join(", ")) |
| }); |
| } |
| } |
| spec_vals.join(" ") |
| } |
| } |
| |
| fn should_show_arg(use_long: bool, arg: &ArgWithOrder) -> bool { |
| if arg.is_set(ArgSettings::Hidden) { |
| return false; |
| } |
| |
| (!arg.is_set(ArgSettings::HiddenLongHelp) && use_long) |
| || (!arg.is_set(ArgSettings::HiddenShortHelp) && !use_long) |
| || arg.is_set(ArgSettings::NextLineHelp) |
| } |
| |
| // Methods to write Parser help. |
| impl<'a> Help<'a> { |
| /// Writes help for all arguments (options, flags, args, subcommands) |
| /// including titles of a Parser Object to the wrapped stream. |
| #[cfg_attr(feature = "lints", allow(useless_let_if_seq))] |
| #[cfg_attr(feature = "cargo-clippy", allow(useless_let_if_seq))] |
| pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> { |
| debugln!("Help::write_all_args;"); |
| let flags = parser.has_flags(); |
| let pos = parser |
| .positionals() |
| .filter(|arg| !arg.is_set(ArgSettings::Hidden)) |
| .count() |
| > 0; |
| let opts = parser.has_opts(); |
| let subcmds = parser.has_visible_subcommands(); |
| |
| let unified_help = parser.is_set(AppSettings::UnifiedHelpMessage); |
| |
| let mut first = true; |
| |
| if unified_help && (flags || opts) { |
| let opts_flags = parser |
| .flags() |
| .map(as_arg_trait) |
| .chain(parser.opts().map(as_arg_trait)); |
| color!(self, "OPTIONS:\n", warning)?; |
| self.write_args(opts_flags)?; |
| first = false; |
| } else { |
| if flags { |
| color!(self, "FLAGS:\n", warning)?; |
| self.write_args(parser.flags().map(as_arg_trait))?; |
| first = false; |
| } |
| if opts { |
| if !first { |
| self.writer.write_all(b"\n\n")?; |
| } |
| color!(self, "OPTIONS:\n", warning)?; |
| self.write_args(parser.opts().map(as_arg_trait))?; |
| first = false; |
| } |
| } |
| |
| if pos { |
| if !first { |
| self.writer.write_all(b"\n\n")?; |
| } |
| color!(self, "ARGS:\n", warning)?; |
| self.write_args_unsorted(parser.positionals().map(as_arg_trait))?; |
| first = false; |
| } |
| |
| if subcmds { |
| if !first { |
| self.writer.write_all(b"\n\n")?; |
| } |
| color!(self, "SUBCOMMANDS:\n", warning)?; |
| self.write_subcommands(parser)?; |
| } |
| |
| Ok(()) |
| } |
| |
| /// Writes help for subcommands of a Parser Object to the wrapped stream. |
| fn write_subcommands(&mut self, parser: &Parser) -> io::Result<()> { |
| debugln!("Help::write_subcommands;"); |
| // The shortest an arg can legally be is 2 (i.e. '-x') |
| self.longest = 2; |
| let mut ord_m = VecMap::new(); |
| for sc in parser |
| .subcommands |
| .iter() |
| .filter(|s| !s.p.is_set(AppSettings::Hidden)) |
| { |
| let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); |
| self.longest = cmp::max(self.longest, str_width(sc.p.meta.name.as_str())); |
| //self.longest = cmp::max(self.longest, sc.p.meta.name.len()); |
| btm.insert(sc.p.meta.name.clone(), sc.clone()); |
| } |
| |
| let mut first = true; |
| for btm in ord_m.values() { |
| for sc in btm.values() { |
| if first { |
| first = false; |
| } else { |
| self.writer.write_all(b"\n")?; |
| } |
| self.write_arg(sc)?; |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Writes version of a Parser Object to the wrapped stream. |
| fn write_version(&mut self, parser: &Parser) -> io::Result<()> { |
| debugln!("Help::write_version;"); |
| write!(self.writer, "{}", parser.meta.version.unwrap_or(""))?; |
| Ok(()) |
| } |
| |
| /// Writes binary name of a Parser Object to the wrapped stream. |
| fn write_bin_name(&mut self, parser: &Parser) -> io::Result<()> { |
| debugln!("Help::write_bin_name;"); |
| macro_rules! write_name { |
| () => {{ |
| let mut name = parser.meta.name.clone(); |
| name = name.replace("{n}", "\n"); |
| color!(self, wrap_help(&name, self.term_w), good)?; |
| }}; |
| } |
| 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 |
| color!(self, bn.replace(" ", "-"), good)? |
| } else { |
| write_name!(); |
| } |
| } else { |
| write_name!(); |
| } |
| Ok(()) |
| } |
| |
| /// Writes default help for a Parser Object to the wrapped stream. |
| pub fn write_default_help(&mut self, parser: &Parser) -> ClapResult<()> { |
| debugln!("Help::write_default_help;"); |
| if let Some(h) = parser.meta.pre_help { |
| self.write_before_after_help(h)?; |
| self.writer.write_all(b"\n\n")?; |
| } |
| |
| macro_rules! write_thing { |
| ($thing:expr) => {{ |
| let mut owned_thing = $thing.to_owned(); |
| owned_thing = owned_thing.replace("{n}", "\n"); |
| write!(self.writer, "{}\n", wrap_help(&owned_thing, self.term_w))? |
| }}; |
| } |
| // Print the version |
| self.write_bin_name(parser)?; |
| self.writer.write_all(b" ")?; |
| self.write_version(parser)?; |
| self.writer.write_all(b"\n")?; |
| if let Some(author) = parser.meta.author { |
| write_thing!(author) |
| } |
| // if self.use_long { |
| // if let Some(about) = parser.meta.long_about { |
| // debugln!("Help::write_default_help: writing long about"); |
| // write_thing!(about) |
| // } else if let Some(about) = parser.meta.about { |
| // debugln!("Help::write_default_help: writing about"); |
| // write_thing!(about) |
| // } |
| // } else |
| if let Some(about) = parser.meta.long_about { |
| debugln!("Help::write_default_help: writing long about"); |
| write_thing!(about) |
| } else if let Some(about) = parser.meta.about { |
| debugln!("Help::write_default_help: writing about"); |
| write_thing!(about) |
| } |
| |
| color!(self, "\nUSAGE:", warning)?; |
| write!( |
| self.writer, |
| "\n{}{}\n\n", |
| TAB, |
| usage::create_usage_no_title(parser, &[]) |
| )?; |
| |
| let flags = parser.has_flags(); |
| let pos = parser.has_positionals(); |
| let opts = parser.has_opts(); |
| let subcmds = parser.has_subcommands(); |
| |
| if flags || opts || pos || subcmds { |
| self.write_all_args(parser)?; |
| } |
| |
| if let Some(h) = parser.meta.more_help { |
| if flags || opts || pos || subcmds { |
| self.writer.write_all(b"\n\n")?; |
| } |
| self.write_before_after_help(h)?; |
| } |
| |
| self.writer.flush().map_err(Error::from) |
| } |
| } |
| |
| /// Possible results for a copying function that stops when a given |
| /// byte was found. |
| enum CopyUntilResult { |
| DelimiterFound(usize), |
| DelimiterNotFound(usize), |
| ReaderEmpty, |
| ReadError(io::Error), |
| WriteError(io::Error), |
| } |
| |
| /// Copies the contents of a reader into a writer until a delimiter byte is found. |
| /// On success, the total number of bytes that were |
| /// copied from reader to writer is returned. |
| fn copy_until<R: Read, W: Write>(r: &mut R, w: &mut W, delimiter_byte: u8) -> CopyUntilResult { |
| debugln!("copy_until;"); |
| |
| let mut count = 0; |
| for wb in r.bytes() { |
| match wb { |
| Ok(b) => { |
| if b == delimiter_byte { |
| return CopyUntilResult::DelimiterFound(count); |
| } |
| match w.write(&[b]) { |
| Ok(c) => count += c, |
| Err(e) => return CopyUntilResult::WriteError(e), |
| } |
| } |
| Err(e) => return CopyUntilResult::ReadError(e), |
| } |
| } |
| if count > 0 { |
| CopyUntilResult::DelimiterNotFound(count) |
| } else { |
| CopyUntilResult::ReaderEmpty |
| } |
| } |
| |
| /// Copies the contents of a reader into a writer until a {tag} is found, |
| /// copying the tag content to a buffer and returning its size. |
| /// In addition to errors, there are three possible outputs: |
| /// - `None`: The reader was consumed. |
| /// - `Some(Ok(0))`: No tag was captured but the reader still contains data. |
| /// - `Some(Ok(length>0))`: a tag with `length` was captured to the `tag_buffer`. |
| fn copy_and_capture<R: Read, W: Write>( |
| r: &mut R, |
| w: &mut W, |
| tag_buffer: &mut Cursor<Vec<u8>>, |
| ) -> Option<io::Result<usize>> { |
| use self::CopyUntilResult::*; |
| debugln!("copy_and_capture;"); |
| |
| // Find the opening byte. |
| match copy_until(r, w, b'{') { |
| // The end of the reader was reached without finding the opening tag. |
| // (either with or without having copied data to the writer) |
| // Return None indicating that we are done. |
| ReaderEmpty | DelimiterNotFound(_) => None, |
| |
| // Something went wrong. |
| ReadError(e) | WriteError(e) => Some(Err(e)), |
| |
| // The opening byte was found. |
| // (either with or without having copied data to the writer) |
| DelimiterFound(_) => { |
| // Lets reset the buffer first and find out how long it is. |
| tag_buffer.set_position(0); |
| let buffer_size = tag_buffer.get_ref().len(); |
| |
| // Find the closing byte,limiting the reader to the length of the buffer. |
| let mut rb = r.take(buffer_size as u64); |
| match copy_until(&mut rb, tag_buffer, b'}') { |
| // We were already at the end of the reader. |
| // Return None indicating that we are done. |
| ReaderEmpty => None, |
| |
| // The closing tag was found. |
| // Return the tag_length. |
| DelimiterFound(tag_length) => Some(Ok(tag_length)), |
| |
| // The end of the reader was found without finding the closing tag. |
| // Write the opening byte and captured text to the writer. |
| // Return 0 indicating that nothing was captured but the reader still contains data. |
| DelimiterNotFound(not_tag_length) => match w.write(b"{") { |
| Err(e) => Some(Err(e)), |
| _ => match w.write(&tag_buffer.get_ref()[0..not_tag_length]) { |
| Err(e) => Some(Err(e)), |
| _ => Some(Ok(0)), |
| }, |
| }, |
| |
| ReadError(e) | WriteError(e) => Some(Err(e)), |
| } |
| } |
| } |
| } |
| |
| // Methods to write Parser help using templates. |
| impl<'a> Help<'a> { |
| /// Write help to stream for the parser in the format defined by the template. |
| /// |
| /// Tags arg given inside curly brackets: |
| /// Valid tags are: |
| /// * `{bin}` - Binary name. |
| /// * `{version}` - Version number. |
| /// * `{author}` - Author information. |
| /// * `{usage}` - Automatically generated or given usage string. |
| /// * `{all-args}` - Help for all arguments (options, flags, positionals arguments, |
| /// and subcommands) including titles. |
| /// * `{unified}` - Unified help for options and flags. |
| /// * `{flags}` - Help for flags. |
| /// * `{options}` - Help for options. |
| /// * `{positionals}` - Help for positionals arguments. |
| /// * `{subcommands}` - Help for subcommands. |
| /// * `{after-help}` - Info to be displayed after the help message. |
| /// * `{before-help}` - Info to be displayed before the help message. |
| /// |
| /// The template system is, on purpose, very simple. Therefore the tags have to written |
| /// in the lowercase and without spacing. |
| fn write_templated_help(&mut self, parser: &Parser, template: &str) -> ClapResult<()> { |
| debugln!("Help::write_templated_help;"); |
| let mut tmplr = Cursor::new(&template); |
| let mut tag_buf = Cursor::new(vec![0u8; 15]); |
| |
| // The strategy is to copy the template from the reader to wrapped stream |
| // until a tag is found. Depending on its value, the appropriate content is copied |
| // to the wrapped stream. |
| // The copy from template is then resumed, repeating this sequence until reading |
| // the complete template. |
| |
| loop { |
| let tag_length = match copy_and_capture(&mut tmplr, &mut self.writer, &mut tag_buf) { |
| None => return Ok(()), |
| Some(Err(e)) => return Err(Error::from(e)), |
| Some(Ok(val)) if val > 0 => val, |
| _ => continue, |
| }; |
| |
| debugln!("Help::write_template_help:iter: tag_buf={};", unsafe { |
| String::from_utf8_unchecked( |
| tag_buf.get_ref()[0..tag_length] |
| .iter() |
| .map(|&i| i) |
| .collect::<Vec<_>>(), |
| ) |
| }); |
| match &tag_buf.get_ref()[0..tag_length] { |
| b"?" => { |
| self.writer.write_all(b"Could not decode tag name")?; |
| } |
| b"bin" => { |
| self.write_bin_name(parser)?; |
| } |
| b"version" => { |
| write!( |
| self.writer, |
| "{}", |
| parser.meta.version.unwrap_or("unknown version") |
| )?; |
| } |
| b"author" => { |
| write!( |
| self.writer, |
| "{}", |
| parser.meta.author.unwrap_or("unknown author") |
| )?; |
| } |
| b"about" => { |
| write!( |
| self.writer, |
| "{}", |
| parser.meta.about.unwrap_or("unknown about") |
| )?; |
| } |
| b"long-about" => { |
| write!( |
| self.writer, |
| "{}", |
| parser.meta.long_about.unwrap_or("unknown about") |
| )?; |
| } |
| b"usage" => { |
| write!(self.writer, "{}", usage::create_usage_no_title(parser, &[]))?; |
| } |
| b"all-args" => { |
| self.write_all_args(parser)?; |
| } |
| b"unified" => { |
| let opts_flags = parser |
| .flags() |
| .map(as_arg_trait) |
| .chain(parser.opts().map(as_arg_trait)); |
| self.write_args(opts_flags)?; |
| } |
| b"flags" => { |
| self.write_args(parser.flags().map(as_arg_trait))?; |
| } |
| b"options" => { |
| self.write_args(parser.opts().map(as_arg_trait))?; |
| } |
| b"positionals" => { |
| self.write_args(parser.positionals().map(as_arg_trait))?; |
| } |
| b"subcommands" => { |
| self.write_subcommands(parser)?; |
| } |
| b"after-help" => { |
| write!( |
| self.writer, |
| "{}", |
| parser.meta.more_help.unwrap_or("unknown after-help") |
| )?; |
| } |
| b"before-help" => { |
| write!( |
| self.writer, |
| "{}", |
| parser.meta.pre_help.unwrap_or("unknown before-help") |
| )?; |
| } |
| // Unknown tag, write it back. |
| r => { |
| self.writer.write_all(b"{")?; |
| self.writer.write_all(r)?; |
| self.writer.write_all(b"}")?; |
| } |
| } |
| } |
| } |
| } |
| |
| fn wrap_help(help: &str, avail_chars: usize) -> String { |
| let wrapper = textwrap::Options::new(avail_chars).break_words(false); |
| help.lines() |
| .map(|line| textwrap::fill(line, &wrapper)) |
| .collect::<Vec<String>>() |
| .join("\n") |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::wrap_help; |
| |
| #[test] |
| fn wrap_help_last_word() { |
| let help = String::from("foo bar baz"); |
| assert_eq!(wrap_help(&help, 5), "foo\nbar\nbaz"); |
| } |
| } |