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/rhai_engine/src/terra_integration/terra_renderer.rs
2025-04-03 09:18:05 +02:00

226 lines
7.5 KiB
Rust

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<Value>),
Object(HashMap<String, Value>),
Null,
}
impl Value {
pub fn from_string(s: impl Into<String>) -> 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<f64> {
if let Value::Number(n) = self {
Some(*n)
} else {
None
}
}
}
/// A mock Terra Context type for demonstration purposes
pub type Context = HashMap<String, Value>;
/// 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<String, Box<dyn Fn(Vec<Value>) -> Value>>,
}
impl TemplateEngine {
pub fn new() -> Self {
Self {
filters: HashMap::new(),
}
}
pub fn register_filter<F>(&mut self, name: &str, filter: F)
where
F: Fn(Vec<Value>) -> Value + 'static, // No Send + Sync
{
self.filters.insert(name.to_string(), Box::new(filter));
}
pub fn compile(&self, template_content: &str) -> Result<Template, String> {
// 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<String, String> {
// 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<RwLock<ScriptManager>>,
template_engine: TemplateEngine,
templates: HashMap<String, Template>,
}
impl TerraRenderer {
pub fn new(script_manager: Arc<RwLock<ScriptManager>>) -> 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::<Vec<String>>()
};
// 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<Value>| {
// Convert Terra Values to Rhai Dynamic values
let rhai_args = args.into_iter()
.map(|v| Self::terra_value_to_rhai_dynamic(v))
.collect::<Vec<_>>();
// 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<Path>) -> 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<String, String> {
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::<Vec<_>>();
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::<String>() {
Value::String(value.into_string().unwrap())
} else if value.is::<i64>() {
Value::Number(value.as_int().unwrap() as f64)
} else if value.is::<f64>() {
Value::Number(value.as_float().unwrap())
} else if value.is::<bool>() {
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::<rhai::Map>() {
// 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::<rhai::Map>() {
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
}
}
}