diff --git a/Cargo.toml b/Cargo.toml index 4a1032e1..ed7d5be4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ termcolor = { version = "1.1", optional = true } terminal_size = { version = "0.1.12", optional = true } lazy_static = { version = "1", optional = true } clap_derive = { path = "./clap_derive", version = "3.0.0-beta.1", optional = true } +regex = { version = "1.0", optional = true } [dev-dependencies] regex = "1.0" @@ -96,7 +97,7 @@ yaml = ["yaml-rust"] cargo = ["lazy_static"] # Disable if you're not using Cargo, enables Cargo-env-var-dependent macros unstable = ["clap_derive/unstable"] # for building with unstable clap features (doesn't require nightly Rust) (currently none) debug = ["clap_derive/debug"] # Enables debug messages -doc = ["yaml"] # All the features which add to documentation +doc = ["yaml", "regex"] # All the features which add to documentation [profile.test] opt-level = 1 diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index dc8e47bd..459f9fd0 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -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(&mut self, w: &mut W) -> ClapResult<()> { + pub fn write_help(&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(&mut self, w: &mut W) -> ClapResult<()> { + pub fn write_long_help(&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(&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(&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(&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) } } diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs index 59b2e2b6..b77d7fc6 100644 --- a/src/build/arg/mod.rs +++ b/src/build/arg/mod.rs @@ -18,6 +18,14 @@ use std::{ }; // Third Party +#[cfg(feature = "regex")] +use ::regex::Regex; + +#[cfg(feature = "regex")] +mod regex; + +#[cfg(feature = "regex")] +pub use self::regex::RegexRef; // Internal use crate::{ @@ -1917,6 +1925,78 @@ impl<'help> Arg<'help> { self } + /// Validates the argument via the given regular expression. + /// + /// As regular expressions are not very user friendly, the additional `err_message` should + /// describe the expected format in clear words. All notes for [`Arg::validator()`] regarding the + /// error message and performance also hold for `validator_regex`. + /// + /// The regular expression can either be borrowed or moved into `validator_regex`. This happens + /// automatically via [`RegexRef`]'s `Into` implementation. + /// + /// **NOTE:** If using YAML then a single vector with two entries should be provided: + /// ```yaml + /// validator_regex: [remove-all-files, needs the exact phrase 'remove-all-files' to continue] + /// ``` + /// + /// # Performance + /// Regular expressions are expensive to compile. You should prefer sharing your regular expression. + /// We use a [`Cow`]-like internal structure to enable both sharing as well as taking ownership of a + /// provided regular expression. + /// + /// # Examples + /// You can use the classical `"\d+"` regular expression to match digits only: + /// ```rust + /// # use clap::{App, Arg}; + /// use regex::Regex; + /// + /// let digits = Regex::new(r"\d+").unwrap(); + /// + /// let res = App::new("prog") + /// .arg(Arg::new("digits") + /// .index(1) + /// .validator_regex(&digits, "only digits are allowed")) + /// .try_get_matches_from(vec![ + /// "prog", "12345" + /// ]); + /// assert!(res.is_ok()); + /// assert_eq!(res.unwrap().value_of("digits"), Some("12345")); + /// ``` + /// However, any valid `Regex` can be used: + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// use regex::Regex; + /// + /// let priority = Regex::new(r"[A-C]").unwrap(); + /// + /// let res = App::new("prog") + /// .arg(Arg::new("priority") + /// .index(1) + /// .validator_regex(priority, "only priorities A, B or C are allowed")) + /// .try_get_matches_from(vec![ + /// "prog", "12345" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.err().unwrap().kind, ErrorKind::ValueValidation) + /// ``` + /// [`Arg::validator()`]: ./struct.Arg.html#method.validator + /// [`RegexRef`]: ./struct.RegexRef.html + #[cfg(feature = "regex")] + pub fn validator_regex( + self, + regex: impl Into>, + err_message: &'help str, + ) -> Self { + let regex = regex.into(); + self.validator(move |s: &str| { + if regex.is_match(s) { + Ok(()) + } else { + Err(err_message) + } + }) + } + /// Specifies the *maximum* number of values are for this argument. For example, if you had a /// `-f ` argument where you wanted up to 3 'files' you would set `.max_values(3)`, and /// this argument would be satisfied if the user provided, 1, 2, or 3 values. @@ -4326,6 +4406,23 @@ impl<'help> From<&'help Yaml> for Arg<'help> { a.set_mut(ArgSettings::RequiredUnlessAll); a } + #[cfg(feature = "regex")] + "validator_regex" => { + if let Some(vec) = v.as_vec() { + debug_assert_eq!(2, vec.len()); + let regex = yaml_str!(vec[0]); + + match Regex::new(regex) { + Err(e) => panic!( + "Failed to convert \"{}\" into regular expression: {}", + regex, e + ), + Ok(regex) => a.validator_regex(regex, yaml_str!(vec[1])), + } + } else { + panic!("Failed to convert YAML value to vector") + } + } s => panic!( "Unknown Arg setting '{}' in YAML file for arg '{}'", s, name_str diff --git a/src/build/arg/regex.rs b/src/build/arg/regex.rs new file mode 100644 index 00000000..54ad0490 --- /dev/null +++ b/src/build/arg/regex.rs @@ -0,0 +1,71 @@ +use ::regex::Regex; +use core::convert::TryFrom; +use core::ops::Deref; +use core::str::FromStr; +use std::borrow::Cow; + +/// Contains either a regular expression or a reference to one. +/// +/// Essentially a [`Cow`] wrapper with custom convenience traits. +/// +/// [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html +#[derive(Debug, Clone)] +pub struct RegexRef<'a>(Cow<'a, Regex>); + +impl<'a> Deref for RegexRef<'a> { + type Target = Regex; + + fn deref(&self) -> &Regex { + self.0.deref() + } +} + +impl<'a> FromStr for RegexRef<'a> { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + Regex::from_str(s).map(|v| RegexRef(Cow::Owned(v))) + } +} + +impl<'a> TryFrom<&'a str> for RegexRef<'a> { + type Error = as FromStr>::Err; + fn try_from(r: &'a str) -> Result { + RegexRef::from_str(r) + } +} + +impl<'a> From<&'a Regex> for RegexRef<'a> { + fn from(r: &'a Regex) -> Self { + RegexRef(Cow::Borrowed(r)) + } +} + +impl<'a> From for RegexRef<'a> { + fn from(r: Regex) -> Self { + RegexRef(Cow::Owned(r)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::convert::TryInto; + #[test] + fn test_try_from_with_valid_string() { + let t: Result = "^Hello, World$".try_into(); + assert!(t.is_ok()) + } + + #[test] + fn test_try_from_with_invalid_string() { + let t: Result = "^Hello, World)$".try_into(); + assert!(t.is_err()); + } + + #[test] + fn from_str() { + let t: Result = RegexRef::from_str("^Hello, World"); + assert!(t.is_ok()); + } +} diff --git a/src/lib.rs b/src/lib.rs index b942110e..971d37a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,9 @@ mod macros; #[cfg(feature = "derive")] mod derive; +#[cfg(feature = "regex")] +pub use crate::build::arg::RegexRef; + mod build; mod mkeymap; mod output; diff --git a/src/output/fmt.rs b/src/output/fmt.rs index a4a7d833..e385defe 100644 --- a/src/output/fmt.rs +++ b/src/output/fmt.rs @@ -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)>, } 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) { + self.pieces.push((msg.into(), Some(Color::Green))); + } + + #[inline] + pub(crate) fn warning(&mut self, msg: impl Into) { + self.pieces.push((msg.into(), Some(Color::Yellow))); + } + + #[inline] + pub(crate) fn error(&mut self, msg: impl Into) { + self.pieces.push((msg.into(), Some(Color::Red))); + } + + #[inline] + pub(crate) fn none(&mut self, msg: impl Into) { + 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 { - 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 ed11da1a..0ab1082a 100644 --- a/src/output/help.rs +++ b/src/output/help.rs @@ -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 { - 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 + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> { write_method!(self, msg, good) } - fn warning(&mut self, msg: &str) -> io::Result<()> { + fn warning + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> { write_method!(self, msg, warning) } - fn none(&mut self, msg: &str) -> io::Result<()> { + fn none + 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: &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: &mut R, - w: &mut W, - tag_buffer: &mut Cursor>, -) -> Option> { - 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::>(); - self.write_args(&opts_flags)?; - } - b"flags" => { - self.write_args(&self.parser.app.get_flags_no_heading().collect::>())?; - } - b"options" => { - self.write_args(&self.parser.app.get_opts_no_heading().collect::>())?; - } - b"positionals" => { - self.write_args(&self.parser.app.get_positionals().collect::>())?; - } - 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::>(); + 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::>())?; + } + "options" => { + self.write_args(&self.parser.app.get_opts_no_heading().collect::>())?; + } + "positionals" => { + self.write_args(&self.parser.app.get_positionals().collect::>())?; + } + "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(()) } } diff --git a/src/output/usage.rs b/src/output/usage.rs index 137c0e54..d3213cb1 100644 --- a/src/output/usage.rs +++ b/src/output/usage.rs @@ -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::>() .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::>() .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] ` 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 { + ) -> Vec { 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 = 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); diff --git a/src/parse/errors.rs b/src/parse/errors.rs index fa96ac57..da5ae641 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -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>, + /// 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, } 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) { + 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 put_usage(c: &mut Colorizer, usage: impl Into) { + 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( - group: &ArgGroup, - other: Option, - usage: U, - color: ColorChoice, - ) -> io::Result - where - O: Into, - 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( + pub(crate) fn argument_conflict( arg: &Arg, - other: Option, - usage: U, + other: Option, + usage: String, color: ColorChoice, - ) -> io::Result - where - O: Into, - 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(arg: &Arg, usage: U, color: ColorChoice) -> io::Result - 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( - bad_val: B, + pub(crate) fn invalid_value( + bad_val: String, good_vals: &[G], arg: &Arg, - usage: U, + usage: String, color: ColorChoice, - ) -> io::Result + ) -> Self where - B: AsRef, G: AsRef + 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 = 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( - 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 - where - S: Into, - D: AsRef + 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( - subcmd: S, - name: N, + pub(crate) fn unrecognized_subcommand( + subcmd: String, + name: String, color: ColorChoice, - ) -> io::Result - where - S: Into, - 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 ...", 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 ...", 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( - required: VecDeque, - usage: U, + pub(crate) fn missing_required_argument( + required: Vec, + usage: String, color: ColorChoice, - ) -> io::Result - 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::>() - .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( - name: N, - usage: U, - color: ColorChoice, - ) -> io::Result - where - N: AsRef + 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(usage: U, color: ColorChoice) -> io::Result - 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( - val: V, + pub(crate) fn too_many_values( + val: String, arg: &Arg, - usage: U, + usage: String, color: ColorChoice, - ) -> io::Result - where - V: AsRef + 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( + pub(crate) fn too_few_values( arg: &Arg, min_vals: u64, curr_vals: usize, - usage: U, + usage: String, color: ColorChoice, - ) -> io::Result - 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 { 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 { - let n: Option<&Arg> = None; - Error::value_validation(n, err, ColorChoice::Auto) - } - - pub(crate) fn wrong_number_of_values( + pub(crate) fn wrong_number_of_values( arg: &Arg, num_vals: u64, curr_vals: usize, - usage: U, + usage: String, color: ColorChoice, - ) -> io::Result - 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( - arg: &Arg, - usage: U, - color: ColorChoice, - ) -> io::Result - 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( - arg: A, + pub(crate) fn unknown_argument( + arg: String, did_you_mean: Option<(String, Option)>, - usage: U, + usage: String, color: ColorChoice, - ) -> io::Result - where - A: Into, - 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(arg: A) -> io::Result - where - A: Into, - { - 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, kind: ErrorKind) -> io::Result { + 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 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: fmt::Error) -> Self { Error::with_description(e.to_string(), ErrorKind::Format) - .expect("Unable to build error message") } } diff --git a/src/parse/matches/arg_matches.rs b/src/parse/matches/arg_matches.rs index 25a7a42f..3fc3362b 100644 --- a/src/parse/matches/arg_matches.rs +++ b/src/parse/matches/arg_matches.rs @@ -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 { ::Err: Display, { if let Some(v) = self.value_of(name) { - v.parse::().or_else(|e| { - Err(Error::value_validation_auto(&format!( - "The argument '{}' isn't a valid value: {}", - v, e - ))?) + v.parse::().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::().or_else(|e| { - Err(Error::value_validation_auto(&format!( - "The argument '{}' isn't a valid value: {}", - v, e - ))?) + v.parse::().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())) } } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 16cc7056..ef7acce3 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -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::().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(&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 { 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![], } } } diff --git a/src/parse/validator.rs b/src/parse/validator.rs index 254c4078..3cde1894 100644 --- a/src/parse/validator.rs +++ b/src/parse/validator.rs @@ -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(), - )?) + )) } } diff --git a/src/util/termcolor.rs b/src/util/termcolor.rs index 680e7fc8..97ac63f8 100644 --- a/src/util/termcolor.rs +++ b/src/util/termcolor.rs @@ -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; - -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, } diff --git a/tests/conflicts.rs b/tests/conflicts.rs index ef3a4b44..a81a2495 100644 --- a/tests/conflicts.rs +++ b/tests/conflicts.rs @@ -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 ); } diff --git a/tests/fixtures/app_regex.yaml b/tests/fixtures/app_regex.yaml new file mode 100644 index 00000000..bed49cfb --- /dev/null +++ b/tests/fixtures/app_regex.yaml @@ -0,0 +1,15 @@ +name: clapregextest +version: "1.0" +about: tests clap regex functionality +author: Benjamin Kästner +settings: + - ArgRequiredElseHelp +args: + - help: + short: h + long: help + about: prints help with a nonstandard description + - filter: + index: 1 + validator_regex: ["^*\\.[a-z]+$", expected extension pattern] + about: file extension pattern diff --git a/tests/fixtures/app_regex_invalid.yaml b/tests/fixtures/app_regex_invalid.yaml new file mode 100644 index 00000000..66425cfa --- /dev/null +++ b/tests/fixtures/app_regex_invalid.yaml @@ -0,0 +1,14 @@ +name: clapregextest +version: "1.0" +about: tests clap regex functionality +author: Benjamin Kästner +settings: + - ArgRequiredElseHelp +args: + - help: + short: h + long: help + about: prints help with a nonstandard description + - filter: + index: 1 + validator_regex: [")", invalid regular expression] diff --git a/tests/validators.rs b/tests/validators.rs index 2b8bb64a..7c9e71d7 100644 --- a/tests/validators.rs +++ b/tests/validators.rs @@ -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 \'\': invalid digit found in string" + assert!( + err.to_string() + .contains("Invalid value for '': invalid digit found in string"), + "{}", + err ); // This message is the only thing that gets printed -- make sure it ends with a newline diff --git a/tests/version.rs b/tests/version.rs index a9a80f2f..cf302d5d 100644 --- a/tests/version.rs +++ b/tests/version.rs @@ -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] diff --git a/tests/yaml.rs b/tests/yaml.rs index c18039d3..a9e73c9f 100644 --- a/tests/yaml.rs +++ b/tests/yaml.rs @@ -99,3 +99,32 @@ fn default_value_if_triggered_by_flag_and_argument() { // First condition triggers, therefore "some" assert_eq!(matches.value_of("positional2").unwrap(), "some"); } + +#[cfg(feature = "regex")] +#[test] +fn regex_with_invalid_string() { + let yml = load_yaml!("fixtures/app_regex.yaml"); + let app = App::from(yml); + let res = app.try_get_matches_from(vec!["prog", "not a proper filter"]); + + assert!(res.is_err()); +} + +#[cfg(feature = "regex")] +#[test] +fn regex_with_valid_string() { + let yml = load_yaml!("fixtures/app_regex.yaml"); + let app = App::from(yml); + + let matches = app.try_get_matches_from(vec!["prog", "*.txt"]).unwrap(); + + assert_eq!(matches.value_of("filter").unwrap(), "*.txt"); +} + +#[cfg(feature = "regex")] +#[test] +#[should_panic] +fn regex_with_invalid_yaml() { + let yml = load_yaml!("fixtures/app_regex_invalid.yaml"); + let _app = App::from(yml); +}