...
This commit is contained in:
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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>
|
6
rhai_engine/src/rhailoader/mod.rs
Normal file
6
rhai_engine/src/rhailoader/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
mod script_manager;
|
||||
|
||||
|
||||
pub use script_manager::ScriptManager;
|
||||
|
@@ -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);
|
||||
}
|
@@ -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),
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@@ -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))
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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>
|
@@ -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>
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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(())
|
||||
}
|
Reference in New Issue
Block a user