1pub mod filters;
2
3use glob::glob;
4use serde_json::Value;
5use std::cell::RefCell;
6use std::rc::Rc;
7use tera::{Context, Filter, Function, Tera};
8
9mod components;
10mod context;
11mod error;
12mod pool;
13
14pub use components::{
15 compute_cache_key, compute_props_hash, Component, ComponentCache, ComponentRegistry,
16 ComponentRenderer, PropDef, PropType, ScopedSlotDef, SlotData,
17};
18pub use context::ContextChain;
19pub use error::Error;
20pub use pool::{MemoryPool, PooledString, StringPool};
21
22thread_local! {
23 static COMPONENT_RENDERER: RefCell<Option<Rc<RefCell<ComponentRenderer>>>> = const { RefCell::new(None) };
24}
25
26#[derive(Clone)]
27pub struct Atom {
28 tera: Tera,
29 components: ComponentRegistry,
30 context_chain: ContextChain,
31 max_loop_iter: usize,
32 debug: bool,
33 use_parallel: bool,
34}
35
36impl Atom {
37 pub fn new() -> Self {
38 let mut tera = Tera::default();
39
40 tera.register_filter("json_encode", filters::json_encode);
42
43 tera.register_filter("upper", filters::upper);
45 tera.register_filter("lower", filters::lower);
46 tera.register_filter("capitalize", filters::capitalize);
47 tera.register_filter("title", filters::title);
48 tera.register_filter("camel_case", filters::camel_case);
49 tera.register_filter("pascal_case", filters::pascal_case);
50 tera.register_filter("snake_case", filters::snake_case);
51 tera.register_filter("kebab_case", filters::kebab_case);
52 tera.register_filter("truncate", filters::truncate);
53 tera.register_filter("slugify", filters::slugify);
54 tera.register_filter("pluralize", filters::pluralize);
55 tera.register_filter("replace", filters::replace);
56 tera.register_filter("remove", filters::remove);
57 tera.register_filter("prepend", filters::prepend);
58 tera.register_filter("append", filters::append);
59 tera.register_filter("strip", filters::strip);
60 tera.register_filter("nl2br", filters::nl2br);
61 tera.register_filter("word_count", filters::word_count);
62 tera.register_filter("char_count", filters::char_count);
63 tera.register_filter("starts_with", filters::starts_with);
64 tera.register_filter("ends_with", filters::ends_with);
65 tera.register_filter("contains", filters::contains);
66
67 tera.register_filter("first", filters::first);
69 tera.register_filter("last", filters::last);
70 tera.register_filter("length", filters::length);
71 tera.register_filter("reverse", filters::reverse);
72 tera.register_filter("sort", filters::sort);
73 tera.register_filter("group_by", filters::group_by);
74 tera.register_filter("where", filters::where_filter);
75 tera.register_filter("pluck", filters::pluck);
76 tera.register_filter("join", filters::join);
77 tera.register_filter("slice", filters::slice);
78 tera.register_filter("uniq", filters::uniq);
79 tera.register_filter("shuffle", filters::shuffle);
80 tera.register_filter("map", filters::map_filter);
81 tera.register_filter("filter", filters::filter_filter);
82 tera.register_filter("each", filters::each_filter);
83 tera.register_filter("reduce", filters::reduce_filter);
84 tera.register_filter("flatten", filters::flatten_filter);
85 tera.register_filter("partition", filters::partition_filter);
86
87 tera.register_filter("round", filters::round);
89 tera.register_filter("abs", filters::abs);
90 tera.register_filter("format", filters::format_number);
91 tera.register_filter("min", filters::min_filter);
92 tera.register_filter("max", filters::max_filter);
93 tera.register_filter("sum", filters::sum);
94 tera.register_filter("avg", filters::avg);
95 tera.register_filter("ceil", filters::ceil);
96 tera.register_filter("floor", filters::floor);
97
98 tera.register_filter("date", filters::date_format);
100
101 tera.register_filter("escape_html", filters::escape_html);
103 tera.register_filter("safe", filters::safe);
104
105 tera.register_filter("json_decode", filters::json_decode);
107 tera.register_filter("urlescape", filters::urlescape);
108 tera.register_filter("urlunescape", filters::urlunescape);
109 tera.register_filter("strip_tags", filters::strip_tags);
110 tera.register_filter("base64_encode", filters::base64_encode);
111 tera.register_filter("base64_decode", filters::base64_decode);
112
113 tera.register_filter("slot", filters::slot_filter);
115 tera.register_filter("has_slot", filters::has_slot_filter);
116 tera.register_filter("scoped_slot", filters::scoped_slot_filter);
117 tera.register_filter("with_scoped_data", filters::with_scoped_data_filter);
118
119 tera.register_filter("stack", filters::stack_filter);
121
122 tera.register_filter("when", filters::when);
124 tera.register_filter("default", filters::default_filter);
125 tera.register_filter("coalesce", filters::coalesce);
126 tera.register_filter("defined", filters::defined);
127 tera.register_filter("undefined", filters::undefined);
128 tera.register_filter("empty", filters::empty);
129 tera.register_filter("not_empty", filters::not_empty);
130
131 tera.register_function("dump", filters::DumpFn);
133 tera.register_function("log", filters::LogFn);
134 tera.register_function("range", filters::RangeFn);
135 tera.register_function("now", filters::NowFn);
136 tera.register_function("cycle", filters::CycleFn::new());
137 tera.register_function("uuid", filters::UuidFn);
138 tera.register_function("random", filters::RandomFn);
139 tera.register_function("choice", filters::ChoiceFn);
140 tera.register_function("file_exists", filters::FileExistsFn);
141 tera.register_function("env", filters::EnvFn);
142 tera.register_function("md5", filters::Md5Fn);
143 tera.register_function("sha256", filters::Sha256Fn);
144 tera.register_function("repeat", filters::RepeatFn);
145 tera.register_function("times", filters::TimesFn);
146 tera.register_function("loop", filters::LoopFn);
147 tera.register_function("iterate", filters::IterateFn);
148 tera.register_function("object", filters::ObjectFn);
149 tera.register_function("merge", filters::MergeFn);
150 tera.register_function("chunk", filters::ChunkFn);
151 tera.register_function("zip", filters::ZipFn);
152 tera.register_function("compact", filters::CompactFn);
153
154 tera.register_function("push", filters::PushFn);
156 tera.register_function("prepend", filters::PrependFn);
157 tera.register_function("set_slot", filters::SetSlotFn);
158 tera.register_function("once", filters::OnceFn);
159
160 Atom {
161 tera,
162 components: ComponentRegistry::new(),
163 context_chain: ContextChain::new(),
164 max_loop_iter: 10000,
165 debug: false,
166 use_parallel: false,
167 }
168 }
169
170 pub fn load_templates(&mut self, glob_pattern: &str) -> std::result::Result<(), Error> {
171 let template_files: Vec<(std::path::PathBuf, Option<String>)> = glob(glob_pattern)
172 .map_err(|e| Error::TemplateLoad {
173 path: glob_pattern.to_string(),
174 message: e.to_string(),
175 })?
176 .filter_map(|p| p.ok())
177 .map(|p| {
178 let name = p
179 .file_name()
180 .and_then(|n| n.to_str())
181 .map(|s| s.to_string())
182 .unwrap_or_default();
183 (p, Some(name))
184 })
185 .collect();
186
187 self.tera
188 .add_template_files(template_files.into_iter())
189 .map_err(|e| Error::TemplateLoad {
190 path: glob_pattern.to_string(),
191 message: e.to_string(),
192 })?;
193 Ok(())
194 }
195
196 pub fn add_template(&mut self, name: &str, content: &str) -> std::result::Result<(), Error> {
197 self.tera
198 .add_raw_template(name, content)
199 .map_err(|e| Error::TemplateParse {
200 name: name.to_string(),
201 message: e.to_string(),
202 })?;
203 Ok(())
204 }
205
206 pub fn register_component(
207 &mut self,
208 path: &str,
209 template: &str,
210 ) -> std::result::Result<(), Error> {
211 self.components.register(path, template)
212 }
213
214 pub fn register_filter<F>(&mut self, name: &str, filter: F)
215 where
216 F: Filter + Send + Sync + 'static,
217 {
218 self.tera.register_filter(name, filter);
219 }
220
221 pub fn register_function<F>(&mut self, name: &str, function: F)
222 where
223 F: Function + Send + Sync + 'static,
224 {
225 self.tera.register_function(name, function);
226 }
227
228 pub fn set_max_loop_iter(&mut self, max: usize) {
229 self.max_loop_iter = max;
230 }
231
232 pub fn set_debug(&mut self, debug: bool) {
233 self.debug = debug;
234 }
235
236 pub fn render(&self, template: &str, context: &Value) -> Result<String, Error> {
237 let mut ctx = Context::from_serialize(context).map_err(|e| Error::Context {
238 message: e.to_string(),
239 })?;
240
241 for (key, value) in self.context_chain.all() {
242 ctx.insert(key, &value);
243 }
244
245 ctx.insert("__atom_components", &self.components.list_components());
246
247 self.tera.render(template, &ctx).map_err(|e| Error::Render {
248 template: template.to_string(),
249 message: e.to_string(),
250 })
251 }
252
253 pub fn render_with_components(
254 &self,
255 template: &str,
256 context: &Value,
257 component_data: &Value,
258 ) -> std::result::Result<String, Error> {
259 let mut ctx = Context::from_serialize(context).map_err(|e| Error::Context {
260 message: e.to_string(),
261 })?;
262
263 if let Some(obj) = component_data.as_object() {
264 for (key, value) in obj {
265 ctx.insert(key, &value);
266 }
267 }
268
269 self.tera.render(template, &ctx).map_err(|e| Error::Render {
270 template: template.to_string(),
271 message: e.to_string(),
272 })
273 }
274
275 pub fn provide(&mut self, key: &str, value: Value) {
276 self.context_chain.provide(key, value);
277 }
278
279 pub fn reload(&mut self) -> std::result::Result<(), Error> {
280 self.tera.full_reload().map_err(|e| Error::TemplateLoad {
281 path: "reload".to_string(),
282 message: e.to_string(),
283 })
284 }
285
286 pub fn template_exists(&self, name: &str) -> bool {
287 self.tera.get_template(name).is_ok()
288 }
289
290 pub fn get_registered_templates(&self) -> Vec<String> {
291 self.tera
292 .get_template_names()
293 .map(|s| s.to_string())
294 .collect()
295 }
296
297 pub fn clear_cache(&mut self) {
298 self.tera.templates.clear();
299 }
300
301 pub fn set_parallel(&mut self, enabled: bool) {
302 self.use_parallel = enabled;
303 }
304
305 pub fn is_parallel(&self) -> bool {
306 self.use_parallel
307 }
308
309 pub fn enable_component_cache(&mut self, enabled: bool) {
310 self.components.enable_cache(enabled);
311 }
312
313 pub fn is_component_cache_enabled(&self) -> bool {
314 self.components.is_cache_enabled()
315 }
316
317 pub fn clear_component_cache(&mut self) {
318 self.components.clear_cache();
319 }
320
321 pub fn component_cache_len(&self) -> usize {
322 self.components.cache_len()
323 }
324
325 #[cfg(feature = "parallel")]
326 pub fn render_many(
327 &self,
328 templates: &[(&str, &Value)],
329 ) -> std::result::Result<Vec<(String, String)>, Error> {
330 use rayon::prelude::*;
331
332 let results: Vec<std::result::Result<(String, String), Error>> = templates
333 .par_iter()
334 .map(|(name, context)| {
335 let rendered = self.render(name, context)?;
336 Ok((name.to_string(), rendered))
337 })
338 .collect();
339
340 let mut output = Vec::new();
341 for result in results {
342 output.push(result?);
343 }
344 Ok(output)
345 }
346
347 #[cfg(not(feature = "parallel"))]
348 pub fn render_many(
349 &self,
350 templates: &[(&str, &Value)],
351 ) -> std::result::Result<Vec<(String, String)>, Error> {
352 let mut results = Vec::new();
353 for (name, context) in templates {
354 let rendered = self.render(name, context)?;
355 results.push((name.to_string(), rendered));
356 }
357 Ok(results)
358 }
359
360 #[cfg(feature = "async")]
361 pub async fn render_async(&self, template: &str, context: &Value) -> Result<String, Error> {
362 let template = template.to_string();
363 let context = context.clone();
364
365 tokio::task::spawn_blocking(move || {
366 let mut tera = Tera::default();
367 tera.register_filter("json_encode", filters::json_encode);
368 tera.render(
369 &template,
370 &Context::from_serialize(&context).map_err(|e| Error::Context {
371 message: e.to_string(),
372 })?,
373 )
374 .map_err(|e| Error::Render {
375 template,
376 message: e.to_string(),
377 })
378 })
379 .await
380 .map_err(|e| Error::Render {
381 template: "async".to_string(),
382 message: e.to_string(),
383 })?
384 }
385
386 #[cfg(feature = "async")]
387 pub async fn render_many_async(
388 &self,
389 templates: &[(&str, &Value)],
390 ) -> std::result::Result<Vec<(String, String)>, Error> {
391 use tokio::task::JoinSet;
392
393 let mut join_set = JoinSet::new();
394
395 for (name, context) in templates {
396 let name = name.to_string();
397 let context = context.clone();
398 let filters = filters::Filters::new();
399
400 join_set.spawn(async move {
401 tokio::task::spawn_blocking(move || {
402 let mut tera = Tera::default();
403 tera.register_filter("json_encode", filters::json_encode);
404 let mut ctx =
405 Context::from_serialize(&context).map_err(|e| Error::Context {
406 message: e.to_string(),
407 })?;
408 tera.render(&name, &ctx)
409 .map(|r| (name, r))
410 .map_err(|e| Error::Render {
411 template: name,
412 message: e.to_string(),
413 })
414 })
415 .await
416 .map_err(|e| Error::Render {
417 template: "async".to_string(),
418 message: e.to_string(),
419 })?
420 });
421 }
422
423 let mut results = Vec::new();
424 while let Some(result) = join_set.join_next().await {
425 results.push(result??);
426 }
427 Ok(results)
428 }
429}
430
431impl Default for Atom {
432 fn default() -> Self {
433 Self::new()
434 }
435}