use regex::Regex;
pub fn is_match(pattern: &str, text: &str) -> Result<bool, String> {
let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
Ok(re.is_match(text))
}
pub fn find_first(pattern: &str, text: &str) -> Result<Option<String>, String> {
let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
Ok(re.find(text).map(|m| m.as_str().to_string()))
}
pub fn find_all(pattern: &str, text: &str) -> Result<Vec<String>, String> {
let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
Ok(re.find_iter(text).map(|m| m.as_str().to_string()).collect())
}
pub fn replace_first(pattern: &str, text: &str, replacement: &str) -> Result<String, String> {
let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
Ok(re.replace(text, replacement).to_string())
}
pub fn replace_all(pattern: &str, text: &str, replacement: &str) -> Result<String, String> {
let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
Ok(re.replace_all(text, replacement).to_string())
}
pub fn split(pattern: &str, text: &str) -> Result<Vec<String>, String> {
let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
Ok(re.split(text).map(ToString::to_string).collect())
}
pub fn capture_first(pattern: &str, text: &str) -> Result<Option<Vec<String>>, String> {
let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
Ok(re.captures(text).map(|caps| {
caps.iter()
.map(|m| m.map(|m| m.as_str().to_string()).unwrap_or_default())
.collect()
}))
}
pub fn capture_all(pattern: &str, text: &str) -> Result<Vec<Vec<String>>, String> {
let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
Ok(re
.captures_iter(text)
.map(|caps| {
caps.iter()
.map(|m| m.map(|m| m.as_str().to_string()).unwrap_or_default())
.collect()
})
.collect())
}
pub fn is_valid_pattern(pattern: &str) -> Result<bool, String> {
Ok(Regex::new(pattern).is_ok())
}
pub fn escape(text: &str) -> Result<String, String> {
Ok(regex::escape(text))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_match_basic() {
assert!(is_match(r"\d+", "123").expect("is_match should succeed in test"));
assert!(is_match(r"[a-z]+", "hello").expect("is_match should succeed in test"));
assert!(!is_match(r"\d+", "abc").expect("is_match should succeed in test"));
}
#[test]
fn test_is_match_invalid_pattern() {
assert!(is_match(r"[", "text").is_err());
assert!(is_match(r"(unclosed", "text").is_err());
}
#[test]
fn test_find_first_basic() {
assert_eq!(
find_first(r"\d+", "abc 123 def").expect("find_first should succeed in test"),
Some("123".to_string())
);
assert_eq!(
find_first(r"[a-z]+", "123 hello 456").expect("find_first should succeed in test"),
Some("hello".to_string())
);
assert_eq!(
find_first(r"\d+", "abc def").expect("find_first should succeed in test"),
None
);
}
#[test]
fn test_find_first_multiple() {
assert_eq!(
find_first(r"\d+", "123 456 789").expect("find_first should succeed in test"),
Some("123".to_string())
);
}
#[test]
fn test_find_all_basic() {
assert_eq!(
find_all(r"\d+", "abc 123 def 456").expect("find_all should succeed in test"),
vec!["123", "456"]
);
assert_eq!(
find_all(r"[a-z]+", "hello world rust").expect("find_all should succeed in test"),
vec!["hello", "world", "rust"]
);
assert_eq!(
find_all(r"\d+", "no numbers").expect("find_all should succeed in test"),
Vec::<String>::new()
);
}
#[test]
fn test_find_all_empty() {
assert!(find_all(r"\d+", "")
.expect("find_all should succeed in test")
.is_empty());
}
#[test]
fn test_replace_first_basic() {
assert_eq!(
replace_first(r"\d+", "abc 123 def 456", "X")
.expect("replace_first should succeed in test"),
"abc X def 456"
);
assert_eq!(
replace_first(r"[a-z]+", "hello world", "X")
.expect("replace_first should succeed in test"),
"X world"
);
}
#[test]
fn test_replace_first_no_match() {
assert_eq!(
replace_first(r"\d+", "no numbers", "X").expect("replace_first should succeed in test"),
"no numbers"
);
}
#[test]
fn test_replace_all_basic() {
assert_eq!(
replace_all(r"\d+", "abc 123 def 456", "X")
.expect("replace_all should succeed in test"),
"abc X def X"
);
assert_eq!(
replace_all(r"[a-z]+", "hello world rust", "X")
.expect("replace_all should succeed in test"),
"X X X"
);
}
#[test]
fn test_replace_all_no_match() {
assert_eq!(
replace_all(r"\d+", "no numbers", "X").expect("replace_all should succeed in test"),
"no numbers"
);
}
#[test]
fn test_split_basic() {
assert_eq!(
split(r"\s+", "hello world rust").expect("split should succeed in test"),
vec!["hello", "world", "rust"]
);
assert_eq!(
split(r",", "a,b,c").expect("split should succeed in test"),
vec!["a", "b", "c"]
);
assert_eq!(
split(r"\d+", "a1b2c").expect("split should succeed in test"),
vec!["a", "b", "c"]
);
}
#[test]
fn test_split_no_match() {
assert_eq!(
split(r"\d+", "no numbers").expect("split should succeed in test"),
vec!["no numbers"]
);
}
#[test]
fn test_split_empty_parts() {
let result = split(r",", "a,b,").expect("split should succeed in test");
assert_eq!(result, vec!["a", "b", ""]);
}
#[test]
fn test_capture_first_basic() {
let result = capture_first(r"(\w+)@(\w+)", "user@example.com")
.expect("capture_first should succeed in test")
.expect("result should be Some in test");
assert_eq!(result[0], "user@example");
assert_eq!(result[1], "user");
assert_eq!(result[2], "example");
}
#[test]
fn test_capture_first_no_match() {
assert_eq!(
capture_first(r"(\w+)@(\w+)", "no email here")
.expect("capture_first should succeed in test"),
None
);
}
#[test]
fn test_capture_first_no_groups() {
let result = capture_first(r"\d+", "abc 123")
.expect("capture_first should succeed in test")
.expect("result should be Some in test");
assert_eq!(result[0], "123");
}
#[test]
fn test_capture_all_basic() {
let result = capture_all(r"(\w+):(\d+)", "name:123 age:45")
.expect("capture_all should succeed in test");
assert_eq!(result.len(), 2);
assert_eq!(result[0][1], "name");
assert_eq!(result[0][2], "123");
assert_eq!(result[1][1], "age");
assert_eq!(result[1][2], "45");
}
#[test]
fn test_capture_all_no_match() {
assert!(capture_all(r"(\w+):(\d+)", "no matches")
.expect("capture_all should succeed in test")
.is_empty());
}
#[test]
fn test_is_valid_pattern_valid() {
assert!(is_valid_pattern(r"\d+").expect("is_valid_pattern should succeed in test"));
assert!(is_valid_pattern(r"[a-z]+").expect("is_valid_pattern should succeed in test"));
assert!(is_valid_pattern(r"(\w+)@(\w+)").expect("is_valid_pattern should succeed in test"));
}
#[test]
fn test_is_valid_pattern_invalid() {
assert!(!is_valid_pattern(r"[").expect("is_valid_pattern should succeed in test"));
assert!(!is_valid_pattern(r"(unclosed").expect("is_valid_pattern should succeed in test"));
assert!(!is_valid_pattern(r"\k<invalid>").expect("is_valid_pattern should succeed in test"));
}
#[test]
fn test_escape_basic() {
assert_eq!(
escape("a.b*c?").expect("escape should succeed in test"),
r"a\.b\*c\?"
);
assert_eq!(
escape("hello").expect("escape should succeed in test"),
"hello"
);
assert_eq!(
escape("[abc]").expect("escape should succeed in test"),
r"\[abc\]"
);
assert_eq!(
escape("(a|b)").expect("escape should succeed in test"),
r"\(a\|b\)"
);
}
#[test]
fn test_escape_special_chars() {
assert_eq!(
escape(r"\.^$*+?{}[]()|\").expect("escape should succeed in test"),
r"\\\.\^\$\*\+\?\{\}\[\]\(\)\|\\"
);
}
#[test]
fn test_email_extraction_workflow() {
let text = "Contact: user@example.com or admin@test.org";
assert!(is_match(r"\w+@\w+\.\w+", text).expect("is_match should succeed in test"));
let first = find_first(r"\w+@\w+\.\w+", text).expect("find_first should succeed in test");
assert_eq!(first, Some("user@example.com".to_string()));
let all = find_all(r"\w+@\w+\.\w+", text).expect("find_all should succeed in test");
assert_eq!(all, vec!["user@example.com", "admin@test.org"]);
}
#[test]
fn test_text_cleanup_workflow() {
let text = "Hello World Rust Programming";
let cleaned = replace_all(r"\s+", text, " ").expect("replace_all should succeed in test");
assert_eq!(cleaned, "Hello World Rust Programming");
let words = split(r"\s+", text).expect("split should succeed in test");
assert_eq!(words, vec!["Hello", "World", "Rust", "Programming"]);
}
#[test]
fn test_url_parsing_workflow() {
let url = "https://example.com:8080/path?key=value";
let pattern = r"(https?)://([^:/]+):(\d+)(/[^?]+)\?(.+)";
let captures = capture_first(pattern, url)
.expect("capture_first should succeed in test")
.expect("result should be Some in test");
assert_eq!(captures[1], "https"); assert_eq!(captures[2], "example.com"); assert_eq!(captures[3], "8080"); assert_eq!(captures[4], "/path"); assert_eq!(captures[5], "key=value"); }
#[test]
fn test_escape_and_match() {
let literal = "a.b*c?";
let escaped = escape(literal).expect("escape should succeed in test");
assert!(is_match(&escaped, "a.b*c?").expect("is_match should succeed in test"));
assert!(!is_match(&escaped, "axbxcx").expect("is_match should succeed in test"));
}
#[test]
fn test_empty_text() {
assert!(!is_match(r"\d+", "").expect("is_match should succeed in test"));
assert_eq!(
find_first(r"\d+", "").expect("find_first should succeed in test"),
None
);
assert!(find_all(r"\d+", "")
.expect("find_all should succeed in test")
.is_empty());
}
#[test]
fn test_empty_pattern_invalid() {
assert!(is_match("", "text").is_ok());
}
#[test]
fn test_unicode_support() {
assert!(is_match(r"你好", "你好世界").expect("is_match should succeed in test"));
assert_eq!(
find_first(r"[а-я]+", "Привет мир").expect("find_first should succeed in test"),
Some("ривет".to_string())
);
assert!(
find_all(r"😀|😃|😄", "Hello 😀 World 😃")
.expect("find_all should succeed in test")
.len()
== 2
);
}
#[test]
fn test_case_sensitivity() {
assert!(is_match(r"hello", "hello").expect("is_match should succeed in test"));
assert!(!is_match(r"hello", "HELLO").expect("is_match should succeed in test"));
assert!(is_match(r"(?i)hello", "HELLO").expect("is_match should succeed in test"));
}
#[test]
fn test_multiline_patterns() {
let text = "line1\nline2\nline3";
assert!(is_match(r"^line1", text).expect("is_match should succeed in test"));
assert!(is_match(r"line3$", text).expect("is_match should succeed in test"));
assert_eq!(
find_all(r"(?m)^line", text).expect("find_all should succeed in test"),
vec!["line", "line", "line"]
);
}
#[test]
fn test_greedy_vs_lazy() {
let text = "<div>content1</div><div>content2</div>";
let greedy = find_first(r"<div>.*</div>", text).expect("find_first should succeed in test");
assert_eq!(
greedy,
Some("<div>content1</div><div>content2</div>".to_string())
);
let lazy = find_first(r"<div>.*?</div>", text).expect("find_first should succeed in test");
assert_eq!(lazy, Some("<div>content1</div>".to_string()));
}
}