Merge pull request #2655 from ldm0/coverage

Parser refactor
This commit is contained in:
Pavan Kumar Sunkara 2021-08-08 20:56:09 +01:00 committed by GitHub
commit eabe828092
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 590 additions and 348 deletions

View file

@ -146,14 +146,10 @@ impl_settings! { AppSettings, AppFlags,
=> Flags::DISABLE_VERSION_FOR_SC,
WaitOnError("waitonerror")
=> Flags::WAIT_ON_ERROR,
TrailingValues("trailingvalues")
=> Flags::TRAILING_VALUES,
Built("built")
=> Flags::BUILT,
BinNameBuilt("binnamebuilt")
=> Flags::BIN_NAME_BUILT,
ValidArgFound("validargfound")
=> Flags::VALID_ARG_FOUND,
InferSubcommands("infersubcommands")
=> Flags::INFER_SUBCOMMANDS,
AllArgsOverrideSelf("allargsoverrideself")
@ -1099,10 +1095,6 @@ pub enum AppSettings {
/// @TODO-v3: @docs write them...maybe rename
NoAutoVersion,
#[doc(hidden)]
/// If the user already passed '--'. Meaning only positional args follow.
TrailingValues,
#[doc(hidden)]
/// If the app is already built, used for caching.
Built,
@ -1110,11 +1102,6 @@ pub enum AppSettings {
#[doc(hidden)]
/// If the app's bin name is already built, used for caching.
BinNameBuilt,
#[doc(hidden)]
/// This is paired with `ArgsNegateSubcommands`. Used to determine if we
/// already met any valid arg(then we shouldn't expect subcommands after it).
ValidArgFound,
}
#[cfg(test)]
@ -1258,19 +1245,11 @@ mod test {
"waitonerror".parse::<AppSettings>().unwrap(),
AppSettings::WaitOnError
);
assert_eq!(
"validargfound".parse::<AppSettings>().unwrap(),
AppSettings::ValidArgFound
);
assert_eq!("built".parse::<AppSettings>().unwrap(), AppSettings::Built);
assert_eq!(
"binnamebuilt".parse::<AppSettings>().unwrap(),
AppSettings::BinNameBuilt
);
assert_eq!(
"trailingvalues".parse::<AppSettings>().unwrap(),
AppSettings::TrailingValues
);
assert_eq!(
"infersubcommands".parse::<AppSettings>().unwrap(),
AppSettings::InferSubcommands

View file

@ -564,9 +564,8 @@ impl Error {
}
}
pub(crate) fn no_equals(arg: &Arg, usage: String, color: ColorChoice) -> Self {
pub(crate) fn no_equals(arg: String, usage: String, color: ColorChoice) -> Self {
let mut c = Colorizer::new(true, color);
let arg = arg.to_string();
start_error(&mut c, "Equal sign is needed when assigning values to '");
c.warning(&arg);
@ -798,7 +797,7 @@ impl Error {
pub(crate) fn too_many_values(
val: String,
arg: &Arg,
arg: String,
usage: String,
color: ColorChoice,
) -> Self {
@ -807,7 +806,7 @@ impl Error {
start_error(&mut c, "The value '");
c.warning(val.clone());
c.none("' was provided to '");
c.warning(arg.to_string());
c.warning(&arg);
c.none("' but it wasn't expecting any more values");
put_usage(&mut c, usage);
try_help(&mut c);
@ -815,7 +814,7 @@ impl Error {
Error {
message: c,
kind: ErrorKind::TooManyValues,
info: vec![arg.to_string(), val],
info: vec![arg, val],
source: None,
}
}

View file

@ -9,7 +9,7 @@ mod validator;
pub(crate) use self::{
arg_matcher::ArgMatcher,
matches::{MatchedArg, SubCommand, ValueType},
parser::{Input, ParseResult, Parser},
parser::{Input, ParseState, Parser},
validator::Validator,
};

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ use crate::{
output::Usage,
parse::{
errors::{Error, ErrorKind, Result as ClapResult},
ArgMatcher, MatchedArg, ParseResult, Parser, ValueType,
ArgMatcher, MatchedArg, ParseState, Parser, ValueType,
},
util::{ChildGraph, Id},
INTERNAL_ERROR_MSG, INVALID_UTF8,
@ -25,15 +25,16 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
pub(crate) fn validate(
&mut self,
needs_val_of: ParseResult,
parse_state: ParseState,
is_subcmd: bool,
matcher: &mut ArgMatcher,
trailing_values: bool,
) -> ClapResult<()> {
debug!("Validator::validate");
let mut reqs_validated = false;
self.p.add_env(matcher)?;
self.p.add_defaults(matcher);
if let ParseResult::Opt(a) = needs_val_of {
self.p.add_env(matcher, trailing_values)?;
self.p.add_defaults(matcher, trailing_values);
if let ParseState::Opt(a) = parse_state {
debug!("Validator::validate: needs_val_of={:?}", a);
self.validate_required(matcher)?;
@ -506,7 +507,7 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
.to_str()
.expect(INVALID_UTF8)
.to_string(),
a,
a.to_string(),
Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(),
));

View file

@ -7,6 +7,7 @@ use std::{
use os_str_bytes::{raw, OsStrBytes};
#[derive(PartialEq, Eq)]
pub(crate) struct ArgStr<'a>(Cow<'a, [u8]>);
impl<'a> ArgStr<'a> {
@ -27,58 +28,47 @@ impl<'a> ArgStr<'a> {
}
pub(crate) fn contains_byte(&self, byte: u8) -> bool {
assert!(byte.is_ascii());
debug_assert!(byte.is_ascii());
self.0.contains(&byte)
}
pub(crate) fn contains_char(&self, ch: char) -> bool {
let mut bytes = [0; 4];
let bytes = ch.encode_utf8(&mut bytes).as_bytes();
for i in 0..self.0.len().saturating_sub(bytes.len() - 1) {
if self.0[i..].starts_with(bytes) {
return true;
}
}
false
self.to_string_lossy().contains(|x| x == ch)
}
pub(crate) fn split_at_byte(&self, byte: u8) -> (ArgStr, ArgStr) {
assert!(byte.is_ascii());
debug_assert!(byte.is_ascii());
for (i, b) in self.0.iter().enumerate() {
if b == &byte {
return self.split_at_unchecked(i);
}
if let Some(i) = self.0.iter().position(|&x| x == byte) {
self.split_at_unchecked(i)
} else {
(self.to_borrowed(), Self(Cow::Borrowed(&[])))
}
(self.to_borrowed(), Self(Cow::Borrowed(&[])))
}
pub(crate) fn trim_start_matches(&'a self, byte: u8) -> ArgStr {
assert!(byte.is_ascii());
debug_assert!(byte.is_ascii());
let mut found = false;
for (i, b) in self.0.iter().enumerate() {
if b != &byte {
return Self(Cow::Borrowed(&self.0[i..]));
} else {
found = true;
}
if let Some(i) = self.0.iter().position(|x| x != &byte) {
Self(Cow::Borrowed(&self.0[i..]))
} else {
Self(Cow::Borrowed(&[]))
}
if found {
return Self(Cow::Borrowed(&[]));
}
self.to_borrowed()
}
// Like `trim_start_matches`, but trims no more than `n` matches
#[inline]
pub(crate) fn trim_start_n_matches(&self, n: usize, ch: u8) -> ArgStr {
assert!(ch.is_ascii());
pub(crate) fn trim_start_n_matches(&'a self, n: usize, byte: u8) -> ArgStr {
debug_assert!(byte.is_ascii());
let i = self.0.iter().take(n).take_while(|c| **c == ch).count();
self.split_at_unchecked(i).1
if let Some(i) = self.0.iter().take(n).position(|x| x != &byte) {
Self(Cow::Borrowed(&self.0[i..]))
} else {
match self.0.get(n..) {
Some(x) => Self(Cow::Borrowed(x)),
None => Self(Cow::Borrowed(&[])),
}
}
}
pub(crate) fn split_at_unchecked(&'a self, i: usize) -> (ArgStr, ArgStr) {
@ -107,6 +97,7 @@ impl<'a> ArgStr<'a> {
self.0.len()
}
#[allow(dead_code)]
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
@ -187,3 +178,81 @@ impl<'a> Iterator for ArgSplit<'a> {
Some(ArgStr(Cow::Borrowed(&self.val[start..])))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[rustfmt::skip]
fn test_trim_start_matches() {
let raw = OsString::from("hello? world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_matches(b'-');
assert_eq!(trimmed, a);
let raw = OsString::from("------------hello? world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_matches(b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("hello? world")));
let raw = OsString::from("------------hel-lo? -world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_matches(b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("hel-lo? -world")));
let raw = OsString::from("hel-lo? -world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_matches(b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("hel-lo? -world")));
let raw = OsString::from("");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_matches(b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("")));
}
#[test]
#[rustfmt::skip]
fn test_trim_start_n_matches() {
let raw = OsString::from("hello? world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_n_matches(2, b'-');
assert_eq!(trimmed, a);
let raw = OsString::from("------------hello? world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_n_matches(2, b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("----------hello? world")));
let raw = OsString::from("------------hello? world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_n_matches(1000, b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("hello? world")));
let raw = OsString::from("------------hel-lo? -world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_n_matches(2, b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("----------hel-lo? -world")));
let raw = OsString::from("-hel-lo? -world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_n_matches(5, b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("hel-lo? -world")));
let raw = OsString::from("hel-lo? -world");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_n_matches(10, b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("hel-lo? -world")));
let raw = OsString::from("");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_n_matches(10, b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("")));
let raw = OsString::from("");
let a = ArgStr::new(&raw);
let trimmed = a.trim_start_n_matches(0, b'-');
assert_eq!(trimmed, ArgStr::new(&OsString::from("")));
}
}

View file

@ -842,6 +842,19 @@ fn issue_1066_allow_leading_hyphen_and_unknown_args_option() {
assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument);
}
#[test]
fn issue_1437_allow_hyphen_values_for_positional_arg() {
let m = App::new("tmp")
.arg(
Arg::new("pat")
.allow_hyphen_values(true)
.required(true)
.takes_value(true),
)
.get_matches_from(["tmp", "-file"]);
assert_eq!(m.value_of("pat"), Some("-file"));
}
#[test]
fn issue_1093_allow_ext_sc() {
let app = App::new("clap-test")