Refactor coloring

This commit is contained in:
CreepySkeleton 2020-08-22 12:43:58 +03:00
parent 16f92288f1
commit 5020333037
12 changed files with 682 additions and 966 deletions

View file

@ -1665,18 +1665,15 @@ impl<'help> App<'help> {
/// app.print_help();
/// ```
/// [`io::stdout()`]: https://doc.rust-lang.org/std/io/fn.stdout.html
/// [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
/// [`-h` (short)]: ./struct.Arg.html#method.about
/// [`--help` (long)]: ./struct.Arg.html#method.long_about
pub fn print_help(&mut self) -> ClapResult<()> {
pub fn print_help(&mut self) -> io::Result<()> {
self._build();
let p = Parser::new(self);
let mut c = Colorizer::new(false, p.color_help());
Help::new(HelpWriter::Buffer(&mut c), &p, false).write_help()?;
Ok(c.print()?)
c.print()
}
/// Prints the full help message to [`io::stdout()`] using a [`BufWriter`] using the same
@ -1696,15 +1693,13 @@ impl<'help> App<'help> {
/// [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
/// [`-h` (short)]: ./struct.Arg.html#method.about
/// [`--help` (long)]: ./struct.Arg.html#method.long_about
pub fn print_long_help(&mut self) -> ClapResult<()> {
pub fn print_long_help(&mut self) -> io::Result<()> {
self._build();
let p = Parser::new(self);
let mut c = Colorizer::new(false, p.color_help());
Help::new(HelpWriter::Buffer(&mut c), &p, true).write_help()?;
Ok(c.print()?)
c.print()
}
/// Writes the full help message to the user to a [`io::Write`] object in the same method as if
@ -1725,11 +1720,12 @@ impl<'help> App<'help> {
/// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
/// [`-h` (short)]: ./struct.Arg.html#method.about
/// [`--help` (long)]: ./struct.Arg.html#method.long_about
pub fn write_help<W: Write>(&mut self, w: &mut W) -> ClapResult<()> {
pub fn write_help<W: Write>(&mut self, w: &mut W) -> io::Result<()> {
self._build();
let p = Parser::new(self);
Help::new(HelpWriter::Normal(w), &p, false).write_help()
Help::new(HelpWriter::Normal(w), &p, false).write_help()?;
w.flush()
}
/// Writes the full help message to the user to a [`io::Write`] object in the same method as if
@ -1750,53 +1746,61 @@ impl<'help> App<'help> {
/// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
/// [`-h` (short)]: ./struct.Arg.html#method.about
/// [`--help` (long)]: ./struct.Arg.html#method.long_about
pub fn write_long_help<W: Write>(&mut self, w: &mut W) -> ClapResult<()> {
pub fn write_long_help<W: Write>(&mut self, w: &mut W) -> io::Result<()> {
self._build();
let p = Parser::new(self);
Help::new(HelpWriter::Normal(w), &p, true).write_help()
Help::new(HelpWriter::Normal(w), &p, true).write_help()?;
w.flush()
}
/// Writes the version message to the user to a [`io::Write`] object as if the user ran `-V`.
/// Returns the version message rendered as if the user ran `-V`.
///
/// **NOTE:** clap has the ability to distinguish between "short" and "long" version messages
/// depending on if the user ran [`-V` (short)] or [`--version` (long)].
///
/// # Examples
/// ### Coloring
///
/// This function does not try to color the message nor it inserts any [ANSI escape codes].
///
/// ### Examples
///
/// ```rust
/// # use clap::App;
/// use std::io;
/// let mut app = App::new("myprog");
/// let mut out = io::stdout();
/// app.write_version(&mut out).expect("failed to write to stdout");
/// let app = App::new("myprog");
/// println!("{}", app.render_version());
/// ```
/// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
/// [`-V` (short)]: ./struct.App.html#method.version
/// [`--version` (long)]: ./struct.App.html#method.long_version
pub fn write_version<W: Write>(&self, w: &mut W) -> ClapResult<()> {
self._write_version(w, false).map_err(From::from)
/// [ANSI escape codes]: https://en.wikipedia.org/wiki/ANSI_escape_code
pub fn render_version(&self) -> String {
self._render_version(false)
}
/// Writes the version message to the user to a [`io::Write`] object.
/// Returns the version message rendered as if the user ran `--version`.
///
/// **NOTE:** clap has the ability to distinguish between "short" and "long" version messages
/// depending on if the user ran [`-V` (short)] or [`--version` (long)].
///
/// # Examples
/// ### Coloring
///
/// This function does not try to color the message nor it inserts any [ANSI escape codes].
///
/// ### Examples
///
/// ```rust
/// # use clap::App;
/// use std::io;
/// let mut app = App::new("myprog");
/// let mut out = io::stdout();
/// app.write_long_version(&mut out).expect("failed to write to stdout");
/// let app = App::new("myprog");
/// println!("{}", app.render_long_version());
/// ```
/// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
/// [`-V` (short)]: ./struct.App.html#method.version
/// [`--version` (long)]: ./struct.App.html#method.long_version
pub fn write_long_version<W: Write>(&self, w: &mut W) -> ClapResult<()> {
self._write_version(w, true).map_err(From::from)
pub fn render_long_version(&self) -> String {
self._render_version(true)
}
/// @TODO-v3-alpha @docs @p2: write docs
@ -2111,7 +2115,7 @@ impl<'help> App<'help> {
let mut matcher = ArgMatcher::default();
// If there are global arguments, or settings we need to propagate them down to subcommands
// before parsing incase we run into a subcommand
// before parsing in case we run into a subcommand
if !self.settings.is_set(AppSettings::Built) {
self._build();
}
@ -2150,12 +2154,9 @@ impl<'help> App<'help> {
for a in self.args.args.iter_mut() {
// Fill in the groups
for g in &a.groups {
let mut found = false;
if let Some(ag) = self.groups.iter_mut().find(|grp| grp.id == *g) {
ag.args.push(a.id.clone());
found = true;
}
if !found {
} else {
let mut ag = ArgGroup::with_id(g.clone());
ag.args.push(a.id.clone());
self.groups.push(ag);
@ -2396,8 +2397,8 @@ impl<'help> App<'help> {
}
}
pub(crate) fn _write_version<W: Write>(&self, w: &mut W, use_long: bool) -> io::Result<()> {
debug!("App::_write_version");
pub(crate) fn _render_version(&self, use_long: bool) -> String {
debug!("App::_render_version");
let ver = if use_long {
self.long_version
@ -2409,12 +2410,12 @@ impl<'help> App<'help> {
if let Some(bn) = self.bin_name.as_ref() {
if bn.contains(' ') {
// In case we're dealing with subcommands i.e. git mv is translated to git-mv
writeln!(w, "{} {}", bn.replace(" ", "-"), ver)
format!("{} {}\n", bn.replace(" ", "-"), ver)
} else {
writeln!(w, "{} {}", &self.name[..], ver)
format!("{} {}\n", &self.name[..], ver)
}
} else {
writeln!(w, "{} {}", &self.name[..], ver)
format!("{} {}\n", &self.name[..], ver)
}
}

View file

@ -1,10 +1,11 @@
use crate::util::termcolor::{Buffer, BufferWriter, ColorChoice};
#[cfg(not(feature = "color"))]
use crate::util::termcolor::{Color, ColorChoice};
#[cfg(feature = "color")]
use crate::util::termcolor::{Color, ColorSpec, WriteColor};
use termcolor::{Color, ColorChoice};
use std::{
fmt::{self, Debug, Formatter},
io::{Result, Write},
fmt::{self, Display, Formatter},
io::{self, Write},
};
#[cfg(feature = "color")]
@ -20,103 +21,102 @@ fn is_a_tty(stderr: bool) -> bool {
atty::is(stream)
}
#[cfg(not(feature = "color"))]
fn is_a_tty(_: bool) -> bool {
debug!("is_a_tty");
false
}
#[derive(Debug)]
pub(crate) struct Colorizer {
writer: BufferWriter,
buffer: Buffer,
}
impl Debug for Colorizer {
#[cfg(feature = "color")]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", String::from_utf8_lossy(self.buffer.as_slice()))
}
#[cfg(not(feature = "color"))]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", String::from_utf8_lossy(&self.buffer))
}
use_stderr: bool,
color_when: ColorChoice,
pieces: Vec<(String, Option<Color>)>,
}
impl Colorizer {
pub(crate) fn new(use_stderr: bool, when: ColorChoice) -> Self {
let checked_when = if is_a_tty(use_stderr) {
when
#[inline]
pub(crate) fn new(use_stderr: bool, color_when: ColorChoice) -> Self {
Colorizer {
use_stderr,
color_when,
pieces: vec![],
}
}
#[inline]
pub(crate) fn good(&mut self, msg: impl Into<String>) {
self.pieces.push((msg.into(), Some(Color::Green)));
}
#[inline]
pub(crate) fn warning(&mut self, msg: impl Into<String>) {
self.pieces.push((msg.into(), Some(Color::Yellow)));
}
#[inline]
pub(crate) fn error(&mut self, msg: impl Into<String>) {
self.pieces.push((msg.into(), Some(Color::Red)));
}
#[inline]
pub(crate) fn none(&mut self, msg: impl Into<String>) {
self.pieces.push((msg.into(), None));
}
}
/// Printing methods.
impl Colorizer {
#[cfg(feature = "color")]
pub(crate) fn print(&self) -> io::Result<()> {
use termcolor::{BufferWriter, ColorSpec, WriteColor};
let color_when = if is_a_tty(self.use_stderr) {
self.color_when
} else {
ColorChoice::Never
};
let writer = if use_stderr {
BufferWriter::stderr(checked_when)
let writer = if self.use_stderr {
BufferWriter::stderr(color_when)
} else {
BufferWriter::stdout(checked_when)
BufferWriter::stdout(color_when)
};
let buffer = writer.buffer();
let mut buffer = writer.buffer();
Self { writer, buffer }
}
for piece in &self.pieces {
let mut color = ColorSpec::new();
color.set_fg(piece.1);
if piece.1 == Some(Color::Red) {
color.set_bold(true);
}
pub(crate) fn print(&self) -> Result<()> {
self.writer.print(&self.buffer)
}
buffer.set_color(&color)?;
buffer.write_all(piece.0.as_bytes())?;
buffer.reset()?;
}
#[cfg(feature = "color")]
pub(crate) fn good(&mut self, msg: &str) -> Result<()> {
self.buffer
.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
self.write_all(msg.as_bytes())?;
self.buffer.reset()
writer.print(&buffer)
}
#[cfg(not(feature = "color"))]
pub(crate) fn good(&mut self, msg: &str) -> Result<()> {
self.none(msg)
pub(crate) fn print(&self) -> io::Result<()> {
// [e]println can't be used here because it panics
// if something went wrong. We don't want that.
if self.use_stderr {
let stderr = std::io::stderr();
let mut stderr = stderr.lock();
write!(stderr, "{}", self)
} else {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
write!(stdout, "{}", self)
}
}
}
#[cfg(feature = "color")]
pub(crate) fn warning(&mut self, msg: &str) -> Result<()> {
self.buffer
.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
self.write_all(msg.as_bytes())?;
self.buffer.reset()
}
/// Color-unaware printing. Never uses coloring.
impl Display for Colorizer {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
for piece in &self.pieces {
Display::fmt(&piece.0, f)?;
}
#[cfg(not(feature = "color"))]
pub(crate) fn warning(&mut self, msg: &str) -> Result<()> {
self.none(msg)
}
#[cfg(feature = "color")]
pub(crate) fn error(&mut self, msg: &str) -> Result<()> {
self.buffer
.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
self.write_all(msg.as_bytes())?;
self.buffer.reset()
}
#[cfg(not(feature = "color"))]
pub(crate) fn error(&mut self, msg: &str) -> Result<()> {
self.none(msg)
}
pub(crate) fn none(&mut self, msg: &str) -> Result<()> {
self.write_all(msg.as_bytes())?;
Ok(())
}
}
impl Write for Colorizer {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.buffer.write(buf)
}
fn flush(&mut self) -> Result<()> {
self.buffer.flush()
}
}

View file

@ -3,7 +3,7 @@ use std::{
borrow::Cow,
cmp,
collections::BTreeMap,
io::{self, Cursor, Read, Write},
io::{self, Write},
usize,
};
@ -11,10 +11,7 @@ use std::{
use crate::{
build::{App, AppSettings, Arg, ArgSettings},
output::{fmt::Colorizer, Usage},
parse::{
errors::{Error, Result as ClapResult},
Parser,
},
parse::Parser,
util::VecMap,
INTERNAL_ERROR_MSG,
};
@ -42,22 +39,6 @@ pub(crate) enum HelpWriter<'writer> {
Buffer(&'writer mut Colorizer),
}
impl<'writer> Write for HelpWriter<'writer> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
HelpWriter::Normal(n) => n.write(buf),
HelpWriter::Buffer(c) => c.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
HelpWriter::Normal(n) => n.flush(),
HelpWriter::Buffer(c) => c.flush(),
}
}
}
/// `clap` Help Writer.
///
/// Wraps a writer stream providing different methods to generate help for `clap` objects.
@ -122,11 +103,11 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
}
/// Writes the parser help to the wrapped stream.
pub(crate) fn write_help(&mut self) -> ClapResult<()> {
pub(crate) fn write_help(&mut self) -> io::Result<()> {
debug!("Help::write_help");
if let Some(h) = self.parser.app.help_str {
self.none(h).map_err(Error::from)?;
self.none(h)?;
} else if let Some(tmpl) = self.parser.app.template {
self.write_templated_help(tmpl)?;
} else {
@ -151,8 +132,11 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
macro_rules! write_method {
($_self:ident, $msg:ident, $meth:ident) => {
match &mut $_self.writer {
HelpWriter::Buffer(c) => c.$meth($msg),
HelpWriter::Normal(w) => write!(w, "{}", $msg),
HelpWriter::Buffer(c) => {
c.$meth(($msg).into());
Ok(())
}
HelpWriter::Normal(w) => w.write_all($msg.as_ref()),
}
};
}
@ -168,15 +152,15 @@ macro_rules! write_nspaces {
// Methods to write Arg help.
impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
fn good(&mut self, msg: &str) -> io::Result<()> {
fn good<T: Into<String> + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> {
write_method!(self, msg, good)
}
fn warning(&mut self, msg: &str) -> io::Result<()> {
fn warning<T: Into<String> + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> {
write_method!(self, msg, warning)
}
fn none(&mut self, msg: &str) -> io::Result<()> {
fn none<T: Into<String> + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> {
write_method!(self, msg, none)
}
@ -693,17 +677,15 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
/// Writes help for all arguments (options, flags, args, subcommands)
/// including titles of a Parser Object to the wrapped stream.
pub(crate) fn write_all_args(&mut self) -> ClapResult<()> {
pub(crate) fn write_all_args(&mut self) -> io::Result<()> {
debug!("Help::write_all_args");
let flags = self.parser.has_flags();
// FIXME: Strange filter/count vs fold... https://github.com/rust-lang/rust/issues/33038
let pos = self.parser.app.get_positionals().fold(0, |acc, arg| {
if should_show_arg(self.use_long, arg) {
acc + 1
} else {
acc
}
}) > 0;
let pos = self
.parser
.app
.get_positionals()
.filter(|arg| should_show_arg(self.use_long, arg))
.any(|_| true);
let opts = self
.parser
.app
@ -850,7 +832,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
}
if let Some(bn) = self.parser.app.bin_name.as_ref() {
if bn.contains(' ') {
// Incase we're dealing with subcommands i.e. git mv is translated to git-mv
// In case we're dealing with subcommands i.e. git mv is translated to git-mv
self.good(&bn.replace(" ", "-"))?
} else {
write_name!();
@ -862,103 +844,6 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
}
}
/// 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 {
debug!("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::*;
debug!("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<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
/// Write help to stream for the parser in the format defined by the template.
@ -966,10 +851,8 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
/// For details about the template language see [`App::help_template`].
///
/// [`App::help_template`]: ./struct.App.html#method.help_template
fn write_templated_help(&mut self, template: &str) -> ClapResult<()> {
fn write_templated_help(&mut self, template: &str) -> io::Result<()> {
debug!("Help::write_templated_help");
let mut tmplr = Cursor::new(&template);
let mut tag_buf = Cursor::new(vec![0u8; 20]);
// 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
@ -977,127 +860,140 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
// 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,
};
macro_rules! tags {
(
match $part:ident {
$( $tag:expr => $action:stmt )*
}
) => {
match $part {
$(
part if part.starts_with(concat!($tag, "}")) => {
$action
let rest = &part[$tag.len()+1..];
self.none(rest)?;
}
)*
debug!(
"Help::write_template_help:iter: tag_buf={}",
String::from_utf8_lossy(&tag_buf.get_ref()[0..tag_length])
);
match &tag_buf.get_ref()[0..tag_length] {
b"?" => {
self.none("Could not decode tag name")?;
}
b"bin" => {
self.write_bin_name()?;
}
b"version" => {
if let Some(output) = self.parser.app.version {
self.none(output)?;
// Unknown tag, write it back.
part => {
self.none("{")?;
self.none(part)?;
}
}
b"author" => {
if let Some(output) = self.parser.app.author {
self.none(&wrap_help(output, self.term_w))?;
};
}
let mut parts = template.split('{');
if let Some(first) = parts.next() {
self.none(first)?;
}
for part in parts {
tags! {
match part {
"bin" => {
self.write_bin_name()?;
}
}
b"author-with-newline" => {
if let Some(output) = self.parser.app.author {
self.none(&wrap_help(output, self.term_w))?;
self.none("\n")?;
"version" => {
if let Some(s) = self.parser.app.version {
self.none(s)?;
}
}
}
b"about" => {
let about = if self.use_long {
self.parser.app.long_about.or(self.parser.app.about)
} else {
self.parser.app.about
};
if let Some(output) = about {
self.none(&wrap_help(output, self.term_w))?;
"author" => {
if let Some(s) = self.parser.app.author {
self.none(&wrap_help(s, self.term_w))?;
}
}
}
b"about-with-newline" => {
let about = if self.use_long {
self.parser.app.long_about.or(self.parser.app.about)
} else {
self.parser.app.about
};
if let Some(output) = about {
self.none(&wrap_help(output, self.term_w))?;
self.none("\n")?;
"author-with-newline" => {
if let Some(s) = self.parser.app.author {
self.none(&wrap_help(s, self.term_w))?;
self.none("\n")?;
}
}
}
b"usage" => {
self.none(&Usage::new(self.parser).create_usage_no_title(&[]))?;
}
b"all-args" => {
self.write_all_args()?;
}
b"unified" => {
let opts_flags = self
.parser
.app
.args
.args
.iter()
.filter(|a| a.has_switch())
.collect::<Vec<_>>();
self.write_args(&opts_flags)?;
}
b"flags" => {
self.write_args(&self.parser.app.get_flags_no_heading().collect::<Vec<_>>())?;
}
b"options" => {
self.write_args(&self.parser.app.get_opts_no_heading().collect::<Vec<_>>())?;
}
b"positionals" => {
self.write_args(&self.parser.app.get_positionals().collect::<Vec<_>>())?;
}
b"subcommands" => {
self.write_subcommands(self.parser.app)?;
}
b"after-help" => {
let after_help = if self.use_long {
self.parser
"about" => {
let about = if self.use_long {
self.parser.app.long_about.or(self.parser.app.about)
} else {
self.parser.app.about
};
if let Some(output) = about {
self.none(wrap_help(output, self.term_w))?;
}
}
"about-with-newline" => {
let about = if self.use_long {
self.parser.app.long_about.or(self.parser.app.about)
} else {
self.parser.app.about
};
if let Some(output) = about {
self.none(wrap_help(output, self.term_w))?;
self.none("\n")?;
}
}
"usage" => {
self.none(Usage::new(self.parser).create_usage_no_title(&[]))?;
}
"all-args" => {
self.write_all_args()?;
}
"unified" => {
let opts_flags = self
.parser
.app
.after_long_help
.or(self.parser.app.after_help)
} else {
self.parser.app.after_help
};
if let Some(output) = after_help {
self.none("\n\n")?;
self.write_before_after_help(output)?;
.args
.args
.iter()
.filter(|a| a.has_switch())
.collect::<Vec<_>>();
self.write_args(&opts_flags)?;
}
}
b"before-help" => {
let before_help = if self.use_long {
self.parser
.app
.before_long_help
.or(self.parser.app.before_help)
} else {
self.parser.app.before_help
};
if let Some(output) = before_help {
self.write_before_after_help(output)?;
self.none("\n\n")?;
"flags" => {
self.write_args(&self.parser.app.get_flags_no_heading().collect::<Vec<_>>())?;
}
"options" => {
self.write_args(&self.parser.app.get_opts_no_heading().collect::<Vec<_>>())?;
}
"positionals" => {
self.write_args(&self.parser.app.get_positionals().collect::<Vec<_>>())?;
}
"subcommands" => {
self.write_subcommands(self.parser.app)?;
}
"after-help" => {
let after_help = if self.use_long {
self.parser
.app
.after_long_help
.or(self.parser.app.after_help)
} else {
self.parser.app.after_help
};
if let Some(output) = after_help {
self.none("\n\n")?;
self.write_before_after_help(output)?;
}
}
"before-help" => {
let before_help = if self.use_long {
self.parser
.app
.before_long_help
.or(self.parser.app.before_help)
} else {
self.parser.app.before_help
};
if let Some(output) = before_help {
self.write_before_after_help(output)?;
self.none("\n\n")?;
}
}
}
// Unknown tag, write it back.
r => {
self.none("{")?;
self.writer.write_all(r)?;
self.none("}")?;
}
}
}
Ok(())
}
}

View file

@ -1,5 +1,5 @@
// std
use std::collections::{BTreeMap, VecDeque};
use std::collections::BTreeMap;
// Internal
use crate::{
@ -241,9 +241,12 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
count
);
}
if !self.p.is_set(AS::DontCollapseArgsInUsage) && count > 1 {
debug!("Usage::get_args_tag:iter: More than one, returning [ARGS]");
return None; // [ARGS]
// [ARGS]
None
} else if count == 1 && incl_reqs {
let pos = self
.p
@ -255,21 +258,23 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
&& !pos.is_set(ArgSettings::Last)
})
.expect(INTERNAL_ERROR_MSG);
debug!(
"Usage::get_args_tag:iter: Exactly one, returning '{}'",
pos.name
);
return Some(format!(
Some(format!(
" [{}]{}",
pos.name_no_brackets(),
pos.multiple_str()
));
))
} else if self.p.is_set(AS::DontCollapseArgsInUsage)
&& self.p.has_positionals()
&& incl_reqs
{
debug!("Usage::get_args_tag:iter: Don't collapse returning all");
return Some(
Some(
self.p
.app
.get_positionals()
@ -279,7 +284,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
.map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
.collect::<Vec<_>>()
.join(""),
);
)
} else if !incl_reqs {
debug!("Usage::get_args_tag:iter: incl_reqs=false, building secondary usage string");
let highest_req_pos = self
@ -295,7 +300,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
})
.max()
.unwrap_or_else(|| Some(self.p.app.get_positionals().count() as u64));
return Some(
Some(
self.p
.app
.get_positionals()
@ -306,9 +311,10 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
.map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
.collect::<Vec<_>>()
.join(""),
);
)
} else {
Some("".into())
}
Some("".into())
}
// Determines if we need the `[FLAGS]` tag in the usage string
@ -316,11 +322,14 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
debug!("Usage::needs_flags_tag");
'outer: for f in self.p.app.get_flags_no_heading() {
debug!("Usage::needs_flags_tag:iter: f={}", f.name);
if let Some(l) = f.long {
if l == "help" || l == "version" {
// Don't print `[FLAGS]` just for help or version
continue;
}
// Don't print `[FLAGS]` just for help or version
if f.long == Some("help") || f.long == Some("version") {
continue;
}
if f.is_set(ArgSettings::Hidden) {
continue;
}
for grp_s in self.p.app.groups_for_arg(&f.id) {
debug!("Usage::needs_flags_tag:iter:iter: grp_s={:?}", grp_s);
@ -335,9 +344,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
continue 'outer;
}
}
if f.is_set(ArgSettings::Hidden) {
continue;
}
debug!("Usage::needs_flags_tag:iter: [FLAGS] required");
return true;
}
@ -347,7 +354,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
}
// Returns the required args in usage string form by fully unrolling all groups
// `incl_last`: should we incldue args that are Arg::Last? (i.e. `prog [foo] -- [last]). We
// `incl_last`: should we include args that are Arg::Last? (i.e. `prog [foo] -- [last]). We
// can't do that for required usages being built for subcommands because it would look like:
// `prog [foo] -- [last] <subcommand>` which is totally wrong.
pub(crate) fn get_required_usage_from(
@ -355,14 +362,14 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
incls: &[Id],
matcher: Option<&ArgMatcher>,
incl_last: bool,
) -> VecDeque<String> {
) -> Vec<String> {
debug!(
"Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}",
incls,
matcher.is_some(),
incl_last
);
let mut ret_val = VecDeque::new();
let mut ret_val = Vec::new();
let mut unrolled_reqs = vec![];
@ -405,7 +412,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
for p in pmap.values() {
debug!("Usage::get_required_usage_from:iter:{:?}", p.id);
if args_in_groups.is_empty() || !args_in_groups.contains(&p.id) {
ret_val.push_back(p.to_string());
ret_val.push(p.to_string());
}
}
for a in unrolled_reqs
@ -423,7 +430,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
.find(&a)
.map(ToString::to_string)
.expect(INTERNAL_ERROR_MSG);
ret_val.push_back(arg);
ret_val.push(arg);
}
let mut g_vec: Vec<String> = vec![];
for g in unrolled_reqs
@ -449,7 +456,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
}
}
for g in g_vec {
ret_val.push_back(g);
ret_val.push(g);
}
debug!("Usage::get_required_usage_from: ret_val={:?}", ret_val);

View file

@ -1,6 +1,5 @@
// Std
use std::{
collections::VecDeque,
convert::From,
fmt::{self, Debug, Display, Formatter},
io,
@ -9,7 +8,7 @@ use std::{
// Internal
use crate::{
build::{Arg, ArgGroup},
build::Arg,
output::fmt::Colorizer,
parse::features::suggestions,
util::{safe_exit, termcolor::ColorChoice},
@ -375,39 +374,36 @@ pub enum ErrorKind {
/// Command Line Argument Parser Error
#[derive(Debug)]
pub struct Error {
/// The cause of the error
pub cause: String,
/// Formatted error message, enhancing the cause message with extra information
pub(crate) message: Colorizer,
/// The type of error
pub kind: ErrorKind,
/// Any additional information passed along, such as the argument name that caused the error
pub info: Option<Vec<String>>,
/// Additional information depending on the error kind, like values and argument names.
/// Useful when you want to render an error of your own.
pub info: Vec<String>,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.message.fmt(f)
Display::fmt(&self.message, f)
}
}
fn start_error(c: &mut Colorizer, msg: &str) -> io::Result<()> {
c.error("error:")?;
c.none(" ")?;
c.none(msg)
fn start_error(c: &mut Colorizer, msg: impl Into<String>) {
c.error("error:");
c.none(" ");
c.none(msg);
}
fn put_usage<U>(c: &mut Colorizer, usage: U) -> io::Result<()>
where
U: Display,
{
c.none(&format!("\n\n{}", usage))
fn put_usage(c: &mut Colorizer, usage: impl Into<String>) {
c.none("\n\n");
c.none(usage);
}
fn try_help(c: &mut Colorizer) -> io::Result<()> {
c.none("\n\nFor more information try ")?;
c.good("--help")?;
c.none("\n")
fn try_help(c: &mut Colorizer) {
c.none("\n\nFor more information try ");
c.good("--help");
c.none("\n");
}
impl Error {
@ -440,604 +436,427 @@ impl Error {
safe_exit(0)
}
#[allow(unused)] // requested by @pksunkara
pub(crate) fn group_conflict<O, U>(
group: &ArgGroup,
other: Option<O>,
usage: U,
color: ColorChoice,
) -> io::Result<Self>
where
O: Into<String>,
U: Display,
{
let mut v = vec![group.name.to_owned()];
let mut c = Colorizer::new(true, color);
start_error(&mut c, "The argument '")?;
c.warning(group.name)?;
c.none("' cannot be used with ")?;
let cause = match other {
Some(name) => {
let n = name.into();
v.push(n.clone());
c.none("'")?;
c.warning(&*n)?;
c.none("'")?;
format!("The argument '{}' cannot be used with '{}'", group.name, n)
}
None => {
let n = "one or more of the other specified arguments";
c.none(n)?;
format!("The argument '{}' cannot be used with {}", group.name, n)
}
};
put_usage(&mut c, usage)?;
try_help(&mut c)?;
Ok(Error {
cause,
message: c,
kind: ErrorKind::ArgumentConflict,
info: Some(v),
})
}
pub(crate) fn argument_conflict<O, U>(
pub(crate) fn argument_conflict(
arg: &Arg,
other: Option<O>,
usage: U,
other: Option<String>,
usage: String,
color: ColorChoice,
) -> io::Result<Self>
where
O: Into<String>,
U: Display,
{
let mut v = vec![arg.name.to_owned()];
) -> Self {
let mut c = Colorizer::new(true, color);
let arg = arg.to_string();
start_error(&mut c, "The argument '")?;
c.warning(&arg.to_string())?;
c.none("' cannot be used with ")?;
start_error(&mut c, "The argument '");
c.warning(arg.clone());
c.none("' cannot be used with ");
let cause = match other {
Some(name) => {
let n = name.into();
v.push(n.clone());
c.none("'")?;
c.warning(&*n)?;
c.none("'")?;
format!("The argument '{}' cannot be used with '{}'", arg, n)
match other {
Some(ref name) => {
c.none("'");
c.warning(name);
c.none("'");
}
None => {
let n = "one or more of the other specified arguments";
c.none(n)?;
format!("The argument '{}' cannot be used with {}", arg, n)
c.none("one or more of the other specified arguments");
}
};
put_usage(&mut c, usage)?;
try_help(&mut c)?;
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause,
let mut info = vec![arg];
if let Some(other) = other {
info.push(other);
}
Error {
message: c,
kind: ErrorKind::ArgumentConflict,
info: Some(v),
})
info,
}
}
pub(crate) fn empty_value<U>(arg: &Arg, usage: U, color: ColorChoice) -> io::Result<Self>
where
U: Display,
{
pub(crate) fn empty_value(arg: &Arg, usage: String, color: ColorChoice) -> Self {
let mut c = Colorizer::new(true, color);
let arg = arg.to_string();
start_error(&mut c, "The argument '")?;
c.warning(&arg.to_string())?;
c.none("' requires a value but none was supplied")?;
put_usage(&mut c, usage)?;
try_help(&mut c)?;
start_error(&mut c, "The argument '");
c.warning(arg.clone());
c.none("' requires a value but none was supplied");
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause: format!(
"The argument '{}' requires a value but none was supplied",
arg
),
Error {
message: c,
kind: ErrorKind::EmptyValue,
info: Some(vec![arg.name.to_owned()]),
})
info: vec![arg],
}
}
pub(crate) fn invalid_value<B, G, U>(
bad_val: B,
pub(crate) fn invalid_value<G>(
bad_val: String,
good_vals: &[G],
arg: &Arg,
usage: U,
usage: String,
color: ColorChoice,
) -> io::Result<Self>
) -> Self
where
B: AsRef<str>,
G: AsRef<str> + Display,
U: Display,
{
let mut c = Colorizer::new(true, color);
let suffix = suggestions::did_you_mean(bad_val.as_ref(), good_vals.iter()).pop();
let suffix = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop();
let mut sorted: Vec<String> = good_vals.iter().map(|v| v.to_string()).collect();
sorted.sort();
start_error(&mut c, "'")?;
c.warning(bad_val.as_ref())?;
c.none("' isn't a valid value for '")?;
c.warning(&arg.to_string())?;
c.none("'\n\t[possible values: ")?;
start_error(&mut c, "'");
c.warning(bad_val.clone());
c.none("' isn't a valid value for '");
c.warning(arg.to_string());
c.none("'\n\t[possible values: ");
if let Some((last, elements)) = sorted.split_last() {
for v in elements {
c.good(v)?;
c.none(", ")?;
c.good(v);
c.none(", ");
}
c.good(last)?;
c.good(last);
}
c.none("]")?;
c.none("]");
if let Some(val) = suffix {
c.none("\n\n\tDid you mean '")?;
c.good(&val)?;
c.none("'?")?;
c.none("\n\n\tDid you mean '");
c.good(val);
c.none("'?");
}
put_usage(&mut c, usage)?;
try_help(&mut c)?;
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause: format!(
"'{}' isn't a valid value for '{}'\n\t\
[possible values: {}]",
bad_val.as_ref(),
arg,
sorted.join(", ")
),
let mut info = vec![arg.to_string(), bad_val];
info.extend(sorted);
Error {
message: c,
kind: ErrorKind::InvalidValue,
info: Some(vec![arg.name.to_owned(), bad_val.as_ref().to_owned()]),
})
info: vec![],
}
}
pub(crate) fn invalid_subcommand<S, D, N, U>(
subcmd: S,
did_you_mean: D,
name: N,
usage: U,
pub(crate) fn invalid_subcommand(
subcmd: String,
did_you_mean: String,
name: String,
usage: String,
color: ColorChoice,
) -> io::Result<Self>
where
S: Into<String>,
D: AsRef<str> + Display,
N: Display,
U: Display,
{
let s = subcmd.into();
) -> Self {
let mut c = Colorizer::new(true, color);
start_error(&mut c, "The subcommand '")?;
c.warning(&*s)?;
c.none("' wasn't recognized\n\n\tDid you mean ")?;
c.good(did_you_mean.as_ref())?;
c.none("")?;
c.none(&format!(
start_error(&mut c, "The subcommand '");
c.warning(subcmd.clone());
c.none("' wasn't recognized\n\n\tDid you mean ");
c.good(did_you_mean);
c.none("");
c.none(format!(
"?\n\nIf you believe you received this message in error, try re-running with '{} ",
name
))?;
c.good("--")?;
c.none(&format!(" {}'", &*s))?;
put_usage(&mut c, usage)?;
try_help(&mut c)?;
));
c.good("--");
c.none(format!(" {}'", subcmd));
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause: format!("The subcommand '{}' wasn't recognized", s),
Error {
message: c,
kind: ErrorKind::InvalidSubcommand,
info: Some(vec![s]),
})
info: vec![subcmd],
}
}
pub(crate) fn unrecognized_subcommand<S, N>(
subcmd: S,
name: N,
pub(crate) fn unrecognized_subcommand(
subcmd: String,
name: String,
color: ColorChoice,
) -> io::Result<Self>
where
S: Into<String>,
N: Display,
{
let s = subcmd.into();
) -> Self {
let mut c = Colorizer::new(true, color);
start_error(&mut c, " The subcommand '")?;
c.warning(&*s)?;
c.none("' wasn't recognized\n\n")?;
c.warning("USAGE:")?;
c.none(&format!("\n\t{} help <subcommands>...", name))?;
try_help(&mut c)?;
start_error(&mut c, " The subcommand '");
c.warning(subcmd.clone());
c.none("' wasn't recognized\n\n");
c.warning("USAGE:");
c.none(format!("\n\t{} help <subcommands>...", name));
try_help(&mut c);
Ok(Error {
cause: format!("The subcommand '{}' wasn't recognized", s),
Error {
message: c,
kind: ErrorKind::UnrecognizedSubcommand,
info: Some(vec![s]),
})
info: vec![subcmd],
}
}
pub(crate) fn missing_required_argument<R, U>(
required: VecDeque<R>,
usage: U,
pub(crate) fn missing_required_argument(
required: Vec<String>,
usage: String,
color: ColorChoice,
) -> io::Result<Self>
where
R: Display,
U: Display,
{
) -> Self {
let mut c = Colorizer::new(true, color);
let cause = format!(
"The following required arguments were not provided:{}",
required
.iter()
.map(|x| format!("\n {}", x))
.collect::<Vec<_>>()
.join(""),
);
start_error(
&mut c,
"The following required arguments were not provided:",
)?;
);
let mut info = vec![];
for v in required {
c.none("\n ")?;
c.good(&v.to_string())?;
c.none("\n ");
c.good(v.to_string());
info.push(v.to_string());
}
put_usage(&mut c, usage)?;
try_help(&mut c)?;
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause,
Error {
message: c,
kind: ErrorKind::MissingRequiredArgument,
info: Some(info),
})
info,
}
}
pub(crate) fn missing_subcommand<N, U>(
name: N,
usage: U,
color: ColorChoice,
) -> io::Result<Self>
where
N: AsRef<str> + Display,
U: Display,
{
pub(crate) fn missing_subcommand(name: String, usage: String, color: ColorChoice) -> Self {
let mut c = Colorizer::new(true, color);
start_error(&mut c, "'")?;
c.warning(name.as_ref())?;
c.none("' requires a subcommand, but one was not provided")?;
put_usage(&mut c, usage)?;
try_help(&mut c)?;
start_error(&mut c, "'");
c.warning(name);
c.none("' requires a subcommand, but one was not provided");
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause: format!("'{}' requires a subcommand, but one was not provided", name),
Error {
message: c,
kind: ErrorKind::MissingSubcommand,
info: None,
})
info: vec![],
}
}
pub(crate) fn invalid_utf8<U>(usage: U, color: ColorChoice) -> io::Result<Self>
where
U: Display,
{
pub(crate) fn invalid_utf8(usage: String, color: ColorChoice) -> Self {
let mut c = Colorizer::new(true, color);
let cause = "Invalid UTF-8 was detected in one or more arguments";
start_error(
&mut c,
"Invalid UTF-8 was detected in one or more arguments",
);
put_usage(&mut c, usage);
try_help(&mut c);
start_error(&mut c, cause)?;
put_usage(&mut c, usage)?;
try_help(&mut c)?;
Ok(Error {
cause: cause.to_string(),
Error {
message: c,
kind: ErrorKind::InvalidUtf8,
info: None,
})
info: vec![],
}
}
pub(crate) fn too_many_values<V, U>(
val: V,
pub(crate) fn too_many_values(
val: String,
arg: &Arg,
usage: U,
usage: String,
color: ColorChoice,
) -> io::Result<Self>
where
V: AsRef<str> + Display + ToOwned,
U: Display,
{
let v = val.as_ref();
) -> Self {
let mut c = Colorizer::new(true, color);
start_error(&mut c, "The value '")?;
c.warning(v)?;
c.none("' was provided to '")?;
c.warning(&arg.to_string())?;
c.none("' but it wasn't expecting any more values")?;
put_usage(&mut c, usage)?;
try_help(&mut c)?;
start_error(&mut c, "The value '");
c.warning(val.clone());
c.none("' was provided to '");
c.warning(arg.to_string());
c.none("' but it wasn't expecting any more values");
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause: format!(
"The value '{}' was provided to '{}', but it wasn't expecting any more values",
v, arg
),
Error {
message: c,
kind: ErrorKind::TooManyValues,
info: Some(vec![arg.name.to_owned(), v.to_owned()]),
})
info: vec![arg.to_string(), val],
}
}
pub(crate) fn too_few_values<U>(
pub(crate) fn too_few_values(
arg: &Arg,
min_vals: u64,
curr_vals: usize,
usage: U,
usage: String,
color: ColorChoice,
) -> io::Result<Self>
where
U: Display,
{
) -> Self {
let mut c = Colorizer::new(true, color);
let verb = Error::singular_or_plural(curr_vals);
start_error(&mut c, "The argument '")?;
c.warning(&arg.to_string())?;
c.none("' requires at least ")?;
c.warning(&min_vals.to_string())?;
c.none(" values, but only ")?;
c.warning(&curr_vals.to_string())?;
c.none(&format!(" {} provided", verb))?;
put_usage(&mut c, usage)?;
try_help(&mut c)?;
start_error(&mut c, "The argument '");
c.warning(arg.to_string());
c.none("' requires at least ");
c.warning(min_vals.to_string());
c.none(" values, but only ");
c.warning(curr_vals.to_string());
c.none(format!(" {} provided", verb));
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause: format!(
"The argument '{}' requires at least {} values, but only {} {} provided",
arg, min_vals, curr_vals, verb
),
Error {
message: c,
kind: ErrorKind::TooFewValues,
info: Some(vec![arg.name.to_owned()]),
})
info: vec![arg.to_string(), curr_vals.to_string(), min_vals.to_string()],
}
}
pub(crate) fn value_validation(
arg: Option<&Arg>,
err: &str,
arg: String,
val: String,
err: String,
color: ColorChoice,
) -> io::Result<Self> {
) -> Self {
let mut c = Colorizer::new(true, color);
start_error(&mut c, "Invalid value")?;
start_error(&mut c, "Invalid value");
if let Some(a) = arg {
c.none(" for '")?;
c.warning(&a.to_string())?;
c.none("'")?;
}
c.none(" for '");
c.warning(arg.clone());
c.none("'");
c.none(&format!(": {}", err))?;
try_help(&mut c)?;
c.none(format!(": {}", err));
try_help(&mut c);
Ok(Error {
cause: format!(
"Invalid value{}: {}",
if let Some(a) = arg {
format!(" for '{}'", a)
} else {
String::new()
},
err
),
Error {
message: c,
kind: ErrorKind::ValueValidation,
info: None,
})
info: vec![arg, val, err],
}
}
pub(crate) fn value_validation_auto(err: &str) -> io::Result<Self> {
let n: Option<&Arg> = None;
Error::value_validation(n, err, ColorChoice::Auto)
}
pub(crate) fn wrong_number_of_values<U>(
pub(crate) fn wrong_number_of_values(
arg: &Arg,
num_vals: u64,
curr_vals: usize,
usage: U,
usage: String,
color: ColorChoice,
) -> io::Result<Self>
where
U: Display,
{
) -> Self {
let mut c = Colorizer::new(true, color);
let verb = Error::singular_or_plural(curr_vals);
start_error(&mut c, "The argument '")?;
c.warning(&arg.to_string())?;
c.none("' requires ")?;
c.warning(&num_vals.to_string())?;
c.none(" values, but ")?;
c.warning(&curr_vals.to_string())?;
c.none(&format!(" {} provided", verb))?;
put_usage(&mut c, usage)?;
try_help(&mut c)?;
start_error(&mut c, "The argument '");
c.warning(arg.to_string());
c.none("' requires ");
c.warning(num_vals.to_string());
c.none(" values, but ");
c.warning(curr_vals.to_string());
c.none(format!(" {} provided", verb));
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause: format!(
"The argument '{}' requires {} values, but {} {} provided",
arg, num_vals, curr_vals, verb
),
Error {
message: c,
kind: ErrorKind::WrongNumberOfValues,
info: Some(vec![arg.name.to_owned()]),
})
info: vec![arg.to_string(), curr_vals.to_string(), num_vals.to_string()],
}
}
pub(crate) fn unexpected_multiple_usage<U>(
arg: &Arg,
usage: U,
color: ColorChoice,
) -> io::Result<Self>
where
U: Display,
{
pub(crate) fn unexpected_multiple_usage(arg: &Arg, usage: String, color: ColorChoice) -> Self {
let mut c = Colorizer::new(true, color);
let arg = arg.to_string();
start_error(&mut c, "The argument '")?;
c.warning(&arg.to_string())?;
c.none("' was provided more than once, but cannot be used multiple times")?;
put_usage(&mut c, usage)?;
try_help(&mut c)?;
start_error(&mut c, "The argument '");
c.warning(arg.clone());
c.none("' was provided more than once, but cannot be used multiple times");
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause: format!(
"The argument '{}' was provided more than once, but cannot be used multiple times",
arg
),
Error {
message: c,
kind: ErrorKind::UnexpectedMultipleUsage,
info: Some(vec![arg.name.to_owned()]),
})
info: vec![arg],
}
}
pub(crate) fn unknown_argument<A, U>(
arg: A,
pub(crate) fn unknown_argument(
arg: String,
did_you_mean: Option<(String, Option<String>)>,
usage: U,
usage: String,
color: ColorChoice,
) -> io::Result<Self>
where
A: Into<String>,
U: Display,
{
let a = arg.into();
) -> Self {
let mut c = Colorizer::new(true, color);
start_error(&mut c, "Found argument '")?;
c.warning(&*a)?;
c.none("' which wasn't expected, or isn't valid in this context")?;
start_error(&mut c, "Found argument '");
c.warning(arg.clone());
c.none("' which wasn't expected, or isn't valid in this context");
if let Some(s) = did_you_mean {
c.none("\n\n\tDid you mean ")?;
c.none("\n\n\tDid you mean ");
if let Some(subcmd) = s.1 {
c.none("to put '")?;
c.good(&format!("--{}", &s.0))?;
c.none("' after the subcommand '")?;
c.good(&subcmd)?;
c.none("'?")?;
c.none("to put '");
c.good(format!("--{}", &s.0));
c.none("' after the subcommand '");
c.good(subcmd);
c.none("'?");
} else {
c.none("'")?;
c.good(&format!("--{}", &s.0))?;
c.none("'?")?;
c.none("'");
c.good(format!("--{}", &s.0));
c.none("'?");
}
}
c.none(&format!(
c.none(format!(
"\n\nIf you tried to supply `{}` as a PATTERN use `-- {}`",
a, a
))?;
put_usage(&mut c, usage)?;
try_help(&mut c)?;
arg, arg
));
put_usage(&mut c, usage);
try_help(&mut c);
Ok(Error {
cause: format!(
"Found argument '{}' which wasn't expected, or isn't valid in this context",
a
),
Error {
message: c,
kind: ErrorKind::UnknownArgument,
info: Some(vec![a]),
})
info: vec![arg],
}
}
pub(crate) fn argument_not_found_auto<A>(arg: A) -> io::Result<Self>
where
A: Into<String>,
{
let a = arg.into();
pub(crate) fn argument_not_found_auto(arg: String) -> Self {
let mut c = Colorizer::new(true, ColorChoice::Auto);
start_error(&mut c, "The argument '")?;
c.warning(&*a)?;
c.none("' wasn't found")?;
try_help(&mut c)?;
start_error(&mut c, "The argument '");
c.warning(arg.clone());
c.none("' wasn't found");
try_help(&mut c);
Ok(Error {
cause: format!("The argument '{}' wasn't found", a),
Error {
message: c,
kind: ErrorKind::ArgumentNotFound,
info: Some(vec![a]),
})
info: vec![arg],
}
}
/// Create an error with a custom description.
///
/// This can be used in combination with `Error::exit` to exit your program
/// with a custom error message.
pub fn with_description(description: impl Into<String>, kind: ErrorKind) -> io::Result<Self> {
pub fn with_description(description: String, kind: ErrorKind) -> Self {
let mut c = Colorizer::new(true, ColorChoice::Auto);
let cause = description.into();
start_error(&mut c, description);
start_error(&mut c, &*cause)?;
Ok(Error {
cause,
Error {
message: c,
kind,
info: None,
})
info: vec![],
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::with_description(e.to_string(), ErrorKind::Io)
.expect("Unable to build error message")
}
}
impl From<fmt::Error> for Error {
fn from(e: fmt::Error) -> Self {
Error::with_description(e.to_string(), ErrorKind::Format)
.expect("Unable to build error message")
}
}

View file

@ -14,7 +14,7 @@ use indexmap::IndexMap;
// Internal
use crate::{
parse::MatchedArg,
util::{Id, Key},
util::{termcolor::ColorChoice, Id, Key},
{Error, INVALID_UTF8},
};
@ -345,14 +345,16 @@ impl ArgMatches {
<R as FromStr>::Err: Display,
{
if let Some(v) = self.value_of(name) {
v.parse::<R>().or_else(|e| {
Err(Error::value_validation_auto(&format!(
"The argument '{}' isn't a valid value: {}",
v, e
))?)
v.parse::<R>().map_err(|e| {
let message = format!(
"The argument '{}' isn't a valid value for '{}': {}",
v, name, e
);
Error::value_validation(name.to_string(), v.to_string(), message, ColorChoice::Auto)
})
} else {
Err(Error::argument_not_found_auto(name)?)
Err(Error::argument_not_found_auto(name.to_string()))
}
}
@ -431,16 +433,20 @@ impl ArgMatches {
{
if let Some(vals) = self.values_of(name) {
vals.map(|v| {
v.parse::<R>().or_else(|e| {
Err(Error::value_validation_auto(&format!(
"The argument '{}' isn't a valid value: {}",
v, e
))?)
v.parse::<R>().map_err(|e| {
let message = format!("The argument '{}' isn't a valid value: {}", v, e);
Error::value_validation(
name.to_string(),
v.to_string(),
message,
ColorChoice::Auto,
)
})
})
.collect()
} else {
Err(Error::argument_not_found_auto(name)?)
Err(Error::argument_not_found_auto(name.to_string()))
}
}

View file

@ -2,7 +2,6 @@
use std::{
cell::Cell,
ffi::{OsStr, OsString},
io::Write,
};
// Internal
@ -483,11 +482,11 @@ impl<'help, 'app> Parser<'help, 'app> {
|| lossy_arg.parse::<f64>().is_ok())
{
return Err(ClapError::unknown_argument(
lossy_arg,
lossy_arg.to_string(),
None,
&*Usage::new(self).create_usage_with_title(&[]),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
}
}
ParseResult::Opt(ref id)
@ -537,12 +536,16 @@ impl<'help, 'app> Parser<'help, 'app> {
let cands: Vec<_> =
cands.iter().map(|cand| format!("'{}'", cand)).collect();
return Err(ClapError::invalid_subcommand(
arg_os.to_string_lossy().into_owned(),
arg_os.to_string_lossy().to_string(),
cands.join(" or "),
self.app.bin_name.as_ref().unwrap_or(&self.app.name),
&*Usage::new(self).create_usage_with_title(&[]),
self.app
.bin_name
.as_ref()
.unwrap_or(&self.app.name)
.to_string(),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
}
}
}
@ -630,11 +633,11 @@ impl<'help, 'app> Parser<'help, 'app> {
{
if p.is_set(ArgSettings::Last) && !self.is_set(AS::TrailingValues) {
return Err(ClapError::unknown_argument(
&*arg_os.to_string_lossy(),
arg_os.to_string_lossy().to_string(),
None,
&*Usage::new(self).create_usage_with_title(&[]),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
}
if !self.is_set(AS::TrailingValues)
@ -672,9 +675,9 @@ impl<'help, 'app> Parser<'help, 'app> {
None => {
if !self.is_set(AS::StrictUtf8) {
return Err(ClapError::invalid_utf8(
&*Usage::new(self).create_usage_with_title(&[]),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
}
arg_os.to_string_lossy().into_owned()
}
@ -686,9 +689,9 @@ impl<'help, 'app> Parser<'help, 'app> {
while let Some((v, _)) = it.next(None) {
if v.to_str().is_none() && !self.is_set(AS::StrictUtf8) {
return Err(ClapError::invalid_utf8(
&*Usage::new(self).create_usage_with_title(&[]),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
}
sc_m.add_val_to(&Id::empty_hash(), v.to_os_string(), ValueType::CommandLine);
}
@ -708,11 +711,11 @@ impl<'help, 'app> Parser<'help, 'app> {
&& !self.is_set(AS::InferSubcommands)
{
return Err(ClapError::unknown_argument(
&*arg_os.to_string_lossy(),
arg_os.to_string_lossy().to_string(),
None,
&*Usage::new(self).create_usage_with_title(&[]),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
} else if !has_args || self.is_set(AS::InferSubcommands) && self.has_subcommands() {
let cands = suggestions::did_you_mean(
&*arg_os.to_string_lossy(),
@ -721,26 +724,34 @@ impl<'help, 'app> Parser<'help, 'app> {
if !cands.is_empty() {
let cands: Vec<_> = cands.iter().map(|cand| format!("'{}'", cand)).collect();
return Err(ClapError::invalid_subcommand(
arg_os.to_string_lossy().into_owned(),
arg_os.to_string_lossy().to_string(),
cands.join(" or "),
self.app.bin_name.as_ref().unwrap_or(&self.app.name),
&*Usage::new(self).create_usage_with_title(&[]),
self.app
.bin_name
.as_ref()
.unwrap_or(&self.app.name)
.to_string(),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
} else {
return Err(ClapError::unrecognized_subcommand(
arg_os.to_string_lossy().into_owned(),
self.app.bin_name.as_ref().unwrap_or(&self.app.name),
arg_os.to_string_lossy().to_string(),
self.app
.bin_name
.as_ref()
.unwrap_or(&self.app.name)
.to_string(),
self.app.color(),
)?);
));
}
} else {
return Err(ClapError::unknown_argument(
&*arg_os.to_string_lossy(),
arg_os.to_string_lossy().to_string(),
None,
&*Usage::new(self).create_usage_with_title(&[]),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
}
}
@ -756,18 +767,17 @@ impl<'help, 'app> Parser<'help, 'app> {
} else if self.is_set(AS::SubcommandRequired) {
let bn = self.app.bin_name.as_ref().unwrap_or(&self.app.name);
return Err(ClapError::missing_subcommand(
bn,
&Usage::new(self).create_usage_with_title(&[]),
bn.to_string(),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
} else if self.is_set(AS::SubcommandRequiredElseHelp) {
debug!("Parser::get_matches_with: SubcommandRequiredElseHelp=true");
let message = self.write_help_err()?;
return Err(ClapError {
cause: String::new(),
message,
kind: ErrorKind::MissingArgumentOrSubcommand,
info: None,
info: vec![],
});
}
}
@ -941,10 +951,14 @@ impl<'help, 'app> Parser<'help, 'app> {
}
} else {
return Err(ClapError::unrecognized_subcommand(
cmd.to_string_lossy().into_owned(),
self.app.bin_name.as_ref().unwrap_or(&self.app.name),
cmd.to_string_lossy().to_string(),
self.app
.bin_name
.as_ref()
.unwrap_or(&self.app.name)
.to_string(),
self.app.color(),
)?);
));
}
bin_name = format!("{} {}", bin_name, &sc.name);
@ -1327,11 +1341,11 @@ impl<'help, 'app> Parser<'help, 'app> {
let arg = format!("-{}", c);
return Err(ClapError::unknown_argument(
&*arg,
arg,
None,
&*Usage::new(self).create_usage_with_title(&[]),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
}
}
Ok(ret)
@ -1360,9 +1374,9 @@ impl<'help, 'app> Parser<'help, 'app> {
debug!("Found Empty - Error");
return Err(ClapError::empty_value(
opt,
&*Usage::new(self).create_usage_with_title(&[]),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
}
debug!("Found - {:?}, len: {}", v, v.len());
debug!(
@ -1375,9 +1389,9 @@ impl<'help, 'app> Parser<'help, 'app> {
debug!("None, but requires equals...Error");
return Err(ClapError::empty_value(
opt,
&*Usage::new(self).create_usage_with_title(&[]),
Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
));
} else if needs_eq && min_vals_zero {
debug!("None and requires equals, but min_vals == 0");
if !opt.default_missing_vals.is_empty() {
@ -1722,24 +1736,16 @@ impl<'help, 'app> Parser<'help, 'app> {
.collect();
Err(ClapError::unknown_argument(
&*format!("--{}", arg),
format!("--{}", arg),
did_you_mean,
&*Usage::new(self).create_usage_with_title(&*used),
Usage::new(self).create_usage_with_title(&*used),
self.app.color(),
)?)
}
// Prints the version to the user and exits if quit=true
fn print_version<W: Write>(&self, w: &mut W, use_long: bool) -> ClapResult<()> {
self.app._write_version(w, use_long)?;
w.flush().map_err(ClapError::from)
))
}
pub(crate) fn write_help_err(&self) -> ClapResult<Colorizer> {
let mut c = Colorizer::new(true, self.color_help());
Help::new(HelpWriter::Buffer(&mut c), self, false).write_help()?;
Ok(c)
}
@ -1753,12 +1759,11 @@ impl<'help, 'app> Parser<'help, 'app> {
let mut c = Colorizer::new(false, self.color_help());
match Help::new(HelpWriter::Buffer(&mut c), self, use_long).write_help() {
Err(e) => e,
Err(e) => e.into(),
_ => ClapError {
cause: String::new(),
message: c,
kind: ErrorKind::DisplayHelp,
info: None,
info: vec![],
},
}
}
@ -1766,16 +1771,13 @@ impl<'help, 'app> Parser<'help, 'app> {
fn version_err(&self, use_long: bool) -> ClapError {
debug!("Parser::version_err");
let mut c = Colorizer::new(false, self.app.color());
match self.print_version(&mut c, use_long) {
Err(e) => e,
_ => ClapError {
cause: String::new(),
message: c,
kind: ErrorKind::DisplayVersion,
info: None,
},
let msg = self.app._render_version(use_long);
let mut c = Colorizer::new(false, self.color_help());
c.none(msg);
ClapError {
message: c,
kind: ErrorKind::DisplayVersion,
info: vec![],
}
}
}

View file

@ -47,9 +47,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
if should_err {
return Err(Error::empty_value(
o,
&*Usage::new(self.p).create_usage_with_title(&[]),
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
)?);
));
}
}
@ -59,10 +59,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
{
let message = self.p.write_help_err()?;
return Err(Error {
cause: String::new(),
message,
kind: ErrorKind::MissingArgumentOrSubcommand,
info: None,
info: vec![],
});
}
self.validate_conflicts(matcher)?;
@ -89,9 +88,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
val
);
return Err(Error::invalid_utf8(
&*Usage::new(self.p).create_usage_with_title(&[]),
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
)?);
));
}
if !arg.possible_vals.is_empty() {
debug!(
@ -117,12 +116,12 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
.cloned()
.collect();
return Err(Error::invalid_value(
val_str,
val_str.to_string(),
&arg.possible_vals,
arg,
&*Usage::new(self.p).create_usage_with_title(&*used),
Usage::new(self.p).create_usage_with_title(&used),
self.p.app.color(),
)?);
));
}
}
if !arg.is_set(ArgSettings::AllowEmptyValues)
@ -132,9 +131,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
debug!("Validator::validate_arg_values: illegal empty val found");
return Err(Error::empty_value(
arg,
&*Usage::new(self.p).create_usage_with_title(&[]),
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
)?);
));
}
// FIXME: `(&mut *vtor)(args...)` can be simplified to `vtor(args...)`
@ -145,7 +144,12 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
let mut vtor = vtor.lock().unwrap();
if let Err(e) = (&mut *vtor)(&*val.to_string_lossy()) {
debug!("error");
return Err(Error::value_validation(Some(arg), &e, self.p.app.color())?);
return Err(Error::value_validation(
arg.to_string(),
val.to_string_lossy().to_string(),
e,
self.p.app.color(),
));
} else {
debug!("good");
}
@ -156,10 +160,11 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
if let Err(e) = (&mut *vtor)(val) {
debug!("error");
return Err(Error::value_validation(
Some(arg),
&(*e).to_string(),
arg.to_string(),
val.to_string_lossy().into(),
e,
self.p.app.color(),
)?);
));
} else {
debug!("good");
}
@ -213,9 +218,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
return Err(Error::argument_conflict(
latter_arg,
Some(former_arg.to_string()),
&*usg,
usg,
self.p.app.color(),
)?);
));
}
}
}
@ -234,9 +239,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
return Err(Error::argument_conflict(
&self.p.app[first],
c_with,
&*usg,
usg,
self.p.app.color(),
)?);
));
}
panic!(INTERNAL_ERROR_MSG);
@ -301,7 +306,7 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
c_with,
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
)?);
));
}
}
}
@ -428,9 +433,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
// Not the first time, and we don't allow multiples
return Err(Error::unexpected_multiple_usage(
a,
&*Usage::new(self.p).create_usage_with_title(&[]),
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
)?);
));
}
Ok(())
}
@ -454,9 +459,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
} else {
ma.vals.len()
},
&*Usage::new(self.p).create_usage_with_title(&[]),
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
)?);
));
}
}
if let Some(num) = a.max_vals {
@ -469,11 +474,12 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
.last()
.expect(INTERNAL_ERROR_MSG)
.to_str()
.expect(INVALID_UTF8),
.expect(INVALID_UTF8)
.to_string(),
a,
&*Usage::new(self.p).create_usage_with_title(&[]),
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
)?);
));
}
}
let min_vals_zero = if let Some(num) = a.min_vals {
@ -484,9 +490,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
a,
num,
ma.vals.len(),
&*Usage::new(self.p).create_usage_with_title(&[]),
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
)?);
));
}
num == 0
} else {
@ -497,9 +503,9 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
if a.is_set(ArgSettings::TakesValue) && !min_vals_zero && ma.vals.is_empty() {
return Err(Error::empty_value(
a,
&*Usage::new(self.p).create_usage_with_title(&[]),
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
)?);
));
}
Ok(())
}
@ -650,8 +656,8 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
Err(Error::missing_required_argument(
req_args,
&*usg.create_usage_with_title(&*used),
usg.create_usage_with_title(&*used),
self.p.app.color(),
)?)
))
}
}

View file

@ -1,5 +1,3 @@
use std::io::{stderr, stdout, Result, Write};
#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum ColorChoice {
Auto,
@ -7,32 +5,9 @@ pub(crate) enum ColorChoice {
Never,
}
pub(crate) type Buffer = Vec<u8>;
pub(crate) struct BufferWriter {
use_stderr: bool,
}
impl BufferWriter {
pub(crate) fn buffer(&self) -> Buffer {
vec![]
}
pub(crate) fn stderr(_: ColorChoice) -> Self {
Self { use_stderr: true }
}
pub(crate) fn stdout(_: ColorChoice) -> Self {
Self { use_stderr: false }
}
pub(crate) fn print(&self, buf: &Buffer) -> Result<()> {
if self.use_stderr {
stderr().lock().write_all(buf)?;
} else {
stdout().lock().write_all(buf)?;
}
Ok(())
}
#[derive(Debug)]
pub(crate) enum Color {
Green,
Yellow,
Red,
}

View file

@ -188,9 +188,11 @@ fn two_conflicting_arguments() {
assert!(a.is_err());
let a = a.unwrap_err();
assert_eq!(
a.cause,
"The argument \'--production\' cannot be used with \'--develop\'"
assert!(
a.to_string()
.contains("The argument \'--production\' cannot be used with \'--develop\'"),
"{}",
a
);
}
@ -216,9 +218,11 @@ fn three_conflicting_arguments() {
assert!(a.is_err());
let a = a.unwrap_err();
assert_eq!(
a.cause,
"The argument \'--two\' cannot be used with \'--one\'"
assert!(
a.to_string()
.contains("The argument \'--two\' cannot be used with \'--one\'"),
"{}",
a
);
}

View file

@ -39,9 +39,11 @@ fn test_validator_msg_newline() {
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(
err.cause,
"Invalid value for \'<test>\': invalid digit found in string"
assert!(
err.to_string()
.contains("Invalid value for '<test>': invalid digit found in string"),
"{}",
err
);
// This message is the only thing that gets printed -- make sure it ends with a newline

View file

@ -40,9 +40,7 @@ fn complex_version_output() {
let _ = a.try_get_matches_from_mut(vec![""]);
// Now we check the output of print_version()
let mut ver = vec![];
a.write_version(&mut ver).unwrap();
assert_eq!(str::from_utf8(&ver).unwrap(), VERSION);
assert_eq!(a.render_version(), VERSION);
}
#[test]