mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 06:12:40 +00:00
1976: Refactor r=pksunkara a=CreepySkeleton 2073: Add new "regex" feature r=CreepySkeleton a=bkaestner Co-authored-by: CreepySkeleton <creepy-skeleton@yandex.ru> Co-authored-by: Benjamin Kästner <benjamin.kaestner@gmail.com>
This commit is contained in:
commit
e4b5407296
19 changed files with 913 additions and 967 deletions
|
@ -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
|
||||
|
|
|
@ -1665,18 +1665,15 @@ impl<'help> App<'help> {
|
|||
/// app.print_help();
|
||||
/// ```
|
||||
/// [`io::stdout()`]: https://doc.rust-lang.org/std/io/fn.stdout.html
|
||||
/// [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
|
||||
/// [`-h` (short)]: ./struct.Arg.html#method.about
|
||||
/// [`--help` (long)]: ./struct.Arg.html#method.long_about
|
||||
pub fn print_help(&mut self) -> ClapResult<()> {
|
||||
pub fn print_help(&mut self) -> io::Result<()> {
|
||||
self._build();
|
||||
|
||||
let p = Parser::new(self);
|
||||
let mut c = Colorizer::new(false, p.color_help());
|
||||
|
||||
Help::new(HelpWriter::Buffer(&mut c), &p, false).write_help()?;
|
||||
|
||||
Ok(c.print()?)
|
||||
c.print()
|
||||
}
|
||||
|
||||
/// Prints the full help message to [`io::stdout()`] using a [`BufWriter`] using the same
|
||||
|
@ -1696,15 +1693,13 @@ impl<'help> App<'help> {
|
|||
/// [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
|
||||
/// [`-h` (short)]: ./struct.Arg.html#method.about
|
||||
/// [`--help` (long)]: ./struct.Arg.html#method.long_about
|
||||
pub fn print_long_help(&mut self) -> ClapResult<()> {
|
||||
pub fn print_long_help(&mut self) -> io::Result<()> {
|
||||
self._build();
|
||||
|
||||
let p = Parser::new(self);
|
||||
let mut c = Colorizer::new(false, p.color_help());
|
||||
|
||||
Help::new(HelpWriter::Buffer(&mut c), &p, true).write_help()?;
|
||||
|
||||
Ok(c.print()?)
|
||||
c.print()
|
||||
}
|
||||
|
||||
/// Writes the full help message to the user to a [`io::Write`] object in the same method as if
|
||||
|
@ -1725,11 +1720,12 @@ impl<'help> App<'help> {
|
|||
/// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
/// [`-h` (short)]: ./struct.Arg.html#method.about
|
||||
/// [`--help` (long)]: ./struct.Arg.html#method.long_about
|
||||
pub fn write_help<W: Write>(&mut self, w: &mut W) -> ClapResult<()> {
|
||||
pub fn write_help<W: Write>(&mut self, w: &mut W) -> io::Result<()> {
|
||||
self._build();
|
||||
|
||||
let p = Parser::new(self);
|
||||
Help::new(HelpWriter::Normal(w), &p, false).write_help()
|
||||
Help::new(HelpWriter::Normal(w), &p, false).write_help()?;
|
||||
w.flush()
|
||||
}
|
||||
|
||||
/// Writes the full help message to the user to a [`io::Write`] object in the same method as if
|
||||
|
@ -1750,53 +1746,61 @@ impl<'help> App<'help> {
|
|||
/// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
/// [`-h` (short)]: ./struct.Arg.html#method.about
|
||||
/// [`--help` (long)]: ./struct.Arg.html#method.long_about
|
||||
pub fn write_long_help<W: Write>(&mut self, w: &mut W) -> ClapResult<()> {
|
||||
pub fn write_long_help<W: Write>(&mut self, w: &mut W) -> io::Result<()> {
|
||||
self._build();
|
||||
|
||||
let p = Parser::new(self);
|
||||
Help::new(HelpWriter::Normal(w), &p, true).write_help()
|
||||
Help::new(HelpWriter::Normal(w), &p, true).write_help()?;
|
||||
w.flush()
|
||||
}
|
||||
|
||||
/// Writes the version message to the user to a [`io::Write`] object as if the user ran `-V`.
|
||||
/// Returns the version message rendered as if the user ran `-V`.
|
||||
///
|
||||
/// **NOTE:** clap has the ability to distinguish between "short" and "long" version messages
|
||||
/// depending on if the user ran [`-V` (short)] or [`--version` (long)].
|
||||
///
|
||||
/// # Examples
|
||||
/// ### Coloring
|
||||
///
|
||||
/// This function does not try to color the message nor it inserts any [ANSI escape codes].
|
||||
///
|
||||
/// ### Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::App;
|
||||
/// use std::io;
|
||||
/// let mut app = App::new("myprog");
|
||||
/// let mut out = io::stdout();
|
||||
/// app.write_version(&mut out).expect("failed to write to stdout");
|
||||
/// let app = App::new("myprog");
|
||||
/// println!("{}", app.render_version());
|
||||
/// ```
|
||||
/// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
/// [`-V` (short)]: ./struct.App.html#method.version
|
||||
/// [`--version` (long)]: ./struct.App.html#method.long_version
|
||||
pub fn write_version<W: Write>(&self, w: &mut W) -> ClapResult<()> {
|
||||
self._write_version(w, false).map_err(From::from)
|
||||
/// [ANSI escape codes]: https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
pub fn render_version(&self) -> String {
|
||||
self._render_version(false)
|
||||
}
|
||||
|
||||
/// Writes the version message to the user to a [`io::Write`] object.
|
||||
/// Returns the version message rendered as if the user ran `--version`.
|
||||
///
|
||||
/// **NOTE:** clap has the ability to distinguish between "short" and "long" version messages
|
||||
/// depending on if the user ran [`-V` (short)] or [`--version` (long)].
|
||||
///
|
||||
/// # Examples
|
||||
/// ### Coloring
|
||||
///
|
||||
/// This function does not try to color the message nor it inserts any [ANSI escape codes].
|
||||
///
|
||||
/// ### Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::App;
|
||||
/// use std::io;
|
||||
/// let mut app = App::new("myprog");
|
||||
/// let mut out = io::stdout();
|
||||
/// app.write_long_version(&mut out).expect("failed to write to stdout");
|
||||
/// let app = App::new("myprog");
|
||||
/// println!("{}", app.render_long_version());
|
||||
/// ```
|
||||
/// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
/// [`-V` (short)]: ./struct.App.html#method.version
|
||||
/// [`--version` (long)]: ./struct.App.html#method.long_version
|
||||
pub fn write_long_version<W: Write>(&self, w: &mut W) -> ClapResult<()> {
|
||||
self._write_version(w, true).map_err(From::from)
|
||||
pub fn render_long_version(&self) -> String {
|
||||
self._render_version(true)
|
||||
}
|
||||
|
||||
/// @TODO-v3-alpha @docs @p2: write docs
|
||||
|
@ -2111,7 +2115,7 @@ impl<'help> App<'help> {
|
|||
let mut matcher = ArgMatcher::default();
|
||||
|
||||
// If there are global arguments, or settings we need to propagate them down to subcommands
|
||||
// before parsing incase we run into a subcommand
|
||||
// before parsing in case we run into a subcommand
|
||||
if !self.settings.is_set(AppSettings::Built) {
|
||||
self._build();
|
||||
}
|
||||
|
@ -2150,12 +2154,9 @@ impl<'help> App<'help> {
|
|||
for a in self.args.args.iter_mut() {
|
||||
// Fill in the groups
|
||||
for g in &a.groups {
|
||||
let mut found = false;
|
||||
if let Some(ag) = self.groups.iter_mut().find(|grp| grp.id == *g) {
|
||||
ag.args.push(a.id.clone());
|
||||
found = true;
|
||||
}
|
||||
if !found {
|
||||
} else {
|
||||
let mut ag = ArgGroup::with_id(g.clone());
|
||||
ag.args.push(a.id.clone());
|
||||
self.groups.push(ag);
|
||||
|
@ -2396,8 +2397,8 @@ impl<'help> App<'help> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn _write_version<W: Write>(&self, w: &mut W, use_long: bool) -> io::Result<()> {
|
||||
debug!("App::_write_version");
|
||||
pub(crate) fn _render_version(&self, use_long: bool) -> String {
|
||||
debug!("App::_render_version");
|
||||
|
||||
let ver = if use_long {
|
||||
self.long_version
|
||||
|
@ -2409,12 +2410,12 @@ impl<'help> App<'help> {
|
|||
if let Some(bn) = self.bin_name.as_ref() {
|
||||
if bn.contains(' ') {
|
||||
// In case we're dealing with subcommands i.e. git mv is translated to git-mv
|
||||
writeln!(w, "{} {}", bn.replace(" ", "-"), ver)
|
||||
format!("{} {}\n", bn.replace(" ", "-"), ver)
|
||||
} else {
|
||||
writeln!(w, "{} {}", &self.name[..], ver)
|
||||
format!("{} {}\n", &self.name[..], ver)
|
||||
}
|
||||
} else {
|
||||
writeln!(w, "{} {}", &self.name[..], ver)
|
||||
format!("{} {}\n", &self.name[..], ver)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<RegexRef<'help>>,
|
||||
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 <file>` 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
|
||||
|
|
71
src/build/arg/regex.rs
Normal file
71
src/build/arg/regex.rs
Normal file
|
@ -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 = <Regex as core::str::FromStr>::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Regex::from_str(s).map(|v| RegexRef(Cow::Owned(v)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for RegexRef<'a> {
|
||||
type Error = <RegexRef<'a> as FromStr>::Err;
|
||||
fn try_from(r: &'a str) -> Result<Self, Self::Error> {
|
||||
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<Regex> 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<RegexRef, _> = "^Hello, World$".try_into();
|
||||
assert!(t.is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from_with_invalid_string() {
|
||||
let t: Result<RegexRef, _> = "^Hello, World)$".try_into();
|
||||
assert!(t.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
let t: Result<RegexRef, _> = RegexRef::from_str("^Hello, World");
|
||||
assert!(t.is_ok());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::util::termcolor::{Buffer, BufferWriter, ColorChoice};
|
||||
#[cfg(not(feature = "color"))]
|
||||
use crate::util::termcolor::{Color, ColorChoice};
|
||||
#[cfg(feature = "color")]
|
||||
use crate::util::termcolor::{Color, ColorSpec, WriteColor};
|
||||
use termcolor::{Color, ColorChoice};
|
||||
|
||||
use std::{
|
||||
fmt::{self, Debug, Formatter},
|
||||
io::{Result, Write},
|
||||
fmt::{self, Display, Formatter},
|
||||
io::{self, Write},
|
||||
};
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
|
@ -20,103 +21,102 @@ fn is_a_tty(stderr: bool) -> bool {
|
|||
atty::is(stream)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
fn is_a_tty(_: bool) -> bool {
|
||||
debug!("is_a_tty");
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Colorizer {
|
||||
writer: BufferWriter,
|
||||
buffer: Buffer,
|
||||
}
|
||||
|
||||
impl Debug for Colorizer {
|
||||
#[cfg(feature = "color")]
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", String::from_utf8_lossy(self.buffer.as_slice()))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", String::from_utf8_lossy(&self.buffer))
|
||||
}
|
||||
use_stderr: bool,
|
||||
color_when: ColorChoice,
|
||||
pieces: Vec<(String, Option<Color>)>,
|
||||
}
|
||||
|
||||
impl Colorizer {
|
||||
pub(crate) fn new(use_stderr: bool, when: ColorChoice) -> Self {
|
||||
let checked_when = if is_a_tty(use_stderr) {
|
||||
when
|
||||
#[inline]
|
||||
pub(crate) fn new(use_stderr: bool, color_when: ColorChoice) -> Self {
|
||||
Colorizer {
|
||||
use_stderr,
|
||||
color_when,
|
||||
pieces: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn good(&mut self, msg: impl Into<String>) {
|
||||
self.pieces.push((msg.into(), Some(Color::Green)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn warning(&mut self, msg: impl Into<String>) {
|
||||
self.pieces.push((msg.into(), Some(Color::Yellow)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn error(&mut self, msg: impl Into<String>) {
|
||||
self.pieces.push((msg.into(), Some(Color::Red)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn none(&mut self, msg: impl Into<String>) {
|
||||
self.pieces.push((msg.into(), None));
|
||||
}
|
||||
}
|
||||
|
||||
/// Printing methods.
|
||||
impl Colorizer {
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn print(&self) -> io::Result<()> {
|
||||
use termcolor::{BufferWriter, ColorSpec, WriteColor};
|
||||
|
||||
let color_when = if is_a_tty(self.use_stderr) {
|
||||
self.color_when
|
||||
} else {
|
||||
ColorChoice::Never
|
||||
};
|
||||
|
||||
let writer = if use_stderr {
|
||||
BufferWriter::stderr(checked_when)
|
||||
let writer = if self.use_stderr {
|
||||
BufferWriter::stderr(color_when)
|
||||
} else {
|
||||
BufferWriter::stdout(checked_when)
|
||||
BufferWriter::stdout(color_when)
|
||||
};
|
||||
|
||||
let buffer = writer.buffer();
|
||||
let mut buffer = writer.buffer();
|
||||
|
||||
Self { writer, buffer }
|
||||
}
|
||||
for piece in &self.pieces {
|
||||
let mut color = ColorSpec::new();
|
||||
color.set_fg(piece.1);
|
||||
if piece.1 == Some(Color::Red) {
|
||||
color.set_bold(true);
|
||||
}
|
||||
|
||||
pub(crate) fn print(&self) -> Result<()> {
|
||||
self.writer.print(&self.buffer)
|
||||
}
|
||||
buffer.set_color(&color)?;
|
||||
buffer.write_all(piece.0.as_bytes())?;
|
||||
buffer.reset()?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn good(&mut self, msg: &str) -> Result<()> {
|
||||
self.buffer
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
|
||||
self.write_all(msg.as_bytes())?;
|
||||
self.buffer.reset()
|
||||
writer.print(&buffer)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub(crate) fn good(&mut self, msg: &str) -> Result<()> {
|
||||
self.none(msg)
|
||||
pub(crate) fn print(&self) -> io::Result<()> {
|
||||
// [e]println can't be used here because it panics
|
||||
// if something went wrong. We don't want that.
|
||||
if self.use_stderr {
|
||||
let stderr = std::io::stderr();
|
||||
let mut stderr = stderr.lock();
|
||||
write!(stderr, "{}", self)
|
||||
} else {
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
write!(stdout, "{}", self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn warning(&mut self, msg: &str) -> Result<()> {
|
||||
self.buffer
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
|
||||
self.write_all(msg.as_bytes())?;
|
||||
self.buffer.reset()
|
||||
}
|
||||
/// Color-unaware printing. Never uses coloring.
|
||||
impl Display for Colorizer {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
for piece in &self.pieces {
|
||||
Display::fmt(&piece.0, f)?;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub(crate) fn warning(&mut self, msg: &str) -> Result<()> {
|
||||
self.none(msg)
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn error(&mut self, msg: &str) -> Result<()> {
|
||||
self.buffer
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
|
||||
self.write_all(msg.as_bytes())?;
|
||||
self.buffer.reset()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub(crate) fn error(&mut self, msg: &str) -> Result<()> {
|
||||
self.none(msg)
|
||||
}
|
||||
|
||||
pub(crate) fn none(&mut self, msg: &str) -> Result<()> {
|
||||
self.write_all(msg.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Colorizer {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||
self.buffer.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<()> {
|
||||
self.buffer.flush()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
borrow::Cow,
|
||||
cmp,
|
||||
collections::BTreeMap,
|
||||
io::{self, Cursor, Read, Write},
|
||||
io::{self, Write},
|
||||
usize,
|
||||
};
|
||||
|
||||
|
@ -11,10 +11,7 @@ use std::{
|
|||
use crate::{
|
||||
build::{App, AppSettings, Arg, ArgSettings},
|
||||
output::{fmt::Colorizer, Usage},
|
||||
parse::{
|
||||
errors::{Error, Result as ClapResult},
|
||||
Parser,
|
||||
},
|
||||
parse::Parser,
|
||||
util::VecMap,
|
||||
INTERNAL_ERROR_MSG,
|
||||
};
|
||||
|
@ -42,22 +39,6 @@ pub(crate) enum HelpWriter<'writer> {
|
|||
Buffer(&'writer mut Colorizer),
|
||||
}
|
||||
|
||||
impl<'writer> Write for HelpWriter<'writer> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match self {
|
||||
HelpWriter::Normal(n) => n.write(buf),
|
||||
HelpWriter::Buffer(c) => c.write(buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
match self {
|
||||
HelpWriter::Normal(n) => n.flush(),
|
||||
HelpWriter::Buffer(c) => c.flush(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `clap` Help Writer.
|
||||
///
|
||||
/// Wraps a writer stream providing different methods to generate help for `clap` objects.
|
||||
|
@ -122,11 +103,11 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
}
|
||||
|
||||
/// Writes the parser help to the wrapped stream.
|
||||
pub(crate) fn write_help(&mut self) -> ClapResult<()> {
|
||||
pub(crate) fn write_help(&mut self) -> io::Result<()> {
|
||||
debug!("Help::write_help");
|
||||
|
||||
if let Some(h) = self.parser.app.help_str {
|
||||
self.none(h).map_err(Error::from)?;
|
||||
self.none(h)?;
|
||||
} else if let Some(tmpl) = self.parser.app.template {
|
||||
self.write_templated_help(tmpl)?;
|
||||
} else {
|
||||
|
@ -151,8 +132,11 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
macro_rules! write_method {
|
||||
($_self:ident, $msg:ident, $meth:ident) => {
|
||||
match &mut $_self.writer {
|
||||
HelpWriter::Buffer(c) => c.$meth($msg),
|
||||
HelpWriter::Normal(w) => write!(w, "{}", $msg),
|
||||
HelpWriter::Buffer(c) => {
|
||||
c.$meth(($msg).into());
|
||||
Ok(())
|
||||
}
|
||||
HelpWriter::Normal(w) => w.write_all($msg.as_ref()),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -168,15 +152,15 @@ macro_rules! write_nspaces {
|
|||
|
||||
// Methods to write Arg help.
|
||||
impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
||||
fn good(&mut self, msg: &str) -> io::Result<()> {
|
||||
fn good<T: Into<String> + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> {
|
||||
write_method!(self, msg, good)
|
||||
}
|
||||
|
||||
fn warning(&mut self, msg: &str) -> io::Result<()> {
|
||||
fn warning<T: Into<String> + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> {
|
||||
write_method!(self, msg, warning)
|
||||
}
|
||||
|
||||
fn none(&mut self, msg: &str) -> io::Result<()> {
|
||||
fn none<T: Into<String> + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> {
|
||||
write_method!(self, msg, none)
|
||||
}
|
||||
|
||||
|
@ -693,17 +677,15 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
||||
/// Writes help for all arguments (options, flags, args, subcommands)
|
||||
/// including titles of a Parser Object to the wrapped stream.
|
||||
pub(crate) fn write_all_args(&mut self) -> ClapResult<()> {
|
||||
pub(crate) fn write_all_args(&mut self) -> io::Result<()> {
|
||||
debug!("Help::write_all_args");
|
||||
let flags = self.parser.has_flags();
|
||||
// FIXME: Strange filter/count vs fold... https://github.com/rust-lang/rust/issues/33038
|
||||
let pos = self.parser.app.get_positionals().fold(0, |acc, arg| {
|
||||
if should_show_arg(self.use_long, arg) {
|
||||
acc + 1
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
}) > 0;
|
||||
let pos = self
|
||||
.parser
|
||||
.app
|
||||
.get_positionals()
|
||||
.filter(|arg| should_show_arg(self.use_long, arg))
|
||||
.any(|_| true);
|
||||
let opts = self
|
||||
.parser
|
||||
.app
|
||||
|
@ -850,7 +832,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
}
|
||||
if let Some(bn) = self.parser.app.bin_name.as_ref() {
|
||||
if bn.contains(' ') {
|
||||
// Incase we're dealing with subcommands i.e. git mv is translated to git-mv
|
||||
// In case we're dealing with subcommands i.e. git mv is translated to git-mv
|
||||
self.good(&bn.replace(" ", "-"))?
|
||||
} else {
|
||||
write_name!();
|
||||
|
@ -862,103 +844,6 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Possible results for a copying function that stops when a given
|
||||
/// byte was found.
|
||||
enum CopyUntilResult {
|
||||
DelimiterFound(usize),
|
||||
DelimiterNotFound(usize),
|
||||
ReaderEmpty,
|
||||
ReadError(io::Error),
|
||||
WriteError(io::Error),
|
||||
}
|
||||
|
||||
/// Copies the contents of a reader into a writer until a delimiter byte is found.
|
||||
/// On success, the total number of bytes that were
|
||||
/// copied from reader to writer is returned.
|
||||
fn copy_until<R: Read, W: Write>(r: &mut R, w: &mut W, delimiter_byte: u8) -> CopyUntilResult {
|
||||
debug!("copy_until");
|
||||
|
||||
let mut count = 0;
|
||||
for wb in r.bytes() {
|
||||
match wb {
|
||||
Ok(b) => {
|
||||
if b == delimiter_byte {
|
||||
return CopyUntilResult::DelimiterFound(count);
|
||||
}
|
||||
match w.write(&[b]) {
|
||||
Ok(c) => count += c,
|
||||
Err(e) => return CopyUntilResult::WriteError(e),
|
||||
}
|
||||
}
|
||||
Err(e) => return CopyUntilResult::ReadError(e),
|
||||
}
|
||||
}
|
||||
if count > 0 {
|
||||
CopyUntilResult::DelimiterNotFound(count)
|
||||
} else {
|
||||
CopyUntilResult::ReaderEmpty
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the contents of a reader into a writer until a {tag} is found,
|
||||
/// copying the tag content to a buffer and returning its size.
|
||||
/// In addition to errors, there are three possible outputs:
|
||||
/// - `None`: The reader was consumed.
|
||||
/// - `Some(Ok(0))`: No tag was captured but the reader still contains data.
|
||||
/// - `Some(Ok(length>0))`: a tag with `length` was captured to the `tag_buffer`.
|
||||
fn copy_and_capture<R: Read, W: Write>(
|
||||
r: &mut R,
|
||||
w: &mut W,
|
||||
tag_buffer: &mut Cursor<Vec<u8>>,
|
||||
) -> Option<io::Result<usize>> {
|
||||
use self::CopyUntilResult::*;
|
||||
debug!("copy_and_capture");
|
||||
|
||||
// Find the opening byte.
|
||||
match copy_until(r, w, b'{') {
|
||||
// The end of the reader was reached without finding the opening tag.
|
||||
// (either with or without having copied data to the writer)
|
||||
// Return None indicating that we are done.
|
||||
ReaderEmpty | DelimiterNotFound(_) => None,
|
||||
|
||||
// Something went wrong.
|
||||
ReadError(e) | WriteError(e) => Some(Err(e)),
|
||||
|
||||
// The opening byte was found.
|
||||
// (either with or without having copied data to the writer)
|
||||
DelimiterFound(_) => {
|
||||
// Lets reset the buffer first and find out how long it is.
|
||||
tag_buffer.set_position(0);
|
||||
let buffer_size = tag_buffer.get_ref().len();
|
||||
|
||||
// Find the closing byte,limiting the reader to the length of the buffer.
|
||||
let mut rb = r.take(buffer_size as u64);
|
||||
match copy_until(&mut rb, tag_buffer, b'}') {
|
||||
// We were already at the end of the reader.
|
||||
// Return None indicating that we are done.
|
||||
ReaderEmpty => None,
|
||||
|
||||
// The closing tag was found.
|
||||
// Return the tag_length.
|
||||
DelimiterFound(tag_length) => Some(Ok(tag_length)),
|
||||
|
||||
// The end of the reader was found without finding the closing tag.
|
||||
// Write the opening byte and captured text to the writer.
|
||||
// Return 0 indicating that nothing was captured but the reader still contains data.
|
||||
DelimiterNotFound(not_tag_length) => match w.write(b"{") {
|
||||
Err(e) => Some(Err(e)),
|
||||
_ => match w.write(&tag_buffer.get_ref()[0..not_tag_length]) {
|
||||
Err(e) => Some(Err(e)),
|
||||
_ => Some(Ok(0)),
|
||||
},
|
||||
},
|
||||
|
||||
ReadError(e) | WriteError(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Methods to write Parser help using templates.
|
||||
impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
||||
/// Write help to stream for the parser in the format defined by the template.
|
||||
|
@ -966,10 +851,8 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
/// For details about the template language see [`App::help_template`].
|
||||
///
|
||||
/// [`App::help_template`]: ./struct.App.html#method.help_template
|
||||
fn write_templated_help(&mut self, template: &str) -> ClapResult<()> {
|
||||
fn write_templated_help(&mut self, template: &str) -> io::Result<()> {
|
||||
debug!("Help::write_templated_help");
|
||||
let mut tmplr = Cursor::new(&template);
|
||||
let mut tag_buf = Cursor::new(vec![0u8; 20]);
|
||||
|
||||
// The strategy is to copy the template from the reader to wrapped stream
|
||||
// until a tag is found. Depending on its value, the appropriate content is copied
|
||||
|
@ -977,127 +860,140 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
// The copy from template is then resumed, repeating this sequence until reading
|
||||
// the complete template.
|
||||
|
||||
loop {
|
||||
let tag_length = match copy_and_capture(&mut tmplr, &mut self.writer, &mut tag_buf) {
|
||||
None => return Ok(()),
|
||||
Some(Err(e)) => return Err(Error::from(e)),
|
||||
Some(Ok(val)) if val > 0 => val,
|
||||
_ => continue,
|
||||
};
|
||||
macro_rules! tags {
|
||||
(
|
||||
match $part:ident {
|
||||
$( $tag:expr => $action:stmt )*
|
||||
}
|
||||
) => {
|
||||
match $part {
|
||||
$(
|
||||
part if part.starts_with(concat!($tag, "}")) => {
|
||||
$action
|
||||
let rest = &part[$tag.len()+1..];
|
||||
self.none(rest)?;
|
||||
}
|
||||
)*
|
||||
|
||||
debug!(
|
||||
"Help::write_template_help:iter: tag_buf={}",
|
||||
String::from_utf8_lossy(&tag_buf.get_ref()[0..tag_length])
|
||||
);
|
||||
match &tag_buf.get_ref()[0..tag_length] {
|
||||
b"?" => {
|
||||
self.none("Could not decode tag name")?;
|
||||
}
|
||||
b"bin" => {
|
||||
self.write_bin_name()?;
|
||||
}
|
||||
b"version" => {
|
||||
if let Some(output) = self.parser.app.version {
|
||||
self.none(output)?;
|
||||
// Unknown tag, write it back.
|
||||
part => {
|
||||
self.none("{")?;
|
||||
self.none(part)?;
|
||||
}
|
||||
}
|
||||
b"author" => {
|
||||
if let Some(output) = self.parser.app.author {
|
||||
self.none(&wrap_help(output, self.term_w))?;
|
||||
};
|
||||
}
|
||||
|
||||
let mut parts = template.split('{');
|
||||
if let Some(first) = parts.next() {
|
||||
self.none(first)?;
|
||||
}
|
||||
|
||||
for part in parts {
|
||||
tags! {
|
||||
match part {
|
||||
"bin" => {
|
||||
self.write_bin_name()?;
|
||||
}
|
||||
}
|
||||
b"author-with-newline" => {
|
||||
if let Some(output) = self.parser.app.author {
|
||||
self.none(&wrap_help(output, self.term_w))?;
|
||||
self.none("\n")?;
|
||||
"version" => {
|
||||
if let Some(s) = self.parser.app.version {
|
||||
self.none(s)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
b"about" => {
|
||||
let about = if self.use_long {
|
||||
self.parser.app.long_about.or(self.parser.app.about)
|
||||
} else {
|
||||
self.parser.app.about
|
||||
};
|
||||
if let Some(output) = about {
|
||||
self.none(&wrap_help(output, self.term_w))?;
|
||||
"author" => {
|
||||
if let Some(s) = self.parser.app.author {
|
||||
self.none(&wrap_help(s, self.term_w))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
b"about-with-newline" => {
|
||||
let about = if self.use_long {
|
||||
self.parser.app.long_about.or(self.parser.app.about)
|
||||
} else {
|
||||
self.parser.app.about
|
||||
};
|
||||
if let Some(output) = about {
|
||||
self.none(&wrap_help(output, self.term_w))?;
|
||||
self.none("\n")?;
|
||||
"author-with-newline" => {
|
||||
if let Some(s) = self.parser.app.author {
|
||||
self.none(&wrap_help(s, self.term_w))?;
|
||||
self.none("\n")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
b"usage" => {
|
||||
self.none(&Usage::new(self.parser).create_usage_no_title(&[]))?;
|
||||
}
|
||||
b"all-args" => {
|
||||
self.write_all_args()?;
|
||||
}
|
||||
b"unified" => {
|
||||
let opts_flags = self
|
||||
.parser
|
||||
.app
|
||||
.args
|
||||
.args
|
||||
.iter()
|
||||
.filter(|a| a.has_switch())
|
||||
.collect::<Vec<_>>();
|
||||
self.write_args(&opts_flags)?;
|
||||
}
|
||||
b"flags" => {
|
||||
self.write_args(&self.parser.app.get_flags_no_heading().collect::<Vec<_>>())?;
|
||||
}
|
||||
b"options" => {
|
||||
self.write_args(&self.parser.app.get_opts_no_heading().collect::<Vec<_>>())?;
|
||||
}
|
||||
b"positionals" => {
|
||||
self.write_args(&self.parser.app.get_positionals().collect::<Vec<_>>())?;
|
||||
}
|
||||
b"subcommands" => {
|
||||
self.write_subcommands(self.parser.app)?;
|
||||
}
|
||||
b"after-help" => {
|
||||
let after_help = if self.use_long {
|
||||
self.parser
|
||||
"about" => {
|
||||
let about = if self.use_long {
|
||||
self.parser.app.long_about.or(self.parser.app.about)
|
||||
} else {
|
||||
self.parser.app.about
|
||||
};
|
||||
if let Some(output) = about {
|
||||
self.none(wrap_help(output, self.term_w))?;
|
||||
}
|
||||
}
|
||||
"about-with-newline" => {
|
||||
let about = if self.use_long {
|
||||
self.parser.app.long_about.or(self.parser.app.about)
|
||||
} else {
|
||||
self.parser.app.about
|
||||
};
|
||||
if let Some(output) = about {
|
||||
self.none(wrap_help(output, self.term_w))?;
|
||||
self.none("\n")?;
|
||||
}
|
||||
}
|
||||
"usage" => {
|
||||
self.none(Usage::new(self.parser).create_usage_no_title(&[]))?;
|
||||
}
|
||||
"all-args" => {
|
||||
self.write_all_args()?;
|
||||
}
|
||||
"unified" => {
|
||||
let opts_flags = self
|
||||
.parser
|
||||
.app
|
||||
.after_long_help
|
||||
.or(self.parser.app.after_help)
|
||||
} else {
|
||||
self.parser.app.after_help
|
||||
};
|
||||
if let Some(output) = after_help {
|
||||
self.none("\n\n")?;
|
||||
self.write_before_after_help(output)?;
|
||||
.args
|
||||
.args
|
||||
.iter()
|
||||
.filter(|a| a.has_switch())
|
||||
.collect::<Vec<_>>();
|
||||
self.write_args(&opts_flags)?;
|
||||
}
|
||||
}
|
||||
b"before-help" => {
|
||||
let before_help = if self.use_long {
|
||||
self.parser
|
||||
.app
|
||||
.before_long_help
|
||||
.or(self.parser.app.before_help)
|
||||
} else {
|
||||
self.parser.app.before_help
|
||||
};
|
||||
if let Some(output) = before_help {
|
||||
self.write_before_after_help(output)?;
|
||||
self.none("\n\n")?;
|
||||
"flags" => {
|
||||
self.write_args(&self.parser.app.get_flags_no_heading().collect::<Vec<_>>())?;
|
||||
}
|
||||
"options" => {
|
||||
self.write_args(&self.parser.app.get_opts_no_heading().collect::<Vec<_>>())?;
|
||||
}
|
||||
"positionals" => {
|
||||
self.write_args(&self.parser.app.get_positionals().collect::<Vec<_>>())?;
|
||||
}
|
||||
"subcommands" => {
|
||||
self.write_subcommands(self.parser.app)?;
|
||||
}
|
||||
"after-help" => {
|
||||
let after_help = if self.use_long {
|
||||
self.parser
|
||||
.app
|
||||
.after_long_help
|
||||
.or(self.parser.app.after_help)
|
||||
} else {
|
||||
self.parser.app.after_help
|
||||
};
|
||||
if let Some(output) = after_help {
|
||||
self.none("\n\n")?;
|
||||
self.write_before_after_help(output)?;
|
||||
}
|
||||
}
|
||||
"before-help" => {
|
||||
let before_help = if self.use_long {
|
||||
self.parser
|
||||
.app
|
||||
.before_long_help
|
||||
.or(self.parser.app.before_help)
|
||||
} else {
|
||||
self.parser.app.before_help
|
||||
};
|
||||
if let Some(output) = before_help {
|
||||
self.write_before_after_help(output)?;
|
||||
self.none("\n\n")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Unknown tag, write it back.
|
||||
r => {
|
||||
self.none("{")?;
|
||||
self.writer.write_all(r)?;
|
||||
self.none("}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// std
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// Internal
|
||||
use crate::{
|
||||
|
@ -241,9 +241,12 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
count
|
||||
);
|
||||
}
|
||||
|
||||
if !self.p.is_set(AS::DontCollapseArgsInUsage) && count > 1 {
|
||||
debug!("Usage::get_args_tag:iter: More than one, returning [ARGS]");
|
||||
return None; // [ARGS]
|
||||
|
||||
// [ARGS]
|
||||
None
|
||||
} else if count == 1 && incl_reqs {
|
||||
let pos = self
|
||||
.p
|
||||
|
@ -255,21 +258,23 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
&& !pos.is_set(ArgSettings::Last)
|
||||
})
|
||||
.expect(INTERNAL_ERROR_MSG);
|
||||
|
||||
debug!(
|
||||
"Usage::get_args_tag:iter: Exactly one, returning '{}'",
|
||||
pos.name
|
||||
);
|
||||
return Some(format!(
|
||||
|
||||
Some(format!(
|
||||
" [{}]{}",
|
||||
pos.name_no_brackets(),
|
||||
pos.multiple_str()
|
||||
));
|
||||
))
|
||||
} else if self.p.is_set(AS::DontCollapseArgsInUsage)
|
||||
&& self.p.has_positionals()
|
||||
&& incl_reqs
|
||||
{
|
||||
debug!("Usage::get_args_tag:iter: Don't collapse returning all");
|
||||
return Some(
|
||||
Some(
|
||||
self.p
|
||||
.app
|
||||
.get_positionals()
|
||||
|
@ -279,7 +284,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
.map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(""),
|
||||
);
|
||||
)
|
||||
} else if !incl_reqs {
|
||||
debug!("Usage::get_args_tag:iter: incl_reqs=false, building secondary usage string");
|
||||
let highest_req_pos = self
|
||||
|
@ -295,7 +300,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
})
|
||||
.max()
|
||||
.unwrap_or_else(|| Some(self.p.app.get_positionals().count() as u64));
|
||||
return Some(
|
||||
Some(
|
||||
self.p
|
||||
.app
|
||||
.get_positionals()
|
||||
|
@ -306,9 +311,10 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
.map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(""),
|
||||
);
|
||||
)
|
||||
} else {
|
||||
Some("".into())
|
||||
}
|
||||
Some("".into())
|
||||
}
|
||||
|
||||
// Determines if we need the `[FLAGS]` tag in the usage string
|
||||
|
@ -316,11 +322,14 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
debug!("Usage::needs_flags_tag");
|
||||
'outer: for f in self.p.app.get_flags_no_heading() {
|
||||
debug!("Usage::needs_flags_tag:iter: f={}", f.name);
|
||||
if let Some(l) = f.long {
|
||||
if l == "help" || l == "version" {
|
||||
// Don't print `[FLAGS]` just for help or version
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't print `[FLAGS]` just for help or version
|
||||
if f.long == Some("help") || f.long == Some("version") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if f.is_set(ArgSettings::Hidden) {
|
||||
continue;
|
||||
}
|
||||
for grp_s in self.p.app.groups_for_arg(&f.id) {
|
||||
debug!("Usage::needs_flags_tag:iter:iter: grp_s={:?}", grp_s);
|
||||
|
@ -335,9 +344,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
continue 'outer;
|
||||
}
|
||||
}
|
||||
if f.is_set(ArgSettings::Hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!("Usage::needs_flags_tag:iter: [FLAGS] required");
|
||||
return true;
|
||||
}
|
||||
|
@ -347,7 +354,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
}
|
||||
|
||||
// Returns the required args in usage string form by fully unrolling all groups
|
||||
// `incl_last`: should we incldue args that are Arg::Last? (i.e. `prog [foo] -- [last]). We
|
||||
// `incl_last`: should we include args that are Arg::Last? (i.e. `prog [foo] -- [last]). We
|
||||
// can't do that for required usages being built for subcommands because it would look like:
|
||||
// `prog [foo] -- [last] <subcommand>` which is totally wrong.
|
||||
pub(crate) fn get_required_usage_from(
|
||||
|
@ -355,14 +362,14 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
incls: &[Id],
|
||||
matcher: Option<&ArgMatcher>,
|
||||
incl_last: bool,
|
||||
) -> VecDeque<String> {
|
||||
) -> Vec<String> {
|
||||
debug!(
|
||||
"Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}",
|
||||
incls,
|
||||
matcher.is_some(),
|
||||
incl_last
|
||||
);
|
||||
let mut ret_val = VecDeque::new();
|
||||
let mut ret_val = Vec::new();
|
||||
|
||||
let mut unrolled_reqs = vec![];
|
||||
|
||||
|
@ -405,7 +412,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
for p in pmap.values() {
|
||||
debug!("Usage::get_required_usage_from:iter:{:?}", p.id);
|
||||
if args_in_groups.is_empty() || !args_in_groups.contains(&p.id) {
|
||||
ret_val.push_back(p.to_string());
|
||||
ret_val.push(p.to_string());
|
||||
}
|
||||
}
|
||||
for a in unrolled_reqs
|
||||
|
@ -423,7 +430,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
.find(&a)
|
||||
.map(ToString::to_string)
|
||||
.expect(INTERNAL_ERROR_MSG);
|
||||
ret_val.push_back(arg);
|
||||
ret_val.push(arg);
|
||||
}
|
||||
let mut g_vec: Vec<String> = vec![];
|
||||
for g in unrolled_reqs
|
||||
|
@ -449,7 +456,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
}
|
||||
}
|
||||
for g in g_vec {
|
||||
ret_val.push_back(g);
|
||||
ret_val.push(g);
|
||||
}
|
||||
|
||||
debug!("Usage::get_required_usage_from: ret_val={:?}", ret_val);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// Std
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
convert::From,
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
io,
|
||||
|
@ -9,7 +8,7 @@ use std::{
|
|||
|
||||
// Internal
|
||||
use crate::{
|
||||
build::{Arg, ArgGroup},
|
||||
build::Arg,
|
||||
output::fmt::Colorizer,
|
||||
parse::features::suggestions,
|
||||
util::{safe_exit, termcolor::ColorChoice},
|
||||
|
@ -375,39 +374,36 @@ pub enum ErrorKind {
|
|||
/// Command Line Argument Parser Error
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
/// The cause of the error
|
||||
pub cause: String,
|
||||
/// Formatted error message, enhancing the cause message with extra information
|
||||
pub(crate) message: Colorizer,
|
||||
/// The type of error
|
||||
pub kind: ErrorKind,
|
||||
/// Any additional information passed along, such as the argument name that caused the error
|
||||
pub info: Option<Vec<String>>,
|
||||
/// Additional information depending on the error kind, like values and argument names.
|
||||
/// Useful when you want to render an error of your own.
|
||||
pub info: Vec<String>,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.message.fmt(f)
|
||||
Display::fmt(&self.message, f)
|
||||
}
|
||||
}
|
||||
|
||||
fn start_error(c: &mut Colorizer, msg: &str) -> io::Result<()> {
|
||||
c.error("error:")?;
|
||||
c.none(" ")?;
|
||||
c.none(msg)
|
||||
fn start_error(c: &mut Colorizer, msg: impl Into<String>) {
|
||||
c.error("error:");
|
||||
c.none(" ");
|
||||
c.none(msg);
|
||||
}
|
||||
|
||||
fn put_usage<U>(c: &mut Colorizer, usage: U) -> io::Result<()>
|
||||
where
|
||||
U: Display,
|
||||
{
|
||||
c.none(&format!("\n\n{}", usage))
|
||||
fn put_usage(c: &mut Colorizer, usage: impl Into<String>) {
|
||||
c.none("\n\n");
|
||||
c.none(usage);
|
||||
}
|
||||
|
||||
fn try_help(c: &mut Colorizer) -> io::Result<()> {
|
||||
c.none("\n\nFor more information try ")?;
|
||||
c.good("--help")?;
|
||||
c.none("\n")
|
||||
fn try_help(c: &mut Colorizer) {
|
||||
c.none("\n\nFor more information try ");
|
||||
c.good("--help");
|
||||
c.none("\n");
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -440,604 +436,427 @@ impl Error {
|
|||
safe_exit(0)
|
||||
}
|
||||
|
||||
#[allow(unused)] // requested by @pksunkara
|
||||
pub(crate) fn group_conflict<O, U>(
|
||||
group: &ArgGroup,
|
||||
other: Option<O>,
|
||||
usage: U,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
O: Into<String>,
|
||||
U: Display,
|
||||
{
|
||||
let mut v = vec![group.name.to_owned()];
|
||||
let mut c = Colorizer::new(true, color);
|
||||
|
||||
start_error(&mut c, "The argument '")?;
|
||||
c.warning(group.name)?;
|
||||
c.none("' cannot be used with ")?;
|
||||
|
||||
let cause = match other {
|
||||
Some(name) => {
|
||||
let n = name.into();
|
||||
v.push(n.clone());
|
||||
|
||||
c.none("'")?;
|
||||
c.warning(&*n)?;
|
||||
c.none("'")?;
|
||||
|
||||
format!("The argument '{}' cannot be used with '{}'", group.name, n)
|
||||
}
|
||||
None => {
|
||||
let n = "one or more of the other specified arguments";
|
||||
|
||||
c.none(n)?;
|
||||
|
||||
format!("The argument '{}' cannot be used with {}", group.name, n)
|
||||
}
|
||||
};
|
||||
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
|
||||
Ok(Error {
|
||||
cause,
|
||||
message: c,
|
||||
kind: ErrorKind::ArgumentConflict,
|
||||
info: Some(v),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn argument_conflict<O, U>(
|
||||
pub(crate) fn argument_conflict(
|
||||
arg: &Arg,
|
||||
other: Option<O>,
|
||||
usage: U,
|
||||
other: Option<String>,
|
||||
usage: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
O: Into<String>,
|
||||
U: Display,
|
||||
{
|
||||
let mut v = vec![arg.name.to_owned()];
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
let arg = arg.to_string();
|
||||
|
||||
start_error(&mut c, "The argument '")?;
|
||||
c.warning(&arg.to_string())?;
|
||||
c.none("' cannot be used with ")?;
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.clone());
|
||||
c.none("' cannot be used with ");
|
||||
|
||||
let cause = match other {
|
||||
Some(name) => {
|
||||
let n = name.into();
|
||||
v.push(n.clone());
|
||||
|
||||
c.none("'")?;
|
||||
c.warning(&*n)?;
|
||||
c.none("'")?;
|
||||
|
||||
format!("The argument '{}' cannot be used with '{}'", arg, n)
|
||||
match other {
|
||||
Some(ref name) => {
|
||||
c.none("'");
|
||||
c.warning(name);
|
||||
c.none("'");
|
||||
}
|
||||
None => {
|
||||
let n = "one or more of the other specified arguments";
|
||||
|
||||
c.none(n)?;
|
||||
|
||||
format!("The argument '{}' cannot be used with {}", arg, n)
|
||||
c.none("one or more of the other specified arguments");
|
||||
}
|
||||
};
|
||||
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause,
|
||||
let mut info = vec![arg];
|
||||
if let Some(other) = other {
|
||||
info.push(other);
|
||||
}
|
||||
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::ArgumentConflict,
|
||||
info: Some(v),
|
||||
})
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn empty_value<U>(arg: &Arg, usage: U, color: ColorChoice) -> io::Result<Self>
|
||||
where
|
||||
U: Display,
|
||||
{
|
||||
pub(crate) fn empty_value(arg: &Arg, usage: String, color: ColorChoice) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
let arg = arg.to_string();
|
||||
|
||||
start_error(&mut c, "The argument '")?;
|
||||
c.warning(&arg.to_string())?;
|
||||
c.none("' requires a value but none was supplied")?;
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.clone());
|
||||
c.none("' requires a value but none was supplied");
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!(
|
||||
"The argument '{}' requires a value but none was supplied",
|
||||
arg
|
||||
),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::EmptyValue,
|
||||
info: Some(vec![arg.name.to_owned()]),
|
||||
})
|
||||
info: vec![arg],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_value<B, G, U>(
|
||||
bad_val: B,
|
||||
pub(crate) fn invalid_value<G>(
|
||||
bad_val: String,
|
||||
good_vals: &[G],
|
||||
arg: &Arg,
|
||||
usage: U,
|
||||
usage: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
) -> Self
|
||||
where
|
||||
B: AsRef<str>,
|
||||
G: AsRef<str> + Display,
|
||||
U: Display,
|
||||
{
|
||||
let mut c = Colorizer::new(true, color);
|
||||
let suffix = suggestions::did_you_mean(bad_val.as_ref(), good_vals.iter()).pop();
|
||||
let suffix = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop();
|
||||
|
||||
let mut sorted: Vec<String> = good_vals.iter().map(|v| v.to_string()).collect();
|
||||
sorted.sort();
|
||||
|
||||
start_error(&mut c, "'")?;
|
||||
c.warning(bad_val.as_ref())?;
|
||||
c.none("' isn't a valid value for '")?;
|
||||
c.warning(&arg.to_string())?;
|
||||
c.none("'\n\t[possible values: ")?;
|
||||
start_error(&mut c, "'");
|
||||
c.warning(bad_val.clone());
|
||||
c.none("' isn't a valid value for '");
|
||||
c.warning(arg.to_string());
|
||||
c.none("'\n\t[possible values: ");
|
||||
|
||||
if let Some((last, elements)) = sorted.split_last() {
|
||||
for v in elements {
|
||||
c.good(v)?;
|
||||
c.none(", ")?;
|
||||
c.good(v);
|
||||
c.none(", ");
|
||||
}
|
||||
|
||||
c.good(last)?;
|
||||
c.good(last);
|
||||
}
|
||||
|
||||
c.none("]")?;
|
||||
c.none("]");
|
||||
|
||||
if let Some(val) = suffix {
|
||||
c.none("\n\n\tDid you mean '")?;
|
||||
c.good(&val)?;
|
||||
c.none("'?")?;
|
||||
c.none("\n\n\tDid you mean '");
|
||||
c.good(val);
|
||||
c.none("'?");
|
||||
}
|
||||
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!(
|
||||
"'{}' isn't a valid value for '{}'\n\t\
|
||||
[possible values: {}]",
|
||||
bad_val.as_ref(),
|
||||
arg,
|
||||
sorted.join(", ")
|
||||
),
|
||||
let mut info = vec![arg.to_string(), bad_val];
|
||||
info.extend(sorted);
|
||||
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::InvalidValue,
|
||||
info: Some(vec![arg.name.to_owned(), bad_val.as_ref().to_owned()]),
|
||||
})
|
||||
info: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_subcommand<S, D, N, U>(
|
||||
subcmd: S,
|
||||
did_you_mean: D,
|
||||
name: N,
|
||||
usage: U,
|
||||
pub(crate) fn invalid_subcommand(
|
||||
subcmd: String,
|
||||
did_you_mean: String,
|
||||
name: String,
|
||||
usage: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
S: Into<String>,
|
||||
D: AsRef<str> + Display,
|
||||
N: Display,
|
||||
U: Display,
|
||||
{
|
||||
let s = subcmd.into();
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
|
||||
start_error(&mut c, "The subcommand '")?;
|
||||
c.warning(&*s)?;
|
||||
c.none("' wasn't recognized\n\n\tDid you mean ")?;
|
||||
c.good(did_you_mean.as_ref())?;
|
||||
c.none("")?;
|
||||
c.none(&format!(
|
||||
start_error(&mut c, "The subcommand '");
|
||||
c.warning(subcmd.clone());
|
||||
c.none("' wasn't recognized\n\n\tDid you mean ");
|
||||
c.good(did_you_mean);
|
||||
c.none("");
|
||||
c.none(format!(
|
||||
"?\n\nIf you believe you received this message in error, try re-running with '{} ",
|
||||
name
|
||||
))?;
|
||||
c.good("--")?;
|
||||
c.none(&format!(" {}'", &*s))?;
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
));
|
||||
c.good("--");
|
||||
c.none(format!(" {}'", subcmd));
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!("The subcommand '{}' wasn't recognized", s),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::InvalidSubcommand,
|
||||
info: Some(vec![s]),
|
||||
})
|
||||
info: vec![subcmd],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unrecognized_subcommand<S, N>(
|
||||
subcmd: S,
|
||||
name: N,
|
||||
pub(crate) fn unrecognized_subcommand(
|
||||
subcmd: String,
|
||||
name: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
S: Into<String>,
|
||||
N: Display,
|
||||
{
|
||||
let s = subcmd.into();
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
|
||||
start_error(&mut c, " The subcommand '")?;
|
||||
c.warning(&*s)?;
|
||||
c.none("' wasn't recognized\n\n")?;
|
||||
c.warning("USAGE:")?;
|
||||
c.none(&format!("\n\t{} help <subcommands>...", name))?;
|
||||
try_help(&mut c)?;
|
||||
start_error(&mut c, " The subcommand '");
|
||||
c.warning(subcmd.clone());
|
||||
c.none("' wasn't recognized\n\n");
|
||||
c.warning("USAGE:");
|
||||
c.none(format!("\n\t{} help <subcommands>...", name));
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!("The subcommand '{}' wasn't recognized", s),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::UnrecognizedSubcommand,
|
||||
info: Some(vec![s]),
|
||||
})
|
||||
info: vec![subcmd],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn missing_required_argument<R, U>(
|
||||
required: VecDeque<R>,
|
||||
usage: U,
|
||||
pub(crate) fn missing_required_argument(
|
||||
required: Vec<String>,
|
||||
usage: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
R: Display,
|
||||
U: Display,
|
||||
{
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
|
||||
let cause = format!(
|
||||
"The following required arguments were not provided:{}",
|
||||
required
|
||||
.iter()
|
||||
.map(|x| format!("\n {}", x))
|
||||
.collect::<Vec<_>>()
|
||||
.join(""),
|
||||
);
|
||||
|
||||
start_error(
|
||||
&mut c,
|
||||
"The following required arguments were not provided:",
|
||||
)?;
|
||||
);
|
||||
|
||||
let mut info = vec![];
|
||||
for v in required {
|
||||
c.none("\n ")?;
|
||||
c.good(&v.to_string())?;
|
||||
c.none("\n ");
|
||||
c.good(v.to_string());
|
||||
info.push(v.to_string());
|
||||
}
|
||||
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause,
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::MissingRequiredArgument,
|
||||
info: Some(info),
|
||||
})
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn missing_subcommand<N, U>(
|
||||
name: N,
|
||||
usage: U,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
N: AsRef<str> + Display,
|
||||
U: Display,
|
||||
{
|
||||
pub(crate) fn missing_subcommand(name: String, usage: String, color: ColorChoice) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
|
||||
start_error(&mut c, "'")?;
|
||||
c.warning(name.as_ref())?;
|
||||
c.none("' requires a subcommand, but one was not provided")?;
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
start_error(&mut c, "'");
|
||||
c.warning(name);
|
||||
c.none("' requires a subcommand, but one was not provided");
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!("'{}' requires a subcommand, but one was not provided", name),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::MissingSubcommand,
|
||||
info: None,
|
||||
})
|
||||
info: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_utf8<U>(usage: U, color: ColorChoice) -> io::Result<Self>
|
||||
where
|
||||
U: Display,
|
||||
{
|
||||
pub(crate) fn invalid_utf8(usage: String, color: ColorChoice) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
|
||||
let cause = "Invalid UTF-8 was detected in one or more arguments";
|
||||
start_error(
|
||||
&mut c,
|
||||
"Invalid UTF-8 was detected in one or more arguments",
|
||||
);
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
start_error(&mut c, cause)?;
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
|
||||
Ok(Error {
|
||||
cause: cause.to_string(),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::InvalidUtf8,
|
||||
info: None,
|
||||
})
|
||||
info: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn too_many_values<V, U>(
|
||||
val: V,
|
||||
pub(crate) fn too_many_values(
|
||||
val: String,
|
||||
arg: &Arg,
|
||||
usage: U,
|
||||
usage: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
V: AsRef<str> + Display + ToOwned,
|
||||
U: Display,
|
||||
{
|
||||
let v = val.as_ref();
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
|
||||
start_error(&mut c, "The value '")?;
|
||||
c.warning(v)?;
|
||||
c.none("' was provided to '")?;
|
||||
c.warning(&arg.to_string())?;
|
||||
c.none("' but it wasn't expecting any more values")?;
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
start_error(&mut c, "The value '");
|
||||
c.warning(val.clone());
|
||||
c.none("' was provided to '");
|
||||
c.warning(arg.to_string());
|
||||
c.none("' but it wasn't expecting any more values");
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!(
|
||||
"The value '{}' was provided to '{}', but it wasn't expecting any more values",
|
||||
v, arg
|
||||
),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::TooManyValues,
|
||||
info: Some(vec![arg.name.to_owned(), v.to_owned()]),
|
||||
})
|
||||
info: vec![arg.to_string(), val],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn too_few_values<U>(
|
||||
pub(crate) fn too_few_values(
|
||||
arg: &Arg,
|
||||
min_vals: u64,
|
||||
curr_vals: usize,
|
||||
usage: U,
|
||||
usage: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
U: Display,
|
||||
{
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
let verb = Error::singular_or_plural(curr_vals);
|
||||
|
||||
start_error(&mut c, "The argument '")?;
|
||||
c.warning(&arg.to_string())?;
|
||||
c.none("' requires at least ")?;
|
||||
c.warning(&min_vals.to_string())?;
|
||||
c.none(" values, but only ")?;
|
||||
c.warning(&curr_vals.to_string())?;
|
||||
c.none(&format!(" {} provided", verb))?;
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.to_string());
|
||||
c.none("' requires at least ");
|
||||
c.warning(min_vals.to_string());
|
||||
c.none(" values, but only ");
|
||||
c.warning(curr_vals.to_string());
|
||||
c.none(format!(" {} provided", verb));
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!(
|
||||
"The argument '{}' requires at least {} values, but only {} {} provided",
|
||||
arg, min_vals, curr_vals, verb
|
||||
),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::TooFewValues,
|
||||
info: Some(vec![arg.name.to_owned()]),
|
||||
})
|
||||
info: vec![arg.to_string(), curr_vals.to_string(), min_vals.to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn value_validation(
|
||||
arg: Option<&Arg>,
|
||||
err: &str,
|
||||
arg: String,
|
||||
val: String,
|
||||
err: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self> {
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
|
||||
start_error(&mut c, "Invalid value")?;
|
||||
start_error(&mut c, "Invalid value");
|
||||
|
||||
if let Some(a) = arg {
|
||||
c.none(" for '")?;
|
||||
c.warning(&a.to_string())?;
|
||||
c.none("'")?;
|
||||
}
|
||||
c.none(" for '");
|
||||
c.warning(arg.clone());
|
||||
c.none("'");
|
||||
|
||||
c.none(&format!(": {}", err))?;
|
||||
try_help(&mut c)?;
|
||||
c.none(format!(": {}", err));
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!(
|
||||
"Invalid value{}: {}",
|
||||
if let Some(a) = arg {
|
||||
format!(" for '{}'", a)
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
err
|
||||
),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::ValueValidation,
|
||||
info: None,
|
||||
})
|
||||
info: vec![arg, val, err],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn value_validation_auto(err: &str) -> io::Result<Self> {
|
||||
let n: Option<&Arg> = None;
|
||||
Error::value_validation(n, err, ColorChoice::Auto)
|
||||
}
|
||||
|
||||
pub(crate) fn wrong_number_of_values<U>(
|
||||
pub(crate) fn wrong_number_of_values(
|
||||
arg: &Arg,
|
||||
num_vals: u64,
|
||||
curr_vals: usize,
|
||||
usage: U,
|
||||
usage: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
U: Display,
|
||||
{
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
let verb = Error::singular_or_plural(curr_vals);
|
||||
|
||||
start_error(&mut c, "The argument '")?;
|
||||
c.warning(&arg.to_string())?;
|
||||
c.none("' requires ")?;
|
||||
c.warning(&num_vals.to_string())?;
|
||||
c.none(" values, but ")?;
|
||||
c.warning(&curr_vals.to_string())?;
|
||||
c.none(&format!(" {} provided", verb))?;
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.to_string());
|
||||
c.none("' requires ");
|
||||
c.warning(num_vals.to_string());
|
||||
c.none(" values, but ");
|
||||
c.warning(curr_vals.to_string());
|
||||
c.none(format!(" {} provided", verb));
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!(
|
||||
"The argument '{}' requires {} values, but {} {} provided",
|
||||
arg, num_vals, curr_vals, verb
|
||||
),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::WrongNumberOfValues,
|
||||
info: Some(vec![arg.name.to_owned()]),
|
||||
})
|
||||
info: vec![arg.to_string(), curr_vals.to_string(), num_vals.to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unexpected_multiple_usage<U>(
|
||||
arg: &Arg,
|
||||
usage: U,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
U: Display,
|
||||
{
|
||||
pub(crate) fn unexpected_multiple_usage(arg: &Arg, usage: String, color: ColorChoice) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
let arg = arg.to_string();
|
||||
|
||||
start_error(&mut c, "The argument '")?;
|
||||
c.warning(&arg.to_string())?;
|
||||
c.none("' was provided more than once, but cannot be used multiple times")?;
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.clone());
|
||||
c.none("' was provided more than once, but cannot be used multiple times");
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!(
|
||||
"The argument '{}' was provided more than once, but cannot be used multiple times",
|
||||
arg
|
||||
),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::UnexpectedMultipleUsage,
|
||||
info: Some(vec![arg.name.to_owned()]),
|
||||
})
|
||||
info: vec![arg],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unknown_argument<A, U>(
|
||||
arg: A,
|
||||
pub(crate) fn unknown_argument(
|
||||
arg: String,
|
||||
did_you_mean: Option<(String, Option<String>)>,
|
||||
usage: U,
|
||||
usage: String,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
A: Into<String>,
|
||||
U: Display,
|
||||
{
|
||||
let a = arg.into();
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, color);
|
||||
|
||||
start_error(&mut c, "Found argument '")?;
|
||||
c.warning(&*a)?;
|
||||
c.none("' which wasn't expected, or isn't valid in this context")?;
|
||||
start_error(&mut c, "Found argument '");
|
||||
c.warning(arg.clone());
|
||||
c.none("' which wasn't expected, or isn't valid in this context");
|
||||
|
||||
if let Some(s) = did_you_mean {
|
||||
c.none("\n\n\tDid you mean ")?;
|
||||
c.none("\n\n\tDid you mean ");
|
||||
|
||||
if let Some(subcmd) = s.1 {
|
||||
c.none("to put '")?;
|
||||
c.good(&format!("--{}", &s.0))?;
|
||||
c.none("' after the subcommand '")?;
|
||||
c.good(&subcmd)?;
|
||||
c.none("'?")?;
|
||||
c.none("to put '");
|
||||
c.good(format!("--{}", &s.0));
|
||||
c.none("' after the subcommand '");
|
||||
c.good(subcmd);
|
||||
c.none("'?");
|
||||
} else {
|
||||
c.none("'")?;
|
||||
c.good(&format!("--{}", &s.0))?;
|
||||
c.none("'?")?;
|
||||
c.none("'");
|
||||
c.good(format!("--{}", &s.0));
|
||||
c.none("'?");
|
||||
}
|
||||
}
|
||||
|
||||
c.none(&format!(
|
||||
c.none(format!(
|
||||
"\n\nIf you tried to supply `{}` as a PATTERN use `-- {}`",
|
||||
a, a
|
||||
))?;
|
||||
put_usage(&mut c, usage)?;
|
||||
try_help(&mut c)?;
|
||||
arg, arg
|
||||
));
|
||||
put_usage(&mut c, usage);
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!(
|
||||
"Found argument '{}' which wasn't expected, or isn't valid in this context",
|
||||
a
|
||||
),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::UnknownArgument,
|
||||
info: Some(vec![a]),
|
||||
})
|
||||
info: vec![arg],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn argument_not_found_auto<A>(arg: A) -> io::Result<Self>
|
||||
where
|
||||
A: Into<String>,
|
||||
{
|
||||
let a = arg.into();
|
||||
pub(crate) fn argument_not_found_auto(arg: String) -> Self {
|
||||
let mut c = Colorizer::new(true, ColorChoice::Auto);
|
||||
|
||||
start_error(&mut c, "The argument '")?;
|
||||
c.warning(&*a)?;
|
||||
c.none("' wasn't found")?;
|
||||
try_help(&mut c)?;
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.clone());
|
||||
c.none("' wasn't found");
|
||||
try_help(&mut c);
|
||||
|
||||
Ok(Error {
|
||||
cause: format!("The argument '{}' wasn't found", a),
|
||||
Error {
|
||||
message: c,
|
||||
kind: ErrorKind::ArgumentNotFound,
|
||||
info: Some(vec![a]),
|
||||
})
|
||||
info: vec![arg],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an error with a custom description.
|
||||
///
|
||||
/// This can be used in combination with `Error::exit` to exit your program
|
||||
/// with a custom error message.
|
||||
pub fn with_description(description: impl Into<String>, kind: ErrorKind) -> io::Result<Self> {
|
||||
pub fn with_description(description: String, kind: ErrorKind) -> Self {
|
||||
let mut c = Colorizer::new(true, ColorChoice::Auto);
|
||||
|
||||
let cause = description.into();
|
||||
start_error(&mut c, description);
|
||||
|
||||
start_error(&mut c, &*cause)?;
|
||||
|
||||
Ok(Error {
|
||||
cause,
|
||||
Error {
|
||||
message: c,
|
||||
kind,
|
||||
info: None,
|
||||
})
|
||||
info: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Error::with_description(e.to_string(), ErrorKind::Io)
|
||||
.expect("Unable to build error message")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fmt::Error> for Error {
|
||||
fn from(e: fmt::Error) -> Self {
|
||||
Error::with_description(e.to_string(), ErrorKind::Format)
|
||||
.expect("Unable to build error message")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use indexmap::IndexMap;
|
|||
// Internal
|
||||
use crate::{
|
||||
parse::MatchedArg,
|
||||
util::{Id, Key},
|
||||
util::{termcolor::ColorChoice, Id, Key},
|
||||
{Error, INVALID_UTF8},
|
||||
};
|
||||
|
||||
|
@ -345,14 +345,16 @@ impl ArgMatches {
|
|||
<R as FromStr>::Err: Display,
|
||||
{
|
||||
if let Some(v) = self.value_of(name) {
|
||||
v.parse::<R>().or_else(|e| {
|
||||
Err(Error::value_validation_auto(&format!(
|
||||
"The argument '{}' isn't a valid value: {}",
|
||||
v, e
|
||||
))?)
|
||||
v.parse::<R>().map_err(|e| {
|
||||
let message = format!(
|
||||
"The argument '{}' isn't a valid value for '{}': {}",
|
||||
v, name, e
|
||||
);
|
||||
|
||||
Error::value_validation(name.to_string(), v.to_string(), message, ColorChoice::Auto)
|
||||
})
|
||||
} else {
|
||||
Err(Error::argument_not_found_auto(name)?)
|
||||
Err(Error::argument_not_found_auto(name.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -431,16 +433,20 @@ impl ArgMatches {
|
|||
{
|
||||
if let Some(vals) = self.values_of(name) {
|
||||
vals.map(|v| {
|
||||
v.parse::<R>().or_else(|e| {
|
||||
Err(Error::value_validation_auto(&format!(
|
||||
"The argument '{}' isn't a valid value: {}",
|
||||
v, e
|
||||
))?)
|
||||
v.parse::<R>().map_err(|e| {
|
||||
let message = format!("The argument '{}' isn't a valid value: {}", v, e);
|
||||
|
||||
Error::value_validation(
|
||||
name.to_string(),
|
||||
v.to_string(),
|
||||
message,
|
||||
ColorChoice::Auto,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Err(Error::argument_not_found_auto(name)?)
|
||||
Err(Error::argument_not_found_auto(name.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
use std::{
|
||||
cell::Cell,
|
||||
ffi::{OsStr, OsString},
|
||||
io::Write,
|
||||
};
|
||||
|
||||
// Internal
|
||||
|
@ -483,11 +482,11 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
|| lossy_arg.parse::<f64>().is_ok())
|
||||
{
|
||||
return Err(ClapError::unknown_argument(
|
||||
lossy_arg,
|
||||
lossy_arg.to_string(),
|
||||
None,
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
}
|
||||
ParseResult::Opt(ref id)
|
||||
|
@ -537,12 +536,16 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
let cands: Vec<_> =
|
||||
cands.iter().map(|cand| format!("'{}'", cand)).collect();
|
||||
return Err(ClapError::invalid_subcommand(
|
||||
arg_os.to_string_lossy().into_owned(),
|
||||
arg_os.to_string_lossy().to_string(),
|
||||
cands.join(" or "),
|
||||
self.app.bin_name.as_ref().unwrap_or(&self.app.name),
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app
|
||||
.bin_name
|
||||
.as_ref()
|
||||
.unwrap_or(&self.app.name)
|
||||
.to_string(),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -630,11 +633,11 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
{
|
||||
if p.is_set(ArgSettings::Last) && !self.is_set(AS::TrailingValues) {
|
||||
return Err(ClapError::unknown_argument(
|
||||
&*arg_os.to_string_lossy(),
|
||||
arg_os.to_string_lossy().to_string(),
|
||||
None,
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
|
||||
if !self.is_set(AS::TrailingValues)
|
||||
|
@ -672,9 +675,9 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
None => {
|
||||
if !self.is_set(AS::StrictUtf8) {
|
||||
return Err(ClapError::invalid_utf8(
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
arg_os.to_string_lossy().into_owned()
|
||||
}
|
||||
|
@ -686,9 +689,9 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
while let Some((v, _)) = it.next(None) {
|
||||
if v.to_str().is_none() && !self.is_set(AS::StrictUtf8) {
|
||||
return Err(ClapError::invalid_utf8(
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
sc_m.add_val_to(&Id::empty_hash(), v.to_os_string(), ValueType::CommandLine);
|
||||
}
|
||||
|
@ -708,11 +711,11 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
&& !self.is_set(AS::InferSubcommands)
|
||||
{
|
||||
return Err(ClapError::unknown_argument(
|
||||
&*arg_os.to_string_lossy(),
|
||||
arg_os.to_string_lossy().to_string(),
|
||||
None,
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
} else if !has_args || self.is_set(AS::InferSubcommands) && self.has_subcommands() {
|
||||
let cands = suggestions::did_you_mean(
|
||||
&*arg_os.to_string_lossy(),
|
||||
|
@ -721,26 +724,34 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
if !cands.is_empty() {
|
||||
let cands: Vec<_> = cands.iter().map(|cand| format!("'{}'", cand)).collect();
|
||||
return Err(ClapError::invalid_subcommand(
|
||||
arg_os.to_string_lossy().into_owned(),
|
||||
arg_os.to_string_lossy().to_string(),
|
||||
cands.join(" or "),
|
||||
self.app.bin_name.as_ref().unwrap_or(&self.app.name),
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app
|
||||
.bin_name
|
||||
.as_ref()
|
||||
.unwrap_or(&self.app.name)
|
||||
.to_string(),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
} else {
|
||||
return Err(ClapError::unrecognized_subcommand(
|
||||
arg_os.to_string_lossy().into_owned(),
|
||||
self.app.bin_name.as_ref().unwrap_or(&self.app.name),
|
||||
arg_os.to_string_lossy().to_string(),
|
||||
self.app
|
||||
.bin_name
|
||||
.as_ref()
|
||||
.unwrap_or(&self.app.name)
|
||||
.to_string(),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(ClapError::unknown_argument(
|
||||
&*arg_os.to_string_lossy(),
|
||||
arg_os.to_string_lossy().to_string(),
|
||||
None,
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -756,18 +767,17 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
} else if self.is_set(AS::SubcommandRequired) {
|
||||
let bn = self.app.bin_name.as_ref().unwrap_or(&self.app.name);
|
||||
return Err(ClapError::missing_subcommand(
|
||||
bn,
|
||||
&Usage::new(self).create_usage_with_title(&[]),
|
||||
bn.to_string(),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
} else if self.is_set(AS::SubcommandRequiredElseHelp) {
|
||||
debug!("Parser::get_matches_with: SubcommandRequiredElseHelp=true");
|
||||
let message = self.write_help_err()?;
|
||||
return Err(ClapError {
|
||||
cause: String::new(),
|
||||
message,
|
||||
kind: ErrorKind::MissingArgumentOrSubcommand,
|
||||
info: None,
|
||||
info: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -941,10 +951,14 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
}
|
||||
} else {
|
||||
return Err(ClapError::unrecognized_subcommand(
|
||||
cmd.to_string_lossy().into_owned(),
|
||||
self.app.bin_name.as_ref().unwrap_or(&self.app.name),
|
||||
cmd.to_string_lossy().to_string(),
|
||||
self.app
|
||||
.bin_name
|
||||
.as_ref()
|
||||
.unwrap_or(&self.app.name)
|
||||
.to_string(),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
|
||||
bin_name = format!("{} {}", bin_name, &sc.name);
|
||||
|
@ -1327,11 +1341,11 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
let arg = format!("-{}", c);
|
||||
|
||||
return Err(ClapError::unknown_argument(
|
||||
&*arg,
|
||||
arg,
|
||||
None,
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
|
@ -1360,9 +1374,9 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
debug!("Found Empty - Error");
|
||||
return Err(ClapError::empty_value(
|
||||
opt,
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
}
|
||||
debug!("Found - {:?}, len: {}", v, v.len());
|
||||
debug!(
|
||||
|
@ -1375,9 +1389,9 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
debug!("None, but requires equals...Error");
|
||||
return Err(ClapError::empty_value(
|
||||
opt,
|
||||
&*Usage::new(self).create_usage_with_title(&[]),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
)?);
|
||||
));
|
||||
} else if needs_eq && min_vals_zero {
|
||||
debug!("None and requires equals, but min_vals == 0");
|
||||
if !opt.default_missing_vals.is_empty() {
|
||||
|
@ -1722,24 +1736,16 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
.collect();
|
||||
|
||||
Err(ClapError::unknown_argument(
|
||||
&*format!("--{}", arg),
|
||||
format!("--{}", arg),
|
||||
did_you_mean,
|
||||
&*Usage::new(self).create_usage_with_title(&*used),
|
||||
Usage::new(self).create_usage_with_title(&*used),
|
||||
self.app.color(),
|
||||
)?)
|
||||
}
|
||||
|
||||
// Prints the version to the user and exits if quit=true
|
||||
fn print_version<W: Write>(&self, w: &mut W, use_long: bool) -> ClapResult<()> {
|
||||
self.app._write_version(w, use_long)?;
|
||||
w.flush().map_err(ClapError::from)
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn write_help_err(&self) -> ClapResult<Colorizer> {
|
||||
let mut c = Colorizer::new(true, self.color_help());
|
||||
|
||||
Help::new(HelpWriter::Buffer(&mut c), self, false).write_help()?;
|
||||
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
|
@ -1753,12 +1759,11 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
let mut c = Colorizer::new(false, self.color_help());
|
||||
|
||||
match Help::new(HelpWriter::Buffer(&mut c), self, use_long).write_help() {
|
||||
Err(e) => e,
|
||||
Err(e) => e.into(),
|
||||
_ => ClapError {
|
||||
cause: String::new(),
|
||||
message: c,
|
||||
kind: ErrorKind::DisplayHelp,
|
||||
info: None,
|
||||
info: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1766,16 +1771,13 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
fn version_err(&self, use_long: bool) -> ClapError {
|
||||
debug!("Parser::version_err");
|
||||
|
||||
let mut c = Colorizer::new(false, self.app.color());
|
||||
|
||||
match self.print_version(&mut c, use_long) {
|
||||
Err(e) => e,
|
||||
_ => ClapError {
|
||||
cause: String::new(),
|
||||
message: c,
|
||||
kind: ErrorKind::DisplayVersion,
|
||||
info: None,
|
||||
},
|
||||
let msg = self.app._render_version(use_long);
|
||||
let mut c = Colorizer::new(false, self.color_help());
|
||||
c.none(msg);
|
||||
ClapError {
|
||||
message: c,
|
||||
kind: ErrorKind::DisplayVersion,
|
||||
info: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
)?)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::io::{stderr, stdout, Result, Write};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub(crate) enum ColorChoice {
|
||||
Auto,
|
||||
|
@ -7,32 +5,9 @@ pub(crate) enum ColorChoice {
|
|||
Never,
|
||||
}
|
||||
|
||||
pub(crate) type Buffer = Vec<u8>;
|
||||
|
||||
pub(crate) struct BufferWriter {
|
||||
use_stderr: bool,
|
||||
}
|
||||
|
||||
impl BufferWriter {
|
||||
pub(crate) fn buffer(&self) -> Buffer {
|
||||
vec![]
|
||||
}
|
||||
|
||||
pub(crate) fn stderr(_: ColorChoice) -> Self {
|
||||
Self { use_stderr: true }
|
||||
}
|
||||
|
||||
pub(crate) fn stdout(_: ColorChoice) -> Self {
|
||||
Self { use_stderr: false }
|
||||
}
|
||||
|
||||
pub(crate) fn print(&self, buf: &Buffer) -> Result<()> {
|
||||
if self.use_stderr {
|
||||
stderr().lock().write_all(buf)?;
|
||||
} else {
|
||||
stdout().lock().write_all(buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Color {
|
||||
Green,
|
||||
Yellow,
|
||||
Red,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
15
tests/fixtures/app_regex.yaml
vendored
Normal file
15
tests/fixtures/app_regex.yaml
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
name: clapregextest
|
||||
version: "1.0"
|
||||
about: tests clap regex functionality
|
||||
author: Benjamin Kästner <benjamin.kaestner@gmail.com>
|
||||
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
|
14
tests/fixtures/app_regex_invalid.yaml
vendored
Normal file
14
tests/fixtures/app_regex_invalid.yaml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: clapregextest
|
||||
version: "1.0"
|
||||
about: tests clap regex functionality
|
||||
author: Benjamin Kästner <benjamin.kaestner@gmail.com>
|
||||
settings:
|
||||
- ArgRequiredElseHelp
|
||||
args:
|
||||
- help:
|
||||
short: h
|
||||
long: help
|
||||
about: prints help with a nonstandard description
|
||||
- filter:
|
||||
index: 1
|
||||
validator_regex: [")", invalid regular expression]
|
|
@ -39,9 +39,11 @@ fn test_validator_msg_newline() {
|
|||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
err.cause,
|
||||
"Invalid value for \'<test>\': invalid digit found in string"
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("Invalid value for '<test>': invalid digit found in string"),
|
||||
"{}",
|
||||
err
|
||||
);
|
||||
|
||||
// This message is the only thing that gets printed -- make sure it ends with a newline
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue