Skip to main content

atom_engine/filters/
string.rs

1use serde_json::Value;
2use std::collections::HashMap;
3use tera::Error;
4
5use super::FilterResult;
6
7pub trait Filter: Send + Sync {
8    fn filter(&self, value: &Value, args: &HashMap<String, Value>) -> FilterResult;
9}
10
11pub fn json_encode(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
12    Ok(Value::String(
13        serde_json::to_string(value).unwrap_or_default(),
14    ))
15}
16
17pub fn upper(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
18    if let Some(s) = value.as_str() {
19        Ok(Value::String(s.to_uppercase()))
20    } else {
21        Ok(value.clone())
22    }
23}
24
25pub fn lower(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
26    if let Some(s) = value.as_str() {
27        Ok(Value::String(s.to_lowercase()))
28    } else {
29        Ok(value.clone())
30    }
31}
32
33pub fn capitalize(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
34    if let Some(s) = value.as_str() {
35        let mut chars = s.chars();
36        match chars.next() {
37            None => Ok(Value::String(String::new())),
38            Some(f) => Ok(Value::String(f.to_uppercase().chain(chars).collect())),
39        }
40    } else {
41        Ok(value.clone())
42    }
43}
44
45pub fn title(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
46    if let Some(s) = value.as_str() {
47        Ok(Value::String(
48            s.split_whitespace()
49                .map(|word| {
50                    let mut chars = word.chars();
51                    match chars.next() {
52                        None => String::new(),
53                        Some(f) => f.to_uppercase().chain(chars).collect(),
54                    }
55                })
56                .collect::<Vec<_>>()
57                .join(" "),
58        ))
59    } else {
60        Ok(value.clone())
61    }
62}
63
64pub fn camel_case(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
65    if let Some(s) = value.as_str() {
66        Ok(Value::String(heck::ToLowerCamelCase::to_lower_camel_case(
67            s,
68        )))
69    } else {
70        Ok(value.clone())
71    }
72}
73
74pub fn pascal_case(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
75    if let Some(s) = value.as_str() {
76        Ok(Value::String(heck::ToLowerCamelCase::to_lower_camel_case(
77            s,
78        )))
79    } else {
80        Ok(value.clone())
81    }
82}
83
84pub fn snake_case(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
85    if let Some(s) = value.as_str() {
86        Ok(Value::String(heck::ToSnakeCase::to_snake_case(s)))
87    } else {
88        Ok(value.clone())
89    }
90}
91
92pub fn kebab_case(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
93    if let Some(s) = value.as_str() {
94        Ok(Value::String(heck::ToKebabCase::to_kebab_case(s)))
95    } else {
96        Ok(value.clone())
97    }
98}
99
100pub fn truncate(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
101    let s = value
102        .as_str()
103        .ok_or_else(|| Error::msg("Expected string"))?;
104    let length = args.get("length").and_then(|v| v.as_u64()).unwrap_or(255) as usize;
105    let suffix = args.get("end").and_then(|v| v.as_str()).unwrap_or("...");
106
107    if s.len() <= length {
108        return Ok(Value::String(s.to_string()));
109    }
110
111    let truncated = s.chars().take(length).collect::<String>();
112    Ok(Value::String(format!("{}{}", truncated, suffix)))
113}
114
115pub fn slugify(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
116    if let Some(s) = value.as_str() {
117        let slug = s
118            .to_lowercase()
119            .chars()
120            .map(|c| if c.is_alphanumeric() { c } else { '-' })
121            .collect::<String>()
122            .split('-')
123            .filter(|s| !s.is_empty())
124            .collect::<Vec<_>>()
125            .join("-");
126        Ok(Value::String(slug))
127    } else {
128        Ok(value.clone())
129    }
130}
131
132pub fn pluralize(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
133    let n = value.as_i64().unwrap_or(0);
134    let singular = args.get("singular").and_then(|v| v.as_str()).unwrap_or("");
135    let plural = args.get("plural").and_then(|v| v.as_str()).unwrap_or("s");
136
137    Ok(Value::String(if n.abs() == 1 {
138        singular.to_string()
139    } else {
140        plural.to_string()
141    }))
142}
143
144pub fn replace(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
145    let s = value
146        .as_str()
147        .ok_or_else(|| Error::msg("Expected string"))?;
148    let old = args.get("old").and_then(|v| v.as_str()).unwrap_or("");
149    let new = args.get("new").and_then(|v| v.as_str()).unwrap_or("");
150    Ok(Value::String(s.replace(old, new)))
151}
152
153pub fn remove(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
154    let s = value
155        .as_str()
156        .ok_or_else(|| Error::msg("Expected string"))?;
157    let to_remove = args.get("string").and_then(|v| v.as_str()).unwrap_or("");
158    Ok(Value::String(s.replace(to_remove, "")))
159}
160
161pub fn prepend(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
162    let s = value
163        .as_str()
164        .ok_or_else(|| Error::msg("Expected string"))?;
165    let prefix = args.get("string").and_then(|v| v.as_str()).unwrap_or("");
166    Ok(Value::String(format!("{}{}", prefix, s)))
167}
168
169pub fn append(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
170    let s = value
171        .as_str()
172        .ok_or_else(|| Error::msg("Expected string"))?;
173    let suffix = args.get("string").and_then(|v| v.as_str()).unwrap_or("");
174    Ok(Value::String(format!("{}{}", s, suffix)))
175}
176
177pub fn strip(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
178    if let Some(s) = value.as_str() {
179        Ok(Value::String(s.trim().to_string()))
180    } else {
181        Ok(value.clone())
182    }
183}
184
185pub fn nl2br(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
186    if let Some(s) = value.as_str() {
187        let escaped = s
188            .replace('&', "&amp;")
189            .replace('<', "&lt;")
190            .replace('>', "&gt;")
191            .replace('\n', "<br>\n");
192        Ok(Value::String(escaped))
193    } else {
194        Ok(value.clone())
195    }
196}
197
198pub fn word_count(value: &Value, _: &HashMap<String, Value>) -> FilterResult {
199    if let Some(s) = value.as_str() {
200        let count = s.split_whitespace().count();
201        Ok(Value::Number(count.into()))
202    } else {
203        Ok(Value::Number(0.into()))
204    }
205}
206
207pub fn char_count(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
208    if let Some(s) = value.as_str() {
209        let include_spaces = args
210            .get("include_spaces")
211            .and_then(|v| v.as_bool())
212            .unwrap_or(true);
213        let count = if include_spaces {
214            s.chars().count()
215        } else {
216            s.replace(' ', "").chars().count()
217        };
218        Ok(Value::Number(count.into()))
219    } else {
220        Ok(Value::Number(0.into()))
221    }
222}
223
224pub fn starts_with(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
225    let s = value
226        .as_str()
227        .ok_or_else(|| Error::msg("Expected string"))?;
228    let prefix = args.get("prefix").and_then(|v| v.as_str()).unwrap_or("");
229    Ok(Value::Bool(s.starts_with(prefix)))
230}
231
232pub fn ends_with(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
233    let s = value
234        .as_str()
235        .ok_or_else(|| Error::msg("Expected string"))?;
236    let suffix = args.get("suffix").and_then(|v| v.as_str()).unwrap_or("");
237    Ok(Value::Bool(s.ends_with(suffix)))
238}
239
240pub fn contains(value: &Value, args: &HashMap<String, Value>) -> FilterResult {
241    let s = value
242        .as_str()
243        .ok_or_else(|| Error::msg("Expected string"))?;
244    let substring = args.get("substring").and_then(|v| v.as_str()).unwrap_or("");
245    Ok(Value::Bool(s.contains(substring)))
246}