mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 22:32:33 +00:00
commit
eabe828092
7 changed files with 590 additions and 348 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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(),
|
||||
));
|
||||
|
|
|
@ -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("")));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue