- Migrate individual modules to independent crates - Refactor dependencies for improved modularity - Update build system and testing infrastructure - Update documentation to reflect new structure
311 lines
9.3 KiB
Rust
311 lines
9.3 KiB
Rust
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::io;
|
|
use std::path::Path;
|
|
use tera::{Context, Tera};
|
|
|
|
/// A builder for creating and rendering templates using the Tera template engine.
|
|
#[derive(Clone)]
|
|
pub struct TemplateBuilder {
|
|
template_path: String,
|
|
context: Context,
|
|
tera: Option<Tera>,
|
|
}
|
|
|
|
impl TemplateBuilder {
|
|
/// Creates a new TemplateBuilder with the specified template path.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `template_path` - The path to the template file
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new TemplateBuilder instance
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// use sal_text::TemplateBuilder;
|
|
///
|
|
/// let builder = TemplateBuilder::open("templates/example.html");
|
|
/// ```
|
|
pub fn open<P: AsRef<Path>>(template_path: P) -> io::Result<Self> {
|
|
let path_str = template_path.as_ref().to_string_lossy().to_string();
|
|
|
|
// Verify the template file exists
|
|
if !Path::new(&path_str).exists() {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::NotFound,
|
|
format!("Template file not found: {}", path_str),
|
|
));
|
|
}
|
|
|
|
Ok(Self {
|
|
template_path: path_str,
|
|
context: Context::new(),
|
|
tera: None,
|
|
})
|
|
}
|
|
|
|
/// Adds a variable to the template context.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `name` - The name of the variable to add
|
|
/// * `value` - The value to associate with the variable
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The builder instance for method chaining
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```no_run
|
|
/// use sal_text::TemplateBuilder;
|
|
///
|
|
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
/// let builder = TemplateBuilder::open("templates/example.html")?
|
|
/// .add_var("title", "Hello World")
|
|
/// .add_var("username", "John Doe");
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
pub fn add_var<S, V>(mut self, name: S, value: V) -> Self
|
|
where
|
|
S: AsRef<str>,
|
|
V: serde::Serialize,
|
|
{
|
|
self.context.insert(name.as_ref(), &value);
|
|
self
|
|
}
|
|
|
|
/// Adds multiple variables to the template context from a HashMap.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `vars` - A HashMap containing variable names and values
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The builder instance for method chaining
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```no_run
|
|
/// use sal_text::TemplateBuilder;
|
|
/// use std::collections::HashMap;
|
|
///
|
|
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
/// let mut vars = HashMap::new();
|
|
/// vars.insert("title", "Hello World");
|
|
/// vars.insert("username", "John Doe");
|
|
///
|
|
/// let builder = TemplateBuilder::open("templates/example.html")?
|
|
/// .add_vars(vars);
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
pub fn add_vars<S, V>(mut self, vars: HashMap<S, V>) -> Self
|
|
where
|
|
S: AsRef<str>,
|
|
V: serde::Serialize,
|
|
{
|
|
for (name, value) in vars {
|
|
self.context.insert(name.as_ref(), &value);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Initializes the Tera template engine with the template file.
|
|
///
|
|
/// This method is called automatically by render() if not called explicitly.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The builder instance for method chaining
|
|
fn initialize_tera(&mut self) -> Result<(), tera::Error> {
|
|
if self.tera.is_none() {
|
|
// Create a new Tera instance with just this template
|
|
let mut tera = Tera::default();
|
|
|
|
// Read the template content
|
|
let template_content = fs::read_to_string(&self.template_path)
|
|
.map_err(|e| tera::Error::msg(format!("Failed to read template file: {}", e)))?;
|
|
|
|
// Add the template to Tera
|
|
let template_name = Path::new(&self.template_path)
|
|
.file_name()
|
|
.and_then(|n| n.to_str())
|
|
.unwrap_or("template");
|
|
|
|
tera.add_raw_template(template_name, &template_content)?;
|
|
self.tera = Some(tera);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Renders the template with the current context.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The rendered template as a string
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```no_run
|
|
/// use sal_text::TemplateBuilder;
|
|
///
|
|
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
/// let result = TemplateBuilder::open("templates/example.html")?
|
|
/// .add_var("title", "Hello World")
|
|
/// .add_var("username", "John Doe")
|
|
/// .render()?;
|
|
///
|
|
/// println!("Rendered template: {}", result);
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
pub fn render(&mut self) -> Result<String, tera::Error> {
|
|
// Initialize Tera if not already done
|
|
self.initialize_tera()?;
|
|
|
|
// Get the template name
|
|
let template_name = Path::new(&self.template_path)
|
|
.file_name()
|
|
.and_then(|n| n.to_str())
|
|
.unwrap_or("template");
|
|
|
|
// Render the template
|
|
let tera = self.tera.as_ref().unwrap();
|
|
tera.render(template_name, &self.context)
|
|
}
|
|
|
|
/// Renders the template and writes the result to a file.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `output_path` - The path where the rendered template should be written
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Result indicating success or failure
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```no_run
|
|
/// use sal_text::TemplateBuilder;
|
|
///
|
|
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
/// TemplateBuilder::open("templates/example.html")?
|
|
/// .add_var("title", "Hello World")
|
|
/// .add_var("username", "John Doe")
|
|
/// .render_to_file("output.html")?;
|
|
/// Ok(())
|
|
/// }
|
|
/// ```
|
|
pub fn render_to_file<P: AsRef<Path>>(&mut self, output_path: P) -> io::Result<()> {
|
|
let rendered = self.render().map_err(|e| {
|
|
io::Error::new(
|
|
io::ErrorKind::Other,
|
|
format!("Template rendering error: {}", e),
|
|
)
|
|
})?;
|
|
|
|
fs::write(output_path, rendered)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::Write;
|
|
use tempfile::NamedTempFile;
|
|
|
|
#[test]
|
|
fn test_template_rendering() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Create a temporary template file
|
|
let temp_file = NamedTempFile::new()?;
|
|
let template_content = "Hello, {{ name }}! Welcome to {{ place }}.\n";
|
|
fs::write(temp_file.path(), template_content)?;
|
|
|
|
// Create a template builder and add variables
|
|
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
|
builder = builder.add_var("name", "John").add_var("place", "Rust");
|
|
|
|
// Render the template
|
|
let result = builder.render()?;
|
|
assert_eq!(result, "Hello, John! Welcome to Rust.\n");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_template_with_multiple_vars() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Create a temporary template file
|
|
let temp_file = NamedTempFile::new()?;
|
|
let template_content = "{% if show_greeting %}Hello, {{ name }}!{% endif %}\n{% for item in items %}{{ item }}{% if not loop.last %}, {% endif %}{% endfor %}\n";
|
|
fs::write(temp_file.path(), template_content)?;
|
|
|
|
// Create a template builder and add variables
|
|
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
|
|
|
// Add variables including a boolean and a vector
|
|
builder = builder
|
|
.add_var("name", "Alice")
|
|
.add_var("show_greeting", true)
|
|
.add_var("items", vec!["apple", "banana", "cherry"]);
|
|
|
|
// Render the template
|
|
let result = builder.render()?;
|
|
assert_eq!(result, "Hello, Alice!\napple, banana, cherry\n");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_template_with_hashmap_vars() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Create a temporary template file
|
|
let mut temp_file = NamedTempFile::new()?;
|
|
writeln!(temp_file, "{{{{ greeting }}}}, {{{{ name }}}}!")?;
|
|
temp_file.flush()?;
|
|
|
|
// Create a HashMap of variables
|
|
let mut vars = HashMap::new();
|
|
vars.insert("greeting", "Hi");
|
|
vars.insert("name", "Bob");
|
|
|
|
// Create a template builder and add variables from HashMap
|
|
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
|
builder = builder.add_vars(vars);
|
|
|
|
// Render the template
|
|
let result = builder.render()?;
|
|
assert_eq!(result, "Hi, Bob!\n");
|
|
|
|
Ok(())
|
|
}
|
|
#[test]
|
|
fn test_render_to_file() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Create a temporary template file
|
|
let temp_file = NamedTempFile::new()?;
|
|
let template_content = "{{ message }}\n";
|
|
fs::write(temp_file.path(), template_content)?;
|
|
|
|
// Create an output file
|
|
let output_file = NamedTempFile::new()?;
|
|
|
|
// Create a template builder, add a variable, and render to file
|
|
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
|
builder = builder.add_var("message", "This is a test");
|
|
builder.render_to_file(output_file.path())?;
|
|
|
|
// Read the output file and verify its contents
|
|
let content = fs::read_to_string(output_file.path())?;
|
|
assert_eq!(content, "This is a test\n");
|
|
|
|
Ok(())
|
|
}
|
|
}
|