ruchy/stdlib/
regex.rs

1//! Regex Operations Module (ruchy/std/regex)
2//!
3//! Thin wrappers around Rust's `regex` crate for pattern matching functionality.
4//!
5//! **Design**: Thin wrappers (complexity ≤2 per function) around `regex` crate.
6//! **Quality**: 100% unit test coverage, property tests, ≥75% mutation coverage.
7
8use regex::Regex;
9
10/// Check if pattern matches text
11///
12/// # Examples
13///
14/// ```
15/// use ruchy::stdlib::regex;
16///
17/// let result = regex::is_match(r"\d+", "123");
18/// assert!(result.unwrap());
19/// ```
20///
21/// # Errors
22///
23/// Returns error if pattern is invalid
24pub fn is_match(pattern: &str, text: &str) -> Result<bool, String> {
25    let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
26    Ok(re.is_match(text))
27}
28
29/// Find first match in text
30///
31/// # Examples
32///
33/// ```
34/// use ruchy::stdlib::regex;
35///
36/// let result = regex::find_first(r"\d+", "abc 123 def");
37/// assert_eq!(result.unwrap(), Some("123".to_string()));
38/// ```
39///
40/// # Errors
41///
42/// Returns error if pattern is invalid
43pub fn find_first(pattern: &str, text: &str) -> Result<Option<String>, String> {
44    let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
45    Ok(re.find(text).map(|m| m.as_str().to_string()))
46}
47
48/// Find all matches in text
49///
50/// # Examples
51///
52/// ```
53/// use ruchy::stdlib::regex;
54///
55/// let result = regex::find_all(r"\d+", "abc 123 def 456");
56/// assert_eq!(result.unwrap(), vec!["123", "456"]);
57/// ```
58///
59/// # Errors
60///
61/// Returns error if pattern is invalid
62pub fn find_all(pattern: &str, text: &str) -> Result<Vec<String>, String> {
63    let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
64    Ok(re.find_iter(text).map(|m| m.as_str().to_string()).collect())
65}
66
67/// Replace first match with replacement
68///
69/// # Examples
70///
71/// ```
72/// use ruchy::stdlib::regex;
73///
74/// let result = regex::replace_first(r"\d+", "abc 123 def 456", "X");
75/// assert_eq!(result.unwrap(), "abc X def 456");
76/// ```
77///
78/// # Errors
79///
80/// Returns error if pattern is invalid
81pub fn replace_first(pattern: &str, text: &str, replacement: &str) -> Result<String, String> {
82    let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
83    Ok(re.replace(text, replacement).to_string())
84}
85
86/// Replace all matches with replacement
87///
88/// # Examples
89///
90/// ```
91/// use ruchy::stdlib::regex;
92///
93/// let result = regex::replace_all(r"\d+", "abc 123 def 456", "X");
94/// assert_eq!(result.unwrap(), "abc X def X");
95/// ```
96///
97/// # Errors
98///
99/// Returns error if pattern is invalid
100pub fn replace_all(pattern: &str, text: &str, replacement: &str) -> Result<String, String> {
101    let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
102    Ok(re.replace_all(text, replacement).to_string())
103}
104
105/// Split text by pattern
106///
107/// # Examples
108///
109/// ```
110/// use ruchy::stdlib::regex;
111///
112/// let result = regex::split(r"\s+", "hello world rust");
113/// assert_eq!(result.unwrap(), vec!["hello", "world", "rust"]);
114/// ```
115///
116/// # Errors
117///
118/// Returns error if pattern is invalid
119pub fn split(pattern: &str, text: &str) -> Result<Vec<String>, String> {
120    let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
121    Ok(re.split(text).map(ToString::to_string).collect())
122}
123
124/// Capture first match with groups
125///
126/// # Examples
127///
128/// ```
129/// use ruchy::stdlib::regex;
130///
131/// let result = regex::capture_first(r"(\w+)@(\w+)", "user@example.com");
132/// let captures = result.unwrap().unwrap();
133/// assert_eq!(captures[1], "user");
134/// assert_eq!(captures[2], "example");
135/// ```
136///
137/// # Errors
138///
139/// Returns error if pattern is invalid
140pub fn capture_first(pattern: &str, text: &str) -> Result<Option<Vec<String>>, String> {
141    let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
142    Ok(re.captures(text).map(|caps| {
143        caps.iter()
144            .map(|m| m.map(|m| m.as_str().to_string()).unwrap_or_default())
145            .collect()
146    }))
147}
148
149/// Capture all matches with groups
150///
151/// # Examples
152///
153/// ```
154/// use ruchy::stdlib::regex;
155///
156/// let result = regex::capture_all(r"(\w+):(\d+)", "name:123 age:45");
157/// let all_captures = result.unwrap();
158/// assert_eq!(all_captures[0][1], "name");
159/// assert_eq!(all_captures[0][2], "123");
160/// ```
161///
162/// # Errors
163///
164/// Returns error if pattern is invalid
165pub fn capture_all(pattern: &str, text: &str) -> Result<Vec<Vec<String>>, String> {
166    let re = Regex::new(pattern).map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?;
167    Ok(re
168        .captures_iter(text)
169        .map(|caps| {
170            caps.iter()
171                .map(|m| m.map(|m| m.as_str().to_string()).unwrap_or_default())
172                .collect()
173        })
174        .collect())
175}
176
177/// Check if pattern is valid regex
178///
179/// # Examples
180///
181/// ```
182/// use ruchy::stdlib::regex;
183///
184/// let result = regex::is_valid_pattern(r"\d+");
185/// assert!(result.unwrap());
186///
187/// let result = regex::is_valid_pattern(r"[");
188/// assert!(!result.unwrap());
189/// ```
190pub fn is_valid_pattern(pattern: &str) -> Result<bool, String> {
191    Ok(Regex::new(pattern).is_ok())
192}
193
194/// Escape special regex characters in text
195///
196/// # Examples
197///
198/// ```
199/// use ruchy::stdlib::regex;
200///
201/// let result = regex::escape("a.b*c?");
202/// assert_eq!(result.unwrap(), r"a\.b\*c\?");
203/// ```
204pub fn escape(text: &str) -> Result<String, String> {
205    Ok(regex::escape(text))
206}