use regex::Regex;
#[polydat::polydat_node(category = Regex)]
fn regex_replace(
input: &str,
pattern: polydat::derive_support::Const<&str>,
replacement: polydat::derive_support::Const<&str>,
#[poly_const(compile_regex, from = pattern)] re: &Regex,
) -> String {
let _ = pattern;
re.replace_all(input, replacement.0).into_owned()
}
fn compile_regex(pattern: &str) -> Regex {
Regex::new(pattern).expect("invalid regex")
}
#[polydat::polydat_node(category = Regex)]
fn regex_match(
input: &str,
pattern: polydat::derive_support::Const<&str>,
#[poly_const(compile_regex, from = pattern)] re: &Regex,
) -> bool {
let matched = re.is_match(input);
if polydat::library::debug_nodes_enabled() {
let snippet: String = input.chars().take(200).collect();
let ellipsis = if input.len() > snippet.len() {
"…"
} else {
""
};
polydat::library::support::audit::debug(&format!(
"regex_match: pattern={:?} input.len={} matched={matched} input.snippet={:?}{ellipsis}",
re.as_str(),
input.len(),
snippet,
));
}
matched
}
#[polydat::polydat_node(category = Regex)]
fn regex_extract(
input: &str,
pattern: polydat::derive_support::Const<&str>,
#[poly_const(compile_regex, from = pattern)] re: &Regex,
) -> String {
if let Some(caps) = re.captures(input) {
caps.get(1)
.or_else(|| caps.get(0))
.map(|m| m.as_str().to_string())
.unwrap_or_default()
} else {
String::new()
}
}
pub use polydat::library::support::pattern::{PatternDialect, compile_pattern, promote_pattern};
fn compile_promoted(pattern: &str) -> Regex {
compile_pattern(pattern).unwrap_or_else(|e| panic!("{e}")).0
}
#[polydat::polydat_node(category = Regex)]
fn pattern_match(
input: &str,
pattern: polydat::derive_support::Const<&str>,
#[poly_const(compile_promoted, from = pattern)] re: &Regex,
) -> bool {
re.is_match(input)
}
#[cfg(test)]
mod pattern_tests {
use super::*;
#[test]
fn literal_is_strict_anchored() {
let (re, d) = compile_pattern("schema").unwrap();
assert_eq!(d, PatternDialect::Literal);
assert!(re.is_match("schema"));
assert!(!re.is_match("schema_v2"));
assert!(!re.is_match("pre_schema"));
}
#[test]
fn glob_star_distinguishes_suffix_series() {
let (m, dm) = compile_pattern("*m").unwrap();
assert_eq!(dm, PatternDialect::Glob);
assert!(m.is_match("100m"));
assert!(!m.is_match("100mi"));
let (mi, _) = compile_pattern("*mi").unwrap();
assert!(mi.is_match("100mi"));
assert!(!mi.is_match("100m"));
}
#[test]
fn regex_metachar_promotes() {
let (re, d) = compile_pattern("(100|200)m").unwrap();
assert_eq!(d, PatternDialect::Regex);
assert!(re.is_match("100m"));
assert!(re.is_match("200m"));
assert!(!re.is_match("300m"));
}
}
#[cfg(test)]
mod tests {
use super::*;
use polydat::ast::{PolydatNode, Value};
#[test]
fn regex_replace_basic() {
let node = RegexReplace::new(r"\d+".to_string(), "NUM".to_string());
let mut out = [Value::None];
node.eval(&[Value::Str("abc 123 def 456".into())], &mut out);
assert_eq!(out[0].as_str(), "abc NUM def NUM");
}
#[test]
fn regex_replace_no_match() {
let node = RegexReplace::new(r"\d+".to_string(), "NUM".to_string());
let mut out = [Value::None];
node.eval(&[Value::Str("no numbers here".into())], &mut out);
assert_eq!(out[0].as_str(), "no numbers here");
}
#[test]
fn regex_match_true() {
let node = RegexMatch::new(r"^\d{3}-\d{4}$".to_string());
let mut out = [Value::None];
node.eval(&[Value::Str("123-4567".into())], &mut out);
assert!(out[0].as_bool());
}
#[test]
fn regex_match_false() {
let node = RegexMatch::new(r"^\d{3}-\d{4}$".to_string());
let mut out = [Value::None];
node.eval(&[Value::Str("hello".into())], &mut out);
assert!(!out[0].as_bool());
}
#[test]
fn regex_extract_capture_group() {
let node = RegexExtract::new(r"name=(\w+)".to_string());
let mut out = [Value::None];
node.eval(&[Value::Str("name=Alice age=30".into())], &mut out);
assert_eq!(out[0].as_str(), "Alice");
}
#[test]
fn regex_extract_no_group() {
let node = RegexExtract::new(r"\d+".to_string());
let mut out = [Value::None];
node.eval(&[Value::Str("abc 42 def".into())], &mut out);
assert_eq!(out[0].as_str(), "42");
}
#[test]
fn regex_extract_no_match() {
let node = RegexExtract::new(r"\d+".to_string());
let mut out = [Value::None];
node.eval(&[Value::Str("no digits".into())], &mut out);
assert_eq!(out[0].as_str(), "");
}
}