Skip to content

Commit

Permalink
refactor(parser): Track str/OsStr values via Box
Browse files Browse the repository at this point in the history
Unfortunately, we can't track using a `ValueParser` inside of `Command`
because its `PartialEq`.  We'll have to wait until a breaking change to
relax that.

Compatibility:
- We now assert on using the wrong method to look up defaults.  This
  shouldn't be a breaking change as it'll assert when getting a real
  value.
- `values_of`, et al delay panicing on the wrong lookup until the
  iterator is being processed.
  • Loading branch information
epage committed May 12, 2022
1 parent cf0282b commit dcf69d1
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 211 deletions.
20 changes: 20 additions & 0 deletions src/builder/arg.rs
Expand Up @@ -63,6 +63,7 @@ pub struct Arg<'help> {
pub(crate) name: &'help str,
pub(crate) help: Option<&'help str>,
pub(crate) long_help: Option<&'help str>,
pub(crate) value_parser: Option<super::ValueParser>,
pub(crate) blacklist: Vec<Id>,
pub(crate) settings: ArgFlags,
pub(crate) overrides: Vec<Id>,
Expand Down Expand Up @@ -4737,6 +4738,17 @@ impl<'help> Arg<'help> {
self.is_set(ArgSettings::AllowInvalidUtf8)
}

/// Configured parser for argument values
pub(crate) fn get_value_parser(&self) -> &super::ValueParser {
if let Some(value_parser) = self.value_parser.as_ref() {
value_parser
} else if self.is_allow_invalid_utf8_set() {
&super::ValueParser(super::ValueParserInner::OsString)
} else {
&super::ValueParser(super::ValueParserInner::String)
}
}

/// Report whether [`Arg::global`] is set
pub fn is_global_set(&self) -> bool {
self.is_set(ArgSettings::Global)
Expand Down Expand Up @@ -5039,6 +5051,14 @@ impl<'help> Arg<'help> {
self.settings.set(ArgSettings::TakesValue);
}

if self.value_parser.is_none() {
if self.is_allow_invalid_utf8_set() {
self.value_parser = Some(super::ValueParser::os_string());
} else {
self.value_parser = Some(super::ValueParser::string());
}
}

if (self.is_use_value_delimiter_set() || self.is_require_value_delimiter_set())
&& self.val_delim.is_none()
{
Expand Down
11 changes: 11 additions & 0 deletions src/builder/command.rs
Expand Up @@ -3678,6 +3678,17 @@ impl<'help> App<'help> {
self.is_set(AppSettings::AllowInvalidUtf8ForExternalSubcommands)
}

/// Configured parser for values passed to an external subcommand
pub(crate) fn get_external_subcommand_value_parser(&self) -> Option<&super::ValueParser> {
if !self.is_allow_external_subcommands_set() {
None
} else if self.is_allow_invalid_utf8_for_external_subcommands_set() {
Some(&super::ValueParser(super::ValueParserInner::OsString))
} else {
Some(&super::ValueParser(super::ValueParserInner::String))
}
}

/// Report whether [`Command::args_conflicts_with_subcommands`] is set
pub fn is_args_conflicts_with_subcommands_set(&self) -> bool {
self.is_set(AppSettings::ArgsNegateSubcommands)
Expand Down
3 changes: 3 additions & 0 deletions src/builder/mod.rs
Expand Up @@ -12,6 +12,7 @@ mod command;
mod possible_value;
mod usage_parser;
mod value_hint;
mod value_parser;

#[cfg(feature = "regex")]
mod regex;
Expand All @@ -29,6 +30,7 @@ pub use arg_settings::{ArgFlags, ArgSettings};
pub use command::Command;
pub use possible_value::PossibleValue;
pub use value_hint::ValueHint;
pub(crate) use value_parser::ValueParser;

#[allow(deprecated)]
pub use command::App;
Expand All @@ -38,3 +40,4 @@ pub use self::regex::RegexRef;

pub(crate) use arg::display_arg_val;
pub(crate) use arg_predicate::ArgPredicate;
pub(crate) use value_parser::ValueParserInner;
59 changes: 59 additions & 0 deletions src/builder/value_parser.rs
@@ -0,0 +1,59 @@
use std::sync::Arc;

use crate::parser::AnyValue;

/// Parse/validate argument values
#[derive(Clone)]
pub struct ValueParser(pub(crate) ValueParserInner);

#[derive(Clone)]
pub(crate) enum ValueParserInner {
String,
OsString,
}

impl ValueParser {
/// `String` parser for argument values
pub const fn string() -> Self {
Self(ValueParserInner::String)
}

/// `OsString` parser for argument values
pub const fn os_string() -> Self {
Self(ValueParserInner::OsString)
}
}

impl ValueParser {
/// Parse into a `Arc<Any>`
///
/// When `arg` is `None`, an external subcommand value is being parsed.
pub fn parse_ref(
&self,
cmd: &crate::Command,
_arg: Option<&crate::Arg>,
value: &std::ffi::OsStr,
) -> Result<AnyValue, crate::Error> {
match &self.0 {
ValueParserInner::String => {
let value = value.to_str().ok_or_else(|| {
crate::Error::invalid_utf8(
cmd,
crate::output::Usage::new(cmd).create_usage_with_title(&[]),
)
})?;
Ok(Arc::new(value.to_owned()))
}
ValueParserInner::OsString => Ok(Arc::new(value.to_owned())),
}
}
}

impl<'help> std::fmt::Debug for ValueParser {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match &self.0 {
ValueParserInner::String => f.debug_struct("ValueParser::string").finish(),
ValueParserInner::OsString => f.debug_struct("ValueParser::os_string").finish(),
}
}
}
36 changes: 17 additions & 19 deletions src/parser/arg_matcher.rs
Expand Up @@ -6,6 +6,7 @@ use std::ops::Deref;

// Internal
use crate::builder::{Arg, ArgPredicate, Command};
use crate::parser::AnyValue;
use crate::parser::{ArgMatches, MatchedArg, SubCommand, ValueSource};
use crate::util::Id;

Expand Down Expand Up @@ -90,11 +91,6 @@ impl ArgMatcher {
}
}

#[cfg(not(feature = "unstable-v4"))]
pub(crate) fn get_mut(&mut self, arg: &Id) -> Option<&mut MatchedArg> {
self.0.args.get_mut(arg)
}

pub(crate) fn get(&self, arg: &Id) -> Option<&MatchedArg> {
self.0.args.get(arg)
}
Expand Down Expand Up @@ -137,7 +133,6 @@ impl ArgMatcher {
let ma = self.entry(id).or_insert(MatchedArg::new());
ma.update_ty(ValueSource::CommandLine);
ma.set_ignore_case(arg.is_ignore_case_set());
ma.invalid_utf8_allowed(arg.is_allow_invalid_utf8_set());
ma.inc_occurrences();
}

Expand All @@ -149,39 +144,42 @@ impl ArgMatcher {
}

#[cfg(feature = "unstable-v4")]
pub(crate) fn inc_occurrence_of_external(&mut self, allow_invalid_utf8: bool) {
pub(crate) fn inc_occurrence_of_external(&mut self) {
let id = &Id::empty_hash();
debug!(
"ArgMatcher::inc_occurrence_of_external: id={:?}, allow_invalid_utf8={}",
id, allow_invalid_utf8
);
debug!("ArgMatcher::inc_occurrence_of_external: id={:?}", id,);
let ma = self.entry(id).or_insert(MatchedArg::new());
ma.update_ty(ValueSource::CommandLine);
ma.invalid_utf8_allowed(allow_invalid_utf8);
ma.inc_occurrences();
}

pub(crate) fn add_val_to(&mut self, arg: &Id, val: OsString, ty: ValueSource, append: bool) {
pub(crate) fn add_val_to(
&mut self,
arg: &Id,
val: AnyValue,
raw_val: OsString,
ty: ValueSource,
append: bool,
) {
if append {
self.append_val_to(arg, val, ty);
self.append_val_to(arg, val, raw_val, ty);
} else {
self.push_val_to(arg, val, ty);
self.push_val_to(arg, val, raw_val, ty);
}
}

fn push_val_to(&mut self, arg: &Id, val: OsString, ty: ValueSource) {
fn push_val_to(&mut self, arg: &Id, val: AnyValue, raw_val: OsString, ty: ValueSource) {
// We will manually inc occurrences later(for flexibility under
// specific circumstances, like only add one occurrence for flag
// when we met: `--flag=one,two`).
let ma = self.entry(arg).or_default();
ma.update_ty(ty);
ma.push_val(val);
ma.push_val(val, raw_val);
}

fn append_val_to(&mut self, arg: &Id, val: OsString, ty: ValueSource) {
fn append_val_to(&mut self, arg: &Id, val: AnyValue, raw_val: OsString, ty: ValueSource) {
let ma = self.entry(arg).or_default();
ma.update_ty(ty);
ma.append_val(val);
ma.append_val(val, raw_val);
}

pub(crate) fn new_val_group(&mut self, arg: &Id) {
Expand Down

0 comments on commit dcf69d1

Please sign in to comment.