Skip to main content

atom_engine/
lib.rs

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        // JSON
41        tera.register_filter("json_encode", filters::json_encode);
42
43        // String filters
44        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        // Collection filters
68        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        // Number filters
88        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        // Date filters
99        tera.register_filter("date", filters::date_format);
100
101        // HTML filters
102        tera.register_filter("escape_html", filters::escape_html);
103        tera.register_filter("safe", filters::safe);
104
105        // Encoding filters
106        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        // Slot helpers
114        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        // Stack filter
120        tera.register_filter("stack", filters::stack_filter);
121
122        // Conditional filters
123        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        // Global functions
132        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        // Component functions
155        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}