1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//! Tera template environment setup and configuration
//!
//! This module provides utilities for building and configuring Tera template
//! engines with glob support for includes and macros. It sets up the template
//! environment with all necessary filters and functions.
//!
//! ## Features
//!
//! - **Glob Support**: Enable `{% include %}` and `{% import %}` across template directories
//! - **Auto-escape Control**: Disable auto-escaping for code generation (literal output)
//! - **Filter Registration**: Automatically register all text transformation filters
//! - **Minimal Mode**: Build lightweight Tera instances for ad-hoc string rendering
//!
//! ## Examples
//!
//! ### Building Tera with Glob Support
//!
//! ```rust,no_run
//! use crate::tera_env::build_tera_with_glob;
//! use std::path::Path;
//!
//! # fn main() -> crate::utils::error::Result<()> {
//! let templates_dir = Path::new("./templates");
//! let mut tera = build_tera_with_glob(templates_dir)?;
//!
//! // Now templates can use {% include %} and {% import %}
//! let mut ctx = tera::Context::new();
//! ctx.insert("name", "World");
//! let result = tera.render("main.tmpl", &ctx)?;
//! # Ok(())
//! # }
//! ```
//!
//! ### Building Minimal Tera Instance
//!
//! ```rust
//! use crate::tera_env::build_tera_minimal;
//!
//! # fn main() -> crate::utils::error::Result<()> {
//! let mut tera = build_tera_minimal()?;
//!
//! // Use for ad-hoc string rendering
//! let mut ctx = tera::Context::new();
//! ctx.insert("name", "hello_world");
//! let result = tera.render_str("{{ name | pascal }}", &ctx)?;
//! assert_eq!(result, "HelloWorld");
//! # Ok(())
//! # }
//! ```
use crate::utils::error::Result;
use std::path::Path;
use tera::Tera;
use crate::register::register_all;
/// Build a Tera instance with template glob support for includes and macros.
///
/// This enables `{% include %}` and `{% import %}` directives across the
/// entire templates directory tree.
pub fn build_tera_with_glob(templates_dir: &Path) -> Result<Tera> {
let glob = format!("{}/**/*.tmpl", templates_dir.display());
// Tera::new() will return an error if no templates match the glob,
// but we want to allow empty template directories during development
let mut tera = Tera::new(&glob).unwrap_or_else(|_| {
// Create empty Tera instance if no templates found
Tera::default()
});
// Disable auto-escape for code generation (we want literal output)
tera.autoescape_on(vec![]);
// Register all text transformation filters
register_all(&mut tera);
Ok(tera)
}
/// Build a minimal Tera instance for ad-hoc string rendering.
///
/// Use this when you need to render individual strings without
/// template file dependencies.
pub fn build_tera_minimal() -> Result<Tera> {
let mut tera = Tera::default();
tera.autoescape_on(vec![]);
register_all(&mut tera);
Ok(tera)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_build_tera_with_glob() -> Result<()> {
let temp_dir = TempDir::new()?;
let templates_dir = temp_dir.path().join("templates");
fs::create_dir_all(&templates_dir)?;
// Create a test template
let template_file = templates_dir.join("test.tmpl");
fs::write(&template_file, "Hello {{ name }}!")?;
let tera = build_tera_with_glob(&templates_dir)?;
// Should be able to render the template
let mut ctx = tera::Context::new();
ctx.insert("name", "World");
let result = tera.render("test.tmpl", &ctx)?;
assert_eq!(result, "Hello World!");
Ok(())
}
#[test]
fn test_build_tera_empty_directory() -> Result<()> {
let temp_dir = TempDir::new()?;
let templates_dir = temp_dir.path().join("templates");
fs::create_dir_all(&templates_dir)?;
// Empty directory should not cause an error
let mut tera = build_tera_with_glob(&templates_dir)?;
// Should still have filters registered
let mut ctx = tera::Context::new();
ctx.insert("name", "hello_world");
let result = tera.render_str("{{ name | pascal }}", &ctx)?;
assert_eq!(result, "HelloWorld");
Ok(())
}
#[test]
fn test_build_tera_minimal() -> Result<()> {
let mut tera = build_tera_minimal()?;
// Should have filters registered
let mut ctx = tera::Context::new();
ctx.insert("name", "hello_world");
let result = tera.render_str("{{ name | snake }}", &ctx)?;
assert_eq!(result, "hello_world");
Ok(())
}
#[test]
fn test_autoescape_disabled() -> Result<()> {
let mut tera = build_tera_minimal()?;
// HTML should not be escaped
let mut ctx = tera::Context::new();
ctx.insert("html", "<script>alert('xss')</script>");
let result = tera.render_str("{{ html }}", &ctx)?;
assert_eq!(result, "<script>alert('xss')</script>");
Ok(())
}
}