diff --git a/components/templates/src/filters.rs b/components/templates/src/filters.rs index b9b53d3767..432f2b3d22 100644 --- a/components/templates/src/filters.rs +++ b/components/templates/src/filters.rs @@ -1,9 +1,12 @@ use std::borrow::Cow; use std::collections::HashMap; use std::hash::BuildHasher; +use std::sync::{Arc, Mutex}; use config::Config; + use libs::base64::engine::{general_purpose::STANDARD as standard_b64, Engine}; +use libs::regex::Regex; use libs::tera::{ to_value, try_get_value, Error as TeraError, Filter as TeraFilter, Result as TeraResult, Tera, Value, @@ -78,6 +81,53 @@ pub fn base64_decode( Ok(to_value(as_str).unwrap()) } +#[derive(Debug)] +pub struct RegexReplaceFilter { + re_cache: Arc>>, +} + +impl RegexReplaceFilter { + pub fn new() -> Self { + return Self { re_cache: Arc::new(Mutex::new(HashMap::new())) }; + } +} + +impl TeraFilter for RegexReplaceFilter { + fn filter(&self, value: &Value, args: &HashMap) -> TeraResult { + let text = try_get_value!("regex_replace", "value", String, value); + let pattern = match args.get("pattern") { + Some(val) => try_get_value!("regex_replace", "pattern", String, val), + None => { + return Err(TeraError::msg( + "Filter `regex_replace` expected an arg called `pattern`", + )) + } + }; + let rep = match args.get("rep") { + Some(val) => try_get_value!("regex_replace", "rep", String, val), + None => { + return Err(TeraError::msg("Filter `regex_replace` expected an arg called `rep`")) + } + }; + + let mut cache = self.re_cache.lock().expect("re_cache lock"); + let replaced = { + match cache.get(&pattern) { + Some(pat) => pat.replace_all(&text, &rep), + None => { + let pat = Regex::new(&pattern) + .map_err(|e| format!("`regex_replace`: failed to compile regex: {}", e))?; + let replaced = pat.replace_all(&text, &rep); + cache.insert(pattern, pat); + replaced + } + } + }; + + Ok(to_value(replaced).unwrap()) + } +} + #[derive(Debug)] pub struct NumFormatFilter { default_language: String, @@ -114,7 +164,9 @@ mod tests { use libs::tera::{to_value, Filter, Tera}; - use super::{base64_decode, base64_encode, MarkdownFilter, NumFormatFilter}; + use super::{ + base64_decode, base64_encode, MarkdownFilter, NumFormatFilter, RegexReplaceFilter, + }; use config::Config; #[test] @@ -251,6 +303,22 @@ mod tests { } } + #[test] + fn regex_replace_filter() { + let value = "Springsteen, Bruce"; + let expected = "Bruce Springsteen"; + let pattern = r"(?P[^,\s]+),\s+(?P\S+)"; + let rep = "$first $last"; + let mut args = HashMap::new(); + args.insert("pattern".to_string(), to_value(pattern).unwrap()); + args.insert("rep".to_string(), to_value(rep).unwrap()); + let regex_replace = RegexReplaceFilter::new(); + let result = regex_replace.filter(&to_value(value).unwrap(), &args); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), to_value(expected).unwrap()); + assert!(regex_replace.re_cache.lock().unwrap().contains_key(pattern)); + } + #[test] fn num_format_filter() { let tests = vec![ diff --git a/components/templates/src/lib.rs b/components/templates/src/lib.rs index 6441ff4999..59ab2abaf9 100644 --- a/components/templates/src/lib.rs +++ b/components/templates/src/lib.rs @@ -28,6 +28,7 @@ pub static ZOLA_TERA: Lazy = Lazy::new(|| { .unwrap(); tera.register_filter("base64_encode", filters::base64_encode); tera.register_filter("base64_decode", filters::base64_decode); + tera.register_filter("regex_replace", filters::RegexReplaceFilter::new()); tera }); diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md index 20c823070d..7eca41ecb1 100644 --- a/docs/content/documentation/templates/overview.md +++ b/docs/content/documentation/templates/overview.md @@ -86,6 +86,14 @@ Encode the variable to base64. ### base64_decode Decode the variable from base64. +### regex_replace +Replace text via regular expressions. + +```jinja2 +{{ "World Hello" | regex_replace(pattern=`(?P\w+), (?P\w+)`, rep=`$greeting $subject`) }} + +``` + ### num_format Format a number into its string representation.