atom_engine/filters/
string.rs1use 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('&', "&")
189 .replace('<', "<")
190 .replace('>', ">")
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}