This commit is contained in:
2025-04-03 12:24:59 +02:00
parent a9a44f96cf
commit 868c870bf0
16 changed files with 33 additions and 713 deletions

View File

@@ -1,6 +0,0 @@
//! Rhai Engine Library
//!
//! This library provides utilities for working with Rhai scripts.
// Simple example of exposing Rhai functions to a hashmap
pub mod simple_example;

View File

@@ -1,21 +0,0 @@
//! Rhai Engine Example
//!
//! This example demonstrates how to expose Rhai scripts to Rust
//! and make the functions available in a hashmap for a rendering engine.
// mod simple_example;
mod terra_integration;
mod test_dynamic_loading;
fn main() {
// println!("Running simple example of exposing Rhai functions to a hashmap...");
// simple_example::main();
// println!("\n-----------------------------------------------------------\n");
// Run the dynamic loading test to demonstrate loading functions dynamically
println!("Running test for dynamic Rhai function loading...");
if let Err(e) = test_dynamic_loading::test_dynamic_loading() {
eprintln!("Error in dynamic loading test: {}", e);
}
}

View File

@@ -1,46 +1,46 @@
# Rhai Terra Integration
# Rhai Tera Integration
This module provides integration between Rhai scripts and Terra templates, allowing you to expose Rhai functions and filters to Terra templates dynamically.
This module provides integration between Rhai scripts and Tera templates, allowing you to expose Rhai functions and filters to Tera templates dynamically.
## Overview
The Rhai Terra Integration module allows you to:
The Rhai Tera Integration module allows you to:
1. Load and compile Rhai scripts dynamically
2. Extract functions from these scripts and expose them as callable functions in Rust
3. Register these functions as filters in Terra templates
4. Render Terra templates with these filters
3. Register these functions as filters in Tera templates
4. Render Tera templates with these filters
5. Directly call Rhai functions from Rust code
## Key Components
- **ScriptManager**: Handles loading, compiling, and exposing Rhai scripts
- **TerraRenderer**: Integrates with the Terra template engine
- **RhaiTerraIntegration**: Provides a simplified interface for the integration
- **TeraRenderer**: Integrates with the Tera template engine
- **RhaiTeraIntegration**: Provides a simplified interface for the integration
## Example Usage
### Loading Scripts and Templates
```rust
use rhai_engine::terra_integration::RhaiTerraIntegration;
use rhai_engine::tera_integration::RhaiTeraIntegration;
use std::collections::HashMap;
// Create a new RhaiTerraIntegration
let mut integration = RhaiTerraIntegration::new();
// Create a new RhaiTeraIntegration
let mut integration = RhaiTeraIntegration::new();
// Load Rhai scripts
integration.load_script("string_utils", "scripts/string_utils.rhai")?;
integration.load_script("math_utils", "scripts/math_utils.rhai")?;
// Load Terra templates
integration.load_template("example", "templates/example.terra")?;
// Load Tera templates
integration.load_template("example", "templates/example.tera")?;
```
### Rendering Templates
```rust
use rhai_engine::terra_integration::{string_value, number_value, object_value};
use rhai_engine::tera_integration::{string_value, number_value, object_value};
// Create context data for the template
let mut context = HashMap::new();
@@ -102,7 +102,7 @@ if let Some(capitalize_fn) = functions.get("string_utils:capitalize") {
## Writing Rhai Scripts
Rhai scripts can define functions that will be automatically exposed to Terra templates as filters. For example:
Rhai scripts can define functions that will be automatically exposed to Tera templates as filters. For example:
```js
// string_utils.rhai
@@ -128,9 +128,9 @@ fn capitalize(text) {
}
```
## Using Filters in Terra Templates
## Using Filters in Tera Templates
Once functions are registered, they can be used as filters in Terra templates:
Once functions are registered, they can be used as filters in Tera templates:
```html
<!DOCTYPE html>

View File

@@ -0,0 +1,6 @@
mod script_manager;
pub use script_manager::ScriptManager;

View File

@@ -1,149 +0,0 @@
//! Simple Example of Exposing Rhai Functions to a Hashmap
//!
//! This example demonstrates how to expose Rhai scripts dynamically to Rust
//! and make the functions available in a hashmap for a rendering engine.
use rhai::{Engine, Scope, Dynamic};
use std::collections::HashMap;
use std::fs;
/// A simple function type that can be stored in a hashmap
type RhaiFunction = Box<dyn Fn(Vec<Dynamic>) -> Dynamic>;
/// Load a Rhai script and expose its functions
pub fn main() {
// Create a new Rhai engine
let mut engine = Engine::new();
// Register string functions that our scripts need
register_string_functions(&mut engine);
// Load and compile the string_utils.rhai script
let script_path = "src/terra_integration/scripts/string_utils.rhai";
let script_content = fs::read_to_string(script_path).expect("Failed to read script file");
let ast = engine.compile(&script_content).expect("Failed to compile script");
// Create a hashmap to store the functions
let mut functions: HashMap<String, RhaiFunction> = HashMap::new();
// Create a clone of the AST for each function
let ast_clone1 = ast.clone();
let ast_clone2 = ast.clone();
let ast_clone3 = ast.clone();
// Register the capitalize function
functions.insert("capitalize".to_string(), Box::new(move |args| {
let mut engine = Engine::new();
register_string_functions(&mut engine);
let mut scope = Scope::new();
let result = engine.call_fn::<Dynamic>(&mut scope, &ast_clone1, "capitalize", args)
.unwrap_or_else(|e| Dynamic::from(format!("Error: {}", e)));
result
}));
// Register the truncate function
functions.insert("truncate".to_string(), Box::new(move |args| {
let mut engine = Engine::new();
register_string_functions(&mut engine);
let mut scope = Scope::new();
let result = engine.call_fn::<Dynamic>(&mut scope, &ast_clone2, "truncate", args)
.unwrap_or_else(|e| Dynamic::from(format!("Error: {}", e)));
result
}));
// Register the slugify function
functions.insert("slugify".to_string(), Box::new(move |args| {
let mut engine = Engine::new();
register_string_functions(&mut engine);
let mut scope = Scope::new();
let result = engine.call_fn::<Dynamic>(&mut scope, &ast_clone3, "slugify", args)
.unwrap_or_else(|e| Dynamic::from(format!("Error: {}", e)));
result
}));
// Now we can use these functions from the hashmap
println!("Functions available:");
for name in functions.keys() {
println!("- {}", name);
}
// Example of calling a function from the hashmap
let capitalize_fn = functions.get("capitalize").expect("Function not found");
let result = capitalize_fn(vec![Dynamic::from("hello world")]);
println!("capitalize('hello world') => {}", result);
let truncate_fn = functions.get("truncate").expect("Function not found");
let result = truncate_fn(vec![Dynamic::from("This is a long text that will be truncated"), Dynamic::from(20)]);
println!("truncate('This is a long text that will be truncated', 20) => {}", result);
let slugify_fn = functions.get("slugify").expect("Function not found");
let result = slugify_fn(vec![Dynamic::from("Hello, World!")]);
println!("slugify('Hello, World!') => {}", result);
// This is how a rendering engine like Terra could use these functions
println!("\nExample of how a rendering engine might use these functions:");
// Simulate a template rendering with a filter
let template_var = "hello world";
println!("Original: {}", template_var);
println!("After capitalize filter: {}", capitalize_fn(vec![Dynamic::from(template_var)]));
let template_var = "This is a description that is too long for a meta tag";
println!("Original: {}", template_var);
println!("After truncate filter: {}", truncate_fn(vec![Dynamic::from(template_var), Dynamic::from(30)]));
let template_var = "Hello, World! This is a Title";
println!("Original: {}", template_var);
println!("After slugify filter: {}", slugify_fn(vec![Dynamic::from(template_var)]));
}
/// Register string functions that our scripts need
fn register_string_functions(engine: &mut Engine) {
// Register string functions
engine.register_fn("substr", |s: &str, start: i64, len: i64| {
let start = start as usize;
let len = len as usize;
if start >= s.len() {
return String::new();
}
let end = std::cmp::min(start + len, s.len());
s[start..end].to_string()
});
engine.register_fn("to_upper", |s: &str| s.to_uppercase());
engine.register_fn("to_lower", |s: &str| s.to_lowercase());
// Register split function that returns an array
engine.register_fn("split", |s: &str, delimiter: &str| {
let parts: Vec<Dynamic> = s.split(delimiter)
.map(|part| Dynamic::from(part.to_string()))
.collect();
Dynamic::from_array(parts)
});
// Register join function for arrays
engine.register_fn("join", |arr: rhai::Array, delimiter: &str| {
let strings: Vec<String> = arr.iter()
.map(|item| item.to_string())
.collect();
strings.join(delimiter)
});
// Register push function for arrays
engine.register_fn("push", |arr: &mut rhai::Array, item: Dynamic| {
arr.push(item);
});
// Register replace function
engine.register_fn("replace", |s: &str, from: &str, to: &str| {
s.replace(from, to)
});
// Register comparison operators for different integer types
engine.register_fn("<=", |a: i64, b: i32| a <= b as i64);
engine.register_fn("<=", |a: i32, b: i64| (a as i64) <= b);
// Register arithmetic operators for different integer types
engine.register_fn("-", |a: i64, b: i32| a - (b as i64));
engine.register_fn("-", |a: i32, b: i64| (a as i64) - b);
}

View File

@@ -1,161 +0,0 @@
//! Example usage of the Terra Integration Module
//!
//! This example demonstrates how to use the RhaiTerraIntegration to:
//! 1. Load Rhai scripts with functions and filters
//! 2. Load Terra templates that use these functions and filters
//! 3. Render templates with dynamic data
//! 4. Access Rhai functions directly from Rust
use crate::terra_integration::{
RhaiTerraIntegration,
string_value,
number_value,
boolean_value,
object_value,
array_value,
Value,
};
use std::collections::HashMap;
use std::path::Path;
use rhai::Dynamic;
/// Run the example
pub fn run_example() -> Result<(), String> {
// Create a new RhaiTerraIntegration
let mut integration = RhaiTerraIntegration::new();
// Load Rhai scripts
integration.load_script("string_utils", "src/terra_integration/scripts/string_utils.rhai")?;
integration.load_script("math_utils", "src/terra_integration/scripts/math_utils.rhai")?;
// Load Terra templates
integration.load_template("example", "src/terra_integration/templates/example.terra")?;
// Create context data for the template
let mut context = HashMap::new();
context.insert("title".to_string(), string_value("welcome to our website"));
context.insert("description".to_string(), string_value("This is a demonstration of integrating Rhai scripts with Terra templates. We can use Rhai functions as filters and directly call them from templates."));
context.insert("content".to_string(), string_value("<p>This is the main content of the page.</p>"));
// Create stats object
let mut stats = HashMap::new();
stats.insert("users".to_string(), number_value(12345));
stats.insert("visitors".to_string(), number_value(50000));
stats.insert("conversions".to_string(), number_value(2500));
stats.insert("revenue".to_string(), number_value(98765.43));
context.insert("stats".to_string(), object_value(stats));
context.insert("current_year".to_string(), number_value(2025.0));
// Render the template
let rendered = integration.render("example", context)?;
println!("Rendered template:\n{}", rendered);
// Demonstrate direct function calls
println!("\nDirect function calls:");
// Call string_utils:capitalize
let result = integration.call_function(
"string_utils:capitalize",
vec![Dynamic::from("hello world")]
)?;
println!("capitalize('hello world') => {}", result);
// Call math_utils:format_number
let result = integration.call_function(
"math_utils:format_number",
vec![Dynamic::from(1234567)]
)?;
println!("format_number(1234567) => {}", result);
// Call math_utils:percentage
let result = integration.call_function(
"math_utils:percentage",
vec![Dynamic::from(75), Dynamic::from(100)]
)?;
println!("percentage(75, 100) => {}", result);
// Call string_utils:slugify
let result = integration.call_function(
"string_utils:slugify",
vec![Dynamic::from("Hello, World!")]
)?;
println!("slugify('Hello, World!') => {}", result);
// Print all available functions
println!("\nAvailable functions:");
for name in integration.get_function_names() {
println!("- {}", name);
}
// Filters are just functions without script: prefix
println!("\nAvailable filters (same as functions without script: prefix):");
for name in integration.get_function_names() {
println!("- {}", name);
}
Ok(())
}
/// Example of how to expose Rhai functions to a rendering engine like Terra
pub fn example_expose_to_rendering_engine() {
// Create a new RhaiTerraIntegration
let mut integration = RhaiTerraIntegration::new();
// Load Rhai scripts
if let Err(e) = integration.load_script("string_utils", "src/terra_integration/scripts/string_utils.rhai") {
eprintln!("Error loading script: {}", e);
return;
}
if let Err(e) = integration.load_script("math_utils", "src/terra_integration/scripts/math_utils.rhai") {
eprintln!("Error loading script: {}", e);
return;
}
// Get all function names
let function_names = integration.get_function_names();
// This HashMap can now be passed to any rendering engine
println!("Functions available to rendering engine:");
for name in &function_names {
println!("- {}", name);
}
// Simulating how a rendering engine might call these functions
println!("\nSimulated rendering engine function calls:");
simulate_function_call(&integration, "string_utils:capitalize", vec![Dynamic::from("hello world")]);
simulate_function_call(&integration, "math_utils:format_number", vec![Dynamic::from(1234567)]);
// Example of how a rendering engine might use these functions
println!("\nRendering engine function calls:");
// Get a specific function
if let Some(capitalize_fn) = functions.get("string_utils:capitalize") {
// Call the function with arguments
match capitalize_fn(vec![Dynamic::from("hello world")]) {
Ok(result) => println!("capitalize('hello world') => {}", result),
Err(e) => println!("Error: {}", e),
}
}
if let Some(format_number_fn) = functions.get("math_utils:format_number") {
match format_number_fn(vec![Dynamic::from(1234567)]) {
Ok(result) => println!("format_number(1234567) => {}", result),
Err(e) => println!("Error: {}", e),
}
}
// This demonstrates how a rendering engine like Terra could access and use
// the Rhai functions through the HashMap
}
// Helper function to simulate function calls in a rendering engine
fn simulate_function_call(integration: &RhaiTerraIntegration, name: &str, args: Vec<Dynamic>) {
println!("Calling function: {}", name);
match integration.call_function(name, args) {
Ok(result) => println!(" Result: {}", result),
Err(e) => println!(" Error: {}", e),
}
}

View File

@@ -1,85 +0,0 @@
//! Terra Integration Module
//!
//! This module provides integration between Rhai scripts and Terra templates.
//! It allows exposing Rhai functions and filters to Terra templates dynamically.
mod script_manager;
mod terra_renderer;
pub use script_manager::ScriptManager;
pub use terra_renderer::{TerraRenderer, Value, Context, Template};
use std::path::Path;
use std::sync::{Arc, RwLock};
use std::collections::HashMap;
/// RhaiTerraIntegration provides a simplified interface for integrating Rhai with Terra
pub struct RhaiTerraIntegration {
script_manager: Arc<RwLock<ScriptManager>>,
renderer: TerraRenderer,
}
impl RhaiTerraIntegration {
/// Create a new RhaiTerraIntegration
pub fn new() -> Self {
let script_manager = Arc::new(RwLock::new(ScriptManager::new()));
let renderer = TerraRenderer::new(script_manager.clone());
Self {
script_manager,
renderer,
}
}
/// Load a Rhai script from a file
pub fn load_script(&mut self, name: &str, path: impl AsRef<Path>) -> Result<(), String> {
self.script_manager.write().unwrap().load_script(name, path)
}
/// Load a Terra template from a file
pub fn load_template(&mut self, name: &str, path: impl AsRef<Path>) -> Result<(), String> {
self.renderer.load_template(name, path)
}
/// Render a template with the given context
pub fn render(&self, template_name: &str, context: HashMap<String, Value>) -> Result<String, String> {
self.renderer.render(template_name, context)
}
/// Call a Rhai function by its full name (script:function)
pub fn call_function(&self, name: &str, args: Vec<rhai::Dynamic>) -> Result<rhai::Dynamic, String> {
self.script_manager.read().unwrap().call_function(name, args)
}
/// Get all available Rhai function names
pub fn get_function_names(&self) -> Vec<String> {
let script_manager = self.script_manager.read().unwrap();
let function_map = script_manager.get_all_functions();
function_map.keys().cloned().collect()
}
}
/// Create a string value for Terra templates
pub fn string_value(s: impl Into<String>) -> Value {
Value::String(s.into())
}
/// Create a number value for Terra templates
pub fn number_value(n: f64) -> Value {
Value::Number(n)
}
/// Create a boolean value for Terra templates
pub fn boolean_value(b: bool) -> Value {
Value::Boolean(b)
}
/// Create an object value for Terra templates
pub fn object_value(map: HashMap<String, Value>) -> Value {
Value::Object(map)
}
/// Create an array value for Terra templates
pub fn array_value(arr: Vec<Value>) -> Value {
Value::Array(arr)
}

View File

@@ -1,146 +0,0 @@
// Data objects for Terra template integration
// This script defines complex objects that can be accessed from Terra templates
// Create a products catalog object
fn create_products() {
let products = [
#{
id: 1,
name: "Laptop",
price: 1299.99,
features: [
"16GB RAM",
"512GB SSD",
"Intel i7 processor"
],
available: true
},
#{
id: 2,
name: "Smartphone",
price: 899.99,
features: [
"6.7 inch display",
"128GB storage",
"12MP camera"
],
available: true
},
#{
id: 3,
name: "Tablet",
price: 499.99,
features: [
"10.9 inch display",
"64GB storage",
"A14 chip"
],
available: false
},
#{
id: 4,
name: "Headphones",
price: 249.99,
features: [
"Noise cancellation",
"Wireless",
"20h battery life"
],
available: true
}
];
products
}
// Get all products
fn get_products() {
create_products()
}
// Get available products only
fn get_available_products() {
let all_products = create_products();
let available = [];
for product in all_products {
if product.available {
available.push(product);
}
}
available
}
// Get a specific product by ID
fn get_product_by_id(id) {
let products = create_products();
// Convert ID to integer to ensure type compatibility
let search_id = id.to_int();
for product in products {
if product.id == search_id {
return product;
}
}
#{} // Return empty object if not found
}
// Calculate total price of all products
fn calculate_total_price() {
let products = create_products();
let total = 0.0;
for product in products {
total += product.price;
}
total
}
// Format price with currency symbol
fn format_price(price) {
"$" + price.to_string()
}
// Get all product features as a flattened array
fn get_all_features() {
let products = create_products();
let all_features = [];
for product in products {
for feature in product.features {
all_features.push(feature);
}
}
all_features
}
// Get a user object
fn get_user() {
#{
name: "John Doe",
email: "john@example.com",
role: "admin",
settings: #{
theme: "dark",
notifications: true,
language: "en"
},
orders: [
#{
id: "ORD-001",
date: "2025-03-25",
total: 1299.99
},
#{
id: "ORD-002",
date: "2025-04-01",
total: 249.99
}
]
}
}

View File

@@ -1,32 +0,0 @@
// Math utility functions for Terra templates
// Format a number with commas as thousands separators
fn format_number(num) {
let str = num.to_string();
let result = "";
let len = str.len;
for i in 0..len {
if i > 0 && (len - i) % 3 == 0 {
result += ",";
}
result += str.substr(i, 1);
}
result
}
// Calculate percentage
fn percentage(value, total) {
if total == 0 {
return "0%";
}
let pct = (value / total) * 100.0;
pct.round().to_string() + "%"
}
// Format currency
fn currency(amount, symbol) {
symbol + format_number(amount.round(2))
}

View File

@@ -1,43 +0,0 @@
// String manipulation functions that will be exposed to Terra templates
// Capitalize the first letter of each word
fn capitalize(text) {
if text.len == 0 {
return "";
}
let words = text.split(" ");
let result = [];
for word in words {
if word.len > 0 {
let first_char = word.substr(0, 1).to_upper();
let rest = word.substr(1, word.len - 1);
result.push(first_char + rest);
} else {
result.push("");
}
}
result.join(" ")
}
// Truncate text with ellipsis
fn truncate(text, max_length) {
if text.len <= max_length {
return text;
}
text.substr(0, max_length - 3) + "..."
}
// Convert text to slug format (lowercase, hyphens)
fn slugify(text) {
let slug = text.to_lower();
slug = slug.replace(" ", "-");
slug = slug.replace(".", "");
slug = slug.replace(",", "");
slug = slug.replace("!", "");
slug = slug.replace("?", "");
slug
}

View File

@@ -1,56 +0,0 @@
// Test utility functions to verify dynamic function discovery
// These functions were not in the original hardcoded lists
// Reverse a string
fn reverse_string(text) {
let result = "";
let i = text.len - 1;
// Using a different approach to reverse the string
// We'll iterate backwards with a while loop instead of using step
while i >= 0 {
result += text.substr(i, 1);
i -= 1;
}
result
}
// Count words in a string - rewritten to avoid split issues
fn count_words(text) {
if text.len == 0 {
return 0;
}
// Manual word counting implementation
let count = 1; // Start with 1 for the first word
let in_word = true;
for i in 0..text.len {
let char = text.substr(i, 1);
if char == " " {
in_word = false;
} else if !in_word {
// Found a non-space after a space = new word
count += 1;
in_word = true;
}
}
if text.substr(0, 1) == " " {
// If text starts with space, reduce count
count -= 1;
}
count
}
// Calculate factorial
fn factorial(n) {
// Ensure consistent integer type using to_int()
let num = n.to_int();
if num <= 1 {
return 1;
}
num * factorial(num-1)
}

View File

@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ title | capitalize }}</title>
<meta name="description" content="{{ description | truncate(160) }}">
<link rel="canonical" href="/{{ title | slugify }}">
</head>
<body>
<header>
<h1>{{ title | capitalize }}</h1>
</header>
<main>
<div class="content">
{{ content }}
</div>
<div class="stats">
<div class="stat">
<span class="label">Total Users</span>
<span class="value">{{ stats.users | format_number }}</span>
</div>
<div class="stat">
<span class="label">Conversion Rate</span>
<span class="value">{{ percentage(stats.conversions, stats.visitors) }}</span>
</div>
<div class="stat">
<span class="label">Revenue</span>
<span class="value">{{ currency(stats.revenue, "$") }}</span>
</div>
</div>
</main>
<footer>
<p>© {{ current_year }} Example Company</p>
</footer>
</body>
</html>

View File

@@ -1,114 +0,0 @@
<html>
<head>
<title>Product Catalog</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
.product {
border: 1px solid #ddd;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
}
.product-available {
background-color: #f0fff0;
}
.product-unavailable {
background-color: #fff0f0;
}
.features {
margin-top: 5px;
}
.price {
font-weight: bold;
color: #2a5885;
}
.total {
margin-top: 20px;
text-align: right;
font-size: 1.2em;
}
.user-info {
background-color: #f5f5f5;
padding: 10px;
margin-top: 20px;
border-radius: 5px;
}
</style>
</head>
<body>
<h1>Product Catalog</h1>
<!-- Call Rhai function to get products and iterate over them -->
{% let products = data_objects:get_products() %}
<h2>All Products</h2>
{% for product in products %}
<div class="product {% if product.available %}product-available{% else %}product-unavailable{% endif %}">
<h3>{{ product.name }}</h3>
<div class="price">{{ data_objects:format_price(product.price) }}</div>
<div class="features">
<strong>Features:</strong>
<ul>
{% for feature in product.features %}
<li>{{ feature }}</li>
{% endfor %}
</ul>
</div>
<div>
{% if product.available %}
<span class="available">In Stock</span>
{% else %}
<span class="unavailable">Out of Stock</span>
{% endif %}
</div>
</div>
{% endfor %}
<!-- Call Rhai function to get only available products -->
{% let available_products = data_objects:get_available_products() %}
<h2>Available Products ({{ available_products.len() }})</h2>
{% for product in available_products %}
<div class="product product-available">
<h3>{{ product.name }}</h3>
<div class="price">{{ data_objects:format_price(product.price) }}</div>
</div>
{% endfor %}
<!-- Display total price using Rhai function -->
<div class="total">
Total Catalog Value: {{ data_objects:format_price(data_objects:calculate_total_price()) }}
</div>
<!-- Display user information from Rhai function -->
{% let user = data_objects:get_user() %}
<div class="user-info">
<h2>User Information</h2>
<p><strong>Name:</strong> {{ user.name }}</p>
<p><strong>Email:</strong> {{ user.email }}</p>
<p><strong>Role:</strong> {{ user.role }}</p>
<p><strong>Theme:</strong> {{ user.settings.theme }}</p>
<h3>Order History</h3>
<ul>
{% for order in user.orders %}
<li>
<strong>{{ order.id }}</strong> ({{ order.date }}) -
{{ data_objects:format_price(order.total) }}
</li>
{% endfor %}
</ul>
</div>
<!-- List all unique features -->
<h2>All Features</h2>
<ul>
{% let features = data_objects:get_all_features() %}
{% for feature in features %}
<li>{{ feature }}</li>
{% endfor %}
</ul>
</body>
</html>

View File

@@ -1,226 +0,0 @@
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
}
}
}

View File

@@ -1,93 +0,0 @@
use crate::terra_integration::RhaiTerraIntegration;
use std::collections::HashMap;
use rhai::Dynamic;
/// Test the dynamic loading of Rhai scripts and functions
pub fn test_dynamic_loading() -> Result<(), String> {
println!("\n=== TESTING DYNAMIC FUNCTION LOADING ===\n");
// Create a new RhaiTerraIntegration
let mut integration = RhaiTerraIntegration::new();
println!("Loading Rhai scripts...");
// Load the original scripts
integration.load_script("string_utils", "src/terra_integration/scripts/string_utils.rhai")?;
integration.load_script("math_utils", "src/terra_integration/scripts/math_utils.rhai")?;
// Load the new test script (which wasn't in the original hardcoded lists)
integration.load_script("test_utils", "src/terra_integration/scripts/test_utils.rhai")?;
// Get function names
let function_names = integration.get_function_names();
// Print all available functions
println!("\nAll available functions:");
for name in &function_names {
println!(" - {}", name);
}
// Group functions by their script prefix to display nicely
let scripts = group_functions_by_script(&function_names);
// Display functions by script
display_functions_by_script(&scripts);
// Test some functions from each script
println!("\nDynamic function testing:");
// Test original string utils functions
test_function(&integration, "string_utils:capitalize", vec![Dynamic::from("hello world")])?;
// Test original math utils functions
test_function(&integration, "math_utils:format_number", vec![Dynamic::from(1234567)])?;
// Test new functions from test_utils
test_function(&integration, "test_utils:reverse_string", vec![Dynamic::from("hello world")])?;
test_function(&integration, "test_utils:count_words",
vec![Dynamic::from("this is a test sentence")])?;
test_function(&integration, "test_utils:factorial",
vec![Dynamic::from(5)])?;
println!("\n=== DYNAMIC FUNCTION LOADING TEST COMPLETE ===\n");
Ok(())
}
// Helper function to group functions by script
fn group_functions_by_script(function_names: &[String]) -> HashMap<String, Vec<String>> {
let mut scripts = HashMap::new();
for name in function_names {
let parts: Vec<&str> = name.split(':').collect();
if parts.len() == 2 {
let script = parts[0].to_string();
let func = parts[1].to_string();
scripts.entry(script).or_insert_with(Vec::new).push(func);
}
}
scripts
}
// Helper function to display functions grouped by script
fn display_functions_by_script(scripts: &HashMap<String, Vec<String>>) {
println!("\nFunctions by script:");
for (script, funcs) in scripts {
println!(" {}:", script);
for func in funcs {
println!(" - {}", func);
}
}
}
// Helper function to test a function and display its result
fn test_function(integration: &RhaiTerraIntegration, name: &str, args: Vec<Dynamic>) -> Result<(), String> {
let result = integration.call_function(name, args)?;
println!(" {}() => {}", name, result);
Ok(())
}