From 070bd50b285ab59236464668a9d99414a6ab9270 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Sun, 12 Apr 2020 03:39:13 +0200 Subject: [PATCH] Finished color refactor --- Cargo.toml | 4 +- clap_derive/tests/custom-string-parsers.rs | 5 +- clap_derive/tests/non_literal_attributes.rs | 5 +- clap_derive/tests/utils.rs | 12 +- clap_generate/src/generators/shells/bash.rs | 2 +- src/build/app/mod.rs | 61 +- src/macros.rs | 14 +- src/output/fmt.rs | 233 +++--- src/output/help.rs | 385 +++++----- src/output/mod.rs | 4 +- src/parse/errors.rs | 756 ++++++++++---------- src/parse/features/suggestions.rs | 53 +- src/parse/matches/arg_matches.rs | 18 +- src/parse/parser.rs | 92 ++- src/parse/validator.rs | 62 +- src/util/mod.rs | 6 + src/util/termcolor.rs | 38 + tests/app_from_crate.rs | 2 +- tests/cargo.rs | 11 +- tests/multiple_occurrences.rs | 16 +- tests/opts.rs | 8 +- tests/possible_values.rs | 1 - tests/subcommands.rs | 6 +- tests/utils.rs | 4 +- tests/version.rs | 4 +- 25 files changed, 861 insertions(+), 941 deletions(-) create mode 100644 src/util/termcolor.rs diff --git a/Cargo.toml b/Cargo.toml index d3deeee7..a23fad00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ indexmap = "1.0.1" strsim = { version = "0.9.0", optional = true } yaml-rust = { version = "0.4.1", optional = true } atty = { version = "0.2", optional = true } -ansi_term = { version = "0.12", optional = true } +termcolor = { version = "1.1", optional = true } vec_map = { version = "0.8", optional = true } term_size = { version = "1.0.0-beta1", optional = true } lazy_static = { version = "1", optional = true } @@ -85,7 +85,7 @@ criterion = { git = "git://github.com/pksunkara/criterion.rs", version = "0.3" } default = ["suggestions", "color", "vec_map", "derive", "std", "cargo"] std = [] # support for no_std in a backwards-compatible way suggestions = ["strsim"] -color = ["ansi_term", "atty"] +color = ["atty", "termcolor"] wrap_help = ["term_size", "textwrap/term_size"] derive = ["clap_derive", "lazy_static"] yaml = ["yaml-rust"] diff --git a/clap_derive/tests/custom-string-parsers.rs b/clap_derive/tests/custom-string-parsers.rs index 89e2f5e4..2ff9b4cc 100644 --- a/clap_derive/tests/custom-string-parsers.rs +++ b/clap_derive/tests/custom-string-parsers.rs @@ -81,7 +81,10 @@ fn test_parse_hex() { ); let err = HexOpt::try_parse_from(&["test", "-n", "gg"]).unwrap_err(); - assert!(err.message.contains("invalid digit found in string"), err); + assert!( + err.to_string().contains("invalid digit found in string"), + err + ); } fn custom_parser_1(_: &str) -> &'static str { diff --git a/clap_derive/tests/non_literal_attributes.rs b/clap_derive/tests/non_literal_attributes.rs index 7111e276..b240ebb6 100644 --- a/clap_derive/tests/non_literal_attributes.rs +++ b/clap_derive/tests/non_literal_attributes.rs @@ -147,5 +147,8 @@ fn test_parse_hex_function_path() { ); let err = HexOpt::try_parse_from(&["test", "-n", "gg"]).unwrap_err(); - assert!(err.message.contains("invalid digit found in string"), err); + assert!( + err.to_string().contains("invalid digit found in string"), + err + ); } diff --git a/clap_derive/tests/utils.rs b/clap_derive/tests/utils.rs index 1deb2809..bc5f3d9c 100644 --- a/clap_derive/tests/utils.rs +++ b/clap_derive/tests/utils.rs @@ -5,7 +5,7 @@ // Accept and endure. Do not touch. #![allow(unused)] -use clap::IntoApp; +use clap::{find_subcmd_mut, match_alias, IntoApp}; pub fn get_help() -> String { let mut output = Vec::new(); @@ -32,10 +32,12 @@ pub fn get_long_help() -> String { } pub fn get_subcommand_long_help(subcmd: &str) -> String { - let output = ::into_app() - .try_get_matches_from(vec!["test", subcmd, "--help"]) - .expect_err("") - .message; + let mut output = Vec::new(); + find_subcmd_mut!(::into_app(), subcmd) + .unwrap() + .write_long_help(&mut output) + .unwrap(); + let output = String::from_utf8(output).unwrap(); eprintln!( "\n%%% SUBCOMMAND `{}` HELP %%%:=====\n{}\n=====\n", diff --git a/clap_generate/src/generators/shells/bash.rs b/clap_generate/src/generators/shells/bash.rs index c5eef8fc..34271387 100644 --- a/clap_generate/src/generators/shells/bash.rs +++ b/clap_generate/src/generators/shells/bash.rs @@ -178,7 +178,7 @@ fn option_details_for_path(app: &App, path: &str) -> String { } fn vals_for(o: &Arg) -> String { - debugln!("Bash::vals_for: o={}", o.name); + debugln!("Bash::vals_for: o={}", o.get_name()); if let Some(ref vals) = o.get_possible_values() { format!("$(compgen -W \"{}\" -- ${{cur}})", vals.join(" ")) diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index 76d2aabc..1720f114 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use std::env; use std::ffi::OsString; use std::fmt; -use std::io::{self, BufRead, BufWriter, Write}; +use std::io::{self, BufRead, Write}; use std::path::Path; use std::process; @@ -20,11 +20,10 @@ use yaml_rust::Yaml; // Internal use crate::build::{app::settings::AppFlags, Arg, ArgGroup, ArgSettings}; use crate::mkeymap::MKeyMap; -use crate::output::fmt::ColorWhen; -use crate::output::{Help, Usage}; +use crate::output::{fmt::Colorizer, Help, HelpWriter, Usage}; use crate::parse::errors::Result as ClapResult; use crate::parse::{ArgMatcher, ArgMatches, Input, Parser}; -use crate::util::{Id, Key}; +use crate::util::{termcolor::ColorChoice, Id, Key}; use crate::INTERNAL_ERROR_MSG; // FIXME (@CreepySkeleton): some of this variants are never constructed @@ -1087,13 +1086,14 @@ impl<'b> App<'b> { /// [`-h` (short)]: ./struct.Arg.html#method.help /// [`--help` (long)]: ./struct.Arg.html#method.long_help pub fn print_help(&mut self) -> ClapResult<()> { - // If there are global arguments, or settings we need to propagate them down to subcommands - // before parsing incase we run into a subcommand self._build(); - let out = io::stdout(); - let mut buf_w = BufWriter::new(out.lock()); - self.write_help(&mut buf_w) + 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()?) } /// Prints the full help message to [`io::stdout()`] using a [`BufWriter`] using the same @@ -1114,13 +1114,14 @@ impl<'b> App<'b> { /// [`-h` (short)]: ./struct.Arg.html#method.help /// [`--help` (long)]: ./struct.Arg.html#method.long_help pub fn print_long_help(&mut self) -> ClapResult<()> { - // If there are global arguments, or settings we need to propagate them down to subcommands - // before parsing incase we run into a subcommand self._build(); - let out = io::stdout(); - let mut buf_w = BufWriter::new(out.lock()); - self.write_long_help(&mut buf_w) + 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()?) } /// Writes the full help message to the user to a [`io::Write`] object in the same method as if @@ -1145,7 +1146,7 @@ impl<'b> App<'b> { self._build(); let p = Parser::new(self); - Help::new(w, &p, false, false).write_help() + Help::new(HelpWriter::Normal(w), &p, false).write_help() } /// Writes the full help message to the user to a [`io::Write`] object in the same method as if @@ -1170,7 +1171,7 @@ impl<'b> App<'b> { self._build(); let p = Parser::new(self); - Help::new(w, &p, true, false).write_help() + Help::new(HelpWriter::Normal(w), &p, true).write_help() } /// Writes the version message to the user to a [`io::Write`] object as if the user ran `-V`. @@ -1264,13 +1265,15 @@ impl<'b> App<'b> { .unwrap_or_else(|e| { // Otherwise, write to stderr and exit if e.use_stderr() { - wlnerr!("{}", e.message); + e.message.print().expect("Error writing Error to stderr"); + if self.settings.is_set(AppSettings::WaitOnError) { wlnerr!("\nPress [ENTER] / [RETURN] to continue..."); let mut s = String::new(); let i = io::stdin(); i.lock().read_line(&mut s).unwrap(); } + drop(e); process::exit(2); } @@ -1294,7 +1297,7 @@ impl<'b> App<'b> { /// let matches = App::new("myprog") /// // Args and options go here... /// .try_get_matches() - /// .unwrap_or_else( |e| e.exit() ); + /// .unwrap_or_else(|e| e.exit()); /// ``` /// [`env::args_os`]: https://doc.rust-lang.org/std/env/fn.args_os.html /// [`ErrorKind::HelpDisplayed`]: ./enum.ErrorKind.html#variant.HelpDisplayed @@ -1338,13 +1341,15 @@ impl<'b> App<'b> { self.try_get_matches_from_mut(itr).unwrap_or_else(|e| { // Otherwise, write to stderr and exit if e.use_stderr() { - wlnerr!("{}", e.message); + e.message.print().expect("Error writing Error to stderr"); + if self.settings.is_set(AppSettings::WaitOnError) { wlnerr!("\nPress [ENTER] / [RETURN] to continue..."); let mut s = String::new(); let i = io::stdin(); i.lock().read_line(&mut s).unwrap(); } + drop(self); drop(e); process::exit(2); @@ -1375,7 +1380,7 @@ impl<'b> App<'b> { /// let matches = App::new("myprog") /// // Args and options go here... /// .try_get_matches_from(arg_vec) - /// .unwrap_or_else( |e| { panic!("An error occurs: {}", e) }); + /// .unwrap_or_else(|e| e.exit()); /// ``` /// [`App::get_matches_from`]: ./struct.App.html#method.get_matches_from /// [`App::try_get_matches`]: ./struct.App.html#method.try_get_matches @@ -1411,7 +1416,7 @@ impl<'b> App<'b> { /// let mut app = App::new("myprog"); /// // Args and options go here... /// let matches = app.try_get_matches_from_mut(arg_vec) - /// .unwrap_or_else( |e| { panic!("An error occurs: {}", e) }); + /// .unwrap_or_else(|e| e.exit()); /// ``` /// [`App`]: ./struct.App.html /// [`App::try_get_matches_from`]: ./struct.App.html#method.try_get_matches_from @@ -1481,9 +1486,6 @@ impl<'b> App<'b> { pub fn _build(&mut self) { debugln!("App::_build;"); - #[cfg(all(feature = "color", windows))] - let _ = ansi_term::enable_ansi_support(); - // Make sure all the globally set flags apply to us as well self.settings = self.settings | self.g_settings; @@ -1896,19 +1898,20 @@ impl<'b> App<'b> { self.args.args.iter().find(|a| a.id == *arg_id) } - // Should we color the output? None=determined by output location, true=yes, false=no - pub(crate) fn color(&self) -> ColorWhen { + // Should we color the output? + pub(crate) fn color(&self) -> ColorChoice { debugln!("App::color;"); debug!("App::color: Color setting..."); + if self.is_set(AppSettings::ColorNever) { sdebugln!("Never"); - ColorWhen::Never + ColorChoice::Never } else if self.is_set(AppSettings::ColorAlways) { sdebugln!("Always"); - ColorWhen::Always + ColorChoice::Always } else { sdebugln!("Auto"); - ColorWhen::Auto + ColorChoice::Auto } } diff --git a/src/macros.rs b/src/macros.rs index da8fd248..eceb075d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -768,19 +768,6 @@ mod debug_macros { } } -// Helper/deduplication macro for printing the correct number of spaces in help messages -// used in: -// src/args/arg_builder/*.rs -// src/app/mod.rs -macro_rules! write_nspaces { - ($dst:expr, $num:expr) => {{ - debugln!("write_spaces!: num={}", $num); - for _ in 0..$num { - $dst.write_all(b" ")?; - } - }}; -} - #[macro_export] #[doc(hidden)] macro_rules! flags { @@ -853,6 +840,7 @@ macro_rules! find_subcmd { macro_rules! find_subcmd_mut { ($app:expr, $sc:expr) => {{ $app.get_subcommands_mut() + .iter_mut() .find(|a| match_alias!(a, $sc, a.get_name())) }}; } diff --git a/src/output/fmt.rs b/src/output/fmt.rs index c4e55a64..a30ba128 100644 --- a/src/output/fmt.rs +++ b/src/output/fmt.rs @@ -1,187 +1,120 @@ +use crate::util::termcolor::{Buffer, BufferWriter, ColorChoice}; #[cfg(feature = "color")] -use ansi_term::{ - ANSIString, - Colour::{Green, Red, Yellow}, -}; +use crate::util::termcolor::{Color, ColorSpec, WriteColor}; -use std::env; -use std::fmt; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum ColorWhen { - Auto, - Always, - Never, -} +use std::fmt::{self, Debug, Formatter}; +use std::io::{Result, Write}; #[cfg(feature = "color")] -pub(crate) fn is_a_tty(stderr: bool) -> bool { +fn is_a_tty(stderr: bool) -> bool { debugln!("is_a_tty: stderr={:?}", stderr); + let stream = if stderr { atty::Stream::Stderr } else { atty::Stream::Stdout }; + atty::is(stream) } #[cfg(not(feature = "color"))] -pub(crate) fn is_a_tty(_: bool) -> bool { +fn is_a_tty(_: bool) -> bool { debugln!("is_a_tty;"); false } -pub(crate) fn is_term_dumb() -> bool { - env::var("TERM").ok() == Some(String::from("dumb")) -} - -pub(crate) struct ColorizerOption { - pub(crate) use_stderr: bool, - pub(crate) when: ColorWhen, -} - pub(crate) struct Colorizer { - when: ColorWhen, + writer: BufferWriter, + buffer: Buffer, } -macro_rules! color { - ($_self:ident, $c:ident, $m:expr) => { - match $_self.when { - ColorWhen::Auto => Format::$c($m), - ColorWhen::Always => Format::$c($m), - ColorWhen::Never => Format::None($m), - } - }; +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)) + } } impl Colorizer { - pub(crate) fn new(option: &ColorizerOption) -> Colorizer { - let is_a_tty = is_a_tty(option.use_stderr); - let is_term_dumb = is_term_dumb(); - Colorizer { - when: if is_a_tty && !is_term_dumb { - option.when - } else { - ColorWhen::Never - }, - } + pub(crate) fn new(use_stderr: bool, when: ColorChoice) -> Self { + let checked_when = if is_a_tty(use_stderr) { + when + } else { + ColorChoice::Never + }; + + let writer = if use_stderr { + BufferWriter::stderr(checked_when) + } else { + BufferWriter::stdout(checked_when) + }; + + let buffer = writer.buffer(); + + Self { writer, buffer } } - pub(crate) fn good(&self, msg: T) -> Format - where - T: fmt::Display + AsRef, - { - debugln!("Colorizer::good;"); - color!(self, Good, msg) + pub(crate) fn print(&self) -> Result<()> { + self.writer.print(&self.buffer) } - pub(crate) fn warning(&self, msg: T) -> Format - where - T: fmt::Display + AsRef, - { - debugln!("Colorizer::warning;"); - color!(self, Warning, msg) + #[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() } - pub(crate) fn error(&self, msg: T) -> Format - where - T: fmt::Display + AsRef, - { - debugln!("Colorizer::error;"); - color!(self, Error, msg) + #[cfg(not(feature = "color"))] + pub(crate) fn good(&mut self, msg: &str) -> Result<()> { + self.none(msg) } - pub(crate) fn none(&self, msg: T) -> Format - where - T: fmt::Display + AsRef, - { - debugln!("Colorizer::none;"); - Format::None(msg) + #[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() + } + + #[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 Default for Colorizer { - fn default() -> Self { - Colorizer::new(&ColorizerOption { - use_stderr: true, - when: ColorWhen::Auto, - }) - } -} - -/// Defines styles for different types of error messages. Defaults to Error=Red, Warning=Yellow, -/// and Good=Green -#[derive(Debug)] -pub(crate) enum Format { - /// Defines the style used for errors, defaults to Red - Error(T), - /// Defines the style used for warnings, defaults to Yellow - Warning(T), - /// Defines the style used for good values, defaults to Green - Good(T), - /// Defines no formatting style - None(T), -} - -#[cfg(feature = "color")] -impl> Format { - fn format(&self) -> ANSIString { - match *self { - Format::Error(ref e) => Red.bold().paint(e.as_ref()), - Format::Warning(ref e) => Yellow.paint(e.as_ref()), - Format::Good(ref e) => Green.paint(e.as_ref()), - Format::None(ref e) => ANSIString::from(e.as_ref()), - } - } -} - -#[cfg(not(feature = "color"))] -impl Format { - fn format(&self) -> &T { - match *self { - Format::Error(ref e) => e, - Format::Warning(ref e) => e, - Format::Good(ref e) => e, - Format::None(ref e) => e, - } - } -} - -#[cfg(feature = "color")] -impl> fmt::Display for Format { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", &self.format()) - } -} - -#[cfg(not(feature = "color"))] -impl fmt::Display for Format { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", &self.format()) - } -} - -#[cfg(all(test, feature = "color"))] -mod test { - use super::Format; - use ansi_term::ANSIString; - use ansi_term::Colour::{Green, Red, Yellow}; - - #[test] - fn colored_output() { - let err = Format::Error("error"); - assert_eq!( - &*format!("{}", err), - &*format!("{}", Red.bold().paint("error")) - ); - let good = Format::Good("good"); - assert_eq!(&*format!("{}", good), &*format!("{}", Green.paint("good"))); - let warn = Format::Warning("warn"); - assert_eq!(&*format!("{}", warn), &*format!("{}", Yellow.paint("warn"))); - let none = Format::None("none"); - assert_eq!( - &*format!("{}", none), - &*format!("{}", ANSIString::from("none")) - ); +impl Write for Colorizer { + fn write(&mut self, buf: &[u8]) -> Result { + self.buffer.write(buf) + } + + fn flush(&mut self) -> Result<()> { + self.buffer.flush() } } diff --git a/src/output/help.rs b/src/output/help.rs index 0e6efd62..b5ecee5a 100644 --- a/src/output/help.rs +++ b/src/output/help.rs @@ -7,8 +7,7 @@ use std::usize; // Internal use crate::build::{App, AppSettings, Arg, ArgSettings}; -use crate::output::fmt::{Colorizer, ColorizerOption, Format}; -use crate::output::Usage; +use crate::output::{fmt::Colorizer, Usage}; use crate::parse::errors::{Error, Result as ClapResult}; use crate::parse::Parser; use crate::util::VecMap; @@ -29,17 +28,36 @@ fn str_width(s: &str) -> usize { const TAB: &str = " "; +pub(crate) enum HelpWriter<'w> { + Normal(&'w mut dyn Write), + Buffer(&'w mut Colorizer), +} + +impl<'w> Write for HelpWriter<'w> { + fn write(&mut self, buf: &[u8]) -> io::Result { + 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. pub(crate) struct Help<'b, 'c, 'd, 'w> { - writer: &'w mut dyn Write, + writer: HelpWriter<'w>, parser: &'d Parser<'b, 'c>, next_line_help: bool, hide_pv: bool, term_w: usize, - color: bool, - cizer: Colorizer, longest: usize, force_next_line: bool, use_long: bool, @@ -48,12 +66,7 @@ pub(crate) struct Help<'b, 'c, 'd, 'w> { // Public Functions impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { /// Create a new `Help` instance. - pub(crate) fn new( - w: &'w mut dyn Write, - parser: &'d Parser<'b, 'c>, - use_long: bool, - stderr: bool, - ) -> Self { + pub(crate) fn new(w: HelpWriter<'w>, parser: &'d Parser<'b, 'c>, use_long: bool) -> Self { debugln!("Help::new;"); let term_w = match parser.app.term_w { Some(0) => usize::MAX, @@ -68,21 +81,15 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { }; let nlh = parser.is_set(AppSettings::NextLineHelp); let hide_pv = parser.is_set(AppSettings::HidePossibleValuesInHelp); - let color = parser.is_set(AppSettings::ColoredHelp); - let cizer = Colorizer::new(&ColorizerOption { - use_stderr: stderr, - when: parser.app.color(), - }); + Help { writer: w, parser, next_line_help: nlh, hide_pv, term_w, - color, longest: 0, force_next_line: false, - cizer, use_long, } } @@ -90,47 +97,56 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { /// Writes the parser help to the wrapped stream. pub(crate) fn write_help(&mut self) -> ClapResult<()> { debugln!("Help::write_help;"); + if let Some(h) = self.parser.app.help_str { - write!(self.writer, "{}", h).map_err(Error::from)?; + self.none(h).map_err(Error::from)?; } else if let Some(tmpl) = self.parser.app.template { self.write_templated_help(tmpl)?; } else { self.write_default_help()?; } - writeln!(self.writer)?; + self.none("\n")?; Ok(()) } } +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), + } + }; +} + +macro_rules! write_nspaces { + ($_self:ident, $num:expr) => {{ + debugln!("write_nspaces!: num={}", $num); + for _ in 0..$num { + $_self.none(" ")?; + } + }}; +} + // Methods to write Arg help. impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { - fn color(&mut self, f: Format<&str>) -> io::Result<()> { - match f { - Format::Good(g) => { - if self.color { - write!(self.writer, "{}", self.cizer.good(g)) - } else { - write!(self.writer, "{}", g) - } - } - Format::Warning(w) => { - if self.color { - write!(self.writer, "{}", self.cizer.warning(w)) - } else { - write!(self.writer, "{}", w) - } - } - Format::Error(e) => { - if self.color { - write!(self.writer, "{}", self.cizer.error(e)) - } else { - write!(self.writer, "{}", e) - } - } - _ => unreachable!(), - } + fn good(&mut self, msg: &str) -> io::Result<()> { + write_method!(self, msg, good) + } + + fn warning(&mut self, msg: &str) -> io::Result<()> { + write_method!(self, msg, warning) + } + + #[allow(dead_code)] + fn error(&mut self, msg: &str) -> io::Result<()> { + write_method!(self, msg, error) + } + + fn none(&mut self, msg: &str) -> io::Result<()> { + write_method!(self, msg, none) } /// Writes help for each argument in the order they were declared to the wrapped stream. @@ -152,7 +168,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { if first { first = false; } else { - self.writer.write_all(b"\n")?; + self.none("\n")?; } self.write_arg(arg, i < arg_c)?; } @@ -189,7 +205,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { if first { first = false; } else { - self.writer.write_all(b"\n")?; + self.none("\n")?; } self.write_arg(arg, false)?; } @@ -210,11 +226,13 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { /// Writes argument's short command to the wrapped stream. fn short(&mut self, arg: &Arg<'c>) -> io::Result<()> { debugln!("Help::short;"); - write!(self.writer, "{}", TAB)?; + + self.none(TAB)?; + if let Some(s) = arg.short { - self.color(Format::Good(&*format!("-{}", s))) + self.good(&format!("-{}", s)) } else if arg.has_switch() { - write!(self.writer, "{}", TAB) + self.none(TAB) } else { Ok(()) } @@ -229,9 +247,9 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { if arg.is_set(ArgSettings::TakesValue) { if let Some(l) = arg.long { if arg.short.is_some() { - write!(self.writer, ", ")?; + self.none(", ")?; } - self.color(Format::Good(&*format!("--{}", l)))? + self.good(&format!("--{}", l))? } let sep = if arg.is_set(ArgSettings::RequireEquals) { @@ -239,12 +257,12 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { } else { " " }; - write!(self.writer, "{}", sep)?; + self.none(sep)?; } else if let Some(l) = arg.long { if arg.short.is_some() { - write!(self.writer, ", ")?; + self.none(", ")?; } - self.color(Format::Good(&*format!("--{}", l)))?; + self.good(&format!("--{}", l))?; } Ok(()) } @@ -263,33 +281,33 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { if let Some(ref vec) = arg.val_names { let mut it = vec.iter().peekable(); while let Some((_, val)) = it.next() { - self.color(Format::Good(&*format!("<{}>", val)))?; + self.good(&format!("<{}>", val))?; if it.peek().is_some() { - write!(self.writer, "{}", delim)?; + self.none(&delim.to_string())?; } } let num = vec.len(); if mult && num == 1 { - self.color(Format::Good("..."))?; + self.good("...")?; } } else if let Some(num) = arg.num_vals { let mut it = (0..num).peekable(); while let Some(_) = it.next() { - self.color(Format::Good(&*format!("<{}>", arg.name)))?; + self.good(&format!("<{}>", arg.name))?; if it.peek().is_some() { - write!(self.writer, "{}", delim)?; + self.none(&delim.to_string())?; } } if mult && num == 1 { - self.color(Format::Good("..."))?; + self.good("...")?; } } else if arg.has_switch() { - self.color(Format::Good(&*format!("<{}>", arg.name)))?; + self.good(&format!("<{}>", arg.name))?; if mult { - self.color(Format::Good("..."))?; + self.good("...")?; } } else { - self.color(Format::Good(&*arg.to_string()))?; + self.good(&arg.to_string())?; } } @@ -332,14 +350,14 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { spcs += 8; } - write_nspaces!(self.writer, spcs); + write_nspaces!(self, spcs); } else { sdebugln!("Yes"); } } else if !(nlh || self.force_next_line) { sdebugln!("No, and not next_line"); write_nspaces!( - self.writer, + self, self.longest + 4 - (str_width(arg.to_string().as_str())) ); } else { @@ -375,7 +393,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { } else { sdebugln!("No"); } - write!(self.writer, "{}", help)?; + self.none(&help)?; Ok(()) } @@ -401,7 +419,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { // Is help on next line, if so then indent if nlh || self.force_next_line { - write!(self.writer, "\n{}{}{}", TAB, TAB, TAB)?; + self.none(&format!("\n{}{}{}", TAB, TAB, TAB))?; } debug!("Help::help: Too long..."); @@ -417,21 +435,21 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { sdebugln!("No"); } if let Some(part) = help.lines().next() { - write!(self.writer, "{}", part)?; + self.none(part)?; } for part in help.lines().skip(1) { - writeln!(self.writer)?; + self.none("\n")?; if nlh || self.force_next_line { - write!(self.writer, "{}{}{}", TAB, TAB, TAB)?; + self.none(&format!("{}{}{}", TAB, TAB, TAB))?; } else if arg.has_switch() { - write_nspaces!(self.writer, self.longest + 12); + write_nspaces!(self, self.longest + 12); } else { - write_nspaces!(self.writer, self.longest + 8); + write_nspaces!(self, self.longest + 8); } - write!(self.writer, "{}", part)?; + self.none(part)?; } if !prevent_nlh && !help.contains('\n') && (nlh || self.force_next_line) { - writeln!(self.writer)?; + self.none("\n")?; } Ok(()) } @@ -462,37 +480,25 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { if let Some(ref pv) = a.default_vals { debugln!("Help::spec_vals: Found default value...[{:?}]", pv); - let pvs = if self.color { - pv.iter() - .map(|&pvs| format!("{}", self.cizer.good(pvs.to_string_lossy()))) - .collect::>() - .join(" ") - } else { - pv.iter() - .map(|&pvs| format!("{}", Format::None(pvs.to_string_lossy()))) - .collect::>() - .join(" ") - }; + let pvs = pv + .iter() + .map(|&pvs| pvs.to_string_lossy()) + .collect::>() + .join(" "); + spec_vals.push(format!(" [default: {}]", pvs)); } } if let Some(ref aliases) = a.aliases { debugln!("Help::spec_vals: Found aliases...{:?}", aliases); - let als = if self.color { - aliases - .iter() - .filter(|&als| als.1) // visible - .map(|&als| format!("{}", self.cizer.good(als.0))) // name - .collect::>() - .join(", ") - } else { - aliases - .iter() - .filter(|&als| als.1) - .map(|&als| als.0) - .collect::>() - .join(", ") - }; + + let als = aliases + .iter() + .filter(|&als| als.1) // visible + .map(|&als| als.0) // name + .collect::>() + .join(", "); + if !als.is_empty() { spec_vals.push(format!(" [aliases: {}]", als)); } @@ -500,17 +506,8 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { if !self.hide_pv && !a.is_set(ArgSettings::HidePossibleValues) { if let Some(ref 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::>() - .join(", ") - ) - } else { - format!(" [possible values: {}]", pv.join(", ")) - }); + + spec_vals.push(format!(" [possible values: {}]", pv.join(", "))); } } spec_vals.join(" ") @@ -521,8 +518,8 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { fn write_subcommand(&mut self, app: &App<'b>) -> io::Result<()> { debugln!("Help::write_subcommand;"); - write!(self.writer, "{}", TAB)?; - self.color(Format::Good(&*app.name))?; + self.none(TAB)?; + self.good(&app.name)?; let spec_vals = self.sc_val(app)?; self.sc_help(app, &*spec_vals)?; Ok(()) @@ -541,10 +538,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { && h_w > (self.term_w - taken); if !(nlh || self.force_next_line) { - write_nspaces!( - self.writer, - self.longest + 4 - (str_width(app.to_string().as_str())) - ); + write_nspaces!(self, self.longest + 4 - (str_width(&app.name))); } Ok(spec_vals) } @@ -554,21 +548,14 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { let mut spec_vals = vec![]; if let Some(ref aliases) = a.aliases { debugln!("Help::spec_vals: Found aliases...{:?}", aliases); - let als = if self.color { - aliases - .iter() - .filter(|&als| als.1) // visible - .map(|&als| format!("{}", self.cizer.good(als.0))) // name - .collect::>() - .join(", ") - } else { - aliases - .iter() - .filter(|&als| als.1) - .map(|&als| als.0) - .collect::>() - .join(", ") - }; + + let als = aliases + .iter() + .filter(|&als| als.1) // visible + .map(|&als| als.0) // name + .collect::>() + .join(", "); + if !als.is_empty() { spec_vals.push(format!(" [aliases: {}]", als)); } @@ -597,7 +584,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { // Is help on next line, if so then indent if nlh || self.force_next_line { - write!(self.writer, "\n{}{}{}", TAB, TAB, TAB)?; + self.none(&format!("\n{}{}{}", TAB, TAB, TAB))?; } debug!("Help::sc_help: Too long..."); @@ -613,19 +600,19 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { sdebugln!("No"); } if let Some(part) = help.lines().next() { - write!(self.writer, "{}", part)?; + self.none(part)?; } for part in help.lines().skip(1) { - writeln!(self.writer)?; + self.none("\n")?; if nlh || self.force_next_line { - write!(self.writer, "{}{}{}", TAB, TAB, TAB)?; + self.none(&format!("{}{}{}", TAB, TAB, TAB))?; } else { - write_nspaces!(self.writer, self.longest + 8); + write_nspaces!(self, self.longest + 8); } - write!(self.writer, "{}", part)?; + self.none(part)?; } if !help.contains('\n') && (nlh || self.force_next_line) { - writeln!(self.writer)?; + self.none("\n")?; } Ok(()) } @@ -657,16 +644,13 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { } }) > 0; - let mut first = true; - - if pos { - if !first { - self.writer.write_all(b"\n\n")?; - } - self.color(Format::Warning("ARGS:\n"))?; + let mut first = if pos { + self.warning("ARGS:\n")?; self.write_args_unsorted(&*positionals!(self.parser.app).collect::>())?; - first = false; - } + false + } else { + true + }; let unified_help = self.parser.is_set(AppSettings::UnifiedHelpMessage); @@ -680,26 +664,26 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { .filter(|a| a.has_switch()) .collect::>(); if !first { - self.writer.write_all(b"\n\n")?; + self.none("\n\n")?; } - self.color(Format::Warning("OPTIONS:\n"))?; + self.warning("OPTIONS:\n")?; self.write_args(&*opts_flags)?; first = false; } else { if flags { if !first { - self.writer.write_all(b"\n\n")?; + self.none("\n\n")?; } - self.color(Format::Warning("FLAGS:\n"))?; + self.warning("FLAGS:\n")?; let flags_v: Vec<_> = flags!(self.parser.app).collect(); self.write_args(&*flags_v)?; first = false; } if opts { if !first { - self.writer.write_all(b"\n\n")?; + self.none("\n\n")?; } - self.color(Format::Warning("OPTIONS:\n"))?; + self.warning("OPTIONS:\n")?; self.write_args(&*opts!(self.parser.app).collect::>())?; first = false; } @@ -713,9 +697,9 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { .map(|heading| heading.unwrap()) { if !first { - self.writer.write_all(b"\n\n")?; + self.none("\n\n")?; } - self.color(Format::Warning(&*format!("{}:\n", heading)))?; + self.warning(&*format!("{}:\n", heading))?; let args = self .parser .app @@ -732,9 +716,9 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { if subcmds { if !first { - self.writer.write_all(b"\n\n")?; + self.none("\n\n")?; } - self.color(Format::Warning("SUBCOMMANDS:\n"))?; + self.warning("SUBCOMMANDS:\n")?; self.write_subcommands(&self.parser.app)?; } @@ -763,7 +747,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { if first { first = false; } else { - self.writer.write_all(b"\n")?; + self.none("\n")?; } self.write_subcommand(sc)?; } @@ -774,7 +758,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { /// Writes version of a Parser Object to the wrapped stream. fn write_version(&mut self) -> io::Result<()> { debugln!("Help::write_version;"); - write!(self.writer, "{}", self.parser.app.version.unwrap_or(""))?; + self.none(self.parser.app.version.unwrap_or(""))?; Ok(()) } @@ -784,13 +768,13 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { let term_w = self.term_w; macro_rules! write_name { () => {{ - self.color(Format::Good(&*wrap_help(&self.parser.app.name, term_w)))?; + self.good(&*wrap_help(&self.parser.app.name, term_w))?; }}; } 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 - self.color(Format::Good(&*bn.replace(" ", "-")))? + self.good(&bn.replace(" ", "-"))? } else { write_name!(); } @@ -805,38 +789,41 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { debugln!("Help::write_default_help;"); if let Some(h) = self.parser.app.pre_help { self.write_before_after_help(h)?; - self.writer.write_all(b"\n\n")?; + self.none("\n\n")?; } macro_rules! write_thing { ($thing:expr) => {{ - write!(self.writer, "{}\n", wrap_help(&$thing, self.term_w))? + self.none(&wrap_help(&$thing, self.term_w))?; + self.none("\n")? }}; } + // Print the version self.write_bin_name()?; - self.writer.write_all(b" ")?; + self.none(" ")?; self.write_version()?; - self.writer.write_all(b"\n")?; + self.none("\n")?; + if let Some(author) = self.parser.app.author { - write_thing!(author) + write_thing!(author); } if self.use_long && self.parser.app.long_about.is_some() { debugln!("Help::write_default_help: writing long about"); - write_thing!(self.parser.app.long_about.unwrap()) + write_thing!(self.parser.app.long_about.unwrap()); } else if self.parser.app.about.is_some() { debugln!("Help::write_default_help: writing about"); - write_thing!(self.parser.app.about.unwrap()) + write_thing!(self.parser.app.about.unwrap()); } - self.color(Format::Warning("\nUSAGE:"))?; - write!( - self.writer, + self.none("\n")?; + self.warning("USAGE:")?; + self.none(&format!( "\n{}{}\n\n", TAB, Usage::new(self.parser).create_usage_no_title(&[]) - )?; + ))?; let flags = self.parser.has_flags(); let pos = self.parser.has_positionals(); @@ -849,7 +836,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { if let Some(h) = self.parser.app.more_help { if flags || opts || pos || subcmds { - self.writer.write_all(b"\n\n")?; + self.none("\n\n")?; } self.write_before_after_help(h)?; } @@ -1002,45 +989,25 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { ); match &tag_buf.get_ref()[0..tag_length] { b"?" => { - self.writer.write_all(b"Could not decode tag name")?; + self.none("Could not decode tag name")?; } b"bin" => { self.write_bin_name()?; } b"version" => { - write!( - self.writer, - "{}", - self.parser.app.version.unwrap_or("unknown version") - )?; + self.none(self.parser.app.version.unwrap_or("unknown version"))?; } b"author" => { - write!( - self.writer, - "{}", - self.parser.app.author.unwrap_or("unknown author") - )?; + self.none(self.parser.app.author.unwrap_or("unknown author"))?; } b"about" => { - write!( - self.writer, - "{}", - self.parser.app.about.unwrap_or("unknown about") - )?; + self.none(self.parser.app.about.unwrap_or("unknown about"))?; } b"long-about" => { - write!( - self.writer, - "{}", - self.parser.app.long_about.unwrap_or("unknown about") - )?; + self.none(self.parser.app.long_about.unwrap_or("unknown about"))?; } b"usage" => { - write!( - self.writer, - "{}", - Usage::new(self.parser).create_usage_no_title(&[]) - )?; + self.none(&Usage::new(self.parser).create_usage_no_title(&[]))?; } b"all-args" => { self.write_all_args()?; @@ -1069,24 +1036,16 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { self.write_subcommands(self.parser.app)?; } b"after-help" => { - write!( - self.writer, - "{}", - self.parser.app.more_help.unwrap_or("unknown after-help") - )?; + self.none(self.parser.app.more_help.unwrap_or("unknown after-help"))?; } b"before-help" => { - write!( - self.writer, - "{}", - self.parser.app.pre_help.unwrap_or("unknown before-help") - )?; + self.none(self.parser.app.pre_help.unwrap_or("unknown before-help"))?; } // Unknown tag, write it back. r => { - self.writer.write_all(b"{")?; + self.none("{")?; self.writer.write_all(r)?; - self.writer.write_all(b"}")?; + self.none("}")?; } } } diff --git a/src/output/mod.rs b/src/output/mod.rs index 44acfe1f..e32aac26 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -1,7 +1,7 @@ mod help; mod usage; -pub mod fmt; +pub(crate) mod fmt; -pub(crate) use self::help::Help; +pub(crate) use self::help::{Help, HelpWriter}; pub(crate) use self::usage::Usage; diff --git a/src/parse/errors.rs b/src/parse/errors.rs index 9d5f608e..1d9d9dde 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -1,16 +1,16 @@ // Std +use std::collections::VecDeque; use std::convert::From; -use std::error::Error as StdError; -use std::fmt as std_fmt; -use std::fmt::Display; -use std::io::{self, Write}; +use std::fmt::{self, Debug, Display, Formatter}; +use std::io; use std::process; use std::result::Result as StdResult; // Internal use crate::build::{Arg, ArgGroup}; -use crate::output::fmt::{ColorWhen, Colorizer, ColorizerOption}; +use crate::output::fmt::Colorizer; use crate::parse::features::suggestions; +use crate::util::termcolor::ColorChoice; /// Short hand for [`Result`] type /// @@ -375,13 +375,38 @@ pub struct Error { /// The cause of the error pub cause: String, /// Formatted error message, enhancing the cause message with extra information - pub message: String, + 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>, } +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.message.fmt(f) + } +} + +fn start_error(c: &mut Colorizer, msg: &str) -> io::Result<()> { + c.error("error:")?; + c.none(" ")?; + c.none(msg) +} + +fn put_usage(c: &mut Colorizer, usage: U) -> io::Result<()> +where + U: Display, +{ + c.none(&format!("\n\n{}", usage)) +} + +fn try_help(c: &mut Colorizer) -> io::Result<()> { + c.none("\n\nFor more information try ")?; + c.good("--help")?; + c.none("\n") +} + impl Error { /// Returns the singular or plural form on the verb to be based on the argument's value. fn singular_or_plural(n: usize) -> String { @@ -403,17 +428,12 @@ impl Error { /// Prints the error to `stderr` and exits with a status of `1` pub fn exit(&self) -> ! { if self.use_stderr() { - wlnerr!("{}", self.message); + self.message.print().expect("Error writing Error to stderr"); process::exit(1); } - let out = io::stdout(); - writeln!(&mut out.lock(), "{}", self.message).expect("Error writing Error to stdout"); - process::exit(0); - } - /// Render and write the error into `w`. - pub fn write_to(&self, w: &mut W) -> io::Result<()> { - write!(w, "{}", self.message) + self.message.print().expect("Error writing Error to stdout"); + process::exit(0); } #[allow(unused)] // requested by @pksunkara @@ -421,136 +441,119 @@ impl Error { group: &ArgGroup, other: Option, usage: U, - color: ColorWhen, - ) -> Self + color: ColorChoice, + ) -> io::Result where O: Into, U: Display, { let mut v = vec![group.name.to_owned()]; - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - let (plain_cause, colored_cause) = match other { + 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()); - ( - format!("The argument '{}' cannot be used with '{}'", group.name, n), - format!( - "The argument '{}' cannot be used with '{}'", - group.name, - c.warning(n) - ), - ) + + 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"; - ( - format!("The argument '{}' cannot be used with {}", group.name, n), - format!( - "The argument '{}' cannot be used with {}", - group.name, - c.none(n) - ), - ) + + c.none(n)?; + + format!("The argument '{}' cannot be used with {}", group.name, n) } }; - Error { - cause: plain_cause, - message: format!( - "{} {}\n\n{}\n\nFor more information try {}", - c.error("error:"), - colored_cause, - usage, - c.good("--help") - ), + + 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( arg: &Arg, other: Option, usage: U, - color: ColorWhen, - ) -> Self + color: ColorChoice, + ) -> io::Result where O: Into, U: Display, { let mut v = vec![arg.name.to_owned()]; - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - let (plain_cause, colored_cause) = match other { + let mut c = Colorizer::new(true, color); + + start_error(&mut c, "The argument '")?; + c.warning(&arg.to_string())?; + c.none("' cannot be used with ")?; + + let cause = match other { Some(name) => { let n = name.into(); v.push(n.clone()); - ( - format!("The argument '{}' cannot be used with '{}'", arg, n), - format!( - "The argument '{}' cannot be used with {}", - c.warning(arg.to_string()), - c.warning(format!("'{}'", n)) - ), - ) + + c.none("'")?; + c.warning(&*n)?; + c.none("'")?; + + format!("The argument '{}' cannot be used with '{}'", arg, n) } None => { let n = "one or more of the other specified arguments"; - ( - format!("The argument '{}' cannot be used with {}", arg, n), - format!( - "The argument '{}' cannot be used with {}", - c.warning(arg.to_string()), - c.none(n) - ), - ) + + c.none(n)?; + + format!("The argument '{}' cannot be used with {}", arg, n) } }; - Error { - cause: plain_cause, - message: format!( - "{} {}\n\n{}\n\nFor more information try {}", - c.error("error:"), - colored_cause, - usage, - c.good("--help") - ), + + put_usage(&mut c, usage)?; + try_help(&mut c)?; + + Ok(Error { + cause, + message: c, kind: ErrorKind::ArgumentConflict, info: Some(v), - } + }) } - pub(crate) fn empty_value(arg: &Arg, usage: U, color: ColorWhen) -> Self + pub(crate) fn empty_value(arg: &Arg, usage: U, color: ColorChoice) -> io::Result where U: Display, { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - Error { + let mut c = Colorizer::new(true, color); + + 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)?; + + Ok(Error { cause: format!( "The argument '{}' requires a value but none was supplied", arg ), - message: format!( - "{} The argument '{}' requires a value but none was supplied\ - \n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - c.warning(arg.to_string()), - usage, - c.good("--help") - ), + message: c, kind: ErrorKind::EmptyValue, info: Some(vec![arg.name.to_owned()]), - } + }) } pub(crate) fn invalid_value( @@ -558,51 +561,57 @@ impl Error { good_vals: &[G], arg: &Arg, usage: U, - color: ColorWhen, - ) -> Self + color: ColorChoice, + ) -> io::Result where B: AsRef, G: AsRef + Display, U: Display, { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - let suffix = suggestions::did_you_mean_value_suffix(bad_val.as_ref(), good_vals.iter()); + let mut c = Colorizer::new(true, color); + let suffix = suggestions::did_you_mean(bad_val.as_ref(), good_vals.iter()).pop(); - let mut sorted = vec![]; - for v in good_vals { - let val = format!("{}", c.good(v)); - sorted.push(val); - } + let mut sorted: Vec = good_vals.iter().map(|v| v.to_string()).collect(); sorted.sort(); - let valid_values = sorted.join(", "); - Error { + + 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: ")?; + + if let Some((last, elements)) = sorted.split_last() { + for v in elements { + c.good(v)?; + c.none(", ")?; + } + + c.good(last)?; + } + + c.none("]")?; + + if let Some(val) = suffix { + c.none("\n\n\tDid you mean '")?; + c.good(&val)?; + c.none("'?")?; + } + + 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, - valid_values - ), - message: format!( - "{} '{}' isn't a valid value for '{}'\n\t\ - [possible values: {}]\n\ - {}\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - c.warning(bad_val.as_ref()), - c.warning(arg.to_string()), - valid_values, - suffix.0, - usage, - c.good("--help") + sorted.join(", ") ), + message: c, kind: ErrorKind::InvalidValue, info: Some(vec![arg.name.to_owned(), bad_val.as_ref().to_owned()]), - } + }) } pub(crate) fn invalid_subcommand( @@ -610,8 +619,8 @@ impl Error { did_you_mean: D, name: N, usage: U, - color: ColorWhen, - ) -> Self + color: ColorChoice, + ) -> io::Result where S: Into, D: AsRef + Display, @@ -619,166 +628,173 @@ impl Error { U: Display, { let s = subcmd.into(); - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - Error { + 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!( + "?\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)?; + + Ok(Error { cause: format!("The subcommand '{}' wasn't recognized", s), - message: format!( - "{} The subcommand '{}' wasn't recognized\n\t\ - Did you mean {}?\n\n\ - If you believe you received this message in error, try \ - re-running with '{} {} {}'\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - c.warning(&*s), - c.good(did_you_mean.as_ref()), - name, - c.good("--"), - &*s, - usage, - c.good("--help") - ), + message: c, kind: ErrorKind::InvalidSubcommand, info: Some(vec![s]), - } + }) } - pub(crate) fn unrecognized_subcommand(subcmd: S, name: N, color: ColorWhen) -> Self + pub(crate) fn unrecognized_subcommand( + subcmd: S, + name: N, + color: ColorChoice, + ) -> io::Result where S: Into, N: Display, { let s = subcmd.into(); - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - Error { + 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 ...", name))?; + try_help(&mut c)?; + + Ok(Error { cause: format!("The subcommand '{}' wasn't recognized", s), - message: format!( - "{} The subcommand '{}' wasn't recognized\n\n\ - {}\n\t\ - {} help ...\n\n\ - For more information try {}", - c.error("error:"), - c.warning(&*s), - c.warning("USAGE:"), - name, - c.good("--help") - ), + message: c, kind: ErrorKind::UnrecognizedSubcommand, info: Some(vec![s]), - } + }) } - pub(crate) fn missing_required_argument(required: R, usage: U, color: ColorWhen) -> Self + pub(crate) fn missing_required_argument( + required: VecDeque, + usage: U, + color: ColorChoice, + ) -> io::Result where R: Display, U: Display, { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - Error { - cause: format!( - "The following required arguments were not provided:{}", - required - ), - message: format!( - "{} The following required arguments were not provided:{}\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - required, - usage, - c.good("--help") - ), + 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::>() + .join(""), + ); + + start_error( + &mut c, + "The following required arguments were not provided:", + )?; + + for v in required { + c.none("\n ")?; + c.good(&v.to_string())?; + } + + put_usage(&mut c, usage)?; + try_help(&mut c)?; + + Ok(Error { + cause, + message: c, kind: ErrorKind::MissingRequiredArgument, info: None, - } + }) } - pub(crate) fn missing_subcommand(name: N, usage: U, color: ColorWhen) -> Self + pub(crate) fn missing_subcommand( + name: N, + usage: U, + color: ColorChoice, + ) -> io::Result where N: AsRef + Display, U: Display, { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - Error { + 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)?; + + Ok(Error { cause: format!("'{}' requires a subcommand, but one was not provided", name), - message: format!( - "{} '{}' requires a subcommand, but one was not provided\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - c.warning(name), - usage, - c.good("--help") - ), + message: c, kind: ErrorKind::MissingSubcommand, info: None, - } + }) } - pub(crate) fn invalid_utf8(usage: U, color: ColorWhen) -> Self + pub(crate) fn invalid_utf8(usage: U, color: ColorChoice) -> io::Result where U: Display, { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - Error { - cause: "Invalid UTF-8 was detected in one or more arguments".to_string(), - message: format!( - "{} Invalid UTF-8 was detected in one or more arguments\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - usage, - c.good("--help") - ), + let mut c = Colorizer::new(true, color); + + let cause = "Invalid UTF-8 was detected in one or more arguments"; + + start_error(&mut c, cause)?; + put_usage(&mut c, usage)?; + try_help(&mut c)?; + + Ok(Error { + cause: cause.to_string(), + message: c, kind: ErrorKind::InvalidUtf8, info: None, - } + }) } - pub(crate) fn too_many_values(val: V, arg: &Arg, usage: U, color: ColorWhen) -> Self + pub(crate) fn too_many_values( + val: V, + arg: &Arg, + usage: U, + color: ColorChoice, + ) -> io::Result where V: AsRef + Display + ToOwned, U: Display, { let v = val.as_ref(); - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - Error { + 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)?; + + Ok(Error { cause: format!( "The value '{}' was provided to '{}', but it wasn't expecting any more values", v, arg ), - message: format!( - "{} The value '{}' was provided to '{}', but it wasn't expecting \ - any more values\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - c.warning(v), - c.warning(arg.to_string()), - usage, - c.good("--help") - ), + message: c, kind: ErrorKind::TooManyValues, info: Some(vec![arg.name.to_owned(), v.to_owned()]), - } + }) } pub(crate) fn too_few_values( @@ -786,45 +802,53 @@ impl Error { min_vals: u64, curr_vals: usize, usage: U, - color: ColorWhen, - ) -> Self + color: ColorChoice, + ) -> io::Result where U: Display, { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); + let mut c = Colorizer::new(true, color); let verb = Error::singular_or_plural(curr_vals); - Error { + + 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 ), - message: format!( - "{} The argument '{}' requires at least {} values, but only {} {} \ - provided\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - c.warning(arg.to_string()), - c.warning(min_vals.to_string()), - c.warning(curr_vals.to_string()), - verb, - usage, - c.good("--help") - ), + message: c, kind: ErrorKind::TooFewValues, info: Some(vec![arg.name.to_owned()]), - } + }) } - pub(crate) fn value_validation(arg: Option<&Arg>, err: &str, color: ColorWhen) -> Self { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - Error { + pub(crate) fn value_validation( + arg: Option<&Arg>, + err: &str, + color: ColorChoice, + ) -> io::Result { + let mut c = Colorizer::new(true, color); + + start_error(&mut c, "Invalid value")?; + + if let Some(a) = arg { + c.none(" for '")?; + c.warning(&a.to_string())?; + c.none("'")?; + } + + c.none(&format!(": {}", err))?; + + Ok(Error { cause: format!( "Invalid value{}: {}", if let Some(a) = arg { @@ -834,24 +858,15 @@ impl Error { }, err ), - message: format!( - "{} Invalid value{}: {}", - c.error("error:"), - if let Some(a) = arg { - format!(" for '{}'", c.warning(a.to_string())) - } else { - "".to_string() - }, - err - ), + message: c, kind: ErrorKind::ValueValidation, info: None, - } + }) } - pub(crate) fn value_validation_auto(err: &str) -> Self { + pub(crate) fn value_validation_auto(err: &str) -> io::Result { let n: Option<&Arg> = None; - Error::value_validation(n, err, ColorWhen::Auto) + Error::value_validation(n, err, ColorChoice::Auto) } pub(crate) fn wrong_number_of_values( @@ -859,168 +874,163 @@ impl Error { num_vals: u64, curr_vals: usize, usage: U, - color: ColorWhen, - ) -> Self + color: ColorChoice, + ) -> io::Result where U: Display, { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); + let mut c = Colorizer::new(true, color); let verb = Error::singular_or_plural(curr_vals); - Error { + + 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 ), - message: format!( - "{} The argument '{}' requires {} values, but {} {} - provided\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - c.warning(arg.to_string()), - c.warning(num_vals.to_string()), - c.warning(curr_vals.to_string()), - verb, - usage, - c.good("--help") - ), + message: c, kind: ErrorKind::WrongNumberOfValues, info: Some(vec![arg.name.to_owned()]), - } + }) } - pub(crate) fn unexpected_multiple_usage(arg: &Arg, usage: U, color: ColorWhen) -> Self + pub(crate) fn unexpected_multiple_usage( + arg: &Arg, + usage: U, + color: ColorChoice, + ) -> io::Result where U: Display, { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); - Error { + let mut c = Colorizer::new(true, color); + + 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)?; + + Ok(Error { cause: format!( "The argument '{}' was provided more than once, but cannot be used multiple times", arg ), - message: format!( - "{} The argument '{}' was provided more than once, but cannot \ - be used multiple times\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - c.warning(arg.to_string()), - usage, - c.good("--help") - ), + message: c, kind: ErrorKind::UnexpectedMultipleUsage, info: Some(vec![arg.name.to_owned()]), - } + }) } pub(crate) fn unknown_argument( arg: A, - did_you_mean: Option, + did_you_mean: Option<(String, Option)>, usage: U, - color: ColorWhen, - ) -> Self + color: ColorChoice, + ) -> io::Result where A: Into, U: Display, { let a = arg.into(); - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: color, - }); + let mut c = Colorizer::new(true, color); - let suggest_pattern = format!("If you tried to supply `{}` as a PATTERN use `-- {}`", a, a); + start_error(&mut c, "Found argument '")?; + c.warning(&*a)?; + c.none("' which wasn't expected, or isn't valid in this context")?; - let did_you_mean_message = match did_you_mean { - Some(s) => format!("{}\n", s), - _ => "\n".to_owned(), - }; + if let Some(s) = did_you_mean { + c.none("\n\n\tDid you mean ")?; - Error { + 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("'?")?; + } else { + c.none("'")?; + c.good(&format!("--{}", &s.0))?; + c.none("'?")?; + } + } + + c.none(&format!( + "\n\nIf you tried to supply `{}` as a PATTERN use `-- {}`", + a, a + ))?; + 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, did_you_mean_message - ), - message: format!( - "{} Found argument '{}' which wasn't expected, or isn't valid in \ - this context{}\ - {}\n\n\ - {}\n\n\ - For more information try {}", - c.error("error:"), - c.warning(&*a), - did_you_mean_message, - suggest_pattern, - usage, - c.good("--help") + "Found argument '{}' which wasn't expected, or isn't valid in this context", + a ), + message: c, kind: ErrorKind::UnknownArgument, info: Some(vec![a]), - } + }) } - pub(crate) fn argument_not_found_auto(arg: A) -> Self + pub(crate) fn argument_not_found_auto(arg: A) -> io::Result where A: Into, { let a = arg.into(); - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: ColorWhen::Auto, - }); - Error { + 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)?; + + Ok(Error { cause: format!("The argument '{}' wasn't found", a), - message: format!("{} The argument '{}' wasn't found", c.error("error:"), a), + message: c, kind: ErrorKind::ArgumentNotFound, info: Some(vec![a]), - } + }) } /// 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, kind: ErrorKind) -> Self { - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: ColorWhen::Auto, - }); + pub fn with_description(description: impl Into, kind: ErrorKind) -> io::Result { + let mut c = Colorizer::new(true, ColorChoice::Auto); let cause = description.into(); - let message = format!("{} {}", c.error("error:"), cause); - Error { + start_error(&mut c, &*cause)?; + + Ok(Error { cause, - message, + message: c, kind, info: None, - } - } -} - -impl StdError for Error {} - -impl Display for Error { - fn fmt(&self, f: &mut std_fmt::Formatter) -> std_fmt::Result { - writeln!(f, "{}", self.message) + }) } } impl From for Error { fn from(e: io::Error) -> Self { Error::with_description(e.to_string(), ErrorKind::Io) + .expect("Unable to build error message") } } -impl From for Error { - fn from(e: std_fmt::Error) -> Self { +impl From for Error { + fn from(e: fmt::Error) -> Self { Error::with_description(e.to_string(), ErrorKind::Format) + .expect("Unable to build error message") } } diff --git a/src/parse/features/suggestions.rs b/src/parse/features/suggestions.rs index 833a5ab8..1795d689 100644 --- a/src/parse/features/suggestions.rs +++ b/src/parse/features/suggestions.rs @@ -3,7 +3,6 @@ use std::cmp::Ordering; // Internal use crate::build::App; -use crate::output::fmt::Format; /// Produces multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. @@ -34,23 +33,18 @@ where } /// Returns a suffix that can be empty, or is the standard 'did you mean' phrase -pub(crate) fn did_you_mean_flag_suffix( +pub(crate) fn did_you_mean_flag( arg: &str, longs: I, subcommands: &mut [App], -) -> (String, Option) +) -> Option<(String, Option)> where T: AsRef, I: IntoIterator, { match did_you_mean(arg, longs).pop() { Some(ref candidate) => { - let suffix = format!( - "\n\tDid you mean {}{}?", - Format::Good("--"), - Format::Good(candidate) - ); - return (suffix, Some(candidate.to_owned())); + return Some((candidate.to_owned(), None)); } None => { for subcommand in subcommands { @@ -61,33 +55,13 @@ where ) .pop() { - let suffix = format!( - "\n\tDid you mean to put '{}{}' after the subcommand '{}'?", - Format::Good("--"), - Format::Good(candidate), - Format::Good(subcommand.get_name()) - ); - return (suffix, Some(candidate.clone())); + return Some((candidate.to_owned(), Some(subcommand.get_name().to_owned()))); } } } } - (String::new(), None) -} -/// Returns a suffix that can be empty, or is the standard 'did you mean' phrase -pub(crate) fn did_you_mean_value_suffix(arg: &str, values: I) -> (String, Option) -where - T: AsRef, - I: IntoIterator, -{ - match did_you_mean(arg, values).pop() { - Some(ref candidate) => { - let suffix = format!("\n\tDid you mean '{}'?", Format::Good(candidate)); - (suffix, Some(candidate.to_owned())) - } - None => (String::new(), None), - } + None } #[cfg(all(test, features = "suggestions"))] @@ -113,22 +87,11 @@ mod test { } #[test] - fn suffix_long() { + fn flag() { let p_vals = ["test", "possible", "values"]; - let suffix = "\n\tDid you mean \'--test\'?"; assert_eq!( - did_you_mean_flag_suffix("tst", p_vals.iter(), []), - (suffix, Some("test")) - ); - } - - #[test] - fn suffix_enum() { - let p_vals = ["test", "possible", "values"]; - let suffix = "\n\tDid you mean \'test\'?"; - assert_eq!( - did_you_mean_value_suffix("tst", p_vals.iter()), - (suffix, Some("test")) + did_you_mean_flag("tst", p_vals.iter(), []), + Some(("test", None)) ); } } diff --git a/src/parse/matches/arg_matches.rs b/src/parse/matches/arg_matches.rs index bdc61334..fa0655fa 100644 --- a/src/parse/matches/arg_matches.rs +++ b/src/parse/matches/arg_matches.rs @@ -1,7 +1,7 @@ // Std use std::borrow::Cow; use std::ffi::{OsStr, OsString}; -use std::fmt::Display; +use std::fmt::{Debug, Display}; use std::iter::{Cloned, Map}; use std::slice::Iter; use std::str::FromStr; @@ -334,14 +334,14 @@ impl ArgMatches { ::Err: Display, { if let Some(v) = self.value_of(name) { - v.parse::().map_err(|e| { - Error::value_validation_auto(&format!( + v.parse::().or_else(|e| { + Err(Error::value_validation_auto(&format!( "The argument '{}' isn't a valid value: {}", v, e - )) + ))?) }) } else { - Err(Error::argument_not_found_auto(name)) + Err(Error::argument_not_found_auto(name)?) } } @@ -420,16 +420,16 @@ impl ArgMatches { { if let Some(vals) = self.values_of(name) { vals.map(|v| { - v.parse::().map_err(|e| { - Error::value_validation_auto(&format!( + v.parse::().or_else(|e| { + Err(Error::value_validation_auto(&format!( "The argument '{}' isn't a valid value: {}", v, e - )) + ))?) }) }) .collect() } else { - Err(Error::argument_not_found_auto(name)) + Err(Error::argument_not_found_auto(name)?) } } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 4872dca9..0d1d1e52 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -11,7 +11,7 @@ use crate::build::app::Propagation; use crate::build::AppSettings as AS; use crate::build::{App, Arg, ArgSettings}; use crate::mkeymap::KeyType; -use crate::output::{Help, Usage}; +use crate::output::{fmt::Colorizer, Help, HelpWriter, Usage}; use crate::parse::errors::Error as ClapError; use crate::parse::errors::ErrorKind; use crate::parse::errors::Result as ClapResult; @@ -20,7 +20,7 @@ use crate::parse::Validator; use crate::parse::{ArgMatcher, SubCommand}; #[cfg(any(target_os = "windows", target_arch = "wasm32"))] use crate::util::OsStrExt3; -use crate::util::{ChildGraph, Id, OsStrExt2}; +use crate::util::{termcolor::ColorChoice, ChildGraph, Id, OsStrExt2}; use crate::INTERNAL_ERROR_MSG; use crate::INVALID_UTF8; @@ -523,7 +523,7 @@ where None, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } } ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => { @@ -556,7 +556,7 @@ where self.app.bin_name.as_ref().unwrap_or(&self.app.name), &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } } } @@ -655,7 +655,7 @@ where None, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } if !self.is_set(AS::TrailingValues) @@ -696,7 +696,7 @@ where return Err(ClapError::invalid_utf8( &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } arg_os.to_string_lossy().into_owned() } @@ -710,7 +710,7 @@ where return Err(ClapError::invalid_utf8( &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } sc_m.add_val_to(&Id::empty_hash(), &v); } @@ -732,7 +732,7 @@ where None, &*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(), sc_names!(self.app)); @@ -744,13 +744,13 @@ where self.app.bin_name.as_ref().unwrap_or(&self.app.name), &*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), self.app.color(), - )); + )?); } } else { return Err(ClapError::unknown_argument( @@ -758,7 +758,7 @@ where None, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } } @@ -776,14 +776,13 @@ where bn, &Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } else if self.is_set(AS::SubcommandRequiredElseHelp) { debugln!("Parser::get_matches_with: SubcommandRequiredElseHelp=true"); - let mut out = vec![]; - self.write_help_err(&mut out)?; + let message = self.write_help_err()?; return Err(ClapError { cause: String::new(), - message: String::from_utf8_lossy(&*out).into_owned(), + message, kind: ErrorKind::MissingArgumentOrSubcommand, info: None, }); @@ -794,6 +793,17 @@ where Validator::new(self).validate(needs_val_of, subcmd_name.is_some(), matcher) } + // Should we color the help? + pub(crate) fn color_help(&self) -> ColorChoice { + debugln!("App::color_help;"); + + if self.is_set(AS::ColoredHelp) { + self.app.color() + } else { + ColorChoice::Never + } + } + // Checks if the arg matches a subcommand name, or any of it's aliases (if defined) fn possible_subcommand(&self, arg_os: &OsStr) -> Option<&str> { debugln!("Parser::possible_subcommand: arg={:?}", arg_os); @@ -871,7 +881,7 @@ where cmd.to_string_lossy().into_owned(), self.app.bin_name.as_ref().unwrap_or(&self.app.name), self.app.color(), - )); + )?); } bin_name = format!("{} {}", bin_name, &*sc.name); @@ -1228,7 +1238,7 @@ where None, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } } Ok(ret) @@ -1259,7 +1269,7 @@ where opt, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } sdebugln!("Found - {:?}, len: {}", v, v.len()); debugln!( @@ -1274,7 +1284,7 @@ where opt, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), - )); + )?); } else { sdebugln!("None"); } @@ -1575,15 +1585,19 @@ where .collect::>(); debugln!("Parser::did_you_mean_error: longs={:?}", longs); - let suffix = suggestions::did_you_mean_flag_suffix( + let did_you_mean = suggestions::did_you_mean_flag( arg, longs.iter().map(|ref x| &x[..]), self.app.subcommands.as_mut_slice(), ); // Add the arg to the matches to build a proper usage string - if let Some(ref name) = suffix.1 { - if let Some(opt) = self.app.args.get(&KeyType::Long(OsString::from(name))) { + if let Some(ref name) = did_you_mean { + if let Some(opt) = self + .app + .args + .get(&KeyType::Long(OsString::from(name.0.clone()))) + { for g in groups_for_arg!(self.app, opt.id) { matcher.inc_occurrence_of(&g); } @@ -1603,18 +1617,12 @@ where .cloned() .collect(); - let did_you_mean_msg = if suffix.0.is_empty() { - None - } else { - Some(suffix.0) - }; - Err(ClapError::unknown_argument( &*format!("--{}", arg), - did_you_mean_msg, + did_you_mean, &*Usage::new(self).create_usage_with_title(&*used), self.app.color(), - )) + )?) } // Prints the version to the user and exits if quit=true @@ -1623,8 +1631,12 @@ where w.flush().map_err(ClapError::from) } - pub(crate) fn write_help_err(&self, w: &mut W) -> ClapResult<()> { - Help::new(w, self, false, true).write_help() + pub(crate) fn write_help_err(&self) -> ClapResult { + let mut c = Colorizer::new(true, self.color_help()); + + Help::new(HelpWriter::Buffer(&mut c), self, false).write_help()?; + + Ok(c) } fn help_err(&self, mut use_long: bool) -> ClapError { @@ -1632,13 +1644,15 @@ where "Parser::help_err: use_long={:?}", use_long && self.use_long_help() ); + use_long = use_long && self.use_long_help(); - let mut buf = vec![]; - match Help::new(&mut buf, self, use_long, false).write_help() { + let mut c = Colorizer::new(false, self.color_help()); + + match Help::new(HelpWriter::Buffer(&mut c), self, use_long).write_help() { Err(e) => e, _ => ClapError { cause: String::new(), - message: String::from_utf8(buf).unwrap_or_default(), + message: c, kind: ErrorKind::HelpDisplayed, info: None, }, @@ -1647,12 +1661,14 @@ where fn version_err(&self, use_long: bool) -> ClapError { debugln!("Parser::version_err: "); - let mut buf = vec![]; - match self.print_version(&mut buf, use_long) { + + 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: String::from_utf8(buf).unwrap_or_default(), + message: c, kind: ErrorKind::VersionDisplayed, info: None, }, diff --git a/src/parse/validator.rs b/src/parse/validator.rs index 0ee85f3a..3adddf07 100644 --- a/src/parse/validator.rs +++ b/src/parse/validator.rs @@ -1,7 +1,6 @@ // Internal use crate::build::app::AppSettings as AS; use crate::build::{Arg, ArgSettings}; -use crate::output::fmt::{Colorizer, ColorizerOption}; use crate::output::Usage; use crate::parse::errors::Result as ClapResult; use crate::parse::errors::{Error, ErrorKind}; @@ -53,7 +52,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { o, &*Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), - )); + )?); } } @@ -61,11 +60,10 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { && matcher.subcommand_name().is_none() && self.p.is_set(AS::ArgRequiredElseHelp) { - let mut out = vec![]; - self.p.write_help_err(&mut out)?; + let message = self.p.write_help_err()?; return Err(Error { cause: String::new(), - message: String::from_utf8_lossy(&*out).into_owned(), + message, kind: ErrorKind::MissingArgumentOrSubcommand, info: None, }); @@ -96,7 +94,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { return Err(Error::invalid_utf8( &*Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), - )); + )?); } if let Some(ref p_vals) = arg.possible_vals { debugln!("Validator::validate_arg_values: possible_vals={:?}", p_vals); @@ -124,7 +122,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { arg, &*Usage::new(self.p).create_usage_with_title(&*used), self.p.app.color(), - )); + )?); } } if !arg.is_set(ArgSettings::AllowEmptyValues) @@ -136,13 +134,13 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { arg, &*Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), - )); + )?); } if let Some(ref vtor) = arg.validator { debug!("Validator::validate_arg_values: checking validator..."); if let Err(e) = vtor(val.to_string_lossy().into_owned()) { sdebugln!("error"); - return Err(Error::value_validation(Some(arg), &e, self.p.app.color())); + return Err(Error::value_validation(Some(arg), &e, self.p.app.color())?); } else { sdebugln!("good"); } @@ -155,7 +153,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { Some(arg), &(*e).to_string(), self.p.app.color(), - )); + )?); } else { sdebugln!("good"); } @@ -177,7 +175,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { Some(other_arg.to_string()), &*usg, self.p.app.color(), - )); + )?); } } } @@ -198,7 +196,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { c_with, &*usg, self.p.app.color(), - )); + )?); } panic!(INTERNAL_ERROR_MSG); @@ -269,17 +267,12 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { if let Some(arg) = self.p.app.find(name) { if arg.exclusive && args_count > 1 { let c_with: Option = None; - let err = Err(Error::argument_conflict( + return Err(Error::argument_conflict( arg, c_with, Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), - )); - debugln!( - "Validator::validate_conflicts_with_everything; ERROR: {:?}", - err - ); - return err; + )?); } } } @@ -407,7 +400,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { a, &*Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), - )); + )?); } Ok(()) } @@ -433,7 +426,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { }, &*Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), - )); + )?); } } if let Some(num) = a.max_vals { @@ -450,7 +443,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { a, &*Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), - )); + )?); } } let min_vals_zero = if let Some(num) = a.min_vals { @@ -463,7 +456,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { ma.vals.len(), &*Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), - )); + )?); } num == 0 } else { @@ -476,7 +469,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { a, &*Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), - )); + )?); } Ok(()) } @@ -619,32 +612,24 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { // `incl`: an arg to include in the error even if not used fn missing_required_error(&self, matcher: &ArgMatcher, incl: Option<&Id>) -> ClapResult<()> { debugln!("Validator::missing_required_error; incl={:?}", incl); - let c = Colorizer::new(&ColorizerOption { - use_stderr: true, - when: self.p.app.color(), - }); debugln!( "Validator::missing_required_error: reqs={:?}", self.p.required ); + let usg = Usage::new(self.p); + let req_args = if let Some(x) = incl { usg.get_required_usage_from(&[x.clone()], Some(matcher), true) - .iter() - .fold(String::new(), |acc, s| { - acc + &format!("\n {}", c.error(s))[..] - }) } else { usg.get_required_usage_from(&[], None, true) - .iter() - .fold(String::new(), |acc, s| { - acc + &format!("\n {}", c.error(s))[..] - }) }; + debugln!( "Validator::missing_required_error: req_args={:#?}", req_args ); + let used: Vec = matcher .arg_names() .filter(|&n| { @@ -657,10 +642,11 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> { .cloned() .chain(incl.cloned()) .collect(); + Err(Error::missing_required_argument( - &*req_args, + req_args, &*usg.create_usage_with_title(&*used), self.p.app.color(), - )) + )?) } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 286aff59..401ddde6 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -11,3 +11,9 @@ pub(crate) use self::{graph::ChildGraph, id::Id, map::VecMap, osstringext::OsStr #[cfg(any(target_os = "windows", target_arch = "wasm32"))] pub(crate) use self::osstringext::OsStrExt3; + +#[cfg(feature = "color")] +pub(crate) use termcolor; + +#[cfg(not(feature = "color"))] +pub(crate) mod termcolor; diff --git a/src/util/termcolor.rs b/src/util/termcolor.rs new file mode 100644 index 00000000..9aff9c2c --- /dev/null +++ b/src/util/termcolor.rs @@ -0,0 +1,38 @@ +use std::io::{stderr, stdout, Result, Write}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub(crate) enum ColorChoice { + Auto, + Always, + Never, +} + +pub(crate) type Buffer = Vec; + +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(buf)?; + } else { + stdout().lock().write(buf)?; + } + + Ok(()) + } +} diff --git a/tests/app_from_crate.rs b/tests/app_from_crate.rs index 91ab0e67..f69cf9c3 100644 --- a/tests/app_from_crate.rs +++ b/tests/app_from_crate.rs @@ -20,7 +20,7 @@ fn app_from_crate() { let err = res.unwrap_err(); assert_eq!(err.kind, ErrorKind::HelpDisplayed); assert_eq!( - err.message, + err.to_string(), EVERYTHING.replace("{{version}}", env!("CARGO_PKG_VERSION")) ); } diff --git a/tests/cargo.rs b/tests/cargo.rs index daf94d02..5d257209 100644 --- a/tests/cargo.rs +++ b/tests/cargo.rs @@ -31,7 +31,10 @@ fn crate_version() { assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!(err.kind, ErrorKind::VersionDisplayed); - assert_eq!(err.message, format!("prog {}", env!("CARGO_PKG_VERSION"))); + assert_eq!( + err.to_string(), + format!("prog {}", env!("CARGO_PKG_VERSION")) + ); } #[test] @@ -44,7 +47,7 @@ fn crate_description() { assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!(err.kind, ErrorKind::HelpDisplayed); - assert_eq!(err.message, DESCRIPTION_ONLY); + assert_eq!(err.to_string(), DESCRIPTION_ONLY); } #[test] @@ -57,7 +60,7 @@ fn crate_authors() { assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!(err.kind, ErrorKind::HelpDisplayed); - assert_eq!(err.message, AUTHORS_ONLY); + assert_eq!(err.to_string(), AUTHORS_ONLY); } #[test] @@ -67,5 +70,5 @@ fn crate_name() { assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!(err.kind, ErrorKind::VersionDisplayed); - assert_eq!(err.message, "clap "); + assert_eq!(err.to_string(), "clap "); } diff --git a/tests/multiple_occurrences.rs b/tests/multiple_occurrences.rs index 1a40bd95..9a6fbe55 100644 --- a/tests/multiple_occurrences.rs +++ b/tests/multiple_occurrences.rs @@ -87,18 +87,18 @@ fn multiple_occurrences_of_before_env() { ); let m = app.clone().try_get_matches_from(vec![""]); - assert!(m.is_ok(), "{}", m.unwrap_err().message); + assert!(m.is_ok(), "{}", m.unwrap_err()); assert_eq!(m.unwrap().occurrences_of("verbose"), 0); let m = app.clone().try_get_matches_from(vec!["", "-v"]); - assert!(m.is_ok(), "{}", m.unwrap_err().message); + assert!(m.is_ok(), "{}", m.unwrap_err()); assert_eq!(m.unwrap().occurrences_of("verbose"), 1); let m = app.clone().try_get_matches_from(vec!["", "-vv"]); - assert!(m.is_ok(), "{}", m.unwrap_err().message); + assert!(m.is_ok(), "{}", m.unwrap_err()); assert_eq!(m.unwrap().occurrences_of("verbose"), 2); let m = app.clone().try_get_matches_from(vec!["", "-vvv"]); - assert!(m.is_ok(), "{}", m.unwrap_err().message); + assert!(m.is_ok(), "{}", m.unwrap_err()); assert_eq!(m.unwrap().occurrences_of("verbose"), 3); } @@ -114,17 +114,17 @@ fn multiple_occurrences_of_after_env() { ); let m = app.clone().try_get_matches_from(vec![""]); - assert!(m.is_ok(), "{}", m.unwrap_err().message); + assert!(m.is_ok(), "{}", m.unwrap_err()); assert_eq!(m.unwrap().occurrences_of("verbose"), 0); let m = app.clone().try_get_matches_from(vec!["", "-v"]); - assert!(m.is_ok(), "{}", m.unwrap_err().message); + assert!(m.is_ok(), "{}", m.unwrap_err()); assert_eq!(m.unwrap().occurrences_of("verbose"), 1); let m = app.clone().try_get_matches_from(vec!["", "-vv"]); - assert!(m.is_ok(), "{}", m.unwrap_err().message); + assert!(m.is_ok(), "{}", m.unwrap_err()); assert_eq!(m.unwrap().occurrences_of("verbose"), 2); let m = app.clone().try_get_matches_from(vec!["", "-vvv"]); - assert!(m.is_ok(), "{}", m.unwrap_err().message); + assert!(m.is_ok(), "{}", m.unwrap_err()); assert_eq!(m.unwrap().occurrences_of("verbose"), 3); } diff --git a/tests/opts.rs b/tests/opts.rs index a2c79058..919c389d 100644 --- a/tests/opts.rs +++ b/tests/opts.rs @@ -5,7 +5,9 @@ use clap::{App, Arg, ArgMatches, ArgSettings, ErrorKind}; #[cfg(feature = "suggestions")] static DYM: &str = "error: Found argument '--optio' which wasn't expected, or isn't valid in this context -\tDid you mean --option? + +\tDid you mean '--option'? + If you tried to supply `--optio` as a PATTERN use `-- --optio` USAGE: @@ -16,7 +18,9 @@ For more information try --help"; #[cfg(feature = "suggestions")] static DYM_ISSUE_1073: &str = "error: Found argument '--files-without-matches' which wasn't expected, or isn't valid in this context -\tDid you mean --files-without-match? + +\tDid you mean '--files-without-match'? + If you tried to supply `--files-without-matches` as a PATTERN use `-- --files-without-matches` USAGE: diff --git a/tests/possible_values.rs b/tests/possible_values.rs index f7b8b971..60f65a3d 100644 --- a/tests/possible_values.rs +++ b/tests/possible_values.rs @@ -17,7 +17,6 @@ For more information try --help"; static PV_ERROR: &'static str = "error: 'slo' isn't a valid value for '--Option ' \t[possible values: fast, slow] - USAGE: clap-test --Option diff --git a/tests/subcommands.rs b/tests/subcommands.rs index e2ea757a..32d4311e 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -58,6 +58,7 @@ SUBCOMMANDS: #[cfg(feature = "suggestions")] static DYM_SUBCMD: &str = "error: The subcommand 'subcm' wasn't recognized + Did you mean 'subcmd'? If you believe you received this message in error, try re-running with 'dym -- subcm' @@ -69,6 +70,7 @@ For more information try --help"; #[cfg(feature = "suggestions")] static DYM_SUBCMD_AMBIGUOUS: &str = "error: The subcommand 'te' wasn't recognized + Did you mean 'test' or 'temp'? If you believe you received this message in error, try re-running with 'dym -- te' @@ -81,7 +83,9 @@ For more information try --help"; #[cfg(feature = "suggestions")] static DYM_ARG: &str = "error: Found argument '--subcm' which wasn't expected, or isn't valid in this context -\tDid you mean to put '--subcmdarg' after the subcommand 'subcmd'? + + Did you mean to put '--subcmdarg' after the subcommand 'subcmd'? + If you tried to supply `--subcm` as a PATTERN use `-- --subcm` USAGE: diff --git a/tests/utils.rs b/tests/utils.rs index 4c2921de..5c0f96c2 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -34,7 +34,7 @@ pub fn compare_output(l: App, args: &str, right: &str, stderr: bool) -> bool { let mut buf = Cursor::new(Vec::with_capacity(50)); let res = l.try_get_matches_from(args.split(' ').collect::>()); let err = res.unwrap_err(); - err.write_to(&mut buf).unwrap(); + write!(&mut buf, "{}", err).unwrap(); let content = buf.into_inner(); let left = String::from_utf8(content).unwrap(); assert_eq!( @@ -51,7 +51,7 @@ pub fn compare_output2(l: App, args: &str, right1: &str, right2: &str, stderr: b let mut buf = Cursor::new(Vec::with_capacity(50)); let res = l.try_get_matches_from(args.split(' ').collect::>()); let err = res.unwrap_err(); - err.write_to(&mut buf).unwrap(); + write!(&mut buf, "{}", err).unwrap(); let content = buf.into_inner(); let left = String::from_utf8(content).unwrap(); assert_eq!( diff --git a/tests/version.rs b/tests/version.rs index 8407b40d..271b876c 100644 --- a/tests/version.rs +++ b/tests/version.rs @@ -17,7 +17,7 @@ fn version_short() { assert!(m.is_err()); let err = m.unwrap_err(); assert_eq!(err.kind, ErrorKind::VersionDisplayed); - assert_eq!(err.message, "test 1.3"); + assert_eq!(err.to_string(), "test 1.3"); } #[test] @@ -31,7 +31,7 @@ fn version_long() { assert!(m.is_err()); let err = m.unwrap_err(); assert_eq!(err.kind, ErrorKind::VersionDisplayed); - assert_eq!(err.message, "test 1.3"); + assert_eq!(err.to_string(), "test 1.3"); } #[test]