blob: ef57dbbab2b12d3d119eb1270ca61c0d6266c687 [file] [log] [blame]
// 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");
}
}