This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
rhaj/tera_factory/examples/hot_reload/main.rs
Timur Gordon 22032f329a feat(tera_factory): Implement hot reload example for Tera templates with Rhai
This commit adds a comprehensive hot reload example that demonstrates how to use the rhai_system for dynamic template rendering with Tera. Key improvements include:

- Refactor the example to use external script files instead of hardcoded Rhai code
- Implement proper module imports using the BasePathModuleResolver approach
- Fix template rendering by using keyword arguments in Tera function calls
- Add support for hot reloading both main and utility scripts
- Remove unnecessary output file generation to keep the example clean
- Fix compatibility issues with Rhai functions (avoiding to_string with parameters)

This example showcases how changes to Rhai scripts are automatically detected and applied to rendered templates without restarting the application, providing a smooth development experience.
2025-05-02 21:34:28 +02:00

195 lines
7.2 KiB
Rust

use std::path::PathBuf;
use std::fs::{self, File};
use std::io::Write;
use std::thread;
use std::time::Duration;
use std::error::Error;
use std::sync::Arc;
use tera::{Context};
use rhai_system::{create_hot_reloadable_system};
use tera_factory::TeraFactory;
/// Function to modify a script file with content from another file
fn modify_script(target_path: &PathBuf, source_path: &PathBuf, delay_secs: u64, message: &str) {
println!("\n🔄 {}", message);
// Read the source script content
let source_content = fs::read_to_string(&source_path)
.expect(&format!("Failed to read source script file: {}", source_path.display()));
// Write the content to the target file
let mut file = File::create(target_path)
.expect("Failed to open target script file for writing");
file.write_all(source_content.as_bytes())
.expect("Failed to write to target script file");
println!("✅ Script modified successfully!");
// Wait before the next modification if delay is specified
if delay_secs > 0 {
thread::sleep(Duration::from_secs(delay_secs));
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Set up the script paths
let script_dir = PathBuf::from("examples/hot_reload/scripts");
// Main script paths
let main_script_path = script_dir.join("main.rhai");
let utils_script_path = script_dir.join("utils.rhai");
// Initial script paths
let initial_main_path = script_dir.join("initial_main.rhai");
let initial_utils_path = script_dir.join("initial_utils.rhai");
// Modified script paths
let modified_main_path = script_dir.join("modified_main.rhai");
let modified_utils_path = script_dir.join("modified_utils.rhai");
println!("Main script path: {:?}", main_script_path);
println!("Utils script path: {:?}", utils_script_path);
// Initialize main.rhai with the content from initial_main.rhai
let initial_main_content = fs::read_to_string(&initial_main_path)
.expect("Failed to read initial main script file");
let mut file = File::create(&main_script_path)
.expect("Failed to open main script file for writing");
file.write_all(initial_main_content.as_bytes())
.expect("Failed to write to main script file");
// Initialize utils.rhai with the content from initial_utils.rhai
let initial_utils_content = fs::read_to_string(&initial_utils_path)
.expect("Failed to read initial utils script file");
let mut file = File::create(&utils_script_path)
.expect("Failed to open utils script file for writing");
file.write_all(initial_utils_content.as_bytes())
.expect("Failed to write to utils script file");
// Set up the template path
let template_dir = PathBuf::from("examples/hot_reload/templates");
let template_path = template_dir.join("index.html.tera");
println!("Template path: {:?}", template_path);
println!("Template exists: {}", template_path.exists());
// Create a hot reloadable Rhai system
let script_paths = vec![main_script_path.clone(), utils_script_path.clone()];
// Use the first script as the main script
let main_script_index = Some(0);
println!("Using main script index: {}", main_script_index.unwrap());
let system = Arc::new(create_hot_reloadable_system(&script_paths, main_script_index)?);
// Create a TeraFactory instance
let factory = TeraFactory::new();
// Create a Tera instance with the hot reloadable Rhai system
println!("Creating Tera with template directory: {}", template_dir.display());
let mut tera = factory.create_tera_with_hot_rhai(
&[template_dir.to_str().unwrap()],
Arc::clone(&system)
)?;
// Manually add the template to Tera
println!("Manually adding template: {}", template_path.display());
let template_content = fs::read_to_string(&template_path)?;
tera.add_raw_template("index.html.tera", &template_content)?;
// List available templates
println!("Available templates: {:?}", tera.get_template_names().collect::<Vec<_>>());
// Create a thread to modify the scripts after a delay
let main_script_path_clone = main_script_path.clone();
let utils_script_path_clone = utils_script_path.clone();
let modified_main_path_clone = modified_main_path.clone();
let modified_utils_path_clone = modified_utils_path.clone();
let _modification_thread = thread::spawn(move || {
// Modify the main script after 5 seconds
modify_script(
&main_script_path_clone,
&modified_main_path_clone,
5,
"Modifying the main script with updated functions..."
);
// Modify the utils script after another 5 seconds
modify_script(
&utils_script_path_clone,
&modified_utils_path_clone,
5,
"Modifying the utils script with updated functions..."
);
});
// Main rendering loop
for i in 1..30 {
// Create a context with some data
let mut context = Context::new();
context.insert("name", "User");
context.insert("current_date", "2025-05-02");
context.insert("current_time", &format!("{:02}:{:02}:{:02}",
(12 + i % 12) % 12, i % 60, i % 60));
// Add some products
let products = vec![
tera::to_value(serde_json::json!({
"name": "Deluxe Widget",
"price": 99.99,
"discount": 15.0
})).unwrap(),
tera::to_value(serde_json::json!({
"name": "Premium Gadget",
"price": 149.95,
"discount": 20.0
})).unwrap(),
tera::to_value(serde_json::json!({
"name": "Basic Tool",
"price": 29.99,
"discount": 5.0
})).unwrap(),
];
context.insert("products", &products);
// Add some categories
let categories = vec![
"Electronics", "Home & Garden", "Clothing", "Sports"
];
context.insert("categories", &categories);
// Render the template
match tera.render("index.html.tera", &context) {
Ok(result) => {
// Print the first 500 characters of the result to avoid flooding the console
let preview = if result.len() > 500 {
format!("{}... (truncated)", &result[..500])
} else {
result.clone()
};
println!("\n--- Render #{} ---\n{}", i, preview);
},
Err(e) => {
println!("Error rendering template: {}", e);
// Print more detailed error information
if let Some(source) = e.source() {
println!("Error source: {}", source);
if let Some(next_source) = source.source() {
println!("Next error source: {}", next_source);
}
}
}
}
// Wait for a second before rendering again
thread::sleep(Duration::from_secs(1));
}
Ok(())
}