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.
195 lines
7.2 KiB
Rust
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(())
|
|
}
|