226 lines
7.5 KiB
Rust
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
|
|
}
|
|
}
|
|
} |