use crate::errors::{error, nil, New};
use crate::types::{int, slice, string};
pub struct Regexp {
inner: regex::Regex,
pub(crate) src: String,
}
impl Clone for Regexp {
fn clone(&self) -> Self {
Regexp { inner: regex::Regex::new(&self.src).unwrap(), src: self.src.clone() }
}
}
impl Regexp {
pub fn MatchString(&self, s: impl AsRef<str>) -> bool {
self.inner.is_match(s.as_ref())
}
pub fn Match(&self, b: &[crate::types::byte]) -> bool {
match std::str::from_utf8(b) {
Ok(s) => self.inner.is_match(s),
Err(_) => false,
}
}
pub fn FindString(&self, s: impl AsRef<str>) -> string {
self.inner.find(s.as_ref()).map(|m| m.as_str().into()).unwrap_or_default()
}
pub fn FindStringIndex(&self, s: impl AsRef<str>) -> slice<int> {
match self.inner.find(s.as_ref()) {
Some(m) => slice(vec![m.start() as int, m.end() as int]),
None => slice::new(),
}
}
pub fn FindAllString(&self, s: impl AsRef<str>, n: int) -> slice<string> {
let s_ref = s.as_ref();
let iter = self.inner.find_iter(s_ref).map(|m| m.as_str().into());
if n < 0 {
iter.collect()
} else {
iter.take(n as usize).collect()
}
}
pub fn FindStringSubmatch(&self, s: impl AsRef<str>) -> slice<string> {
match self.inner.captures(s.as_ref()) {
Some(caps) => (0..caps.len())
.map(|i| caps.get(i).map(|m| m.as_str().into()).unwrap_or_default())
.collect(),
None => slice::new(),
}
}
pub fn FindAllStringSubmatch(&self, s: impl AsRef<str>, n: int) -> slice<slice<string>> {
let iter = self.inner.captures_iter(s.as_ref()).map(|caps| {
(0..caps.len())
.map(|i| caps.get(i).map(|m| m.as_str().into()).unwrap_or_default())
.collect::<slice<string>>()
});
if n < 0 { iter.collect() } else { iter.take(n as usize).collect() }
}
pub fn ReplaceAllString(&self, s: impl AsRef<str>, repl: impl AsRef<str>) -> string {
self.inner.replace_all(s.as_ref(), repl.as_ref()).into_owned().into()
}
pub fn ReplaceAllLiteralString(&self, s: impl AsRef<str>, repl: impl AsRef<str>) -> string {
let replacer = regex::NoExpand(repl.as_ref());
self.inner.replace_all(s.as_ref(), replacer).into_owned().into()
}
pub fn ReplaceAllStringFunc<F>(&self, s: impl AsRef<str>, mut f: F) -> string
where
F: FnMut(string) -> string,
{
self.inner.replace_all(s.as_ref(), |caps: ®ex::Captures| {
f(caps.get(0).map(|m| m.as_str().into()).unwrap_or_default()).as_str().to_string()
}).into_owned().into()
}
pub fn Split(&self, s: impl AsRef<str>, n: int) -> slice<string> {
let s_ref = s.as_ref();
if n == 0 { return slice::new(); }
let mut out: slice<string> = slice::new();
let mut last = 0usize;
let mut count = 0i64;
for m in self.inner.find_iter(s_ref) {
if n > 0 && count >= n - 1 { break; }
out.push(s_ref[last..m.start()].into());
last = m.end();
count += 1;
}
out.push(s_ref[last..].into());
out
}
pub fn NumSubexp(&self) -> int {
self.inner.captures_len() as int - 1
}
pub fn String(&self) -> string { self.src.clone().into() }
}
#[allow(non_snake_case)]
pub fn Compile(pat: impl AsRef<str>) -> (Regexp, error) {
match regex::Regex::new(pat.as_ref()) {
Ok(r) => (Regexp { inner: r, src: pat.as_ref().into() }, nil),
Err(e) => (dummy_regexp(), New(&e.to_string())),
}
}
#[allow(non_snake_case)]
pub fn MustCompile(pat: impl AsRef<str>) -> Regexp {
let (re, err) = Compile(pat);
if err != nil { panic!("regexp: MustCompile: {}", err); }
re
}
#[allow(non_snake_case)]
pub fn MatchString(pat: impl AsRef<str>, s: impl AsRef<str>) -> (bool, error) {
let (re, err) = Compile(pat);
if err != nil { return (false, err); }
(re.MatchString(s), nil)
}
#[allow(non_snake_case)]
pub fn QuoteMeta(s: impl AsRef<str>) -> string {
regex::escape(s.as_ref()).into()
}
fn dummy_regexp() -> Regexp {
static DUMMY_SRC: &str = "";
Regexp {
inner: regex::Regex::new(DUMMY_SRC).unwrap(),
src: String::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn match_string_basic() {
let (re, err) = Compile(r"a.c");
assert_eq!(err, nil);
assert!(re.MatchString("abc"));
assert!(re.MatchString("a_c"));
assert!(!re.MatchString("ac"));
}
#[test]
fn bad_pattern_returns_error() {
let (_re, err) = Compile(r"([");
assert!(err != nil);
}
#[test]
fn must_compile_panics_on_bad() {
let result = std::panic::catch_unwind(|| MustCompile(r"([") );
assert!(result.is_err());
}
#[test]
fn find_string_and_all() {
let re = MustCompile(r"\d+");
assert_eq!(re.FindString("a1b22c333"), "1");
assert_eq!(re.FindAllString("a1b22c333", -1), vec!["1", "22", "333"]);
assert_eq!(re.FindAllString("a1b22c333", 2), vec!["1", "22"]);
}
#[test]
fn find_string_submatch() {
let re = MustCompile(r"(\w+)=(\d+)");
let caps = re.FindStringSubmatch("port=8080");
assert_eq!(caps, vec!["port=8080", "port", "8080"]);
}
#[test]
fn replace_all_string() {
let re = MustCompile(r"\d+");
assert_eq!(re.ReplaceAllString("a1 b22 c333", "X"), "aX bX cX");
}
#[test]
fn replace_with_backref() {
let re = MustCompile(r"(\w+)=(\d+)");
assert_eq!(re.ReplaceAllString("port=80 ttl=60", "$2:$1"), "80:port 60:ttl");
}
#[test]
fn replace_with_func() {
let re = MustCompile(r"\d+");
let out = re.ReplaceAllStringFunc("a1 b22", |m| format!("[{}]", m).into());
assert_eq!(out, "a[1] b[22]");
}
#[test]
fn split_n() {
let re = MustCompile(r",\s*");
assert_eq!(re.Split("a, b, c, d", -1), vec!["a", "b", "c", "d"]);
assert_eq!(re.Split("a, b, c, d", 2), vec!["a", "b, c, d"]);
}
#[test]
fn match_string_global_helper() {
let (ok, err) = MatchString(r"^\d+$", "12345");
assert_eq!(err, nil);
assert!(ok);
}
#[test]
fn quote_meta_escapes() {
assert_eq!(QuoteMeta("1.2.3"), "1\\.2\\.3");
}
#[test]
fn num_subexp_counts_groups() {
let re = MustCompile(r"(\w+)-(\d+)");
assert_eq!(re.NumSubexp(), 2);
}
}