1pub mod filters;
32
33use glob::glob;
34use serde_json::Value;
35use std::cell::RefCell;
36use std::rc::Rc;
37use tera::{Context, Filter, Function, Tera};
38
39mod components;
40mod context;
41mod error;
42mod pool;
43
44pub use components::{
45 compute_cache_key, compute_props_hash, Component, ComponentCache, ComponentRegistry,
46 ComponentRenderer, PropDef, PropType, ScopedSlotDef, SlotData,
47};
48pub use context::ContextChain;
49pub use error::Error;
50pub use pool::{MemoryPool, PooledString, StringPool};
51
52thread_local! {
53 static COMPONENT_RENDERER: RefCell<Option<Rc<RefCell<ComponentRenderer>>>> = const { RefCell::new(None) };
54}
55
56#[derive(Clone)]
73pub struct Atom {
74 tera: Tera,
75 components: ComponentRegistry,
76 context_chain: ContextChain,
77 max_loop_iter: usize,
78 debug: bool,
79 use_parallel: bool,
80}
81
82impl Atom {
83 pub fn new() -> Self {
93 let mut tera = Tera::default();
94
95 tera.register_filter("json_encode", filters::json_encode);
97
98 tera.register_filter("upper", filters::upper);
100 tera.register_filter("lower", filters::lower);
101 tera.register_filter("capitalize", filters::capitalize);
102 tera.register_filter("title", filters::title);
103 tera.register_filter("camel_case", filters::camel_case);
104 tera.register_filter("pascal_case", filters::pascal_case);
105 tera.register_filter("snake_case", filters::snake_case);
106 tera.register_filter("kebab_case", filters::kebab_case);
107 tera.register_filter("truncate", filters::truncate);
108 tera.register_filter("slugify", filters::slugify);
109 tera.register_filter("pluralize", filters::pluralize);
110 tera.register_filter("replace", filters::replace);
111 tera.register_filter("remove", filters::remove);
112 tera.register_filter("prepend", filters::prepend);
113 tera.register_filter("append", filters::append);
114 tera.register_filter("strip", filters::strip);
115 tera.register_filter("nl2br", filters::nl2br);
116 tera.register_filter("word_count", filters::word_count);
117 tera.register_filter("char_count", filters::char_count);
118 tera.register_filter("starts_with", filters::starts_with);
119 tera.register_filter("ends_with", filters::ends_with);
120 tera.register_filter("contains", filters::contains);
121
122 tera.register_filter("first", filters::first);
124 tera.register_filter("last", filters::last);
125 tera.register_filter("length", filters::length);
126 tera.register_filter("reverse", filters::reverse);
127 tera.register_filter("sort", filters::sort);
128 tera.register_filter("group_by", filters::group_by);
129 tera.register_filter("where", filters::where_filter);
130 tera.register_filter("pluck", filters::pluck);
131 tera.register_filter("join", filters::join);
132 tera.register_filter("slice", filters::slice);
133 tera.register_filter("uniq", filters::uniq);
134 tera.register_filter("shuffle", filters::shuffle);
135 tera.register_filter("map", filters::map_filter);
136 tera.register_filter("filter", filters::filter_filter);
137 tera.register_filter("each", filters::each_filter);
138 tera.register_filter("reduce", filters::reduce_filter);
139 tera.register_filter("flatten", filters::flatten_filter);
140 tera.register_filter("partition", filters::partition_filter);
141
142 tera.register_filter("round", filters::round);
144 tera.register_filter("abs", filters::abs);
145 tera.register_filter("format", filters::format_number);
146 tera.register_filter("min", filters::min_filter);
147 tera.register_filter("max", filters::max_filter);
148 tera.register_filter("sum", filters::sum);
149 tera.register_filter("avg", filters::avg);
150 tera.register_filter("ceil", filters::ceil);
151 tera.register_filter("floor", filters::floor);
152
153 tera.register_filter("date", filters::date_format);
155
156 tera.register_filter("escape_html", filters::escape_html);
158 tera.register_filter("safe", filters::safe);
159
160 tera.register_filter("json_decode", filters::json_decode);
162 tera.register_filter("urlescape", filters::urlescape);
163 tera.register_filter("urlunescape", filters::urlunescape);
164 tera.register_filter("strip_tags", filters::strip_tags);
165 tera.register_filter("base64_encode", filters::base64_encode);
166 tera.register_filter("base64_decode", filters::base64_decode);
167
168 tera.register_filter("slot", filters::slot_filter);
170 tera.register_filter("has_slot", filters::has_slot_filter);
171 tera.register_filter("scoped_slot", filters::scoped_slot_filter);
172 tera.register_filter("with_scoped_data", filters::with_scoped_data_filter);
173
174 tera.register_filter("stack", filters::stack_filter);
176
177 tera.register_filter("when", filters::when);
179 tera.register_filter("default", filters::default_filter);
180 tera.register_filter("coalesce", filters::coalesce);
181 tera.register_filter("defined", filters::defined);
182 tera.register_filter("undefined", filters::undefined);
183 tera.register_filter("empty", filters::empty);
184 tera.register_filter("not_empty", filters::not_empty);
185
186 tera.register_function("dump", filters::DumpFn);
188 tera.register_function("log", filters::LogFn);
189 tera.register_function("range", filters::RangeFn);
190 tera.register_function("now", filters::NowFn);
191 tera.register_function("cycle", filters::CycleFn::new());
192 tera.register_function("uuid", filters::UuidFn);
193 tera.register_function("random", filters::RandomFn);
194 tera.register_function("choice", filters::ChoiceFn);
195 tera.register_function("file_exists", filters::FileExistsFn);
196 tera.register_function("env", filters::EnvFn);
197 tera.register_function("md5", filters::Md5Fn);
198 tera.register_function("sha256", filters::Sha256Fn);
199 tera.register_function("repeat", filters::RepeatFn);
200 tera.register_function("times", filters::TimesFn);
201 tera.register_function("loop", filters::LoopFn);
202 tera.register_function("iterate", filters::IterateFn);
203 tera.register_function("object", filters::ObjectFn);
204 tera.register_function("merge", filters::MergeFn);
205 tera.register_function("chunk", filters::ChunkFn);
206 tera.register_function("zip", filters::ZipFn);
207 tera.register_function("compact", filters::CompactFn);
208
209 tera.register_function("push", filters::PushFn);
211 tera.register_function("prepend", filters::PrependFn);
212 tera.register_function("set_slot", filters::SetSlotFn);
213 tera.register_function("once", filters::OnceFn);
214
215 Atom {
216 tera,
217 components: ComponentRegistry::new(),
218 context_chain: ContextChain::new(),
219 max_loop_iter: 10000,
220 debug: false,
221 use_parallel: false,
222 }
223 }
224
225 pub fn load_templates(&mut self, glob_pattern: &str) -> std::result::Result<(), Error> {
238 let template_files: Vec<(std::path::PathBuf, Option<String>)> = glob(glob_pattern)
239 .map_err(|e| Error::TemplateLoad {
240 path: glob_pattern.to_string(),
241 message: e.to_string(),
242 })?
243 .filter_map(|p| p.ok())
244 .map(|p| {
245 let name = p
246 .file_name()
247 .and_then(|n| n.to_str())
248 .map(|s| s.to_string())
249 .unwrap_or_default();
250 (p, Some(name))
251 })
252 .collect();
253
254 self.tera
255 .add_template_files(template_files.into_iter())
256 .map_err(|e| Error::TemplateLoad {
257 path: glob_pattern.to_string(),
258 message: e.to_string(),
259 })?;
260 Ok(())
261 }
262
263 pub fn add_template(&mut self, name: &str, content: &str) -> std::result::Result<(), Error> {
277 self.tera
278 .add_raw_template(name, content)
279 .map_err(|e| Error::TemplateParse {
280 name: name.to_string(),
281 message: e.to_string(),
282 })?;
283 Ok(())
284 }
285
286 pub fn register_component(
300 &mut self,
301 path: &str,
302 template: &str,
303 ) -> std::result::Result<(), Error> {
304 self.components.register(path, template)
305 }
306
307 pub fn register_filter<F>(&mut self, name: &str, filter: F)
314 where
315 F: Filter + Send + Sync + 'static,
316 {
317 self.tera.register_filter(name, filter);
318 }
319
320 pub fn register_function<F>(&mut self, name: &str, function: F)
327 where
328 F: Function + Send + Sync + 'static,
329 {
330 self.tera.register_function(name, function);
331 }
332
333 pub fn set_max_loop_iter(&mut self, max: usize) {
337 self.max_loop_iter = max;
338 }
339
340 pub fn set_debug(&mut self, debug: bool) {
344 self.debug = debug;
345 }
346
347 pub fn render(&self, template: &str, context: &Value) -> Result<String, Error> {
366 let mut ctx = Context::from_serialize(context).map_err(|e| Error::Context {
367 message: e.to_string(),
368 })?;
369
370 for (key, value) in self.context_chain.all() {
371 ctx.insert(key, &value);
372 }
373
374 ctx.insert("__atom_components", &self.components.list_components());
375
376 self.tera.render(template, &ctx).map_err(|e| Error::Render {
377 template: template.to_string(),
378 message: e.to_string(),
379 })
380 }
381
382 pub fn render_with_components(
393 &self,
394 template: &str,
395 context: &Value,
396 component_data: &Value,
397 ) -> std::result::Result<String, Error> {
398 let mut ctx = Context::from_serialize(context).map_err(|e| Error::Context {
399 message: e.to_string(),
400 })?;
401
402 if let Some(obj) = component_data.as_object() {
403 for (key, value) in obj {
404 ctx.insert(key, &value);
405 }
406 }
407
408 self.tera.render(template, &ctx).map_err(|e| Error::Render {
409 template: template.to_string(),
410 message: e.to_string(),
411 })
412 }
413
414 pub fn provide(&mut self, key: &str, value: Value) {
437 self.context_chain.provide(key, value);
438 }
439
440 pub fn reload(&mut self) -> std::result::Result<(), Error> {
444 self.tera.full_reload().map_err(|e| Error::TemplateLoad {
445 path: "reload".to_string(),
446 message: e.to_string(),
447 })
448 }
449
450 pub fn template_exists(&self, name: &str) -> bool {
460 self.tera.get_template(name).is_ok()
461 }
462
463 pub fn get_registered_templates(&self) -> Vec<String> {
469 self.tera
470 .get_template_names()
471 .map(|s| s.to_string())
472 .collect()
473 }
474
475 pub fn clear_cache(&mut self) {
479 self.tera.templates.clear();
480 }
481
482 pub fn set_parallel(&mut self, enabled: bool) {
487 self.use_parallel = enabled;
488 }
489
490 pub fn is_parallel(&self) -> bool {
492 self.use_parallel
493 }
494
495 pub fn enable_component_cache(&mut self, enabled: bool) {
499 self.components.enable_cache(enabled);
500 }
501
502 pub fn is_component_cache_enabled(&self) -> bool {
504 self.components.is_cache_enabled()
505 }
506
507 pub fn clear_component_cache(&mut self) {
509 self.components.clear_cache();
510 }
511
512 pub fn component_cache_len(&self) -> usize {
514 self.components.cache_len()
515 }
516
517 #[cfg(feature = "parallel")]
530 pub fn render_many(
531 &self,
532 templates: &[(&str, &Value)],
533 ) -> std::result::Result<Vec<(String, String)>, Error> {
534 use rayon::prelude::*;
535
536 let results: Vec<std::result::Result<(String, String), Error>> = templates
537 .par_iter()
538 .map(|(name, context)| {
539 let rendered = self.render(name, context)?;
540 Ok((name.to_string(), rendered))
541 })
542 .collect();
543
544 let mut output = Vec::new();
545 for result in results {
546 output.push(result?);
547 }
548 Ok(output)
549 }
550
551 #[cfg(not(feature = "parallel"))]
555 pub fn render_many(
556 &self,
557 templates: &[(&str, &Value)],
558 ) -> std::result::Result<Vec<(String, String)>, Error> {
559 let mut results = Vec::new();
560 for (name, context) in templates {
561 let rendered = self.render(name, context)?;
562 results.push((name.to_string(), rendered));
563 }
564 Ok(results)
565 }
566
567 #[cfg(feature = "async")]
589 pub async fn render_async(&self, template: &str, context: &Value) -> Result<String, Error> {
590 let template = template.to_string();
591 let context = context.clone();
592
593 tokio::task::spawn_blocking(move || {
594 let mut tera = Tera::default();
595 tera.register_filter("json_encode", filters::json_encode);
596 tera.render(
597 &template,
598 &Context::from_serialize(&context).map_err(|e| Error::Context {
599 message: e.to_string(),
600 })?,
601 )
602 .map_err(|e| Error::Render {
603 template,
604 message: e.to_string(),
605 })
606 })
607 .await
608 .map_err(|e| Error::Render {
609 template: "async".to_string(),
610 message: e.to_string(),
611 })?
612 }
613
614 #[cfg(feature = "async")]
626 pub async fn render_many_async(
627 &self,
628 templates: &[(&str, &Value)],
629 ) -> std::result::Result<Vec<(String, String)>, Error> {
630 use tokio::task::JoinSet;
631
632 let mut join_set = JoinSet::new();
633
634 for (name, context) in templates {
635 let name = name.to_string();
636 let context = context.clone();
637 let filters = filters::Filters::new();
638
639 join_set.spawn(async move {
640 tokio::task::spawn_blocking(move || {
641 let mut tera = Tera::default();
642 tera.register_filter("json_encode", filters::json_encode);
643 let mut ctx =
644 Context::from_serialize(&context).map_err(|e| Error::Context {
645 message: e.to_string(),
646 })?;
647 tera.render(&name, &ctx)
648 .map(|r| (name, r))
649 .map_err(|e| Error::Render {
650 template: name,
651 message: e.to_string(),
652 })
653 })
654 .await
655 .map_err(|e| Error::Render {
656 template: "async".to_string(),
657 message: e.to_string(),
658 })?
659 });
660 }
661
662 let mut results = Vec::new();
663 while let Some(result) = join_set.join_next().await {
664 results.push(result??);
665 }
666 Ok(results)
667 }
668}
669
670impl Default for Atom {
671 fn default() -> Self {
672 Self::new()
673 }
674}