doge_runtime/stdlib/
hunt.rs1use regex::Regex;
12
13use crate::error::{DogeError, DogeResult};
14use crate::value::Value;
15
16fn str_arg<'a>(fname: &str, v: &'a Value) -> DogeResult<&'a str> {
19 crate::stdlib::str_arg("hunt", fname, v)
20}
21
22fn compile(fname: &str, pat: &Value) -> Result<Regex, DogeError> {
25 let pat = str_arg(fname, pat)?;
26 Regex::new(pat).map_err(|_| DogeError::value_error(format!("not a valid pattern: \"{pat}\"")))
27}
28
29pub fn hunt_test(pat: &Value, text: &Value) -> DogeResult {
31 let re = compile("test", pat)?;
32 let text = str_arg("test", text)?;
33 Ok(Value::Bool(re.is_match(text)))
34}
35
36pub fn hunt_find(pat: &Value, text: &Value) -> DogeResult {
39 let re = compile("find", pat)?;
40 let text = str_arg("find", text)?;
41 Ok(match re.find(text) {
42 Some(m) => Value::str(m.as_str()),
43 None => Value::None,
44 })
45}
46
47pub fn hunt_find_all(pat: &Value, text: &Value) -> DogeResult {
50 let re = compile("find_all", pat)?;
51 let text = str_arg("find_all", text)?;
52 Ok(Value::list(
53 re.find_iter(text).map(|m| Value::str(m.as_str())).collect(),
54 ))
55}
56
57pub fn hunt_groups(pat: &Value, text: &Value) -> DogeResult {
61 let re = compile("groups", pat)?;
62 let text = str_arg("groups", text)?;
63 Ok(match re.captures(text) {
64 Some(caps) => Value::list(
65 caps.iter()
66 .map(|g| match g {
67 Some(m) => Value::str(m.as_str()),
68 None => Value::None,
69 })
70 .collect(),
71 ),
72 None => Value::None,
73 })
74}
75
76pub fn hunt_replace(pat: &Value, text: &Value, repl: &Value) -> DogeResult {
79 let re = compile("replace", pat)?;
80 let text = str_arg("replace", text)?;
81 let repl = str_arg("replace", repl)?;
82 Ok(Value::str(re.replace_all(text, repl)))
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::error::ErrorKind;
89
90 fn s(v: &str) -> Value {
91 Value::str(v)
92 }
93
94 #[test]
95 fn test_matches_anywhere() {
96 assert!(matches!(
97 hunt_test(&s("[0-9]+"), &s("order 42")).unwrap(),
98 Value::Bool(true)
99 ));
100 assert!(matches!(
101 hunt_test(&s("^woof"), &s("bark")).unwrap(),
102 Value::Bool(false)
103 ));
104 }
105
106 #[test]
107 fn find_returns_first_match_or_none() {
108 assert!(
109 matches!(hunt_find(&s("[0-9]+"), &s("order 42")).unwrap(), Value::Str(m) if &*m == "42")
110 );
111 assert!(matches!(
112 hunt_find(&s("[0-9]+"), &s("no digits")).unwrap(),
113 Value::None
114 ));
115 }
116
117 #[test]
118 fn find_all_collects_every_match() {
119 match hunt_find_all(&s("[0-9]+"), &s("1 and 22 and 333")).unwrap() {
120 Value::List(items) => {
121 let items = items.borrow();
122 assert_eq!(items.len(), 3);
123 assert!(matches!(&items[2], Value::Str(m) if &**m == "333"));
124 }
125 _ => panic!("expected a list"),
126 }
127 match hunt_find_all(&s("[0-9]+"), &s("none here")).unwrap() {
128 Value::List(items) => assert!(items.borrow().is_empty()),
129 _ => panic!("expected a list"),
130 }
131 }
132
133 #[test]
134 fn groups_returns_whole_match_then_captures() {
135 match hunt_groups(&s("(\\w+)@(\\w+)"), &s("doge@shibe")).unwrap() {
136 Value::List(items) => {
137 let items = items.borrow();
138 assert_eq!(items.len(), 3);
139 assert!(matches!(&items[0], Value::Str(m) if &**m == "doge@shibe"));
140 assert!(matches!(&items[1], Value::Str(m) if &**m == "doge"));
141 assert!(matches!(&items[2], Value::Str(m) if &**m == "shibe"));
142 }
143 _ => panic!("expected a list"),
144 }
145 assert!(matches!(
146 hunt_groups(&s("(\\w+)@(\\w+)"), &s("no at sign")).unwrap(),
147 Value::None
148 ));
149 }
150
151 #[test]
152 fn groups_non_participating_is_none() {
153 match hunt_groups(&s("(a)|(b)"), &s("a")).unwrap() {
154 Value::List(items) => {
155 let items = items.borrow();
156 assert!(matches!(&items[1], Value::Str(m) if &**m == "a"));
157 assert!(matches!(&items[2], Value::None));
158 }
159 _ => panic!("expected a list"),
160 }
161 }
162
163 #[test]
164 fn replace_swaps_every_match_with_backrefs() {
165 assert!(matches!(
166 hunt_replace(&s("[0-9]+"), &s("a1b22c"), &s("#")).unwrap(),
167 Value::Str(m) if &*m == "a#b#c"
168 ));
169 assert!(matches!(
170 hunt_replace(&s("(\\w+)@(\\w+)"), &s("doge@shibe"), &s("$2.$1")).unwrap(),
171 Value::Str(m) if &*m == "shibe.doge"
172 ));
173 }
174
175 #[test]
176 fn invalid_pattern_is_a_value_error() {
177 assert_eq!(
178 hunt_find(&s("["), &s("x")).unwrap_err().kind,
179 ErrorKind::ValueError
180 );
181 }
182
183 #[test]
184 fn non_str_argument_is_a_type_error() {
185 assert_eq!(
186 hunt_test(&Value::int(1), &s("x")).unwrap_err().kind,
187 ErrorKind::TypeError
188 );
189 assert_eq!(
190 hunt_find(&s("x"), &Value::int(1)).unwrap_err().kind,
191 ErrorKind::TypeError
192 );
193 }
194}