mirror of
https://github.com/clap-rs/clap
synced 2025-03-04 23:37:32 +00:00
Allow RegexSet for validator_regex
This commit is contained in:
parent
089c4160cf
commit
7dc176ab2a
4 changed files with 79 additions and 18 deletions
|
@ -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;
|
||||
|
|
|
@ -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
36
tests/regex.rs
Normal 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)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
#![cfg(feature = "unicode")]
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unicode")]
|
||||
fn possible_values_case_insensitive() {
|
||||
let m = clap::App::new("pv")
|
||||
.arg(
|
||||
|
|
Loading…
Add table
Reference in a new issue