use crate::terra_integration::script_manager::ScriptManager; use std::collections::HashMap; use std::path::Path; use std::fs; use std::sync::{Arc, RwLock}; /// A mock Terra Value type for demonstration purposes /// In a real implementation, this would be the actual Terra Value type #[derive(Debug, Clone)] pub enum Value { String(String), Number(f64), Boolean(bool), Array(Vec), Object(HashMap), Null, } impl Value { pub fn from_string(s: impl Into) -> Self { Value::String(s.into()) } pub fn from_number(n: f64) -> Self { Value::Number(n) } pub fn from_bool(b: bool) -> Self { Value::Boolean(b) } pub fn as_string(&self) -> Option<&String> { if let Value::String(s) = self { Some(s) } else { None } } pub fn as_number(&self) -> Option { if let Value::Number(n) = self { Some(*n) } else { None } } } /// A mock Terra Context type for demonstration purposes pub type Context = HashMap; /// A mock Terra Template type for demonstration purposes #[derive(Debug, Clone)] pub struct Template { content: String, } /// A mock Terra TemplateEngine type for demonstration purposes pub struct TemplateEngine { // Using a simple closure that doesn't need Send + Sync filters: HashMap) -> Value>>, } impl TemplateEngine { pub fn new() -> Self { Self { filters: HashMap::new(), } } pub fn register_filter(&mut self, name: &str, filter: F) where F: Fn(Vec) -> Value + 'static, // No Send + Sync { self.filters.insert(name.to_string(), Box::new(filter)); } pub fn compile(&self, template_content: &str) -> Result { // In a real implementation, this would compile the template // For this example, we just store the content Ok(Template { content: template_content.to_string(), }) } pub fn render(&self, _template: &Template, context: &Context) -> Result { // In a real implementation, this would render the template with the context // For this example, we just return a placeholder Ok(format!("Rendered template with {} filters and {} context variables", self.filters.len(), context.len())) } } /// TerraRenderer integrates Rhai scripts with Terra templates pub struct TerraRenderer { script_manager: Arc>, template_engine: TemplateEngine, templates: HashMap, } impl TerraRenderer { pub fn new(script_manager: Arc>) -> Self { let mut template_engine = TemplateEngine::new(); // Register all Rhai filters with Terra // Create a new local scope to limit the lifetime of the lock let filters = { // Get a read lock on the script manager let manager = script_manager.read().unwrap(); // Get all the filters (returns a reference) let filters_ref = manager.get_all_filters(); // Create a copy of the filter names for use outside the lock filters_ref.keys().cloned().collect::>() }; // Register each filter for name in filters { let script_manager_clone = script_manager.clone(); let name_clone = name.clone(); template_engine.register_filter(&name, move |args: Vec| { // Convert Terra Values to Rhai Dynamic values let rhai_args = args.into_iter() .map(|v| Self::terra_value_to_rhai_dynamic(v)) .collect::>(); // Call the filter by name from the script manager match script_manager_clone.read().unwrap().call_filter(&name_clone, rhai_args) { Ok(result) => Self::rhai_dynamic_to_terra_value(result), Err(e) => Value::String(format!("Error: {}", e)), } }); } Self { script_manager, template_engine, templates: HashMap::new(), } } /// Load a template from a file pub fn load_template(&mut self, name: &str, path: impl AsRef) -> Result<(), String> { let path = path.as_ref(); // Read the template file let template_content = fs::read_to_string(path) .map_err(|e| format!("Failed to read template file {}: {}", path.display(), e))?; // Compile the template let template = self.template_engine.compile(&template_content)?; // Store the compiled template self.templates.insert(name.to_string(), template); Ok(()) } /// Render a template with the given context pub fn render(&self, template_name: &str, context: Context) -> Result { let template = self.templates.get(template_name) .ok_or_else(|| format!("Template not found: {}", template_name))?; // Render the template self.template_engine.render(template, &context) } /// Convert a Terra Value to a Rhai Dynamic fn terra_value_to_rhai_dynamic(value: Value) -> rhai::Dynamic { use rhai::Dynamic; match value { Value::String(s) => Dynamic::from(s), Value::Number(n) => Dynamic::from(n), Value::Boolean(b) => Dynamic::from(b), Value::Array(arr) => { let rhai_arr = arr.into_iter() .map(Self::terra_value_to_rhai_dynamic) .collect::>(); Dynamic::from(rhai_arr) }, Value::Object(obj) => { let mut rhai_map = rhai::Map::new(); for (k, v) in obj { rhai_map.insert(k.into(), Self::terra_value_to_rhai_dynamic(v)); } Dynamic::from(rhai_map) }, Value::Null => Dynamic::UNIT, } } /// Convert a Rhai Dynamic to a Terra Value fn rhai_dynamic_to_terra_value(value: rhai::Dynamic) -> Value { if value.is::() { Value::String(value.into_string().unwrap()) } else if value.is::() { Value::Number(value.as_int().unwrap() as f64) } else if value.is::() { Value::Number(value.as_float().unwrap()) } else if value.is::() { Value::Boolean(value.as_bool().unwrap()) } else if value.is_array() { let arr = value.into_array().unwrap(); let terra_arr = arr.into_iter() .map(Self::rhai_dynamic_to_terra_value) .collect(); Value::Array(terra_arr) } else if value.is::() { // Use a different approach to handle maps // Create a new HashMap for Terra let mut terra_map = HashMap::new(); // Get the Map as a reference and convert each key-value pair if let Some(map) = value.try_cast::() { for (k, v) in map.iter() { terra_map.insert(k.to_string(), Self::rhai_dynamic_to_terra_value(v.clone())); } } Value::Object(terra_map) } else { Value::Null } } }