mirror of
https://github.com/clap-rs/clap
synced 2024-12-14 06:42: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,
|
||||
};
|
||||
|
||||
|
|
|
@ -20,17 +20,43 @@ use crate::{
|
|||
INTERNAL_ERROR_MSG, INVALID_UTF8,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) enum ParseResult {
|
||||
Flag,
|
||||
FlagSubCommand(String),
|
||||
FlagSubCommandShort(String),
|
||||
pub(crate) enum ParseState {
|
||||
ValuesDone,
|
||||
Opt(Id),
|
||||
Pos(Id),
|
||||
MaybeHyphenValue,
|
||||
NotFound,
|
||||
AttachedValueNotConsumed,
|
||||
}
|
||||
|
||||
/// Recoverable Parsing results.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum ParseResult {
|
||||
FlagSubCommand(String),
|
||||
Opt(Id),
|
||||
ValuesDone,
|
||||
/// Value attached to the short flag is not consumed(e.g. 'u' for `-cu` is
|
||||
/// not consumed).
|
||||
AttachedValueNotConsumed,
|
||||
/// This long flag doesn't need a value but is provided one.
|
||||
UnneededAttachedValue {
|
||||
rest: String,
|
||||
used: Vec<Id>,
|
||||
arg: String,
|
||||
},
|
||||
/// This flag might be an hyphen Value.
|
||||
MaybeHyphenValue,
|
||||
/// Equals required but not provided.
|
||||
EqualsNotProvided {
|
||||
arg: String,
|
||||
},
|
||||
/// Failed to match a Arg.
|
||||
NoMatchingArg {
|
||||
arg: String,
|
||||
},
|
||||
/// No argument found e.g. parser is given `-` when parsing a flag.
|
||||
NoArg,
|
||||
/// This is a Help flag.
|
||||
HelpFlag,
|
||||
/// This is a version flag.
|
||||
VersionFlag,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -319,8 +345,14 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
|
||||
let mut subcmd_name: Option<String> = None;
|
||||
let mut keep_state = false;
|
||||
let mut needs_val_of = ParseResult::NotFound;
|
||||
let mut parse_state = ParseState::ValuesDone;
|
||||
let mut pos_counter = 1;
|
||||
|
||||
// Already met any valid arg(then we shouldn't expect subcommands after it).
|
||||
let mut valid_arg_found = false;
|
||||
// If the user already passed '--'. Meaning only positional args follow.
|
||||
let mut trailing_values = false;
|
||||
|
||||
// Count of positional args
|
||||
let positional_count = self.app.args.keys().filter(|x| x.is_position()).count();
|
||||
// If any arg sets .last(true)
|
||||
|
@ -349,14 +381,83 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
arg_os.as_raw_bytes()
|
||||
);
|
||||
|
||||
// Has the user already passed '--'? Meaning only positional args follow
|
||||
if !self.is_set(AS::TrailingValues) {
|
||||
let can_be_subcommand = self.is_set(AS::SubcommandPrecedenceOverArg)
|
||||
|| !matches!(needs_val_of, ParseResult::Opt(_) | ParseResult::Pos(_));
|
||||
// Correct pos_counter.
|
||||
pos_counter = {
|
||||
let is_second_to_last = pos_counter + 1 == positional_count;
|
||||
|
||||
if can_be_subcommand {
|
||||
// The last positional argument, or second to last positional
|
||||
// argument may be set to .multiple_values(true)
|
||||
let low_index_mults = is_second_to_last
|
||||
&& self.app.get_positionals().any(|a| {
|
||||
a.is_set(ArgSettings::MultipleValues)
|
||||
&& (positional_count != a.index.unwrap_or(0))
|
||||
})
|
||||
&& self
|
||||
.app
|
||||
.get_positionals()
|
||||
.last()
|
||||
.map_or(false, |p_name| !p_name.is_set(ArgSettings::Last));
|
||||
|
||||
let missing_pos = self.is_set(AS::AllowMissingPositional)
|
||||
&& is_second_to_last
|
||||
&& !trailing_values;
|
||||
|
||||
debug!(
|
||||
"Parser::get_matches_with: Positional counter...{}",
|
||||
pos_counter
|
||||
);
|
||||
debug!(
|
||||
"Parser::get_matches_with: Low index multiples...{:?}",
|
||||
low_index_mults
|
||||
);
|
||||
|
||||
if low_index_mults || missing_pos {
|
||||
let skip_current = if let Some(n) = remaining_args.get(0) {
|
||||
if let Some(p) = self
|
||||
.app
|
||||
.get_positionals()
|
||||
.find(|p| p.index == Some(pos_counter))
|
||||
{
|
||||
// If next value looks like a new_arg or it's a
|
||||
// subcommand, skip positional argument under current
|
||||
// pos_counter(which means current value cannot be a
|
||||
// positional argument with a value next to it), assume
|
||||
// current value matches the next arg.
|
||||
let n = ArgStr::new(n);
|
||||
self.is_new_arg(&n, p)
|
||||
|| self.possible_subcommand(&n, valid_arg_found).is_some()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if skip_current {
|
||||
debug!("Parser::get_matches_with: Bumping the positional counter...");
|
||||
pos_counter + 1
|
||||
} else {
|
||||
pos_counter
|
||||
}
|
||||
} else if trailing_values
|
||||
&& (self.is_set(AS::AllowMissingPositional) || contains_last)
|
||||
{
|
||||
// Came to -- and one positional has .last(true) set, so we go immediately
|
||||
// to the last (highest index) positional
|
||||
debug!("Parser::get_matches_with: .last(true) and --, setting last pos");
|
||||
positional_count
|
||||
} else {
|
||||
pos_counter
|
||||
}
|
||||
};
|
||||
|
||||
// Has the user already passed '--'? Meaning only positional args follow
|
||||
if !trailing_values {
|
||||
if self.is_set(AS::SubcommandPrecedenceOverArg)
|
||||
|| !matches!(parse_state, ParseState::Opt(_) | ParseState::Pos(_))
|
||||
{
|
||||
// Does the arg match a subcommand name, or any of its aliases (if defined)
|
||||
let sc_name = self.possible_subcommand(&arg_os);
|
||||
let sc_name = self.possible_subcommand(&arg_os, valid_arg_found);
|
||||
debug!("Parser::get_matches_with: sc={:?}", sc_name);
|
||||
if let Some(sc_name) = sc_name {
|
||||
if sc_name == "help" && !self.is_set(AS::NoAutoHelp) {
|
||||
|
@ -367,50 +468,107 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
}
|
||||
}
|
||||
|
||||
// Is this a new argument, or a value for previous option?
|
||||
if self.is_new_arg(&arg_os, &needs_val_of) {
|
||||
if arg_os == "--" {
|
||||
debug!("Parser::get_matches_with: setting TrailingVals=true");
|
||||
self.app.set(AS::TrailingValues);
|
||||
continue;
|
||||
} else if arg_os.starts_with("--") {
|
||||
needs_val_of = self.parse_long_arg(matcher, &arg_os, remaining_args)?;
|
||||
if arg_os.starts_with("--") {
|
||||
let parse_result = self.parse_long_arg(
|
||||
matcher,
|
||||
&arg_os,
|
||||
&parse_state,
|
||||
&mut valid_arg_found,
|
||||
trailing_values,
|
||||
);
|
||||
debug!(
|
||||
"Parser::get_matches_with: After parse_long_arg {:?}",
|
||||
needs_val_of
|
||||
parse_result
|
||||
);
|
||||
match needs_val_of {
|
||||
ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => {
|
||||
match parse_result {
|
||||
ParseResult::NoArg => {
|
||||
debug!("Parser::get_matches_with: setting TrailingVals=true");
|
||||
trailing_values = true;
|
||||
continue;
|
||||
}
|
||||
ParseResult::FlagSubCommand(ref name) => {
|
||||
debug!("Parser::get_matches_with: FlagSubCommand found in long arg {:?}", name);
|
||||
subcmd_name = Some(name.to_owned());
|
||||
ParseResult::ValuesDone => {
|
||||
parse_state = ParseState::ValuesDone;
|
||||
continue;
|
||||
}
|
||||
ParseResult::Opt(id) => {
|
||||
parse_state = ParseState::Opt(id);
|
||||
continue;
|
||||
}
|
||||
ParseResult::FlagSubCommand(name) => {
|
||||
debug!(
|
||||
"Parser::get_matches_with: FlagSubCommand found in long arg {:?}",
|
||||
&name
|
||||
);
|
||||
subcmd_name = Some(name);
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
ParseResult::EqualsNotProvided { arg } => {
|
||||
return Err(ClapError::no_equals(
|
||||
arg,
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
));
|
||||
}
|
||||
} else if arg_os.starts_with("-")
|
||||
&& arg_os.len() != 1
|
||||
&& !(self.is_set(AS::AllowNegativeNumbers)
|
||||
&& arg_os.to_string_lossy().parse::<f64>().is_ok())
|
||||
{
|
||||
ParseResult::NoMatchingArg { arg } => {
|
||||
let remaining_args: Vec<_> = remaining_args
|
||||
.iter()
|
||||
.map(|x| x.to_str().expect(INVALID_UTF8))
|
||||
.collect();
|
||||
return Err(self.did_you_mean_error(&arg, matcher, &remaining_args));
|
||||
}
|
||||
ParseResult::UnneededAttachedValue { rest, used, arg } => {
|
||||
return Err(ClapError::too_many_values(
|
||||
rest,
|
||||
arg,
|
||||
Usage::new(self).create_usage_no_title(&used),
|
||||
self.app.color(),
|
||||
))
|
||||
}
|
||||
ParseResult::HelpFlag => {
|
||||
return Err(self.help_err(true));
|
||||
}
|
||||
ParseResult::VersionFlag => {
|
||||
return Err(self.version_err(true));
|
||||
}
|
||||
ParseResult::MaybeHyphenValue => {
|
||||
// Maybe a hyphen value, do nothing.
|
||||
}
|
||||
ParseResult::AttachedValueNotConsumed => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
} else if arg_os.starts_with("-") {
|
||||
// Arg looks like a short flag, and not a possible number
|
||||
|
||||
// Try to parse short args like normal, if AllowLeadingHyphen or
|
||||
// AllowNegativeNumbers is set, parse_short_arg will *not* throw
|
||||
// an error, and instead return Ok(None)
|
||||
needs_val_of = self.parse_short_arg(matcher, &arg_os)?;
|
||||
let parse_result = self.parse_short_arg(
|
||||
matcher,
|
||||
&arg_os,
|
||||
&parse_state,
|
||||
pos_counter,
|
||||
&mut valid_arg_found,
|
||||
trailing_values,
|
||||
);
|
||||
// If it's None, we then check if one of those two AppSettings was set
|
||||
debug!(
|
||||
"Parser::get_matches_with: After parse_short_arg {:?}",
|
||||
needs_val_of
|
||||
parse_result
|
||||
);
|
||||
match needs_val_of {
|
||||
ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => {
|
||||
match parse_result {
|
||||
ParseResult::NoArg => {
|
||||
// Is a single dash `-`, try positional.
|
||||
}
|
||||
ParseResult::ValuesDone => {
|
||||
parse_state = ParseState::ValuesDone;
|
||||
continue;
|
||||
}
|
||||
ParseResult::FlagSubCommandShort(ref name) => {
|
||||
ParseResult::Opt(id) => {
|
||||
parse_state = ParseState::Opt(id);
|
||||
continue;
|
||||
}
|
||||
ParseResult::FlagSubCommand(name) => {
|
||||
// If there are more short flags to be processed, we should keep the state, and later
|
||||
// revisit the current group of short flags skipping the subcommand.
|
||||
keep_state = self
|
||||
|
@ -430,95 +588,62 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
self.flag_subcmd_skip
|
||||
);
|
||||
|
||||
subcmd_name = Some(name.to_owned());
|
||||
subcmd_name = Some(name);
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
ParseResult::EqualsNotProvided { arg } => {
|
||||
return Err(ClapError::no_equals(
|
||||
arg,
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
))
|
||||
}
|
||||
ParseResult::NoMatchingArg { arg } => {
|
||||
return Err(ClapError::unknown_argument(
|
||||
arg,
|
||||
None,
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
));
|
||||
}
|
||||
ParseResult::HelpFlag => {
|
||||
return Err(self.help_err(false));
|
||||
}
|
||||
ParseResult::VersionFlag => {
|
||||
return Err(self.version_err(false));
|
||||
}
|
||||
ParseResult::MaybeHyphenValue => {
|
||||
// Maybe a hyphen value, do nothing.
|
||||
}
|
||||
ParseResult::UnneededAttachedValue { .. }
|
||||
| ParseResult::AttachedValueNotConsumed => unreachable!(),
|
||||
}
|
||||
}
|
||||
} else if let ParseResult::Opt(id) = needs_val_of {
|
||||
// Check to see if parsing a value from a previous arg
|
||||
|
||||
if let ParseState::Opt(id) = &parse_state {
|
||||
// Assume this is a value of a previous arg.
|
||||
|
||||
// get the option so we can check the settings
|
||||
needs_val_of = self.add_val_to_arg(
|
||||
&self.app[&id],
|
||||
let parse_result = self.add_val_to_arg(
|
||||
&self.app[id],
|
||||
arg_os,
|
||||
matcher,
|
||||
ValueType::CommandLine,
|
||||
true,
|
||||
trailing_values,
|
||||
);
|
||||
parse_state = match parse_result {
|
||||
ParseResult::Opt(id) => ParseState::Opt(id),
|
||||
ParseResult::ValuesDone => ParseState::ValuesDone,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// get the next value from the iterator
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let is_second_to_last = pos_counter + 1 == positional_count;
|
||||
|
||||
// The last positional argument, or second to last positional
|
||||
// argument may be set to .multiple_values(true)
|
||||
let low_index_mults = is_second_to_last
|
||||
&& self.app.get_positionals().any(|a| {
|
||||
a.is_set(ArgSettings::MultipleValues)
|
||||
&& (positional_count != a.index.unwrap_or(0))
|
||||
})
|
||||
&& self
|
||||
.app
|
||||
.get_positionals()
|
||||
.last()
|
||||
.map_or(false, |p_name| !p_name.is_set(ArgSettings::Last));
|
||||
|
||||
let missing_pos = self.is_set(AS::AllowMissingPositional)
|
||||
&& is_second_to_last
|
||||
&& !self.is_set(AS::TrailingValues);
|
||||
|
||||
debug!(
|
||||
"Parser::get_matches_with: Positional counter...{}",
|
||||
pos_counter
|
||||
);
|
||||
debug!(
|
||||
"Parser::get_matches_with: Low index multiples...{:?}",
|
||||
low_index_mults
|
||||
);
|
||||
|
||||
pos_counter = if low_index_mults || missing_pos {
|
||||
let bump = if let Some(n) = remaining_args.get(0) {
|
||||
needs_val_of = if let Some(p) = self
|
||||
.app
|
||||
.get_positionals()
|
||||
.find(|p| p.index == Some(pos_counter))
|
||||
{
|
||||
ParseResult::Pos(p.id.clone())
|
||||
} else {
|
||||
ParseResult::ValuesDone
|
||||
};
|
||||
|
||||
// If the arg doesn't looks like a new_arg and it's not a
|
||||
// subcommand, don't bump the position counter.
|
||||
let n = ArgStr::new(n);
|
||||
self.is_new_arg(&n, &needs_val_of) || self.possible_subcommand(&n).is_some()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if bump {
|
||||
debug!("Parser::get_matches_with: Bumping the positional counter...");
|
||||
pos_counter + 1
|
||||
} else {
|
||||
pos_counter
|
||||
}
|
||||
} else if self.is_set(AS::TrailingValues)
|
||||
&& (self.is_set(AS::AllowMissingPositional) || contains_last)
|
||||
{
|
||||
// Came to -- and one positional has .last(true) set, so we go immediately
|
||||
// to the last (highest index) positional
|
||||
debug!("Parser::get_matches_with: .last(true) and --, setting last pos");
|
||||
positional_count
|
||||
} else {
|
||||
pos_counter
|
||||
};
|
||||
|
||||
if let Some(p) = self.app.args.get(&pos_counter) {
|
||||
if p.is_set(ArgSettings::Last) && !self.is_set(AS::TrailingValues) {
|
||||
if p.is_set(ArgSettings::Last) && !trailing_values {
|
||||
return Err(ClapError::unknown_argument(
|
||||
arg_os.to_string_lossy().to_string(),
|
||||
None,
|
||||
|
@ -528,7 +653,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
}
|
||||
|
||||
if self.is_set(AS::TrailingVarArg) && pos_counter == positional_count {
|
||||
self.app.settings.set(AS::TrailingValues);
|
||||
trailing_values = true;
|
||||
}
|
||||
|
||||
self.seen.push(p.id.clone());
|
||||
|
@ -536,7 +661,14 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
// doesn't have any value. This behaviour is right because
|
||||
// positional arguments are always present continiously.
|
||||
let append = self.arg_have_val(matcher, p);
|
||||
self.add_val_to_arg(p, arg_os, matcher, ValueType::CommandLine, append);
|
||||
self.add_val_to_arg(
|
||||
p,
|
||||
arg_os,
|
||||
matcher,
|
||||
ValueType::CommandLine,
|
||||
append,
|
||||
trailing_values,
|
||||
);
|
||||
|
||||
// Increase occurrence no matter if we are appending, occurrences
|
||||
// of positional argument equals to number of values rather than
|
||||
|
@ -546,8 +678,11 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
// Only increment the positional counter if it doesn't allow multiples
|
||||
if !p.settings.is_set(ArgSettings::MultipleValues) {
|
||||
pos_counter += 1;
|
||||
parse_state = ParseState::ValuesDone;
|
||||
} else {
|
||||
parse_state = ParseState::Pos(p.id.clone());
|
||||
}
|
||||
self.app.settings.set(AS::ValidArgFound);
|
||||
valid_arg_found = true;
|
||||
} else if self.is_set(AS::AllowExternalSubcommands) {
|
||||
// Get external subcommand name
|
||||
let sc_name = match arg_os.to_str() {
|
||||
|
@ -589,10 +724,15 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
|
||||
self.remove_overrides(matcher);
|
||||
|
||||
return Validator::new(self).validate(needs_val_of, subcmd_name.is_some(), matcher);
|
||||
return Validator::new(self).validate(
|
||||
parse_state,
|
||||
subcmd_name.is_some(),
|
||||
matcher,
|
||||
trailing_values,
|
||||
);
|
||||
} else {
|
||||
// Start error processing
|
||||
return Err(self.match_arg_error(&arg_os));
|
||||
return Err(self.match_arg_error(&arg_os, valid_arg_found, trailing_values));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -624,14 +764,19 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
|
||||
self.remove_overrides(matcher);
|
||||
|
||||
Validator::new(self).validate(needs_val_of, subcmd_name.is_some(), matcher)
|
||||
Validator::new(self).validate(parse_state, subcmd_name.is_some(), matcher, trailing_values)
|
||||
}
|
||||
|
||||
fn match_arg_error(&self, arg_os: &ArgStr) -> ClapError {
|
||||
fn match_arg_error(
|
||||
&self,
|
||||
arg_os: &ArgStr,
|
||||
valid_arg_found: bool,
|
||||
trailing_values: bool,
|
||||
) -> ClapError {
|
||||
// If argument follows a `--`
|
||||
if self.is_set(AS::TrailingValues) {
|
||||
if trailing_values {
|
||||
// If the arg matches a subcommand name, or any of its aliases (if defined)
|
||||
if self.possible_subcommand(arg_os).is_some() {
|
||||
if self.possible_subcommand(arg_os, valid_arg_found).is_some() {
|
||||
return ClapError::unnecessary_double_dash(
|
||||
arg_os.to_string_lossy().to_string(),
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
|
@ -691,10 +836,10 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
}
|
||||
|
||||
// Checks if the arg matches a subcommand name, or any of its aliases (if defined)
|
||||
fn possible_subcommand(&self, arg_os: &ArgStr) -> Option<&str> {
|
||||
fn possible_subcommand(&self, arg_os: &ArgStr, valid_arg_found: bool) -> Option<&str> {
|
||||
debug!("Parser::possible_subcommand: arg={:?}", arg_os);
|
||||
|
||||
if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound)) {
|
||||
if !(self.is_set(AS::ArgsNegateSubcommands) && valid_arg_found) {
|
||||
if self.is_set(AS::InferSubcommands) {
|
||||
// For subcommand `test`, we accepts it's prefix: `t`, `te`,
|
||||
// `tes` and `test`.
|
||||
|
@ -824,34 +969,32 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
Err(parser.help_err(self.app.is_set(AS::UseLongFormatForHelpSubcommand)))
|
||||
}
|
||||
|
||||
fn is_new_arg(&self, arg_os: &ArgStr, last_result: &ParseResult) -> bool {
|
||||
debug!("Parser::is_new_arg: {:?}:{:?}", arg_os, last_result);
|
||||
fn is_new_arg(&self, next: &ArgStr, current_positional: &Arg) -> bool {
|
||||
debug!(
|
||||
"Parser::is_new_arg: {:?}:{:?}",
|
||||
next, current_positional.name
|
||||
);
|
||||
|
||||
let want_value = match last_result {
|
||||
ParseResult::Opt(name) | ParseResult::Pos(name) => {
|
||||
self.is_set(AS::AllowLeadingHyphen)
|
||||
|| self.app[name].is_set(ArgSettings::AllowHyphenValues)
|
||||
if self.is_set(AS::AllowLeadingHyphen)
|
||||
|| self.app[¤t_positional.id].is_set(ArgSettings::AllowHyphenValues)
|
||||
|| (self.is_set(AS::AllowNegativeNumbers)
|
||||
&& arg_os.to_string_lossy().parse::<f64>().is_ok())
|
||||
}
|
||||
ParseResult::ValuesDone => return true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
debug!("Parser::is_new_arg: want_value={:?}", want_value);
|
||||
|
||||
// Is this a new argument, or values from a previous option?
|
||||
if want_value {
|
||||
&& next.to_string_lossy().parse::<f64>().is_ok())
|
||||
{
|
||||
// If allow hyphen, this isn't a new arg.
|
||||
debug!("Parser::is_new_arg: Allow hyphen");
|
||||
false
|
||||
} else if arg_os.starts_with("--") {
|
||||
} else if next.starts_with("--") {
|
||||
// If this is a long flag, this is a new arg.
|
||||
debug!("Parser::is_new_arg: -- found");
|
||||
true
|
||||
} else if arg_os.starts_with("-") {
|
||||
} else if next.starts_with("-") {
|
||||
debug!("Parser::is_new_arg: - found");
|
||||
// a singe '-' by itself is a value and typically means "stdin" on unix systems
|
||||
arg_os.len() != 1
|
||||
// If this is a short flag, this is a new arg. But a singe '-' by
|
||||
// itself is a value and typically means "stdin" on unix systems.
|
||||
next.len() != 1
|
||||
} else {
|
||||
debug!("Parser::is_new_arg: value");
|
||||
// Nothing special, this is a value.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@ -950,7 +1093,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
|
||||
// Retrieves the names of all args the user has supplied thus far, except required ones
|
||||
// because those will be listed in self.required
|
||||
fn check_for_help_and_version_str(&self, arg: &ArgStr) -> ClapResult<()> {
|
||||
fn check_for_help_and_version_str(&self, arg: &ArgStr) -> Option<ParseResult> {
|
||||
debug!("Parser::check_for_help_and_version_str");
|
||||
debug!(
|
||||
"Parser::check_for_help_and_version_str: Checking if --{:?} is help or version...",
|
||||
|
@ -961,7 +1104,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
if let Some(h) = help.long {
|
||||
if arg == h && !self.is_set(AS::NoAutoHelp) {
|
||||
debug!("Help");
|
||||
return Err(self.help_err(true));
|
||||
return Some(ParseResult::HelpFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -970,16 +1113,16 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
if let Some(v) = version.long {
|
||||
if arg == v && !self.is_set(AS::NoAutoVersion) {
|
||||
debug!("Version");
|
||||
return Err(self.version_err(true));
|
||||
return Some(ParseResult::VersionFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Neither");
|
||||
Ok(())
|
||||
None
|
||||
}
|
||||
|
||||
fn check_for_help_and_version_char(&self, arg: char) -> ClapResult<()> {
|
||||
fn check_for_help_and_version_char(&self, arg: char) -> Option<ParseResult> {
|
||||
debug!("Parser::check_for_help_and_version_char");
|
||||
debug!(
|
||||
"Parser::check_for_help_and_version_char: Checking if -{} is help or version...",
|
||||
|
@ -990,7 +1133,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
if let Some(h) = help.short {
|
||||
if arg == h && !self.is_set(AS::NoAutoHelp) {
|
||||
debug!("Help");
|
||||
return Err(self.help_err(false));
|
||||
return Some(ParseResult::HelpFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -999,13 +1142,13 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
if let Some(v) = version.short {
|
||||
if arg == v && !self.is_set(AS::NoAutoVersion) {
|
||||
debug!("Version");
|
||||
return Err(self.version_err(false));
|
||||
return Some(ParseResult::VersionFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Neither");
|
||||
Ok(())
|
||||
None
|
||||
}
|
||||
|
||||
fn use_long_help(&self) -> bool {
|
||||
|
@ -1031,17 +1174,28 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
&mut self,
|
||||
matcher: &mut ArgMatcher,
|
||||
full_arg: &ArgStr,
|
||||
remaining_args: &[OsString],
|
||||
) -> ClapResult<ParseResult> {
|
||||
parse_state: &ParseState,
|
||||
valid_arg_found: &mut bool,
|
||||
trailing_values: bool,
|
||||
) -> ParseResult {
|
||||
// maybe here lifetime should be 'a
|
||||
debug!("Parser::parse_long_arg");
|
||||
|
||||
if matches!(parse_state, ParseState::Opt(opt) | ParseState::Pos(opt) if
|
||||
self.app[opt].is_set(ArgSettings::AllowHyphenValues))
|
||||
{
|
||||
return ParseResult::MaybeHyphenValue;
|
||||
}
|
||||
|
||||
// Update the current index
|
||||
self.cur_idx.set(self.cur_idx.get() + 1);
|
||||
debug!("Parser::parse_long_arg: cur_idx:={}", self.cur_idx.get());
|
||||
|
||||
debug!("Parser::parse_long_arg: Does it contain '='...");
|
||||
let long_arg = full_arg.trim_start_n_matches(2, b'-');
|
||||
if long_arg.is_empty() {
|
||||
return ParseResult::NoArg;
|
||||
}
|
||||
let (arg, val) = if full_arg.contains_byte(b'=') {
|
||||
let (p0, p1) = long_arg.split_at_byte(b'=');
|
||||
debug!("Yes '{:?}'", p1);
|
||||
|
@ -1071,17 +1225,15 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
};
|
||||
|
||||
if let Some(opt) = opt {
|
||||
self.app.settings.set(AS::ValidArgFound);
|
||||
*valid_arg_found = true;
|
||||
self.seen.push(opt.id.clone());
|
||||
if opt.is_set(ArgSettings::TakesValue) {
|
||||
debug!(
|
||||
"Parser::parse_long_arg: Found an opt with value '{:?}'",
|
||||
&val
|
||||
);
|
||||
return self.parse_opt(&val, opt, matcher);
|
||||
}
|
||||
debug!("Parser::parse_long_arg: Found a flag");
|
||||
if let Some(rest) = val {
|
||||
self.parse_opt(&val, opt, matcher, trailing_values)
|
||||
} else if let Some(rest) = val {
|
||||
debug!(
|
||||
"Parser::parse_long_arg: Got invalid literal `{:?}`",
|
||||
rest.to_os_string()
|
||||
|
@ -1096,33 +1248,25 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
return Err(ClapError::too_many_values(
|
||||
rest.to_string_lossy().into(),
|
||||
opt,
|
||||
Usage::new(self).create_usage_no_title(&used),
|
||||
self.app.color(),
|
||||
));
|
||||
ParseResult::UnneededAttachedValue {
|
||||
rest: rest.to_string_lossy().to_string(),
|
||||
used,
|
||||
arg: opt.to_string(),
|
||||
}
|
||||
self.check_for_help_and_version_str(&arg)?;
|
||||
debug!("Parser::parse_long_arg: Presence validated");
|
||||
return Ok(self.parse_flag(opt, matcher));
|
||||
}
|
||||
|
||||
if let Some(sc_name) = self.possible_long_flag_subcommand(&arg) {
|
||||
Ok(ParseResult::FlagSubCommand(sc_name.to_string()))
|
||||
} else if self.is_set(AS::AllowLeadingHyphen) {
|
||||
Ok(ParseResult::MaybeHyphenValue)
|
||||
} else if let Some(parse_result) = self.check_for_help_and_version_str(&arg) {
|
||||
parse_result
|
||||
} else {
|
||||
debug!("Parser::parse_long_arg: Didn't match anything");
|
||||
let remaining_args: Vec<_> = remaining_args
|
||||
.iter()
|
||||
.map(|x| x.to_str().expect(INVALID_UTF8))
|
||||
.collect();
|
||||
Err(self.did_you_mean_error(
|
||||
arg.to_str().expect(INVALID_UTF8),
|
||||
matcher,
|
||||
&remaining_args,
|
||||
))
|
||||
debug!("Parser::parse_long_arg: Presence validated");
|
||||
self.parse_flag(opt, matcher)
|
||||
}
|
||||
} else if let Some(sc_name) = self.possible_long_flag_subcommand(&arg) {
|
||||
ParseResult::FlagSubCommand(sc_name.to_string())
|
||||
} else if self.is_set(AS::AllowLeadingHyphen) {
|
||||
ParseResult::MaybeHyphenValue
|
||||
} else {
|
||||
ParseResult::NoMatchingArg {
|
||||
arg: arg.to_string_lossy().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1130,22 +1274,31 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
&mut self,
|
||||
matcher: &mut ArgMatcher,
|
||||
full_arg: &ArgStr,
|
||||
) -> ClapResult<ParseResult> {
|
||||
parse_state: &ParseState,
|
||||
// change this to possible pos_arg when removing the usage of &mut Parser.
|
||||
pos_counter: usize,
|
||||
valid_arg_found: &mut bool,
|
||||
trailing_values: bool,
|
||||
) -> ParseResult {
|
||||
debug!("Parser::parse_short_arg: full_arg={:?}", full_arg);
|
||||
let arg_os = full_arg.trim_start_matches(b'-');
|
||||
let arg = arg_os.to_string_lossy();
|
||||
|
||||
// If AllowLeadingHyphen is set, we want to ensure `-val` gets parsed as `-val` and not
|
||||
// `-v` `-a` `-l` assuming `v` `a` and `l` are all, or mostly, valid shorts.
|
||||
if self.is_set(AS::AllowLeadingHyphen) && arg.chars().any(|c| !self.app.contains_short(c)) {
|
||||
debug!(
|
||||
"Parser::parse_short_arg: LeadingHyphenAllowed yet -{} isn't valid",
|
||||
arg
|
||||
);
|
||||
return Ok(ParseResult::MaybeHyphenValue);
|
||||
if (self.is_set(AS::AllowNegativeNumbers) && arg.parse::<f64>().is_ok())
|
||||
|| (self.is_set(AS::AllowLeadingHyphen)
|
||||
&& arg.chars().any(|c| !self.app.contains_short(c)))
|
||||
|| matches!(parse_state, ParseState::Opt(opt) | ParseState::Pos(opt)
|
||||
if self.app[opt].is_set(ArgSettings::AllowHyphenValues))
|
||||
|| self
|
||||
.app
|
||||
.args
|
||||
.get(&pos_counter)
|
||||
.map_or(false, |arg| arg.is_set(ArgSettings::AllowHyphenValues))
|
||||
{
|
||||
return ParseResult::MaybeHyphenValue;
|
||||
}
|
||||
|
||||
let mut ret = ParseResult::NotFound;
|
||||
let mut ret = ParseResult::NoArg;
|
||||
|
||||
let skip = self.flag_subcmd_skip;
|
||||
self.flag_subcmd_skip = 0;
|
||||
|
@ -1169,10 +1322,12 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
"Parser::parse_short_arg:iter:{}: Found valid opt or flag",
|
||||
c
|
||||
);
|
||||
self.app.settings.set(AS::ValidArgFound);
|
||||
*valid_arg_found = true;
|
||||
self.seen.push(opt.id.clone());
|
||||
if !opt.is_set(ArgSettings::TakesValue) {
|
||||
self.check_for_help_and_version_char(c)?;
|
||||
if let Some(parse_result) = self.check_for_help_and_version_char(c) {
|
||||
return parse_result;
|
||||
}
|
||||
ret = self.parse_flag(opt, matcher);
|
||||
continue;
|
||||
}
|
||||
|
@ -1204,11 +1359,13 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
//
|
||||
// e.g. `-xvf`, when RequireEquals && x.min_vals == 0, we don't
|
||||
// consume the `vf`, even if it's provided as value.
|
||||
match self.parse_opt(&val, opt, matcher)? {
|
||||
match self.parse_opt(&val, opt, matcher, trailing_values) {
|
||||
ParseResult::AttachedValueNotConsumed => continue,
|
||||
x => return Ok(x),
|
||||
x => return x,
|
||||
}
|
||||
} else if let Some(sc_name) = self.app.find_short_subcmd(c) {
|
||||
}
|
||||
|
||||
return if let Some(sc_name) = self.app.find_short_subcmd(c) {
|
||||
debug!("Parser::parse_short_arg:iter:{}: subcommand={}", c, sc_name);
|
||||
let name = sc_name.to_string();
|
||||
let done_short_args = {
|
||||
|
@ -1225,19 +1382,14 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
if done_short_args {
|
||||
self.flag_subcmd_at = None;
|
||||
}
|
||||
return Ok(ParseResult::FlagSubCommandShort(name));
|
||||
ParseResult::FlagSubCommand(name)
|
||||
} else {
|
||||
let arg = format!("-{}", c);
|
||||
|
||||
return Err(ClapError::unknown_argument(
|
||||
arg,
|
||||
None,
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
));
|
||||
ParseResult::NoMatchingArg {
|
||||
arg: format!("-{}", c),
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(ret)
|
||||
ret
|
||||
}
|
||||
|
||||
fn parse_opt(
|
||||
|
@ -1245,7 +1397,8 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
attached_value: &Option<ArgStr>,
|
||||
opt: &Arg<'help>,
|
||||
matcher: &mut ArgMatcher,
|
||||
) -> ClapResult<ParseResult> {
|
||||
trailing_values: bool,
|
||||
) -> ParseResult {
|
||||
debug!(
|
||||
"Parser::parse_opt; opt={}, val={:?}",
|
||||
opt.name, attached_value
|
||||
|
@ -1272,39 +1425,34 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
};
|
||||
self.inc_occurrence_of_arg(matcher, opt);
|
||||
if attached_value.is_some() {
|
||||
return Ok(ParseResult::AttachedValueNotConsumed);
|
||||
ParseResult::AttachedValueNotConsumed
|
||||
} else {
|
||||
return Ok(ParseResult::ValuesDone);
|
||||
ParseResult::ValuesDone
|
||||
}
|
||||
} else {
|
||||
debug!("Requires equals but not provided. Error.");
|
||||
return Err(ClapError::no_equals(
|
||||
opt,
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
));
|
||||
ParseResult::EqualsNotProvided {
|
||||
arg: opt.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(fv) = attached_value {
|
||||
} else if let Some(fv) = attached_value {
|
||||
let v = fv.trim_start_n_matches(1, b'=');
|
||||
if opt.is_set(ArgSettings::ForbidEmptyValues) && v.is_empty() {
|
||||
debug!("Found Empty - Error");
|
||||
return Err(ClapError::empty_value(
|
||||
opt,
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
));
|
||||
}
|
||||
debug!("Found - {:?}, len: {}", v, v.len());
|
||||
debug!(
|
||||
"Parser::parse_opt: {:?} contains '='...{:?}",
|
||||
fv,
|
||||
fv.starts_with("=")
|
||||
);
|
||||
self.add_val_to_arg(opt, v, matcher, ValueType::CommandLine, false);
|
||||
self.add_val_to_arg(
|
||||
opt,
|
||||
v,
|
||||
matcher,
|
||||
ValueType::CommandLine,
|
||||
false,
|
||||
trailing_values,
|
||||
);
|
||||
self.inc_occurrence_of_arg(matcher, opt);
|
||||
Ok(ParseResult::ValuesDone)
|
||||
ParseResult::ValuesDone
|
||||
} else {
|
||||
debug!("Parser::parse_opt: More arg vals required...");
|
||||
self.inc_occurrence_of_arg(matcher, opt);
|
||||
|
@ -1312,7 +1460,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
for group in self.app.groups_for_arg(&opt.id) {
|
||||
matcher.new_val_group(&group);
|
||||
}
|
||||
Ok(ParseResult::Opt(opt.id.clone()))
|
||||
ParseResult::Opt(opt.id.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1323,14 +1471,15 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
matcher: &mut ArgMatcher,
|
||||
ty: ValueType,
|
||||
append: bool,
|
||||
trailing_values: bool,
|
||||
) -> ParseResult {
|
||||
debug!("Parser::add_val_to_arg; arg={}, val={:?}", arg.name, val);
|
||||
debug!(
|
||||
"Parser::add_val_to_arg; trailing_vals={:?}, DontDelimTrailingVals={:?}",
|
||||
self.is_set(AS::TrailingValues),
|
||||
"Parser::add_val_to_arg; trailing_values={:?}, DontDelimTrailingVals={:?}",
|
||||
trailing_values,
|
||||
self.is_set(AS::DontDelimitTrailingValues)
|
||||
);
|
||||
if !(self.is_set(AS::TrailingValues) && self.is_set(AS::DontDelimitTrailingValues)) {
|
||||
if !(trailing_values && self.is_set(AS::DontDelimitTrailingValues)) {
|
||||
if let Some(delim) = arg.val_delim {
|
||||
let arg_split = val.split(delim);
|
||||
let vals = if let Some(t) = arg.terminator {
|
||||
|
@ -1434,7 +1583,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
matcher.add_index_to(&flag.id, self.cur_idx.get(), ValueType::CommandLine);
|
||||
self.inc_occurrence_of_arg(matcher, flag);
|
||||
|
||||
ParseResult::Flag
|
||||
ParseResult::ValuesDone
|
||||
}
|
||||
|
||||
fn remove_overrides(&mut self, matcher: &mut ArgMatcher) {
|
||||
|
@ -1501,21 +1650,27 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_defaults(&mut self, matcher: &mut ArgMatcher) {
|
||||
pub(crate) fn add_defaults(&mut self, matcher: &mut ArgMatcher, trailing_values: bool) {
|
||||
debug!("Parser::add_defaults");
|
||||
|
||||
for o in self.app.get_opts() {
|
||||
debug!("Parser::add_defaults:iter:{}:", o.name);
|
||||
self.add_value(o, matcher, ValueType::DefaultValue);
|
||||
self.add_value(o, matcher, ValueType::DefaultValue, trailing_values);
|
||||
}
|
||||
|
||||
for p in self.app.get_positionals() {
|
||||
debug!("Parser::add_defaults:iter:{}:", p.name);
|
||||
self.add_value(p, matcher, ValueType::DefaultValue);
|
||||
self.add_value(p, matcher, ValueType::DefaultValue, trailing_values);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_value(&self, arg: &Arg<'help>, matcher: &mut ArgMatcher, ty: ValueType) {
|
||||
fn add_value(
|
||||
&self,
|
||||
arg: &Arg<'help>,
|
||||
matcher: &mut ArgMatcher,
|
||||
ty: ValueType,
|
||||
trailing_values: bool,
|
||||
) {
|
||||
if !arg.default_vals_ifs.is_empty() {
|
||||
debug!("Parser::add_value: has conditional defaults");
|
||||
if matcher.get(&arg.id).is_none() {
|
||||
|
@ -1532,7 +1687,14 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
|
||||
if add {
|
||||
if let Some(default) = default {
|
||||
self.add_val_to_arg(arg, ArgStr::new(default), matcher, ty, false);
|
||||
self.add_val_to_arg(
|
||||
arg,
|
||||
ArgStr::new(default),
|
||||
matcher,
|
||||
ty,
|
||||
false,
|
||||
trailing_values,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1605,7 +1767,11 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_env(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> {
|
||||
pub(crate) fn add_env(
|
||||
&mut self,
|
||||
matcher: &mut ArgMatcher,
|
||||
trailing_values: bool,
|
||||
) -> ClapResult<()> {
|
||||
if self.app.is_set(AS::DisableEnv) {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -1616,9 +1782,24 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
if let Some((_, Some(ref val))) = a.env {
|
||||
let val = ArgStr::new(val);
|
||||
if a.is_set(ArgSettings::TakesValue) {
|
||||
self.add_val_to_arg(a, val, matcher, ValueType::EnvVariable, false);
|
||||
self.add_val_to_arg(
|
||||
a,
|
||||
val,
|
||||
matcher,
|
||||
ValueType::EnvVariable,
|
||||
false,
|
||||
trailing_values,
|
||||
);
|
||||
} else {
|
||||
self.check_for_help_and_version_str(&val)?;
|
||||
match self.check_for_help_and_version_str(&val) {
|
||||
Some(ParseResult::HelpFlag) => {
|
||||
return Err(self.help_err(true));
|
||||
}
|
||||
Some(ParseResult::VersionFlag) => {
|
||||
return Err(self.version_err(true));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
matcher.add_index_to(&a.id, self.cur_idx.get(), ValueType::EnvVariable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(&[])))
|
||||
}
|
||||
}
|
||||
|
||||
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..]));
|
||||
if let Some(i) = self.0.iter().position(|x| x != &byte) {
|
||||
Self(Cow::Borrowed(&self.0[i..]))
|
||||
} else {
|
||||
found = true;
|
||||
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