Allow RegexSet for validator_regex

This commit is contained in:
Pavan Kumar Sunkara 2021-10-16 22:05:42 +01:00
parent 089c4160cf
commit 7dc176ab2a
4 changed files with 79 additions and 18 deletions

View file

@ -2282,6 +2282,7 @@ impl<'help> Arg<'help> {
/// 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]
/// ```
@ -2292,7 +2293,9 @@ impl<'help> Arg<'help> {
/// provided regular expression.
///
/// # Examples
///
/// You can use the classical `"\d+"` regular expression to match digits only:
///
/// ```rust
/// # use clap::{App, Arg};
/// use regex::Regex;
@ -2309,7 +2312,9 @@ impl<'help> Arg<'help> {
/// 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;

View file

@ -1,49 +1,67 @@
use ::regex::Regex;
use core::convert::TryFrom;
use core::ops::Deref;
use core::str::FromStr;
use ::regex::{Error, Regex, RegexSet};
use core::{convert::TryFrom, ops::Deref, str::FromStr};
use std::borrow::Cow;
/// Contains either a regular expression or a reference to one.
/// Contains either a regular expression or a set of them or a reference to one.
///
/// Essentially a [`Cow`] wrapper with custom convenience traits.
///
/// [`Cow`]: std::borrow::Cow
#[derive(Debug, Clone)]
pub struct RegexRef<'a>(Cow<'a, Regex>);
pub enum RegexRef<'a> {
/// Used if the underlying is a regex set
RegexSet(Cow<'a, RegexSet>),
/// Used if the underlying is a regex
Regex(Cow<'a, Regex>),
}
impl<'a> Deref for RegexRef<'a> {
type Target = Regex;
fn deref(&self) -> &Regex {
self.0.deref()
impl<'a> RegexRef<'a> {
pub(crate) fn is_match(&self, text: &str) -> bool {
match self {
Self::Regex(r) => r.deref().is_match(text),
Self::RegexSet(r) => r.deref().is_match(text),
}
}
}
impl<'a> FromStr for RegexRef<'a> {
type Err = <Regex as core::str::FromStr>::Err;
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Regex::from_str(s).map(|v| RegexRef(Cow::Owned(v)))
Regex::from_str(s).map(|v| Self::Regex(Cow::Owned(v)))
}
}
impl<'a> TryFrom<&'a str> for RegexRef<'a> {
type Error = <RegexRef<'a> as FromStr>::Err;
type Error = <Self as FromStr>::Err;
fn try_from(r: &'a str) -> Result<Self, Self::Error> {
RegexRef::from_str(r)
Self::from_str(r)
}
}
impl<'a> From<&'a Regex> for RegexRef<'a> {
fn from(r: &'a Regex) -> Self {
RegexRef(Cow::Borrowed(r))
Self::Regex(Cow::Borrowed(r))
}
}
impl<'a> From<Regex> for RegexRef<'a> {
fn from(r: Regex) -> Self {
RegexRef(Cow::Owned(r))
Self::Regex(Cow::Owned(r))
}
}
impl<'a> From<&'a RegexSet> for RegexRef<'a> {
fn from(r: &'a RegexSet) -> Self {
Self::RegexSet(Cow::Borrowed(r))
}
}
impl<'a> From<RegexSet> for RegexRef<'a> {
fn from(r: RegexSet) -> Self {
Self::RegexSet(Cow::Owned(r))
}
}
@ -51,6 +69,7 @@ impl<'a> From<Regex> for RegexRef<'a> {
mod tests {
use super::*;
use core::convert::TryInto;
#[test]
fn test_try_from_with_valid_string() {
let t: Result<RegexRef, _> = "^Hello, World$".try_into();

36
tests/regex.rs Normal file
View file

@ -0,0 +1,36 @@
#![cfg(feature = "regex")]
use clap::{App, Arg, ErrorKind};
use regex::{Regex, RegexSet};
#[test]
fn validator_regex() {
let priority = Regex::new(r"[A-C]").unwrap();
let m = App::new("prog")
.arg(
Arg::new("priority")
.index(1)
.validator_regex(priority, "A, B or C are allowed"),
)
.try_get_matches_from(vec!["prog", "12345"]);
assert!(m.is_err());
assert_eq!(m.err().unwrap().kind, ErrorKind::ValueValidation)
}
#[test]
fn validator_regex_with_regex_set() {
let priority = RegexSet::new(&[r"[A-C]", r"[X-Z]"]).unwrap();
let m = App::new("prog")
.arg(
Arg::new("priority")
.index(1)
.validator_regex(priority, "A, B, C, X, Y or Z are allowed"),
)
.try_get_matches_from(vec!["prog", "12345"]);
assert!(m.is_err());
assert_eq!(m.err().unwrap().kind, ErrorKind::ValueValidation)
}

View file

@ -1,5 +1,6 @@
#![cfg(feature = "unicode")]
#[test]
#[cfg(feature = "unicode")]
fn possible_values_case_insensitive() {
let m = clap::App::new("pv")
.arg(