1#![warn(missing_docs)]
2
3use std::{
4 collections::HashMap,
5 path::{Path, PathBuf},
6 result::Result,
7};
8
9pub type TemplateResult<T> = Result<T, std::io::Error>;
11
12use async_trait::async_trait;
13use handlebars::Handlebars;
14use oak_core::Parser;
15use oak_dejavu::{DejavuLanguage, DejavuLexer, DejavuParser};
16use oak_jinja::{JinjaLanguage, JinjaLexer, JinjaParser};
17use serde::Serialize;
18use walkdir::WalkDir;
19
20#[cfg(feature = "askama")]
22use askama::Template;
23
24#[cfg(feature = "tera")]
25use tera::{Context, Tera};
26
27#[cfg(feature = "liquid")]
28use liquid::{Object, ParserBuilder, Value};
29
30#[cfg(feature = "tinytemplate")]
31use tinytemplate::TinyTemplate;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
35pub enum TemplateEngine {
36 Handlebars,
38 DejaVu,
40 Askama,
42 Jinja2,
44 Tera,
46 Liquid,
48 TinyTemplate,
50 Custom,
52}
53
54impl TemplateEngine {
55 pub fn all() -> &'static [Self] {
57 &[Self::Handlebars, Self::DejaVu, Self::Askama, Self::Jinja2, Self::Tera, Self::Liquid, Self::TinyTemplate, Self::Custom]
58 }
59
60 pub fn display_name(&self) -> &'static str {
62 match self {
63 Self::Handlebars => "Handlebars",
64 Self::DejaVu => "DejaVu",
65 Self::Askama => "Askama",
66 Self::Jinja2 => "Jinja2",
67 Self::Tera => "Tera",
68 Self::Liquid => "Liquid",
69 Self::TinyTemplate => "TinyTemplate",
70 Self::Custom => "自定义",
71 }
72 }
73
74 pub fn description(&self) -> &'static str {
76 match self {
77 Self::Handlebars => "Handlebars 模板引擎,流行的 JavaScript 模板语言",
78 Self::DejaVu => "DejaVu 模板引擎,编译期零成本抽象",
79 Self::Askama => "Askama 模板引擎,基于 Rust 的类型安全模板",
80 Self::Jinja2 => "Jinja2 模板引擎,Python 生态系统中流行的模板语言",
81 Self::Tera => "Tera 模板引擎,受 Jinja2 启发的 Rust 模板引擎",
82 Self::Liquid => "Liquid 模板引擎,Shopify 开发的模板语言",
83 Self::TinyTemplate => "TinyTemplate 模板引擎,轻量级的 Rust 模板引擎",
84 Self::Custom => "自定义模板引擎",
85 }
86 }
87
88 pub fn default_extension(&self) -> &'static str {
90 match self {
91 Self::Handlebars => "hbs",
92 Self::DejaVu => "dejavu",
93 Self::Askama => "html",
94 Self::Jinja2 => "jinja2",
95 Self::Tera => "tera",
96 Self::Liquid => "liquid",
97 Self::TinyTemplate => "tt",
98 Self::Custom => "tpl",
99 }
100 }
101}
102
103pub trait TemplateContext: std::fmt::Debug + Clone + std::any::Any {
106 fn as_any(&self) -> &dyn std::any::Any;
108}
109
110#[async_trait]
112pub trait TemplateRenderer: Send + Sync {
113 fn render(&self, template_name: &str, context: &serde_json::Value) -> TemplateResult<String>;
115
116 async fn render_async(&self, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
118 Ok(self.render(template_name, context)?)
119 }
120
121 fn register_template(&mut self, name: &str, content: &str) -> TemplateResult<()>;
123
124 fn register_template_file(&mut self, name: &str, path: &Path) -> TemplateResult<()>;
126
127 fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> TemplateResult<()>;
129}
130
131pub struct HandlebarsRenderer {
133 handlebars: Handlebars<'static>,
134}
135
136impl HandlebarsRenderer {
137 pub fn new() -> Self {
139 let mut handlebars = Handlebars::new();
140 handlebars.set_strict_mode(false);
141 Self { handlebars }
142 }
143
144 pub fn inner(&self) -> &Handlebars<'static> {
146 &self.handlebars
147 }
148
149 pub fn inner_mut(&mut self) -> &mut Handlebars<'static> {
151 &mut self.handlebars
152 }
153}
154
155impl Default for HandlebarsRenderer {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161#[async_trait]
162impl TemplateRenderer for HandlebarsRenderer {
163 fn render(&self, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
164 self.handlebars.render(template_name, context).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to render template '{}': {}", template_name, e)))
165 }
166
167 fn register_template(&mut self, name: &str, content: &str) -> TemplateResult<()> {
168 self.handlebars.register_template_string(name, content).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to register template '{}': {}", name, e)))?;
169 Ok(())
170 }
171
172 fn register_template_file(&mut self, name: &str, path: &Path) -> TemplateResult<()> {
173 self.handlebars.register_template_file(name, path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to register template file '{}': {}", path.display(), e)))?;
174 Ok(())
175 }
176
177 fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> TemplateResult<()> {
178 let dir_path = dir;
179 let ext = extension.unwrap_or("hbs");
180
181 for entry in WalkDir::new(dir_path) {
182 let entry = entry?;
183 let path = entry.path();
184
185 if path.is_file() {
186 if let Some(file_ext) = path.extension() {
187 if file_ext == ext {
188 let relative_path = path.strip_prefix(dir_path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
189 let template_name = relative_path.with_extension("").to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");
190
191 self.register_template_file(&template_name, path)?;
192 }
193 }
194 }
195 }
196
197 Ok(())
198 }
199}
200
201pub struct Jinja2Renderer {
203 templates: HashMap<String, String>,
204 language: JinjaLanguage,
205}
206
207impl Jinja2Renderer {
208 pub fn new() -> Self {
210 Self { templates: HashMap::new(), language: JinjaLanguage::default() }
211 }
212
213 fn render_content(&self, content: &str, context: &serde_json::Value) -> TemplateResult<String> {
215 let _lexer = JinjaLexer::new(&self.language);
218 let parser = JinjaParser::new(&self.language);
219
220 let mut session = oak_core::parser::ParseSession::<JinjaLanguage>::new(16);
222
223 let _output = parser.parse(content, &[], &mut session);
225
226 let mut result = content.to_string();
229
230 if let serde_json::Value::Object(map) = context {
232 for (key, value) in map {
233 let placeholder = format!("{{{{ {} }}}}", key);
234 let value_str = match value {
235 serde_json::Value::String(s) => s.clone(),
236 serde_json::Value::Number(n) => n.to_string(),
237 serde_json::Value::Bool(b) => b.to_string(),
238 _ => value.to_string(),
239 };
240 result = result.replace(&placeholder, &value_str);
241 }
242 }
243
244 Ok(result)
245 }
246}
247
248impl Default for Jinja2Renderer {
249 fn default() -> Self {
250 Self::new()
251 }
252}
253
254#[async_trait]
255impl TemplateRenderer for Jinja2Renderer {
256 fn render(&self, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
257 let template_content = self.templates.get(template_name).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, format!("Template '{}' not found", template_name)))?;
258
259 self.render_content(template_content, context)
260 }
261
262 fn register_template(&mut self, name: &str, content: &str) -> TemplateResult<()> {
263 self.templates.insert(name.to_string(), content.to_string());
264 Ok(())
265 }
266
267 fn register_template_file(&mut self, name: &str, path: &Path) -> TemplateResult<()> {
268 let content = std::fs::read_to_string(path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to read template file '{}': {}", path.display(), e)))?;
269 self.register_template(name, &content)
270 }
271
272 fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> TemplateResult<()> {
273 let dir_path = dir;
274 let ext = extension.unwrap_or("jinja2");
275
276 for entry in WalkDir::new(dir_path) {
277 let entry = entry?;
278 let path = entry.path();
279
280 if path.is_file() {
281 if let Some(file_ext) = path.extension() {
282 if file_ext == ext {
283 let relative_path = path.strip_prefix(dir_path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
284 let template_name = relative_path.with_extension("").to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");
285
286 self.register_template_file(&template_name, path)?;
287 }
288 }
289 }
290 }
291
292 Ok(())
293 }
294}
295
296pub struct DejaVuRenderer {
298 templates: HashMap<String, String>,
299 language: DejavuLanguage,
300}
301
302impl DejaVuRenderer {
303 pub fn new() -> Self {
305 let language = DejavuLanguage::default();
307
308 Self { templates: HashMap::new(), language }
309 }
310
311 pub fn with_template_config(template_config: oak_dejavu::language::TemplateConfig) -> Self {
313 let language = DejavuLanguage { syntax_mode: oak_dejavu::language::SyntaxMode::Template, template: template_config };
314
315 Self { templates: HashMap::new(), language }
316 }
317
318 fn render_content(&self, content: &str, context: &serde_json::Value) -> TemplateResult<String> {
320 let lexer = DejavuLexer::new(&self.language);
322 let parser = DejavuParser::new(&self.language);
323
324 let mut session = oak_core::parser::ParseSession::<DejavuLanguage>::new(16);
326
327 let _parse_result = parser.parse(content, &[], &mut session);
329
330 let mut result = content.to_string();
336
337 if let serde_json::Value::Object(map) = context {
339 for (key, value) in map {
340 let placeholder = format!("{{{{ {} }}}}", key);
341 let value_str = match value {
342 serde_json::Value::String(s) => s.clone(),
343 serde_json::Value::Number(n) => n.to_string(),
344 serde_json::Value::Bool(b) => b.to_string(),
345 _ => value.to_string(),
346 };
347 result = result.replace(&placeholder, &value_str);
348 }
349 }
350
351 Ok(result)
352 }
353}
354
355impl Default for DejaVuRenderer {
356 fn default() -> Self {
357 Self::new()
358 }
359}
360
361#[async_trait]
362impl TemplateRenderer for DejaVuRenderer {
363 fn render(&self, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
364 let template_content = self.templates.get(template_name).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, format!("Template '{}' not found", template_name)))?;
365
366 self.render_content(template_content, context)
367 }
368
369 fn register_template(&mut self, name: &str, content: &str) -> TemplateResult<()> {
370 self.templates.insert(name.to_string(), content.to_string());
371 Ok(())
372 }
373
374 fn register_template_file(&mut self, name: &str, path: &Path) -> TemplateResult<()> {
375 let content = std::fs::read_to_string(path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to read template file '{}': {}", path.display(), e)))?;
376 self.register_template(name, &content)
377 }
378
379 fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> TemplateResult<()> {
380 let dir_path = dir;
381 let ext = extension.unwrap_or("dejavu");
382
383 for entry in WalkDir::new(dir_path) {
384 let entry = entry?;
385 let path = entry.path();
386
387 if path.is_file() {
388 if let Some(file_ext) = path.extension() {
389 if file_ext == ext || file_ext == "doki" {
390 let relative_path = path.strip_prefix(dir_path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
391 let template_name = relative_path.with_extension("").to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");
392
393 self.register_template_file(&template_name, path)?;
394 }
395 }
396 }
397 }
398
399 Ok(())
400 }
401}
402
403#[cfg(feature = "tera")]
405pub struct TeraRenderer {
406 tera: Tera,
407}
408
409#[cfg(feature = "tera")]
410impl TeraRenderer {
411 pub fn new() -> Self {
413 let tera = Tera::default();
414 Self { tera }
415 }
416
417 pub fn inner(&self) -> &Tera {
419 &self.tera
420 }
421
422 pub fn inner_mut(&mut self) -> &mut Tera {
424 &mut self.tera
425 }
426}
427
428#[cfg(feature = "tera")]
429impl Default for TeraRenderer {
430 fn default() -> Self {
431 Self::new()
432 }
433}
434
435#[cfg(feature = "tera")]
436#[async_trait]
437impl TemplateRenderer for TeraRenderer {
438 fn render(&self, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
439 let mut tera_context = Context::new();
440
441 if let serde_json::Value::Object(map) = context {
443 for (key, value) in map {
444 match value {
445 serde_json::Value::String(s) => tera_context.insert(key, s),
446 serde_json::Value::Number(n) => {
447 if let Some(i) = n.as_i64() {
448 tera_context.insert(key, &i);
449 }
450 else if let Some(f) = n.as_f64() {
451 tera_context.insert(key, &f);
452 }
453 }
454 serde_json::Value::Bool(b) => tera_context.insert(key, b),
455 serde_json::Value::Array(arr) => {
456 let mut vec = Vec::new();
457 for item in arr {
458 vec.push(item.clone());
459 }
460 tera_context.insert(key, &vec);
461 }
462 serde_json::Value::Object(obj) => {
463 let mut map = std::collections::HashMap::new();
464 for (k, v) in obj {
465 map.insert(k.clone(), v.clone());
466 }
467 tera_context.insert(key, &map);
468 }
469 _ => {}
470 }
471 }
472 }
473
474 self.tera.render(template_name, &tera_context).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to render template '{}': {}", template_name, e)))
475 }
476
477 fn register_template(&mut self, name: &str, content: &str) -> TemplateResult<()> {
478 self.tera.add_raw_template(name, content).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to register template '{}': {}", name, e)))?;
479 Ok(())
480 }
481
482 fn register_template_file(&mut self, name: &str, path: &Path) -> TemplateResult<()> {
483 let content = std::fs::read_to_string(path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to read template file '{}': {}", path.display(), e)))?;
484 self.register_template(name, &content)
485 }
486
487 fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> TemplateResult<()> {
488 let dir_path = dir;
489 let ext = extension.unwrap_or("tera");
490
491 for entry in WalkDir::new(dir_path) {
492 let entry = entry?;
493 let path = entry.path();
494
495 if path.is_file() {
496 if let Some(file_ext) = path.extension() {
497 if file_ext == ext {
498 let relative_path = path.strip_prefix(dir_path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
499 let template_name = relative_path.with_extension("").to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");
500
501 self.register_template_file(&template_name, path)?;
502 }
503 }
504 }
505 }
506
507 Ok(())
508 }
509}
510
511#[cfg(feature = "liquid")]
513pub struct LiquidRenderer {
514 parser: liquid::Parser,
515 templates: HashMap<String, String>,
516}
517
518#[cfg(feature = "liquid")]
519impl LiquidRenderer {
520 pub fn new() -> Self {
522 let parser = ParserBuilder::with_stdlib().build().unwrap();
523 Self { parser, templates: HashMap::new() }
524 }
525
526 pub fn inner(&self) -> &liquid::Parser {
528 &self.parser
529 }
530}
531
532#[cfg(feature = "liquid")]
533impl Default for LiquidRenderer {
534 fn default() -> Self {
535 Self::new()
536 }
537}
538
539#[cfg(feature = "liquid")]
540#[async_trait]
541impl TemplateRenderer for LiquidRenderer {
542 fn render(&self, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
543 let template_content = self.templates.get(template_name).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, format!("Template '{}' not found", template_name)))?;
544
545 let template = self.parser.parse(template_content).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to parse template '{}': {}", template_name, e)))?;
546
547 let liquid_context = self.convert_to_liquid_context(context);
548
549 template.render(&liquid_context).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to render template '{}': {}", template_name, e)))
550 }
551
552 fn register_template(&mut self, name: &str, content: &str) -> TemplateResult<()> {
553 self.templates.insert(name.to_string(), content.to_string());
554 Ok(())
555 }
556
557 fn register_template_file(&mut self, name: &str, path: &Path) -> TemplateResult<()> {
558 let content = std::fs::read_to_string(path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to read template file '{}': {}", path.display(), e)))?;
559 self.register_template(name, &content)
560 }
561
562 fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> TemplateResult<()> {
563 let dir_path = dir;
564 let ext = extension.unwrap_or("liquid");
565
566 for entry in WalkDir::new(dir_path) {
567 let entry = entry?;
568 let path = entry.path();
569
570 if path.is_file() {
571 if let Some(file_ext) = path.extension() {
572 if file_ext == ext {
573 let relative_path = path.strip_prefix(dir_path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
574 let template_name = relative_path.with_extension("").to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");
575
576 self.register_template_file(&template_name, path)?;
577 }
578 }
579 }
580 }
581
582 Ok(())
583 }
584}
585
586#[cfg(feature = "liquid")]
587impl LiquidRenderer {
588 fn convert_to_liquid_context(&self, value: &serde_json::Value) -> Object {
590 let mut object = Object::new();
591
592 match value {
593 serde_json::Value::Object(map) => {
594 for (key, val) in map {
595 object.insert(key.clone(), self.convert_to_liquid_value(val));
596 }
597 }
598 _ => {
599 object.insert("value".to_string(), self.convert_to_liquid_value(value));
601 }
602 }
603
604 object
605 }
606
607 fn convert_to_liquid_value(&self, value: &serde_json::Value) -> Value {
609 match value {
610 serde_json::Value::String(s) => Value::scalar(s.clone()),
611 serde_json::Value::Number(n) => {
612 if let Some(i) = n.as_i64() {
613 Value::scalar(i)
614 }
615 else if let Some(f) = n.as_f64() {
616 Value::scalar(f)
617 }
618 else {
619 Value::Nil
620 }
621 }
622 serde_json::Value::Bool(b) => Value::scalar(*b),
623 serde_json::Value::Array(arr) => {
624 let mut vec = Vec::new();
625 for item in arr {
626 vec.push(self.convert_to_liquid_value(item));
627 }
628 Value::array(vec)
629 }
630 serde_json::Value::Object(obj) => {
631 let mut map = Object::new();
632 for (k, v) in obj {
633 map.insert(k.clone(), self.convert_to_liquid_value(v));
634 }
635 Value::Object(map)
636 }
637 serde_json::Value::Null => Value::Nil,
638 }
639 }
640}
641
642#[cfg(feature = "tinytemplate")]
644pub struct TinyTemplateRenderer {
645 tiny_template: TinyTemplate,
646}
647
648#[cfg(feature = "tinytemplate")]
649impl TinyTemplateRenderer {
650 pub fn new() -> Self {
652 let mut tiny_template = TinyTemplate::new();
653 tiny_template.set_default_formatter(&tinytemplate::format_unescaped);
654 Self { tiny_template }
655 }
656
657 pub fn inner(&self) -> &TinyTemplate {
659 &self.tiny_template
660 }
661
662 pub fn inner_mut(&mut self) -> &mut TinyTemplate {
664 &mut self.tiny_template
665 }
666}
667
668#[cfg(feature = "tinytemplate")]
669impl Default for TinyTemplateRenderer {
670 fn default() -> Self {
671 Self::new()
672 }
673}
674
675#[cfg(feature = "tinytemplate")]
676#[async_trait]
677impl TemplateRenderer for TinyTemplateRenderer {
678 fn render(&self, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
679 self.tiny_template.render(template_name, context).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to render template '{}': {}", template_name, e)))
681 }
682
683 fn register_template(&mut self, name: &str, content: &str) -> TemplateResult<()> {
684 self.tiny_template.add_template(name, content).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to register template '{}': {}", name, e)))?;
685 Ok(())
686 }
687
688 fn register_template_file(&mut self, name: &str, path: &Path) -> TemplateResult<()> {
689 let content = std::fs::read_to_string(path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to read template file '{}': {}", path.display(), e)))?;
690 self.register_template(name, &content)
691 }
692
693 fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> TemplateResult<()> {
694 let dir_path = dir;
695 let ext = extension.unwrap_or("tt");
696
697 for entry in WalkDir::new(dir_path) {
698 let entry = entry?;
699 let path = entry.path();
700
701 if path.is_file() {
702 if let Some(file_ext) = path.extension() {
703 if file_ext == ext {
704 let relative_path = path.strip_prefix(dir_path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
705 let template_name = relative_path.with_extension("").to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");
706
707 self.register_template_file(&template_name, path)?;
708 }
709 }
710 }
711 }
712
713 Ok(())
714 }
715}
716
717#[cfg(feature = "askama")]
720pub struct AskamaRenderer {
721 }
724
725#[cfg(feature = "askama")]
726impl AskamaRenderer {
727 pub fn new() -> Self {
729 Self {}
730 }
731}
732
733#[cfg(feature = "askama")]
734impl Default for AskamaRenderer {
735 fn default() -> Self {
736 Self::new()
737 }
738}
739
740#[cfg(feature = "askama")]
741#[async_trait]
742impl TemplateRenderer for AskamaRenderer {
743 fn render(&self, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
744 Err(std::io::Error::new(std::io::ErrorKind::Unsupported, "Askama renderer doesn't support dynamic template rendering"))
747 }
748
749 fn register_template(&mut self, name: &str, content: &str) -> TemplateResult<()> {
750 Ok(())
752 }
753
754 fn register_template_file(&mut self, name: &str, path: &Path) -> TemplateResult<()> {
755 Ok(())
757 }
758
759 fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> TemplateResult<()> {
760 Ok(())
762 }
763}
764
765pub struct TemplateManager {
767 renderers: HashMap<TemplateEngine, Box<dyn TemplateRenderer>>,
768 template_dirs: Vec<PathBuf>,
769}
770
771impl TemplateManager {
772 pub fn new() -> Self {
774 let mut renderers = HashMap::new();
775
776 renderers.insert(TemplateEngine::Handlebars, Box::new(HandlebarsRenderer::new()) as Box<dyn TemplateRenderer>);
777
778 renderers.insert(TemplateEngine::Jinja2, Box::new(Jinja2Renderer::new()) as Box<dyn TemplateRenderer>);
779
780 renderers.insert(TemplateEngine::DejaVu, Box::new(DejaVuRenderer::new()) as Box<dyn TemplateRenderer>);
782
783 #[cfg(feature = "tera")]
785 {
786 renderers.insert(TemplateEngine::Tera, Box::new(TeraRenderer::new()) as Box<dyn TemplateRenderer>);
787 }
788
789 #[cfg(feature = "liquid")]
791 {
792 renderers.insert(TemplateEngine::Liquid, Box::new(LiquidRenderer::new()) as Box<dyn TemplateRenderer>);
793 }
794
795 #[cfg(feature = "tinytemplate")]
797 {
798 renderers.insert(TemplateEngine::TinyTemplate, Box::new(TinyTemplateRenderer::new()) as Box<dyn TemplateRenderer>);
799 }
800
801 #[cfg(feature = "askama")]
803 {
804 renderers.insert(TemplateEngine::Askama, Box::new(AskamaRenderer::new()) as Box<dyn TemplateRenderer>);
805 }
806
807 Self { renderers, template_dirs: Vec::new() }
808 }
809
810 pub fn register_renderer(&mut self, engine: TemplateEngine, renderer: Box<dyn TemplateRenderer>) {
812 self.renderers.insert(engine, renderer);
813 }
814
815 pub fn register_template(&mut self, engine: TemplateEngine, name: &str, content: &str) -> TemplateResult<()> {
817 let renderer = self.renderers.get_mut(&engine).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, format!("Template engine '{:?}' not registered", engine)))?;
818 renderer.register_template(name, content)
819 }
820
821 pub fn register_template_file(&mut self, engine: TemplateEngine, name: &str, path: &Path) -> TemplateResult<()> {
823 let renderer = self.renderers.get_mut(&engine).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, format!("Template engine '{:?}' not registered", engine)))?;
824 renderer.register_template_file(name, path)
825 }
826
827 pub fn add_template_dir<P: AsRef<Path>>(&mut self, dir: P) {
829 self.template_dirs.push(dir.as_ref().to_path_buf());
830 }
831
832 pub fn load_templates(&mut self, engine: TemplateEngine) -> TemplateResult<()> {
834 let ext = engine.default_extension();
835 let dirs: Vec<_> = self.template_dirs.clone();
836
837 if let Some(renderer) = self.renderers.get_mut(&engine) {
838 for dir in dirs {
839 if dir.exists() {
840 renderer.register_templates_from_dir(dir.as_path(), Some(ext))?;
841 }
842 }
843 }
844 Ok(())
845 }
846
847 pub fn render(&self, engine: TemplateEngine, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
849 let renderer = self.renderers.get(&engine).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, format!("Template engine '{:?}' not registered", engine)))?;
850
851 renderer.render(template_name, context)
852 }
853
854 pub async fn render_async(&self, engine: TemplateEngine, template_name: &str, context: &serde_json::Value) -> TemplateResult<String> {
856 let renderer = self.renderers.get(&engine).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, format!("Template engine '{:?}' not registered", engine)))?;
857
858 renderer.render_async(template_name, context).await
859 }
860}
861
862impl Default for TemplateManager {
863 fn default() -> Self {
864 Self::new()
865 }
866}
867
868pub trait ToJsonValue {
870 fn to_json_value(&self) -> serde_json::Value;
872}
873
874impl ToJsonValue for String {
875 fn to_json_value(&self) -> serde_json::Value {
876 serde_json::Value::String(self.clone())
877 }
878}
879
880impl ToJsonValue for &str {
881 fn to_json_value(&self) -> serde_json::Value {
882 serde_json::Value::String(self.to_string())
883 }
884}
885
886impl ToJsonValue for i64 {
887 fn to_json_value(&self) -> serde_json::Value {
888 serde_json::Value::Number(serde_json::Number::from(*self))
889 }
890}
891
892impl ToJsonValue for i32 {
893 fn to_json_value(&self) -> serde_json::Value {
894 serde_json::Value::Number(serde_json::Number::from(*self))
895 }
896}
897
898impl ToJsonValue for f64 {
899 fn to_json_value(&self) -> serde_json::Value {
900 serde_json::Value::Number(serde_json::Number::from_f64(*self).unwrap())
901 }
902}
903
904impl ToJsonValue for bool {
905 fn to_json_value(&self) -> serde_json::Value {
906 serde_json::Value::Bool(*self)
907 }
908}
909
910impl ToJsonValue for usize {
911 fn to_json_value(&self) -> serde_json::Value {
912 serde_json::Value::Number(serde_json::Number::from(*self))
913 }
914}
915
916impl<T: ToJsonValue> ToJsonValue for Vec<T> {
917 fn to_json_value(&self) -> serde_json::Value {
918 serde_json::Value::Array(self.iter().map(|v| v.to_json_value()).collect())
919 }
920}
921
922impl<K: ToJsonValue, V: ToJsonValue> ToJsonValue for std::collections::HashMap<K, V> {
923 fn to_json_value(&self) -> serde_json::Value {
924 let mut map = serde_json::Map::new();
925 for (k, v) in self {
926 if let serde_json::Value::String(key) = k.to_json_value() {
927 map.insert(key, v.to_json_value());
928 }
929 }
930 serde_json::Value::Object(map)
931 }
932}
933
934impl<T: ToJsonValue> ToJsonValue for Option<T> {
935 fn to_json_value(&self) -> serde_json::Value {
936 match self {
937 Some(v) => v.to_json_value(),
938 None => serde_json::Value::Null,
939 }
940 }
941}
942
943impl ToJsonValue for PathBuf {
944 fn to_json_value(&self) -> serde_json::Value {
945 serde_json::Value::String(self.to_string_lossy().to_string())
946 }
947}