Compare commits
11 Commits
developmen
...
main
Author | SHA1 | Date | |
---|---|---|---|
f409246ed3 | |||
1157d6da64 | |||
6389da0372 | |||
3b4db8b66b | |||
a361f6d4ba | |||
4b1f305b07 | |||
452150a2fc | |||
d3c5f97469 | |||
e16d4270c0 | |||
868c870bf0 | |||
a9a44f96cf |
@ -1,3 +1,6 @@
|
||||
# rhaj
|
||||
|
||||
DSL's for our ecosystem
|
||||
```bash
|
||||
#tests, following one shows how we can dynamically load the functions from a set of rhai scripts
|
||||
cargo run --bin test_dynamic_loading
|
||||
```
|
515
_archive/lib.rs
515
_archive/lib.rs
@ -1,515 +0,0 @@
|
||||
// rhai_macros_derive/src/lib.rs
|
||||
|
||||
// We will add our derive macro implementations here.
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, format_ident, quote_spanned};
|
||||
use syn::{parse_macro_input, Ident, Type, ItemFn, spanned::Spanned, PathArguments, GenericArgument, DeriveInput, Data, LitStr};
|
||||
|
||||
// Old ToRhaiMap and FromRhaiMap definitions will be removed from here.
|
||||
// The export_fn macro definition starts after this.
|
||||
|
||||
// Trait definitions removed from here as proc-macro crates cannot export them.
|
||||
// They should be defined in a regular library crate (e.g., rhai_wrapper or a new rhai_traits crate).
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn export_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let func = parse_macro_input!(item as ItemFn);
|
||||
|
||||
let fn_name = &func.sig.ident;
|
||||
let wrapper_fn_name = Ident::new(&format!("rhai_wrapper_{}", fn_name), fn_name.span());
|
||||
let original_fn_generics = &func.sig.generics;
|
||||
let fn_inputs = &func.sig.inputs;
|
||||
// num_expected_args has been removed as Rhai handles arity with typed arguments
|
||||
|
||||
let mut arg_conversions: Vec<TokenStream2> = Vec::new(); // For let converted_arg_0 = ...
|
||||
let mut original_fn_call_args: Vec<TokenStream2> = Vec::new(); // For fn_name(converted_arg_0, ...)
|
||||
|
||||
let mut wrapper_args_dynamic_defs: Vec<TokenStream2> = Vec::new(); // Stores definitions like `wrapper_arg_0: ::rhai::Dynamic`
|
||||
|
||||
for (i, fn_arg) in fn_inputs.iter().enumerate() {
|
||||
if let syn::FnArg::Typed(pat_type) = fn_arg {
|
||||
let arg_ident_original_pat = match *pat_type.pat.clone() {
|
||||
syn::Pat::Ident(pat_ident) => pat_ident.ident,
|
||||
_ => panic!("#[export_fn] only supports simple identifiers as argument patterns, not complex patterns."),
|
||||
};
|
||||
let arg_ident_original_pat_str = arg_ident_original_pat.to_string();
|
||||
let wrapper_arg_name_ident = Ident::new(&format!("wrapper_arg_{}", i), pat_type.pat.span());
|
||||
let wrapper_arg_name_str = wrapper_arg_name_ident.to_string();
|
||||
|
||||
// Populate definitions for the wrapper function signature
|
||||
wrapper_args_dynamic_defs.push(quote! { #wrapper_arg_name_ident: ::rhai::Dynamic });
|
||||
|
||||
// Generate a unique ident for the converted argument variable
|
||||
let converted_rhai_arg_ident = Ident::new(&format!("converted_rhai_arg_{}", i), pat_type.pat.span());
|
||||
|
||||
let arg_rust_type = &*pat_type.ty;
|
||||
let arg_type_str = quote!(#arg_rust_type).to_string().replace(' ', "");
|
||||
|
||||
if let Type::Reference(ref type_ref) = *pat_type.ty {
|
||||
let is_mutable = type_ref.mutability.is_some();
|
||||
let referent_type = &*type_ref.elem;
|
||||
let lock_guard_ident = Ident::new(&format!("lock_guard_{}", i), pat_type.pat.span());
|
||||
|
||||
let conversion_logic = if is_mutable {
|
||||
quote! {
|
||||
let mut #lock_guard_ident = #wrapper_arg_name_ident.write_lock::<#referent_type>()
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be a mutable reference to {}. Ensure it's not already locked.",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i, stringify!(#referent_type)
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
let #converted_rhai_arg_ident = &mut *#lock_guard_ident;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let #lock_guard_ident = #wrapper_arg_name_ident.read_lock::<#referent_type>()
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be an immutable reference to {}.",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i, stringify!(#referent_type)
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
let #converted_rhai_arg_ident = &*#lock_guard_ident;
|
||||
}
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "&str" {
|
||||
let str_lock_guard_ident = Ident::new(&format!("str_lock_guard_{}", i), pat_type.pat.span());
|
||||
let conversion_logic = quote! {
|
||||
let #str_lock_guard_ident = #wrapper_arg_name_ident.read_lock::<rhai::ImmutableString>()
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type &str (requires read_lock on ImmutableString)",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
let #converted_rhai_arg_ident = #str_lock_guard_ident.as_str();
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "INT" || arg_type_str == "i64" {
|
||||
let conversion_logic = quote! {
|
||||
let int_option: Option<::rhai::INT> = #wrapper_arg_name_ident.as_int();
|
||||
let #converted_rhai_arg_ident = int_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type i64 (INT)",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "FLOAT" || arg_type_str == "f64" {
|
||||
let conversion_logic = quote! {
|
||||
let float_option: Option<::rhai::FLOAT> = #wrapper_arg_name_ident.as_float();
|
||||
let #converted_rhai_arg_ident = float_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type f64 (FLOAT)",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "bool" {
|
||||
let conversion_logic = quote! {
|
||||
let bool_option: Option<bool> = #wrapper_arg_name_ident.as_bool();
|
||||
let #converted_rhai_arg_ident = bool_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type bool",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else if arg_type_str == "String" {
|
||||
let conversion_logic = quote! {
|
||||
let #converted_rhai_arg_ident = #wrapper_arg_name_ident.clone().into_string()
|
||||
.map_err(|_| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be of type String",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i
|
||||
),
|
||||
format!("but found type {}", #wrapper_arg_name_ident.type_name()),
|
||||
::rhai::Position::NONE
|
||||
)))?;
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
} else {
|
||||
let owned_type_path = &*pat_type.ty; // No deref needed here, quote! can handle &Type
|
||||
let arg_type_for_conversion_code = quote!{#owned_type_path};
|
||||
let conversion_logic = quote! {
|
||||
let dyn_val_for_cast = #wrapper_arg_name_ident.clone(); // Clone for try_cast
|
||||
let actual_type_name = #wrapper_arg_name_ident.type_name();
|
||||
let #converted_rhai_arg_ident:#arg_type_for_conversion_code = match <#arg_type_for_conversion_code as std::convert::TryFrom<::rhai::Dynamic>>::try_from(dyn_val_for_cast) {
|
||||
Ok(val) => val,
|
||||
Err(_e) => {
|
||||
let cast_option: Option<#arg_type_for_conversion_code> = #wrapper_arg_name_ident.clone().try_cast::<#arg_type_for_conversion_code>();
|
||||
cast_option
|
||||
.ok_or_else(|| Box::new(::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
format!("Argument '{}' (dynamic arg '{}') at index {} must be convertible to type {}. TryFrom and try_cast failed.",
|
||||
#arg_ident_original_pat_str, #wrapper_arg_name_str, #i, stringify!(#owned_type_path)
|
||||
),
|
||||
format!("but found type {}", actual_type_name),
|
||||
::rhai::Position::NONE
|
||||
)))?
|
||||
}
|
||||
};
|
||||
};
|
||||
arg_conversions.push(conversion_logic);
|
||||
original_fn_call_args.push(quote! { #converted_rhai_arg_ident });
|
||||
}
|
||||
} else {
|
||||
// Handle receivers like self, &self, &mut self if necessary
|
||||
panic!("#[export_fn] does not support methods with 'self' receivers. Apply to static or free functions.");
|
||||
}
|
||||
}
|
||||
|
||||
let return_type = match &func.sig.output {
|
||||
syn::ReturnType::Default => quote!( () ),
|
||||
syn::ReturnType::Type(_, ty) => quote!( #ty ),
|
||||
};
|
||||
|
||||
// Determine the return type for the wrapper function's Result
|
||||
// If original is (), wrapper returns Dynamic (representing unit). Otherwise, original return type.
|
||||
let return_type_for_wrapper = if quote!(#return_type).to_string().replace(" ", "") == "()" {
|
||||
quote!(::rhai::Dynamic)
|
||||
} else {
|
||||
quote!(#return_type)
|
||||
};
|
||||
|
||||
// How to convert the result of the original function call into the wrapper's Ok Result.
|
||||
let return_conversion = if let syn::ReturnType::Type(_, _ty) = &func.sig.output {
|
||||
// If original function returns non-(), result is already #return_type_for_wrapper (which is #return_type)
|
||||
// So, it can be directly used.
|
||||
quote! { Ok(result) }
|
||||
} else {
|
||||
// If original function returns (), result is (), convert to Dynamic::UNIT for Rhai
|
||||
quote! { Ok(().into()) }
|
||||
};
|
||||
|
||||
let (impl_generics, _ty_generics, where_clause) = original_fn_generics.split_for_impl();
|
||||
|
||||
let expanded = quote! {
|
||||
#func // Re-emit the original function
|
||||
|
||||
// _cx is NativeCallContext. The arguments are now individual ::rhai::Dynamic types.
|
||||
pub fn #wrapper_fn_name #impl_generics (_cx: ::rhai::NativeCallContext, #(#wrapper_args_dynamic_defs),*) -> Result<#return_type_for_wrapper, Box<::rhai::EvalAltResult>> #where_clause {
|
||||
// Arity check is implicitly handled by Rhai's function registration for typed arguments.
|
||||
// If the script calls with wrong number of args, Rhai won't find a matching function signature.
|
||||
|
||||
#(#arg_conversions)*
|
||||
|
||||
// The following allow might be needed if the original function's result is directly usable
|
||||
// without explicit conversion to Dynamic, but the wrapper expects Dynamic for unit returns.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
let result = #fn_name(#(#original_fn_call_args),*);
|
||||
#return_conversion
|
||||
}
|
||||
};
|
||||
|
||||
// eprintln!("Generated code for {}:\n{}", stringify!(#fn_name), expanded.to_string());
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
|
||||
#[proc_macro_derive(FromRhaiMap, attributes(rhai_map_field))]
|
||||
pub fn derive_from_rhai_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = &input.ident;
|
||||
|
||||
// Helper function to check if a type is Option<T> and extract T
|
||||
// Returns (is_option, inner_type_quote_option)
|
||||
fn get_option_inner_type(ty: &Type) -> (bool, Option<&Type>) {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if type_path.path.segments.len() == 1 && type_path.path.segments.first().unwrap().ident == "Option" {
|
||||
if let PathArguments::AngleBracketed(params) = &type_path.path.segments.first().unwrap().arguments {
|
||||
if params.args.len() == 1 {
|
||||
if let GenericArgument::Type(inner_ty) = params.args.first().unwrap() {
|
||||
return (true, Some(inner_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(false, None)
|
||||
}
|
||||
|
||||
// Helper function to check if a type is Vec<T> and extract T
|
||||
// Returns (is_vec, inner_type_quote_option)
|
||||
fn get_vec_inner_type(ty: &Type) -> (bool, Option<&Type>) {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if type_path.path.segments.len() == 1 && type_path.path.segments.first().unwrap().ident == "Vec" {
|
||||
if let PathArguments::AngleBracketed(params) = &type_path.path.segments.first().unwrap().arguments {
|
||||
if params.args.len() == 1 {
|
||||
if let GenericArgument::Type(inner_ty) = params.args.first().unwrap() {
|
||||
return (true, Some(inner_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(false, None)
|
||||
}
|
||||
|
||||
// Helper to get the string representation of a type, simplified (e.g., "MyStruct", "String", "i64")
|
||||
fn get_simple_type_str(ty: &Type) -> String {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if let Some(segment) = type_path.path.segments.last() {
|
||||
return segment.ident.to_string();
|
||||
}
|
||||
}
|
||||
quote!(#ty).to_string().replace(' ', "").replace("::", "_") // fallback, might need refinement
|
||||
}
|
||||
|
||||
// Helper to check if a type is a primitive type based on its string representation
|
||||
fn is_primitive_type_str(simple_type_str: &str) -> bool {
|
||||
["String", "INT", "i64", "FLOAT", "f64", "bool"].contains(&simple_type_str)
|
||||
}
|
||||
|
||||
let fields_data = match &input.data {
|
||||
Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => &fields.named,
|
||||
_ => panic!("FromRhaiMapDerive only supports structs with named fields"),
|
||||
};
|
||||
|
||||
let mut field_value_declarations = Vec::new();
|
||||
let mut struct_field_assignments = Vec::new();
|
||||
|
||||
for field in fields_data.iter() {
|
||||
let field_name_ident = field.ident.as_ref().unwrap(); // Changed variable name for clarity
|
||||
let field_name_str = field_name_ident.to_string();
|
||||
let field_name_str_lit = LitStr::new(&field_name_str, field_name_ident.span()); // Corrected: Use LitStr for string literals
|
||||
let field_ty = &field.ty;
|
||||
let field_value_ident = format_ident!("__field_val_{}", field_name_str);
|
||||
|
||||
let (is_option, option_inner_ty_opt) = get_option_inner_type(field_ty);
|
||||
let type_for_vec_check = if is_option { option_inner_ty_opt.unwrap() } else { field_ty };
|
||||
let (is_vec, vec_inner_ty_opt) = get_vec_inner_type(type_for_vec_check);
|
||||
|
||||
let assignment_code = if is_option {
|
||||
let option_inner_ty = option_inner_ty_opt.expect("Option inner type not found");
|
||||
if is_vec { // Option<Vec<T>>
|
||||
let vec_element_ty = vec_inner_ty_opt.expect("Option<Vec<T>> inner T not found");
|
||||
let vec_element_ty_str = get_simple_type_str(vec_element_ty);
|
||||
|
||||
let element_conversion_logic = if is_primitive_type_str(&vec_element_ty_str) {
|
||||
quote! {
|
||||
let el_for_cast = el.clone();
|
||||
let actual_type_name = el.type_name();
|
||||
el_for_cast.try_cast::<#vec_element_ty>().ok_or_else(|| format!(
|
||||
"Element in Option<Vec> for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#vec_element_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = el.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for element in Option<Vec> for key '{}', got {}", #field_name_str_lit, el.type_name()))?;
|
||||
#vec_element_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
map.get(#field_name_str_lit).cloned().map(|dyn_val_array| {
|
||||
dyn_val_array.into_array().map_err(|e| e.to_string())?.into_iter().map(|el| {
|
||||
#element_conversion_logic
|
||||
}).collect::<Result<Vec<#vec_element_ty>, String>>()
|
||||
}).transpose()?
|
||||
}
|
||||
} else { // Option<T>
|
||||
let option_inner_ty_str = get_simple_type_str(option_inner_ty);
|
||||
let conversion_logic = if is_primitive_type_str(&option_inner_ty_str) {
|
||||
quote! {
|
||||
let val_for_cast = val.clone();
|
||||
let actual_type_name = val.type_name();
|
||||
val_for_cast.try_cast::<#option_inner_ty>().ok_or_else(|| format!(
|
||||
"Value in Option for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#option_inner_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = val.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for Option key '{}', got {}", #field_name_str_lit, val.type_name()))?;
|
||||
#option_inner_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
map.get(#field_name_str_lit).cloned().map(|val| {
|
||||
#conversion_logic
|
||||
}).transpose()?
|
||||
}
|
||||
}
|
||||
} else if is_vec { // Vec<T>
|
||||
let vec_inner_ty = vec_inner_ty_opt.expect("Vec inner type not found");
|
||||
let vec_inner_ty_str = get_simple_type_str(vec_inner_ty);
|
||||
|
||||
let element_conversion_logic = if is_primitive_type_str(&vec_inner_ty_str) {
|
||||
quote! {
|
||||
let el_for_cast = el.clone();
|
||||
let actual_type_name = el.type_name();
|
||||
el_for_cast.try_cast::<#vec_inner_ty>().ok_or_else(|| format!(
|
||||
"Element in Vec for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#vec_inner_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = el.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for element in Vec for key '{}', got {}", #field_name_str_lit, el.type_name()))?;
|
||||
#vec_inner_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
|
||||
quote! {{
|
||||
let dyn_val_array = map.get(#field_name_str_lit).cloned()
|
||||
.ok_or_else(|| format!("Key '{}' not found for Vec", #field_name_str_lit))?
|
||||
.into_array().map_err(|e| e.to_string())?;
|
||||
dyn_val_array.into_iter().map(|el| {
|
||||
#element_conversion_logic
|
||||
}).collect::<Result<Vec<#vec_inner_ty>, String>>()?
|
||||
}}
|
||||
} else { // Direct field T
|
||||
let field_ty_str = get_simple_type_str(field_ty);
|
||||
let conversion_logic = if is_primitive_type_str(&field_ty_str) {
|
||||
quote! {
|
||||
let dyn_val_for_cast = dyn_val_cloned.clone();
|
||||
let actual_type_name = dyn_val_cloned.type_name();
|
||||
dyn_val_for_cast.try_cast::<#field_ty>().ok_or_else(|| format!(
|
||||
"Value in map for key '{}' is not of type {}, but of type {}",
|
||||
#field_name_str_lit, stringify!(#field_ty), actual_type_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let map_val = dyn_val_cloned.try_cast::<::rhai::Map>().ok_or_else(|| format!("Expected map for key '{}', got {}", #field_name_str_lit, dyn_val_cloned.type_name()))?;
|
||||
#field_ty::from_rhai_map(map_val)
|
||||
}
|
||||
};
|
||||
quote! {{
|
||||
let dyn_val_cloned = map.get(#field_name_str_lit).cloned()
|
||||
.ok_or_else(|| format!("Key '{}' not found", #field_name_str_lit))?;
|
||||
#conversion_logic?
|
||||
}}
|
||||
};
|
||||
|
||||
field_value_declarations.push(quote! { let #field_value_ident = #assignment_code; });
|
||||
struct_field_assignments.push(quote_spanned!(field_name_ident.span()=> #field_name_ident: #field_value_ident));
|
||||
}
|
||||
|
||||
let gen = quote! {
|
||||
impl #name {
|
||||
pub fn from_rhai_map(map: ::rhai::Map) -> Result<Self, String> {
|
||||
#(#field_value_declarations)*
|
||||
Ok(Self {
|
||||
#(#struct_field_assignments),*
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
proc_macro::TokenStream::from(gen)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ToRhaiMap)]
|
||||
pub fn derive_to_rhai_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
let name = &ast.ident;
|
||||
|
||||
let mut field_insertions = Vec::new();
|
||||
|
||||
if let syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) = ast.data {
|
||||
for field in fields.named {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let field_name_str = field_name.to_string();
|
||||
let ty = &field.ty;
|
||||
|
||||
let mut _is_vec_of_custom_struct = false;
|
||||
let mut is_direct_custom_struct = false;
|
||||
|
||||
let _field_type_name_for_logic = quote!(#ty).to_string().replace(" ", "");
|
||||
|
||||
if let syn::Type::Path(type_path) = ty {
|
||||
if type_path.path.segments.len() == 1 && type_path.path.segments.first().unwrap().ident == "Vec" {
|
||||
if let syn::PathArguments::AngleBracketed(angle_bracketed_args) = &type_path.path.segments.first().unwrap().arguments {
|
||||
if let Some(syn::GenericArgument::Type(_inner_ty_syn @ syn::Type::Path(inner_type_path))) = angle_bracketed_args.args.first() {
|
||||
if let Some(inner_segment) = inner_type_path.path.segments.last() {
|
||||
let inner_type_str = inner_segment.ident.to_string();
|
||||
if !["String", "INT", "i64", "FLOAT", "f64", "bool"].contains(&inner_type_str.as_str()) {
|
||||
_is_vec_of_custom_struct = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !["String", "INT", "i64", "FLOAT", "f64", "bool"].contains(&type_path.path.segments.first().unwrap().ident.to_string().as_str()) {
|
||||
// It's not a Vec, and not a primitive, so assume it's a direct custom struct
|
||||
is_direct_custom_struct = true;
|
||||
}
|
||||
}
|
||||
|
||||
if _is_vec_of_custom_struct {
|
||||
field_insertions.push(quote! {
|
||||
{
|
||||
let rhai_array: rhai::Array = self.#field_name.iter().map(|item| {
|
||||
// item is &CustomStruct, to_rhai_map() takes &self
|
||||
rhai::Dynamic::from(item.to_rhai_map())
|
||||
}).collect();
|
||||
map.insert(#field_name_str.into(), rhai::Dynamic::from(rhai_array));
|
||||
}
|
||||
});
|
||||
// FIX: Logic for direct custom struct field
|
||||
} else if is_direct_custom_struct {
|
||||
field_insertions.push(quote! {
|
||||
map.insert(#field_name_str.into(), rhai::Dynamic::from(self.#field_name.to_rhai_map()));
|
||||
});
|
||||
} else {
|
||||
// This handles Vec<Primitive> and direct Primitives
|
||||
let type_str = quote!(#ty).to_string().replace(" ", "");
|
||||
if type_str.starts_with("Vec<") {
|
||||
field_insertions.push(quote! {
|
||||
{
|
||||
let rhai_array: rhai::Array = self.#field_name.iter().map(|item| {
|
||||
// item is &Primitive, clone it for .into()
|
||||
item.clone().into()
|
||||
}).collect();
|
||||
map.insert(#field_name_str.into(), rhai::Dynamic::from(rhai_array));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Direct primitive field
|
||||
field_insertions.push(quote! {
|
||||
map.insert(#field_name_str.into(), self.#field_name.clone().into());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
|
||||
|
||||
let expanded = quote! {
|
||||
impl #impl_generics ToRhaiMap for #name #ty_generics #where_clause {
|
||||
fn to_rhai_map(&self) -> rhai::Map {
|
||||
let mut map = rhai::Map::new();
|
||||
#(#field_insertions)*
|
||||
map
|
||||
}
|
||||
}
|
||||
};
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
// Rest of the code remains the same
|
BIN
_archive/listen/.DS_Store
vendored
BIN
_archive/listen/.DS_Store
vendored
Binary file not shown.
2620
_archive/listen/Cargo.lock
generated
2620
_archive/listen/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
rhai = { version = "1.15.0", features = ["sync"] }
|
||||
rhai_system = { path = "../rhai_system" }
|
||||
bytes = "1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tera = "1.0"
|
||||
once_cell = "1"
|
||||
rhai_tera = { path = "../rhai_tera" }
|
||||
calendar = { path = "../components/calendar" }
|
||||
|
||||
[[example]]
|
||||
name = "send_rhai_script"
|
||||
path = "examples/send_rhai_script.rs"
|
||||
required-features = []
|
||||
|
||||
[dev-dependencies] # Examples often use dev-dependencies, but reqwest is more like a direct dep for the example's purpose.
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"] } # Updated for async example client
|
||||
# tokio is already a main dependency, so the example can use it.
|
@ -1,38 +0,0 @@
|
||||
use reqwest::Client;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = Client::new();
|
||||
let server_url = "http://127.0.0.1:8000";
|
||||
|
||||
// Simple Rhai script that creates a map
|
||||
let rhai_script = r#"
|
||||
let message = "Hello from Rhai script!";
|
||||
let number = 40 + 2;
|
||||
#{
|
||||
greeting: message,
|
||||
calculation_result: number
|
||||
}
|
||||
"#;
|
||||
|
||||
println!("Sending Rhai script to server:\n{}", rhai_script);
|
||||
|
||||
let response = client
|
||||
.post(server_url)
|
||||
.header("Content-Type", "text/plain") // Or application/rhai, but plain text is fine
|
||||
.body(rhai_script.to_string())
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let status = response.status();
|
||||
let response_text = response.text().await?;
|
||||
|
||||
println!("\nServer responded with status: {}", status);
|
||||
println!("Response body:\n{}", response_text);
|
||||
|
||||
if !status.is_success() {
|
||||
return Err(format!("Server returned error: {} - {}", status, response_text).into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
// This script is used to initialize the Rhai system.
|
||||
// It can be left empty or used to define globally available functions/variables.
|
||||
// print("init.rhai loaded!");
|
@ -1,111 +0,0 @@
|
||||
use hyper::{
|
||||
service::{make_service_fn, service_fn},
|
||||
Body,
|
||||
Request,
|
||||
Response,
|
||||
Server,
|
||||
StatusCode,
|
||||
Method,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::path::Path;
|
||||
|
||||
use rhai_system::{create_hot_reloadable_system, System};
|
||||
use rhai::Dynamic;
|
||||
use hyper::body::to_bytes;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8000));
|
||||
|
||||
let init_script_path = Path::new("scripts/init.rhai");
|
||||
|
||||
let rhai_sys = match create_hot_reloadable_system(&[init_script_path], Some(0)) {
|
||||
Ok(system) => Arc::new(system),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create Rhai system: {}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let rhai_sys_clone = Arc::clone(&rhai_sys);
|
||||
|
||||
let make_svc = make_service_fn(move |_conn| {
|
||||
let rhai_system_for_service = Arc::clone(&rhai_sys_clone);
|
||||
async {
|
||||
Ok::<_, Infallible>(service_fn(move |req: Request<Body>| {
|
||||
let rhai_system_for_request = Arc::clone(&rhai_system_for_service);
|
||||
handle_request(rhai_system_for_request, req)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
println!("Rhai script server running at http://{}", addr);
|
||||
println!("Send POST requests with Rhai script in the body to execute.");
|
||||
|
||||
Server::bind(&addr).serve(make_svc).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_request(rhai_sys: Arc<System>, req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
match *req.method() {
|
||||
Method::POST => {
|
||||
let body_bytes = match to_bytes(req.into_body()).await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => {
|
||||
eprintln!("Error reading request body: {}", e);
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(Body::from(format!("Error reading request body: {}", e)))
|
||||
.unwrap());
|
||||
}
|
||||
};
|
||||
|
||||
let script_string = match String::from_utf8(body_bytes.to_vec()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Request body is not valid UTF-8: {}", e);
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Body::from(format!("Request body is not valid UTF-8: {}", e)))
|
||||
.unwrap());
|
||||
}
|
||||
};
|
||||
|
||||
if script_string.trim().is_empty() {
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Body::from("Rhai script body cannot be empty."))
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
println!("Executing Rhai script: \n{}", script_string);
|
||||
|
||||
match rhai_sys.engine.eval::<Dynamic>(&script_string) {
|
||||
Ok(result) => {
|
||||
let response_body = format!("{}", result);
|
||||
println!("Script result: {}", response_body);
|
||||
Ok(Response::new(Body::from(response_body)))
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!("Rhai script execution error: {}", e);
|
||||
eprintln!("{}", error_msg);
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(Body::from(error_msg))
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::METHOD_NOT_ALLOWED)
|
||||
.header("Allow", "POST")
|
||||
.body(Body::from("Method Not Allowed. Please use POST with Rhai script in the body."))
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
282
_archive/rhai_autobind_macros/Cargo.lock
generated
282
_archive/rhai_autobind_macros/Cargo.lock
generated
@ -1,282 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1acc213aa1e33611a4b20b31b738af675113e1c9944d6e3d79e3e7318ce0205"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags",
|
||||
"instant",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rhai_codegen",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"thin-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_autobind_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rhai",
|
||||
"serde",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_codegen"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
[package]
|
||||
name = "rhai_autobind_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
heck = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
rhai = "1.18.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
@ -1,33 +0,0 @@
|
||||
// calculator.rhai
|
||||
// This script demonstrates using the Calculator struct from Rust in Rhai
|
||||
|
||||
// Create a new calculator
|
||||
let calc = new_calculator();
|
||||
println("Created a new calculator with value: " + calc.value);
|
||||
|
||||
// Perform some calculations
|
||||
calc.add(5);
|
||||
println("After adding 5: " + calc.value);
|
||||
|
||||
calc.multiply(2);
|
||||
println("After multiplying by 2: " + calc.value);
|
||||
|
||||
calc.subtract(3);
|
||||
println("After subtracting 3: " + calc.value);
|
||||
|
||||
calc.divide(2);
|
||||
println("After dividing by 2: " + calc.value);
|
||||
|
||||
// Set value directly
|
||||
calc.value = 100;
|
||||
println("After setting value to 100: " + calc.value);
|
||||
|
||||
// Clear the calculator
|
||||
calc.clear();
|
||||
println("After clearing: " + calc.value);
|
||||
|
||||
// Chain operations
|
||||
let result = calc.add(10).multiply(2).subtract(5).divide(3);
|
||||
println("Result of chained operations: " + result);
|
||||
|
||||
println("Final calculator value: " + calc.value);
|
@ -1,97 +0,0 @@
|
||||
use rhai::{Engine, EvalAltResult, CustomType, TypeBuilder};
|
||||
use rhai_autobind_macros::rhai_model_export;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
// Dummy DB type for the example, as rhai_model_export requires a db_type
|
||||
struct DummyDb;
|
||||
impl DummyDb {
|
||||
fn new() -> Self { DummyDb }
|
||||
}
|
||||
|
||||
// Define a simple Calculator struct with the rhai_autobind attribute
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, rhai::CustomType)]
|
||||
#[rhai_model_export(db_type = "DummyDb")] // Provide the required db_type
|
||||
pub struct Calculator {
|
||||
pub value: f64,
|
||||
}
|
||||
|
||||
impl Calculator {
|
||||
// Constructor
|
||||
pub fn new() -> Self {
|
||||
Calculator { value: 0.0 }
|
||||
}
|
||||
|
||||
// Add method
|
||||
pub fn add(&mut self, x: f64) -> f64 {
|
||||
self.value += x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Subtract method
|
||||
pub fn subtract(&mut self, x: f64) -> f64 {
|
||||
self.value -= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Multiply method
|
||||
pub fn multiply(&mut self, x: f64) -> f64 {
|
||||
self.value *= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Divide method
|
||||
pub fn divide(&mut self, x: f64) -> f64 {
|
||||
if x == 0.0 {
|
||||
println!("Error: Division by zero!");
|
||||
return self.value;
|
||||
}
|
||||
self.value /= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Clear method
|
||||
pub fn clear(&mut self) -> f64 {
|
||||
self.value = 0.0;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Get value method
|
||||
pub fn get_value(&self) -> f64 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
println!("Rhai Calculator Example");
|
||||
println!("======================");
|
||||
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register the Calculator type with the engine using the generated function
|
||||
let dummy_db = DummyDb::new(); // Create an instance of the dummy DB
|
||||
Calculator::register_rhai_bindings_for_calculator(&mut engine, dummy_db);
|
||||
|
||||
// Register print function for output
|
||||
engine.register_fn("println", |s: &str| println!("{}", s));
|
||||
|
||||
// Create a calculator instance to demonstrate it works
|
||||
let calc = Calculator::new();
|
||||
println!("Initial value: {}", calc.value);
|
||||
|
||||
// Load and run the Rhai script
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("examples")
|
||||
.join("rhai_autobind_example")
|
||||
.join("calculator.rhai");
|
||||
|
||||
println!("Loading Rhai script from: {}", script_path.display());
|
||||
|
||||
match engine.eval_file::<()>(script_path) {
|
||||
Ok(_) => println!("Script executed successfully"),
|
||||
Err(e) => eprintln!("Error executing script: {}\nAt: {:?}", e, e.position()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, format_ident};
|
||||
use syn::{parse_macro_input, Ident, Fields, Visibility, ItemStruct, LitStr, ExprArray, Expr, Lit};
|
||||
use syn::spanned::Spanned; // Add the Spanned trait for span() method
|
||||
use heck::ToSnakeCase; // For converting struct name to snake_case for function names
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MacroArgs {
|
||||
db_type: syn::Type,
|
||||
collection_name: Option<String>,
|
||||
methods: Vec<String>, // To store method names to be registered
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for MacroArgs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut db_type_str: Option<LitStr> = None;
|
||||
let mut collection_name_str: Option<LitStr> = None;
|
||||
let mut methods_array: Option<ExprArray> = None;
|
||||
|
||||
while !input.is_empty() {
|
||||
let ident: Ident = input.parse()?;
|
||||
let _eq_token: syn::token::Eq = input.parse()?;
|
||||
if ident == "db_type" {
|
||||
db_type_str = Some(input.parse()?);
|
||||
} else if ident == "collection_name" {
|
||||
collection_name_str = Some(input.parse()?);
|
||||
} else if ident == "methods" {
|
||||
methods_array = Some(input.parse()?);
|
||||
} else {
|
||||
return Err(syn::Error::new(ident.span(), "Unknown attribute argument"));
|
||||
}
|
||||
if !input.is_empty() {
|
||||
let _comma: syn::token::Comma = input.parse()?;
|
||||
}
|
||||
}
|
||||
|
||||
let db_type_str = db_type_str.ok_or_else(|| syn::Error::new(input.span(), "Missing required attribute `db_type`"))?;
|
||||
let db_type: syn::Type = syn::parse_str(&db_type_str.value())?;
|
||||
let collection_name = collection_name_str.map(|s| s.value());
|
||||
|
||||
let mut methods = Vec::new();
|
||||
if let Some(array_expr) = methods_array {
|
||||
for expr in array_expr.elems {
|
||||
if let Expr::Lit(expr_lit) = expr {
|
||||
if let Lit::Str(lit_str) = expr_lit.lit {
|
||||
methods.push(lit_str.value());
|
||||
} else {
|
||||
return Err(syn::Error::new(expr_lit.lit.span(), "Method name must be a string literal"));
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new(expr.span(), "Method name must be a string literal"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MacroArgs { db_type, collection_name, methods })
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn rhai_model_export(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let macro_args = parse_macro_input!(attr as MacroArgs);
|
||||
let input_struct = parse_macro_input!(item as ItemStruct);
|
||||
|
||||
let struct_name = &input_struct.ident;
|
||||
let struct_name_str = struct_name.to_string();
|
||||
let struct_name_snake_case = struct_name_str.to_snake_case(); // Use heck for snake_case
|
||||
|
||||
let db_type = macro_args.db_type;
|
||||
let collection_name = macro_args.collection_name.unwrap_or_else(|| {
|
||||
// Basic pluralization
|
||||
if struct_name_snake_case.ends_with('y') {
|
||||
format!("{}ies", &struct_name_snake_case[..struct_name_snake_case.len()-1])
|
||||
} else if struct_name_snake_case.ends_with('s') {
|
||||
format!("{}es", struct_name_snake_case)
|
||||
} else {
|
||||
format!("{}s", struct_name_snake_case)
|
||||
}
|
||||
});
|
||||
|
||||
let generated_registration_fn_name = format_ident!("register_rhai_bindings_for_{}", struct_name_snake_case);
|
||||
let constructor_fn_name = format!("new_{}", struct_name_snake_case);
|
||||
|
||||
let mut field_registrations = Vec::new();
|
||||
if let Fields::Named(fields) = &input_struct.fields {
|
||||
for field in fields.named.iter() {
|
||||
if let (Some(field_name), Visibility::Public(_)) = (&field.ident, &field.vis) {
|
||||
let field_name_str = field_name.to_string();
|
||||
let field_type = &field.ty;
|
||||
|
||||
// Register getter
|
||||
field_registrations.push(quote! {
|
||||
engine.register_get(#field_name_str, |s: &mut #struct_name| s.#field_name.clone());
|
||||
});
|
||||
// Register setter
|
||||
field_registrations.push(quote! {
|
||||
engine.register_set(#field_name_str, |s: &mut #struct_name, val: #field_type| s.#field_name = val);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut method_registrations = Vec::new();
|
||||
for method_name_str in ¯o_args.methods {
|
||||
let method_ident = format_ident!("{}", method_name_str);
|
||||
let rhai_method_name = method_name_str.clone(); // Rhai function name can be the same as Rust method name
|
||||
method_registrations.push(quote! {
|
||||
engine.register_fn(#rhai_method_name, #struct_name::#method_ident);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Create a formatted string of method names for printing
|
||||
let methods_str = format!("{:?}", macro_args.methods);
|
||||
|
||||
let output = quote! {
|
||||
#input_struct // Re-emit the original struct definition
|
||||
|
||||
impl #struct_name {
|
||||
pub fn #generated_registration_fn_name(
|
||||
engine: &mut rhai::Engine,
|
||||
db: #db_type // db is now passed but not used by default for non-DB methods
|
||||
) {
|
||||
// For rhai_wrapper if/when DB operations are added
|
||||
// use rhai_wrapper::*;
|
||||
|
||||
engine.build_type::<#struct_name>();
|
||||
|
||||
// Register constructor (e.g., new_calculator for Calculator::new())
|
||||
engine.register_fn(#constructor_fn_name, #struct_name::new);
|
||||
|
||||
// Register field getters and setters
|
||||
#(#field_registrations)*
|
||||
|
||||
// Register specified instance methods
|
||||
#(#method_registrations)*
|
||||
|
||||
// Placeholder for DB function registrations
|
||||
// e.g., get_by_id, get_all, save, delete
|
||||
|
||||
println!("Registered {} with Rhai (collection: '{}'). Constructor: '{}'. Methods: {}. Fields accessible.",
|
||||
#struct_name_str,
|
||||
#collection_name,
|
||||
#constructor_fn_name,
|
||||
#methods_str
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(output)
|
||||
}
|
@ -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;
|
BIN
_archive/talk/.DS_Store
vendored
BIN
_archive/talk/.DS_Store
vendored
Binary file not shown.
2146
_archive/talk/Cargo.lock
generated
2146
_archive/talk/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "talk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.4.0"
|
||||
actix-files = "0.6.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
rhai = "1.18.0"
|
||||
heromodels = { path = "../../db/heromodels" }
|
||||
rhai_wrapper = { path = "../rhai_wrapper" }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
@ -1,331 +0,0 @@
|
||||
use actix_files::Files;
|
||||
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::models::calendar::Calendar;
|
||||
use heromodels::models::governance::{Proposal, Ballot, VoteOption, ProposalStatus, VoteEventStatus};
|
||||
use rhai::Engine;
|
||||
use rhai_wrapper::wrap_vec_return;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use chrono::{Utc, Duration};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ScriptRequest {
|
||||
script: String,
|
||||
model_type: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ScriptResponse {
|
||||
output: String,
|
||||
success: bool,
|
||||
}
|
||||
|
||||
// Function to set up the calendar model in the Rhai engine
|
||||
fn setup_calendar_engine(engine: &mut Engine, db: Arc<OurDB>) {
|
||||
// Register the Calendar type with Rhai
|
||||
Calendar::register_rhai_bindings_for_calendar(engine, db.clone());
|
||||
|
||||
// Register a function to get the database instance
|
||||
engine.register_fn("get_db", move || db.clone());
|
||||
|
||||
// Register a calendar builder function
|
||||
engine.register_fn("calendar__builder", |id: i64| {
|
||||
Calendar::new(id as u32)
|
||||
});
|
||||
|
||||
// Register setter methods for Calendar properties
|
||||
engine.register_fn("set_description", |calendar: &mut Calendar, desc: String| {
|
||||
calendar.description = Some(desc);
|
||||
});
|
||||
|
||||
// Register getter methods for Calendar properties
|
||||
engine.register_fn("get_description", |calendar: Calendar| -> String {
|
||||
calendar.description.clone().unwrap_or_default()
|
||||
});
|
||||
|
||||
// Register getter for base_data.id
|
||||
engine.register_fn("get_id", |calendar: Calendar| -> i64 {
|
||||
calendar.base_data.id as i64
|
||||
});
|
||||
|
||||
// Register additional functions needed by the script
|
||||
engine.register_fn("set_calendar", |_db: Arc<OurDB>, _calendar: Calendar| {
|
||||
// In a real implementation, this would save the calendar to the database
|
||||
println!("Calendar saved: {}", _calendar.name);
|
||||
});
|
||||
|
||||
engine.register_fn("get_calendar_by_id", |_db: Arc<OurDB>, id: i64| -> Calendar {
|
||||
// In a real implementation, this would retrieve the calendar from the database
|
||||
Calendar::new(id as u32)
|
||||
});
|
||||
|
||||
// Register a function to check if a calendar exists
|
||||
engine.register_fn("calendar_exists", |_db: Arc<OurDB>, id: i64| -> bool {
|
||||
// In a real implementation, this would check if the calendar exists in the database
|
||||
id == 1 || id == 2
|
||||
});
|
||||
|
||||
// Define the function separately to use with the wrap_vec_return macro
|
||||
fn get_all_calendars(_db: Arc<OurDB>) -> Vec<Calendar> {
|
||||
// In a real implementation, this would retrieve all calendars from the database
|
||||
vec![Calendar::new(1), Calendar::new(2)]
|
||||
}
|
||||
|
||||
// Register the function with the wrap_vec_return macro
|
||||
engine.register_fn("get_all_calendars", wrap_vec_return!(get_all_calendars, Arc<OurDB> => Calendar));
|
||||
|
||||
engine.register_fn("delete_calendar_by_id", |_db: Arc<OurDB>, _id: i64| {
|
||||
// In a real implementation, this would delete the calendar from the database
|
||||
println!("Calendar deleted with ID: {}", _id);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to set up the governance model in the Rhai engine
|
||||
fn setup_governance_engine(engine: &mut Engine, db: Arc<OurDB>) {
|
||||
// Register the Proposal type with Rhai
|
||||
Proposal::register_rhai_bindings_for_proposal(engine, db.clone());
|
||||
|
||||
// Register the Ballot type with Rhai
|
||||
Ballot::register_rhai_bindings_for_ballot(engine, db.clone());
|
||||
|
||||
// Register a function to get the database instance
|
||||
engine.register_fn("get_db", move || db.clone());
|
||||
|
||||
// Register builder functions for Proposal and related types
|
||||
engine.register_fn("create_proposal", |id: i64, creator_id: String, title: String, description: String| {
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
Proposal::new(id as u32, creator_id, title, description, start_date, end_date)
|
||||
});
|
||||
|
||||
engine.register_fn("create_vote_option", |id: i64, text: String| {
|
||||
VoteOption::new(id as u8, text)
|
||||
});
|
||||
|
||||
engine.register_fn("create_ballot", |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| {
|
||||
Ballot::new(id as u32, user_id as u32, vote_option_id as u8, shares_count)
|
||||
});
|
||||
|
||||
// Register getter and setter methods for Proposal properties
|
||||
engine.register_fn("get_title", |proposal: Proposal| -> String {
|
||||
proposal.title.clone()
|
||||
});
|
||||
|
||||
engine.register_fn("get_description", |proposal: Proposal| -> String {
|
||||
proposal.description.clone()
|
||||
});
|
||||
|
||||
engine.register_fn("get_creator_id", |proposal: Proposal| -> String {
|
||||
proposal.creator_id.clone()
|
||||
});
|
||||
|
||||
engine.register_fn("get_id", |proposal: Proposal| -> i64 {
|
||||
proposal.base_data.id as i64
|
||||
});
|
||||
|
||||
engine.register_fn("get_status", |proposal: Proposal| -> String {
|
||||
format!("{:?}", proposal.status)
|
||||
});
|
||||
|
||||
engine.register_fn("get_vote_status", |proposal: Proposal| -> String {
|
||||
format!("{:?}", proposal.vote_status)
|
||||
});
|
||||
|
||||
// Register methods for proposal operations
|
||||
engine.register_fn("add_option_to_proposal", |proposal: Proposal, option_id: i64, option_text: String| -> Proposal {
|
||||
proposal.add_option(option_id as u8, option_text)
|
||||
});
|
||||
|
||||
engine.register_fn("cast_vote_on_proposal", |proposal: Proposal, ballot_id: i64, user_id: i64, option_id: i64, shares: i64| -> Proposal {
|
||||
proposal.cast_vote(ballot_id as u32, user_id as u32, option_id as u8, shares)
|
||||
});
|
||||
|
||||
engine.register_fn("change_proposal_status", |proposal: Proposal, status_str: String| -> Proposal {
|
||||
let new_status = match status_str.as_str() {
|
||||
"Draft" => ProposalStatus::Draft,
|
||||
"Active" => ProposalStatus::Active,
|
||||
"Approved" => ProposalStatus::Approved,
|
||||
"Rejected" => ProposalStatus::Rejected,
|
||||
"Cancelled" => ProposalStatus::Cancelled,
|
||||
_ => ProposalStatus::Draft,
|
||||
};
|
||||
proposal.change_proposal_status(new_status)
|
||||
});
|
||||
|
||||
engine.register_fn("change_vote_event_status", |proposal: Proposal, status_str: String| -> Proposal {
|
||||
let new_status = match status_str.as_str() {
|
||||
"Open" => VoteEventStatus::Open,
|
||||
"Closed" => VoteEventStatus::Closed,
|
||||
"Cancelled" => VoteEventStatus::Cancelled,
|
||||
_ => VoteEventStatus::Open,
|
||||
};
|
||||
proposal.change_vote_event_status(new_status)
|
||||
});
|
||||
|
||||
// Register functions for database operations
|
||||
engine.register_fn("save_proposal", |_db: Arc<OurDB>, proposal: Proposal| {
|
||||
println!("Proposal saved: {}", proposal.title);
|
||||
});
|
||||
|
||||
engine.register_fn("get_proposal_by_id", |_db: Arc<OurDB>, id: i64| -> Proposal {
|
||||
// In a real implementation, this would retrieve the proposal from the database
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
Proposal::new(id as u32, "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date)
|
||||
});
|
||||
|
||||
// Register a function to check if a proposal exists
|
||||
engine.register_fn("proposal_exists", |_db: Arc<OurDB>, id: i64| -> bool {
|
||||
// In a real implementation, this would check if the proposal exists in the database
|
||||
id == 1 || id == 2
|
||||
});
|
||||
|
||||
// Define the function for get_all_proposals
|
||||
fn get_all_proposals(_db: Arc<OurDB>) -> Vec<Proposal> {
|
||||
// In a real implementation, this would retrieve all proposals from the database
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
vec![
|
||||
Proposal::new(1, "Creator 1", "Proposal 1", "Description 1", start_date, end_date),
|
||||
Proposal::new(2, "Creator 2", "Proposal 2", "Description 2", start_date, end_date)
|
||||
]
|
||||
}
|
||||
|
||||
// Register the function with the wrap_vec_return macro
|
||||
engine.register_fn("get_all_proposals", wrap_vec_return!(get_all_proposals, Arc<OurDB> => Proposal));
|
||||
|
||||
engine.register_fn("delete_proposal_by_id", |_db: Arc<OurDB>, _id: i64| {
|
||||
// In a real implementation, this would delete the proposal from the database
|
||||
println!("Proposal deleted with ID: {}", _id);
|
||||
});
|
||||
|
||||
// Register helper functions for accessing proposal options and ballots
|
||||
engine.register_fn("get_option_count", |proposal: Proposal| -> i64 {
|
||||
proposal.options.len() as i64
|
||||
});
|
||||
|
||||
engine.register_fn("get_option_at", |proposal: Proposal, index: i64| -> VoteOption {
|
||||
if index >= 0 && index < proposal.options.len() as i64 {
|
||||
proposal.options[index as usize].clone()
|
||||
} else {
|
||||
VoteOption::new(0, "Invalid Option")
|
||||
}
|
||||
});
|
||||
|
||||
engine.register_fn("get_option_text", |option: VoteOption| -> String {
|
||||
option.text.clone()
|
||||
});
|
||||
|
||||
engine.register_fn("get_option_votes", |option: VoteOption| -> i64 {
|
||||
option.count
|
||||
});
|
||||
|
||||
engine.register_fn("get_ballot_count", |proposal: Proposal| -> i64 {
|
||||
proposal.ballots.len() as i64
|
||||
});
|
||||
|
||||
engine.register_fn("get_ballot_at", |proposal: Proposal, index: i64| -> Ballot {
|
||||
if index >= 0 && index < proposal.ballots.len() as i64 {
|
||||
proposal.ballots[index as usize].clone()
|
||||
} else {
|
||||
Ballot::new(0, 0, 0, 0)
|
||||
}
|
||||
});
|
||||
|
||||
engine.register_fn("get_ballot_user_id", |ballot: Ballot| -> i64 {
|
||||
ballot.user_id as i64
|
||||
});
|
||||
|
||||
engine.register_fn("get_ballot_option_id", |ballot: Ballot| -> i64 {
|
||||
ballot.vote_option_id as i64
|
||||
});
|
||||
|
||||
engine.register_fn("get_ballot_shares", |ballot: Ballot| -> i64 {
|
||||
ballot.shares_count
|
||||
});
|
||||
}
|
||||
|
||||
// Endpoint to execute Rhai scripts
|
||||
async fn execute_script(req: web::Json<ScriptRequest>) -> impl Responder {
|
||||
// Create a string to capture stdout
|
||||
let output = Arc::new(Mutex::new(String::new()));
|
||||
|
||||
// Initialize Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register print function to capture output
|
||||
let output_clone = output.clone();
|
||||
engine.register_fn("print", move |text: String| {
|
||||
if let Ok(mut output_guard) = output_clone.lock() {
|
||||
output_guard.push_str(&text);
|
||||
output_guard.push('\n');
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize database
|
||||
let db = Arc::new(OurDB::new("temp_rhai_playground_db", true).expect("Failed to create database"));
|
||||
|
||||
// Set up the engine based on the model type
|
||||
match req.model_type.as_str() {
|
||||
"calendar" => setup_calendar_engine(&mut engine, db),
|
||||
"governance" => setup_governance_engine(&mut engine, db),
|
||||
_ => {
|
||||
return HttpResponse::BadRequest().json(ScriptResponse {
|
||||
output: "Invalid model type. Supported types: 'calendar', 'governance'".to_string(),
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the script
|
||||
match engine.eval::<()>(&req.script) {
|
||||
Ok(_) => {
|
||||
let output_str = output.lock().unwrap_or_else(|_| panic!("Failed to lock output")).clone();
|
||||
HttpResponse::Ok().json(ScriptResponse {
|
||||
output: output_str,
|
||||
success: true,
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
HttpResponse::Ok().json(ScriptResponse {
|
||||
output: format!("Script execution failed: {}", e),
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Endpoint to get example scripts
|
||||
async fn get_example_script(path: web::Path<String>) -> impl Responder {
|
||||
let script_type = path.into_inner();
|
||||
|
||||
let script_content = match script_type.as_str() {
|
||||
"calendar" => {
|
||||
std::fs::read_to_string("../../db/heromodels/examples/calendar_rhai/calendar.rhai")
|
||||
.unwrap_or_else(|_| "// Failed to load calendar example".to_string())
|
||||
}
|
||||
"governance" => {
|
||||
std::fs::read_to_string("../../db/heromodels/examples/governance_rhai/governance.rhai")
|
||||
.unwrap_or_else(|_| "// Failed to load governance example".to_string())
|
||||
}
|
||||
_ => "// Invalid example type".to_string(),
|
||||
};
|
||||
|
||||
HttpResponse::Ok().body(script_content)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
println!("Starting Rhai Web Playground server at http://localhost:8080");
|
||||
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.service(web::resource("/api/execute").route(web::post().to(execute_script)))
|
||||
.service(web::resource("/api/example/{script_type}").route(web::get().to(get_example_script)))
|
||||
.service(Files::new("/", "./static").index_file("index.html"))
|
||||
})
|
||||
.bind("127.0.0.1:8080")?
|
||||
.run()
|
||||
.await
|
||||
}
|
@ -1,235 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Rhai Script Playground</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.65.2/lib/codemirror.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<style>
|
||||
.btn-success { background-color: #20c997 !important; border-color: #20c997 !important; color: white !important; }
|
||||
.btn-danger { background-color: #f87171 !important; border-color: #f87171 !important; color: white !important; }
|
||||
.btn-warning { background-color: #ffc107 !important; border-color: #ffc107 !important; color: black !important; }
|
||||
|
||||
/* Toast styling */
|
||||
.toast-container {
|
||||
z-index: 1050;
|
||||
}
|
||||
|
||||
.toast {
|
||||
min-width: 300px;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<header class="py-3 mb-3 border-bottom">
|
||||
<h1 class="text-center">Rhai Script Talk</h1>
|
||||
</header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 pe-md-2">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Script Input</h5>
|
||||
<div>
|
||||
<select id="modelSelect" class="form-select form-select-sm">
|
||||
<option value="calendar">Calendar Model</option>
|
||||
<option value="governance">Governance Model</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<textarea id="scriptInput"></textarea>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-between">
|
||||
<button id="loadExampleBtn" class="btn btn-outline-secondary">Load Example</button>
|
||||
<button id="runScriptBtn" class="btn btn-primary">Run Script</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 ps-md-2">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Output</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="input-group me-2" style="width: 250px;">
|
||||
<select id="listenerSelect" class="form-select form-select-sm">
|
||||
<option value="">Custom URL...</option>
|
||||
<option value="http://localhost:3000/governance">Governance Listener</option>
|
||||
<option value="http://localhost:3000/finance">Finance Listener</option>
|
||||
<option value="http://localhost:3000/calendar">Calendar Listener</option>
|
||||
</select>
|
||||
<input type="text" id="listenerUrl" class="form-control form-control-sm" placeholder="Listener URL" style="display: none;">
|
||||
</div>
|
||||
<button id="connectListenerBtn" class="btn btn-sm btn-outline-secondary d-flex align-items-center">
|
||||
<span id="statusCircle" class="me-2" style="width: 8px; height: 8px; border-radius: 50%; background-color: #6c757d; display: inline-block;"></span>
|
||||
<span id="connectBtnText">Connect</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<pre id="outputArea" class="p-3"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast container for notifications -->
|
||||
<div class="toast-container position-fixed bottom-0 end-0 p-3"></div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const listenerUrlInput = document.getElementById('listenerUrl');
|
||||
const listenerSelect = document.getElementById('listenerSelect');
|
||||
const connectListenerBtn = document.getElementById('connectListenerBtn');
|
||||
const statusCircle = document.getElementById('statusCircle');
|
||||
const toastContainer = document.querySelector('.toast-container');
|
||||
|
||||
// Handle listener selection dropdown
|
||||
listenerSelect.addEventListener('change', () => {
|
||||
const selectedValue = listenerSelect.value;
|
||||
|
||||
if (selectedValue === '') {
|
||||
// Show custom URL input when "Custom URL..." is selected
|
||||
listenerUrlInput.style.display = 'block';
|
||||
listenerUrlInput.focus();
|
||||
} else {
|
||||
// Hide custom URL input when a predefined option is selected
|
||||
listenerUrlInput.style.display = 'none';
|
||||
// Clear any previous custom URL
|
||||
listenerUrlInput.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Function to show toast notifications
|
||||
function showToast(message, type = 'error') {
|
||||
const toastId = 'toast-' + Date.now();
|
||||
const bgClass = type === 'error' ? 'bg-danger' : (type === 'warning' ? 'bg-warning' : 'bg-info');
|
||||
const headerText = type === 'error' ? 'Error' : (type === 'warning' ? 'Warning' : 'Info');
|
||||
|
||||
// Create the toast element
|
||||
const toastDiv = document.createElement('div');
|
||||
toastDiv.className = 'toast mb-3';
|
||||
toastDiv.id = toastId;
|
||||
toastDiv.setAttribute('role', 'alert');
|
||||
toastDiv.setAttribute('aria-live', 'assertive');
|
||||
toastDiv.setAttribute('aria-atomic', 'true');
|
||||
|
||||
toastDiv.innerHTML = `
|
||||
<div class="toast-header ${bgClass} text-white">
|
||||
<strong class="me-auto">${headerText}</strong>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
${message}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add to container
|
||||
toastContainer.appendChild(toastDiv);
|
||||
|
||||
// Initialize and show the toast
|
||||
const toast = new bootstrap.Toast(toastDiv, {
|
||||
autohide: true,
|
||||
delay: 10000
|
||||
});
|
||||
toast.show();
|
||||
|
||||
// Remove the toast element after it's hidden
|
||||
toastDiv.addEventListener('hidden.bs.toast', () => {
|
||||
toastDiv.remove();
|
||||
});
|
||||
}
|
||||
|
||||
connectListenerBtn.addEventListener('click', async () => {
|
||||
// Get URL from either dropdown or text input
|
||||
let url = listenerSelect.value;
|
||||
|
||||
// If custom URL option is selected, use the text input value
|
||||
if (url === '') {
|
||||
url = listenerUrlInput.value.trim();
|
||||
if (!url) {
|
||||
showToast('Please enter a listener URL.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const connectBtnText = document.getElementById('connectBtnText');
|
||||
const statusCircle = document.getElementById('statusCircle');
|
||||
|
||||
// Set to connecting state
|
||||
connectBtnText.textContent = 'Connecting...';
|
||||
connectListenerBtn.classList.remove('btn-outline-secondary', 'btn-success', 'btn-danger');
|
||||
connectListenerBtn.classList.add('btn-warning');
|
||||
statusCircle.style.backgroundColor = '#e6a800'; // Darker yellow for the circle
|
||||
|
||||
// Disable the button during connection attempts
|
||||
connectListenerBtn.disabled = true;
|
||||
|
||||
// Function to attempt a ping
|
||||
const attemptPing = async () => {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
// Set a short timeout to fail faster
|
||||
signal: AbortSignal.timeout(800)
|
||||
});
|
||||
return true; // Connection successful
|
||||
} catch (error) {
|
||||
console.error('Ping attempt failed:', error);
|
||||
return false; // Connection failed
|
||||
}
|
||||
};
|
||||
|
||||
// Try pinging multiple times with 1-second intervals
|
||||
let isConnected = false;
|
||||
const maxAttempts = 5;
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
connectBtnText.textContent = `Connecting... (${attempt}/${maxAttempts})`;
|
||||
|
||||
isConnected = await attemptPing();
|
||||
if (isConnected) {
|
||||
break; // Exit the loop if connection is successful
|
||||
}
|
||||
|
||||
if (attempt < maxAttempts) {
|
||||
// Wait 1 second before the next attempt
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
// Re-enable the button
|
||||
connectListenerBtn.disabled = false;
|
||||
|
||||
if (isConnected) {
|
||||
// Set to connected state
|
||||
connectBtnText.textContent = 'Connected';
|
||||
connectListenerBtn.classList.remove('btn-outline-secondary', 'btn-warning', 'btn-danger');
|
||||
connectListenerBtn.classList.add('btn-success');
|
||||
statusCircle.style.backgroundColor = '#198754'; // Darker green for the circle
|
||||
} else {
|
||||
// Set to offline state
|
||||
connectBtnText.textContent = 'Connect';
|
||||
connectListenerBtn.classList.remove('btn-outline-secondary', 'btn-warning', 'btn-success');
|
||||
connectListenerBtn.classList.add('btn-danger');
|
||||
statusCircle.style.backgroundColor = '#dc3545'; // Darker red for the circle
|
||||
|
||||
// Show error toast
|
||||
showToast(`Could not connect to ${url}. Please check the URL and try again.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.65.2/lib/codemirror.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.65.2/mode/javascript/javascript.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,131 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize CodeMirror editor
|
||||
const editor = CodeMirror.fromTextArea(document.getElementById('scriptInput'), {
|
||||
mode: 'javascript',
|
||||
lineNumbers: true,
|
||||
theme: 'default',
|
||||
indentUnit: 4,
|
||||
tabSize: 4,
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true
|
||||
});
|
||||
|
||||
// Get DOM elements
|
||||
const runScriptBtn = document.getElementById('runScriptBtn');
|
||||
const loadExampleBtn = document.getElementById('loadExampleBtn');
|
||||
const modelSelect = document.getElementById('modelSelect');
|
||||
const outputArea = document.getElementById('outputArea');
|
||||
const toastContainer = document.querySelector('.toast-container');
|
||||
|
||||
// Function to show toast notifications
|
||||
function showToast(message, type = 'error') {
|
||||
const toastId = 'toast-' + Date.now();
|
||||
const bgClass = type === 'error' ? 'bg-danger' : (type === 'warning' ? 'bg-warning' : 'bg-info');
|
||||
const headerText = type === 'error' ? 'Error' : (type === 'warning' ? 'Warning' : 'Info');
|
||||
|
||||
// Create the toast element
|
||||
const toastDiv = document.createElement('div');
|
||||
toastDiv.className = 'toast mb-3';
|
||||
toastDiv.id = toastId;
|
||||
toastDiv.setAttribute('role', 'alert');
|
||||
toastDiv.setAttribute('aria-live', 'assertive');
|
||||
toastDiv.setAttribute('aria-atomic', 'true');
|
||||
|
||||
toastDiv.innerHTML = `
|
||||
<div class="toast-header ${bgClass} text-white">
|
||||
<strong class="me-auto">${headerText}</strong>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
${message}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add to container
|
||||
toastContainer.appendChild(toastDiv);
|
||||
|
||||
// Initialize and show the toast
|
||||
const toast = new bootstrap.Toast(toastDiv, {
|
||||
autohide: true,
|
||||
delay: 10000
|
||||
});
|
||||
toast.show();
|
||||
|
||||
// Remove the toast element after it's hidden
|
||||
toastDiv.addEventListener('hidden.bs.toast', () => {
|
||||
toastDiv.remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Function to run the script
|
||||
async function runScript() {
|
||||
const script = editor.getValue();
|
||||
const modelType = modelSelect.value;
|
||||
|
||||
if (!script.trim()) {
|
||||
showToast('Please enter a script to run.');
|
||||
return;
|
||||
}
|
||||
|
||||
outputArea.textContent = 'Running script...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
script,
|
||||
model_type: modelType
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
outputArea.textContent = result.output || 'Script executed successfully with no output.';
|
||||
} else {
|
||||
outputArea.textContent = result.output || 'Script execution failed.';
|
||||
showToast(result.error || 'Script execution failed.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
outputArea.textContent = 'Script execution failed. See error details.';
|
||||
showToast(`Error: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to load example script
|
||||
async function loadExample() {
|
||||
const modelType = modelSelect.value;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/example/${modelType}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server returned ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const exampleScript = await response.text();
|
||||
|
||||
editor.setValue(exampleScript);
|
||||
outputArea.textContent = `Loaded ${modelType} example script. Click "Run Script" to execute.`;
|
||||
} catch (error) {
|
||||
// Don't show error in output area, use toast instead
|
||||
showToast(`Failed to load ${modelType} example: ${error.message}`, 'error');
|
||||
|
||||
// Clear the editor if it was a new load (not if there was already content)
|
||||
if (!editor.getValue().trim()) {
|
||||
editor.setValue('// Write your Rhai script here');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
runScriptBtn.addEventListener('click', runScript);
|
||||
loadExampleBtn.addEventListener('click', loadExample);
|
||||
|
||||
// Load default example on page load
|
||||
loadExample();
|
||||
});
|
@ -1,49 +0,0 @@
|
||||
.card {
|
||||
height: calc(100vh - 150px);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #f1f3f5;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
overflow: hidden;
|
||||
height: calc(100% - 110px);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
background-color: #f8f9fa;
|
||||
border-top: 1px solid #dee2e6;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
#outputArea {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
white-space: pre-wrap;
|
||||
background-color: #f8f9fa;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
#modelSelect {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.card {
|
||||
height: 400px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
||||
2
|
Binary file not shown.
794
engine/Cargo.lock
generated
794
engine/Cargo.lock
generated
@ -1,794 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||
dependencies = [
|
||||
"bincode_derive",
|
||||
"serde",
|
||||
"unty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode_derive"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
|
||||
dependencies = [
|
||||
"virtue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "engine"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"heromodels",
|
||||
"heromodels-derive",
|
||||
"heromodels_core",
|
||||
"rhai",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "heromodels"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
"heromodels-derive",
|
||||
"heromodels_core",
|
||||
"ourdb",
|
||||
"rhai",
|
||||
"rhai_client_macros",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tst",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heromodels-derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heromodels_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ourdb"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"log",
|
||||
"rand",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags",
|
||||
"instant",
|
||||
"no-std-compat",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rhai_codegen",
|
||||
"rust_decimal",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"thin-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_client_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rhai",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_codegen"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.37.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tst"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ourdb",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unty"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "virtue"
|
||||
version = "0.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
@ -1,37 +0,0 @@
|
||||
[package]
|
||||
name = "engine"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Central Rhai engine for heromodels"
|
||||
|
||||
[dependencies]
|
||||
rhai = { version = "1.21.0", features = ["std", "sync", "decimal", "internals"] }
|
||||
heromodels = { path = "../../db/heromodels", features = ["rhai"] }
|
||||
heromodels_core = { path = "../../db/heromodels_core" }
|
||||
chrono = "0.4"
|
||||
heromodels-derive = { path = "../../db/heromodels-derive" }
|
||||
|
||||
[features]
|
||||
default = ["calendar", "finance"]
|
||||
calendar = []
|
||||
finance = []
|
||||
# Flow module is now updated to use our approach to Rhai engine registration
|
||||
flow = []
|
||||
legal = []
|
||||
projects = []
|
||||
biz = []
|
||||
|
||||
[[example]]
|
||||
name = "calendar_example"
|
||||
path = "examples/calendar/example.rs"
|
||||
required-features = ["calendar"]
|
||||
|
||||
[[example]]
|
||||
name = "flow_example"
|
||||
path = "examples/flow/example.rs"
|
||||
required-features = ["flow"]
|
||||
|
||||
[[example]]
|
||||
name = "finance_example"
|
||||
path = "examples/finance/example.rs"
|
||||
required-features = ["finance"]
|
@ -1,93 +0,0 @@
|
||||
# HeroModels Rhai
|
||||
|
||||
A central Rhai scripting engine for the HeroModels project that provides a unified way to interact with all HeroModels types through Rhai scripts.
|
||||
|
||||
## Overview
|
||||
|
||||
This crate provides:
|
||||
|
||||
1. A central Rhai engine that registers all HeroModels modules
|
||||
2. Helper functions for evaluating Rhai scripts and ASTs
|
||||
3. Example scripts demonstrating how to use HeroModels with Rhai
|
||||
4. A mock database implementation for testing and examples
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
use engine::{create_heromodels_engine, eval_script};
|
||||
use engine::mock_db::create_mock_db;
|
||||
|
||||
// Create a mock database
|
||||
let db = create_mock_db();
|
||||
|
||||
// Create the Rhai engine with all modules registered
|
||||
let engine = create_heromodels_engine(db);
|
||||
|
||||
// Run a Rhai script
|
||||
let result = eval_script(&engine, r#"
|
||||
let calendar = new_calendar("My Calendar");
|
||||
calendar.set_description("My personal calendar");
|
||||
print(`Created calendar: ${calendar.get_name()}`);
|
||||
calendar
|
||||
"#);
|
||||
|
||||
match result {
|
||||
Ok(val) => println!("Script returned: {:?}", val),
|
||||
Err(err) => eprintln!("Script error: {}", err),
|
||||
}
|
||||
```
|
||||
|
||||
### Using Specific Modules
|
||||
|
||||
If you only need specific modules, you can register them individually:
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
use rhai::Engine;
|
||||
use engine::mock_db::create_mock_db;
|
||||
|
||||
// Create a mock database
|
||||
let db = create_mock_db();
|
||||
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register only the calendar module
|
||||
heromodels::models::calendar::register_calendar_rhai_module(&mut engine, db.clone());
|
||||
|
||||
// Now you can use calendar-related functions in your scripts
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
This crate includes several examples demonstrating how to use different HeroModels modules with Rhai:
|
||||
|
||||
- `calendar_example`: Working with calendars, events, and attendees
|
||||
- `flow_example`: Working with flows, steps, and signature requirements
|
||||
- `legal_example`: Working with contracts, revisions, and signers
|
||||
- `projects_example`: Working with projects and their properties
|
||||
- `finance_example`: Working with financial models (if available)
|
||||
|
||||
To run an example:
|
||||
|
||||
```bash
|
||||
cargo run --example calendar_example
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
The crate supports the following features:
|
||||
|
||||
- `flow`: Enable the Flow module
|
||||
- `legal`: Enable the Legal module
|
||||
- `projects`: Enable the Projects module
|
||||
- `biz`: Enable the Business module
|
||||
|
||||
By default, only the Calendar module is always enabled.
|
||||
|
||||
## License
|
||||
|
||||
Same as the HeroModels project.
|
@ -1,16 +0,0 @@
|
||||
fn main() {
|
||||
// Tell Cargo to re-run this build script if the calendar/rhai.rs file changes
|
||||
println!("cargo:rerun-if-changed=../heromodels/src/models/calendar/rhai.rs");
|
||||
|
||||
// Tell Cargo to re-run this build script if the flow/rhai.rs file changes
|
||||
println!("cargo:rerun-if-changed=../heromodels/src/models/flow/rhai.rs");
|
||||
|
||||
// Tell Cargo to re-run this build script if the legal/rhai.rs file changes
|
||||
println!("cargo:rerun-if-changed=../heromodels/src/models/legal/rhai.rs");
|
||||
|
||||
// Tell Cargo to re-run this build script if the projects/rhai.rs file changes
|
||||
println!("cargo:rerun-if-changed=../heromodels/src/models/projects/rhai.rs");
|
||||
|
||||
// Tell Cargo to re-run this build script if the biz/rhai.rs file changes
|
||||
println!("cargo:rerun-if-changed=../heromodels/src/models/biz/rhai.rs");
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
// calendar_script.rhai
|
||||
// Example Rhai script for working with Calendar models
|
||||
|
||||
// Constants for AttendanceStatus
|
||||
const NO_RESPONSE = "NoResponse";
|
||||
const ACCEPTED = "Accepted";
|
||||
const DECLINED = "Declined";
|
||||
const TENTATIVE = "Tentative";
|
||||
|
||||
// Create a new calendar using builder pattern
|
||||
let my_calendar = new_calendar()
|
||||
.name("Team Calendar")
|
||||
.description("Calendar for team events and meetings");
|
||||
|
||||
print(`Created calendar: ${my_calendar.name} (${my_calendar.id})`);
|
||||
|
||||
|
||||
// Add attendees to the event
|
||||
let alice = new_attendee()
|
||||
.with_contact_id(1)
|
||||
.with_status(NO_RESPONSE);
|
||||
let bob = new_attendee()
|
||||
.with_contact_id(2)
|
||||
.with_status(ACCEPTED);
|
||||
let charlie = new_attendee()
|
||||
.with_contact_id(3)
|
||||
.with_status(TENTATIVE);
|
||||
|
||||
|
||||
// Create a new event using builder pattern
|
||||
// Note: Timestamps are in seconds since epoch
|
||||
let now = timestamp_now();
|
||||
let one_hour = 60 * 60;
|
||||
let meeting = new_event()
|
||||
.title("Weekly Sync")
|
||||
.reschedule(now, now + one_hour)
|
||||
.location("Conference Room A")
|
||||
.description("Regular team sync meeting")
|
||||
.add_attendee(alice)
|
||||
.add_attendee(bob)
|
||||
.add_attendee(charlie)
|
||||
.save_event();
|
||||
|
||||
print(`Created event: ${meeting.title}`);
|
||||
|
||||
meeting.delete_event();
|
||||
|
||||
print(`Deleted event: ${meeting.title}`);
|
||||
|
||||
// Print attendees info
|
||||
let attendees = meeting.attendees;
|
||||
print(`Added attendees to the event`);
|
||||
|
||||
// Update Charlie's attendee status directly
|
||||
meeting.update_attendee_status(3, ACCEPTED);
|
||||
print(`Updated Charlie's status to: ${ACCEPTED}`);
|
||||
|
||||
// Add the event to the calendar
|
||||
my_calendar.add_event_to_calendar(meeting);
|
||||
// Print events info
|
||||
print(`Added event to calendar`);
|
||||
|
||||
// Save the calendar to the database
|
||||
let saved_calendar = my_calendar.save_calendar();
|
||||
print(`Calendar saved to database with ID: ${saved_calendar.id}`);
|
||||
|
||||
// Retrieve the calendar from the database using the ID from the saved calendar
|
||||
let retrieved_calendar = get_calendar_by_id(saved_calendar.id);
|
||||
if retrieved_calendar != () {
|
||||
print(`Retrieved calendar: ${retrieved_calendar.name}`);
|
||||
print(`Retrieved calendar successfully`);
|
||||
} else {
|
||||
print("Failed to retrieve calendar from database");
|
||||
}
|
||||
|
||||
// List all calendars in the database
|
||||
let all_calendars = list_calendars();
|
||||
print("\nListing all calendars in database:");
|
||||
let calendar_count = 0;
|
||||
for calendar in all_calendars {
|
||||
print(` - Calendar: ${calendar.name} (ID: ${calendar.id})`);
|
||||
calendar_count += 1;
|
||||
}
|
||||
print(`Total calendars: ${calendar_count}`);
|
||||
|
||||
// List all events in the database
|
||||
let all_events = list_events();
|
||||
print("\nListing all events in database:");
|
||||
let event_count = 0;
|
||||
for event in all_events {
|
||||
print(` - Event: ${event.title} (ID: ${event.id})`);
|
||||
event_count += 1;
|
||||
}
|
||||
print(`Total events: ${event_count}`);
|
||||
|
||||
// Helper function to get current timestamp
|
||||
fn timestamp_now() {
|
||||
// This would typically be provided by the host application
|
||||
// For this example, we'll use a fixed timestamp
|
||||
1685620800 // June 1, 2023, 12:00 PM
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use std::path::Path;
|
||||
use rhai::{Engine, Scope};
|
||||
use heromodels::models::calendar::{Calendar, Event, Attendee, AttendanceStatus};
|
||||
use engine::{create_heromodels_engine, eval_file};
|
||||
use engine::mock_db::{create_mock_db, seed_mock_db};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Calendar Rhai Example");
|
||||
println!("=====================");
|
||||
|
||||
// Create a mock database
|
||||
let db = create_mock_db();
|
||||
|
||||
// Seed the database with some initial data
|
||||
seed_mock_db(db.clone());
|
||||
|
||||
// Create the Rhai engine using our central engine creator
|
||||
let mut engine = create_heromodels_engine(db.clone());
|
||||
|
||||
// Register timestamp helper functions
|
||||
register_timestamp_helpers(&mut engine);
|
||||
|
||||
// Get the path to the script
|
||||
let script_path = Path::new(file!())
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("calendar_script.rhai");
|
||||
|
||||
println!("\nRunning script: {}", script_path.display());
|
||||
println!("---------------------");
|
||||
|
||||
// Run the script
|
||||
match eval_file(&engine, &script_path.to_string_lossy()) {
|
||||
Ok(result) => {
|
||||
if !result.is_unit() {
|
||||
println!("\nScript returned: {:?}", result);
|
||||
}
|
||||
println!("\nScript executed successfully!");
|
||||
Ok(())
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("\nError running script: {}", err);
|
||||
Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, err.to_string())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register timestamp helper functions with the engine
|
||||
fn register_timestamp_helpers(engine: &mut Engine) {
|
||||
use chrono::{DateTime, Utc, TimeZone, NaiveDateTime};
|
||||
|
||||
// Function to get current timestamp
|
||||
engine.register_fn("timestamp_now", || {
|
||||
Utc::now().timestamp() as i64
|
||||
});
|
||||
|
||||
// Function to format a timestamp
|
||||
engine.register_fn("format_timestamp", |ts: i64| {
|
||||
let dt = Utc.timestamp_opt(ts, 0).single()
|
||||
.expect("Invalid timestamp");
|
||||
dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
|
||||
});
|
||||
|
||||
println!("Timestamp helper functions registered successfully.");
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use std::path::Path;
|
||||
use rhai::{Engine, Scope};
|
||||
use heromodels::models::finance::account::Account;
|
||||
use heromodels::models::finance::asset::{Asset, AssetType};
|
||||
use heromodels::models::finance::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus};
|
||||
use engine::{create_heromodels_engine, eval_file};
|
||||
use engine::mock_db::{create_mock_db, seed_mock_db};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Finance Rhai Example");
|
||||
println!("===================");
|
||||
|
||||
// Create a mock database
|
||||
let db = create_mock_db();
|
||||
|
||||
// Seed the database with some initial data
|
||||
seed_mock_db(db.clone());
|
||||
|
||||
// Create the Rhai engine using our central engine creator
|
||||
let mut engine = create_heromodels_engine(db.clone());
|
||||
|
||||
// Register timestamp helper functions
|
||||
register_timestamp_helpers(&mut engine);
|
||||
|
||||
// Get the path to the script
|
||||
let script_path = Path::new(file!())
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("finance_script.rhai");
|
||||
|
||||
println!("\nRunning script: {}", script_path.display());
|
||||
println!("---------------------");
|
||||
|
||||
// Run the script
|
||||
match eval_file(&engine, &script_path.to_string_lossy()) {
|
||||
Ok(result) => {
|
||||
if !result.is_unit() {
|
||||
println!("\nScript returned: {:?}", result);
|
||||
}
|
||||
println!("\nScript executed successfully!");
|
||||
Ok(())
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("\nError running script: {}", err);
|
||||
Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, err.to_string())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register timestamp helper functions with the engine
|
||||
fn register_timestamp_helpers(engine: &mut Engine) {
|
||||
use chrono::{DateTime, Utc, TimeZone, NaiveDateTime};
|
||||
|
||||
// Function to get current timestamp
|
||||
engine.register_fn("timestamp_now", || {
|
||||
Utc::now().timestamp() as i64
|
||||
});
|
||||
|
||||
// Function to format a timestamp
|
||||
engine.register_fn("format_timestamp", |ts: i64| {
|
||||
let dt = Utc.timestamp_opt(ts, 0).single()
|
||||
.expect("Invalid timestamp");
|
||||
dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
|
||||
});
|
||||
|
||||
println!("Timestamp helper functions registered successfully.");
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
// finance_script.rhai
|
||||
// Example Rhai script for working with Finance models
|
||||
|
||||
// Constants for AssetType
|
||||
const NATIVE = "Native";
|
||||
const ERC20 = "Erc20";
|
||||
const ERC721 = "Erc721";
|
||||
const ERC1155 = "Erc1155";
|
||||
|
||||
// Constants for ListingStatus
|
||||
const ACTIVE = "Active";
|
||||
const SOLD = "Sold";
|
||||
const CANCELLED = "Cancelled";
|
||||
const EXPIRED = "Expired";
|
||||
|
||||
// Constants for ListingType
|
||||
const FIXED_PRICE = "FixedPrice";
|
||||
const AUCTION = "Auction";
|
||||
const EXCHANGE = "Exchange";
|
||||
|
||||
// Constants for BidStatus
|
||||
const BID_ACTIVE = "Active";
|
||||
const BID_ACCEPTED = "Accepted";
|
||||
const BID_REJECTED = "Rejected";
|
||||
const BID_CANCELLED = "Cancelled";
|
||||
|
||||
// Create a new account using builder pattern
|
||||
let alice_account = new_account()
|
||||
.name("Alice's Account")
|
||||
.user_id(101)
|
||||
.description("Alice's primary trading account")
|
||||
.ledger("ethereum")
|
||||
.address("0x1234567890abcdef1234567890abcdef12345678")
|
||||
.pubkey("0xabcdef1234567890abcdef1234567890abcdef12");
|
||||
|
||||
print(`Created account: ${alice_account.get_name()} (User ID: ${alice_account.get_user_id()})`);
|
||||
|
||||
// Save the account to the database
|
||||
let saved_alice = set_account(alice_account);
|
||||
print(`Account saved to database with ID: ${saved_alice.get_id()}`);
|
||||
|
||||
// Create a new asset using builder pattern
|
||||
let token_asset = new_asset()
|
||||
.name("HERO Token")
|
||||
.description("Herocode governance token")
|
||||
.amount(1000.0)
|
||||
.address("0x9876543210abcdef9876543210abcdef98765432")
|
||||
.asset_type(ERC20)
|
||||
.decimals(18);
|
||||
|
||||
print(`Created asset: ${token_asset.get_name()} (${token_asset.get_amount()} ${token_asset.get_asset_type()})`);
|
||||
|
||||
// Save the asset to the database
|
||||
let saved_token = set_asset(token_asset);
|
||||
print(`Asset saved to database with ID: ${saved_token.get_id()}`);
|
||||
|
||||
// Add the asset to Alice's account
|
||||
saved_alice = saved_alice.add_asset(saved_token.get_id());
|
||||
saved_alice = set_account(saved_alice);
|
||||
print(`Added asset ${saved_token.get_name()} to ${saved_alice.get_name()}`);
|
||||
|
||||
// Create a new NFT asset
|
||||
let nft_asset = new_asset()
|
||||
.name("Herocode #42")
|
||||
.description("Unique digital collectible")
|
||||
.amount(1.0)
|
||||
.address("0xabcdef1234567890abcdef1234567890abcdef12")
|
||||
.asset_type(ERC721)
|
||||
.decimals(0);
|
||||
|
||||
// Save the NFT to the database
|
||||
let saved_nft = set_asset(nft_asset);
|
||||
print(`NFT saved to database with ID: ${saved_nft.get_id()}`);
|
||||
|
||||
// Create Bob's account
|
||||
let bob_account = new_account()
|
||||
.name("Bob's Account")
|
||||
.user_id(102)
|
||||
.description("Bob's trading account")
|
||||
.ledger("ethereum")
|
||||
.address("0xfedcba0987654321fedcba0987654321fedcba09")
|
||||
.pubkey("0x654321fedcba0987654321fedcba0987654321fe");
|
||||
|
||||
// Save Bob's account
|
||||
let saved_bob = set_account(bob_account);
|
||||
print(`Created and saved Bob's account with ID: ${saved_bob.get_id()}`);
|
||||
|
||||
// Create a listing for the NFT
|
||||
let nft_listing = new_listing()
|
||||
.seller_id(saved_alice.get_id())
|
||||
.asset_id(saved_nft.get_id())
|
||||
.price(0.5)
|
||||
.currency("ETH")
|
||||
.listing_type(AUCTION)
|
||||
.title("Rare Herocode NFT")
|
||||
.description("One of a kind digital collectible")
|
||||
.image_url("https://example.com/nft/42.png")
|
||||
.expires_at(timestamp_now() + 86400) // 24 hours from now
|
||||
.add_tag("rare")
|
||||
.add_tag("collectible")
|
||||
.add_tag("digital art")
|
||||
.set_listing();
|
||||
|
||||
// Save the listing
|
||||
print(`Created listing: ${nft_listing.get_title()} (ID: ${nft_listing.get_id()})`);
|
||||
print(`Listing status: ${nft_listing.get_status()}, Type: ${nft_listing.get_listing_type()}`);
|
||||
print(`Listing price: ${nft_listing.get_price()} ${nft_listing.get_currency()}`);
|
||||
|
||||
// Create a bid from Bob
|
||||
let bob_bid = new_bid()
|
||||
.listing_id(nft_listing.get_id().to_string())
|
||||
.bidder_id(saved_bob.get_id())
|
||||
.amount(1.5)
|
||||
.currency("ETH")
|
||||
.set_bid();
|
||||
|
||||
// Save the bid
|
||||
print(`Created bid from ${saved_bob.get_name()} for ${bob_bid.get_amount()} ${bob_bid.get_currency()}`);
|
||||
|
||||
// Add the bid to the listing
|
||||
nft_listing.add_bid(bob_bid);
|
||||
nft_listing.set_listing();
|
||||
print(`Added bid to listing ${nft_listing.get_title()}`);
|
||||
|
||||
// Create another bid with higher amount
|
||||
let charlie_account = new_account()
|
||||
.name("Charlie's Account")
|
||||
.user_id(103)
|
||||
.description("Charlie's trading account")
|
||||
.ledger("ethereum")
|
||||
.address("0x1122334455667788991122334455667788990011")
|
||||
.pubkey("0x8877665544332211887766554433221188776655");
|
||||
|
||||
let saved_charlie = set_account(charlie_account);
|
||||
print(`Created and saved Charlie's account with ID: ${saved_charlie.get_id()}`);
|
||||
|
||||
let charlie_bid = new_bid()
|
||||
.listing_id(nft_listing.get_id().to_string())
|
||||
.bidder_id(saved_charlie.get_id())
|
||||
.amount(2.5)
|
||||
.currency("ETH")
|
||||
.set_bid();
|
||||
|
||||
print(`Created higher bid from ${saved_charlie.get_name()} for ${charlie_bid.get_amount()} ${charlie_bid.get_currency()}`);
|
||||
|
||||
// Add the higher bid to the listing
|
||||
nft_listing.add_bid(charlie_bid)
|
||||
.set_listing();
|
||||
|
||||
|
||||
|
||||
print(`Added higher bid to listing ${nft_listing.get_title()}`);
|
||||
|
||||
nft_listing.sale_price(2.5)
|
||||
.set_listing();
|
||||
|
||||
// Complete the sale to the highest bidder (Charlie)
|
||||
nft_listing.complete_sale(saved_charlie.get_id())
|
||||
.set_listing();
|
||||
|
||||
print(`Completed sale of ${nft_listing.get_title()} to ${saved_charlie.get_name()}`);
|
||||
print(`New listing status: ${saved_listing.get_status()}`);
|
||||
|
||||
// Retrieve the listing from the database
|
||||
let retrieved_listing = get_listing_by_id(saved_listing.get_id());
|
||||
print(`Retrieved listing: ${retrieved_listing.get_title()} (Status: ${retrieved_listing.get_status()})`);
|
||||
|
||||
// Create a fixed price listing
|
||||
let token_listing = new_listing()
|
||||
.seller_id(saved_alice.get_id())
|
||||
.asset_id(saved_token.get_id())
|
||||
.price(100.0)
|
||||
.currency("USDC")
|
||||
.listing_type(FIXED_PRICE)
|
||||
.title("HERO Tokens for Sale")
|
||||
.description("100 HERO tokens at fixed price")
|
||||
.set_listing();
|
||||
|
||||
// Save the fixed price listing
|
||||
print(`Created fixed price listing: ${token_listing.get_title()} (ID: ${token_listing.get_id()})`);
|
||||
|
||||
// Cancel the listing
|
||||
token_listing.cancel();
|
||||
token_listing.set_listing();
|
||||
print(`Cancelled listing: ${token_listing.get_title()}`);
|
||||
print(`Listing status: ${token_listing.get_status()}`);
|
||||
|
||||
// Print summary of all accounts
|
||||
print("\nAccount Summary:");
|
||||
print(`Alice (ID: ${saved_alice.get_id()}): ${saved_alice.get_assets().len()} assets`);
|
||||
print(`Bob (ID: ${saved_bob.get_id()}): ${saved_bob.get_assets().len()} assets`);
|
||||
print(`Charlie (ID: ${saved_charlie.get_id()}): ${saved_charlie.get_assets().len()} assets`);
|
||||
|
||||
// Print summary of all listings
|
||||
print("\nListing Summary:");
|
||||
print(`NFT Auction (ID: ${nft_listing.get_id()}): ${nft_listing.get_status()}`);
|
||||
print(`Token Sale (ID: ${token_listing.get_id()}): ${token_listing.get_status()}`);
|
||||
|
||||
// Print summary of all bids
|
||||
print("\nBid Summary:");
|
||||
print(`Bob's bid: ${bob_bid.get_amount()} ${bob_bid.get_currency()} (Status: ${bob_bid.get_status()})`);
|
||||
print(`Charlie's bid: ${charlie_bid.get_amount()} ${charlie_bid.get_currency()} (Status: ${charlie_bid.get_status()})`);
|
@ -1,120 +0,0 @@
|
||||
use std::path::Path;
|
||||
use rhai::{Scope};
|
||||
use heromodels::models::flow::{Flow, FlowStep, SignatureRequirement};
|
||||
use engine::{create_heromodels_engine, eval_file};
|
||||
use engine::mock_db::{create_mock_db, seed_mock_db};
|
||||
use heromodels_core::Model;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Flow Rhai Example");
|
||||
println!("=================");
|
||||
|
||||
// Create a mock database
|
||||
let db = create_mock_db();
|
||||
|
||||
// Seed the database with initial data
|
||||
seed_mock_db(db.clone());
|
||||
|
||||
// Create the Rhai engine with all modules registered
|
||||
let engine = create_heromodels_engine(db.clone());
|
||||
|
||||
// Get the path to the script
|
||||
let script_path = Path::new(file!())
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("flow_script.rhai");
|
||||
|
||||
println!("\nRunning script: {}", script_path.display());
|
||||
println!("---------------------");
|
||||
|
||||
// Run the script
|
||||
match eval_file(&engine, &script_path.to_string_lossy()) {
|
||||
Ok(result) => {
|
||||
if !result.is_unit() {
|
||||
println!("\nScript returned: {:?}", result);
|
||||
}
|
||||
println!("\nScript executed successfully!");
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("\nError running script: {}", err);
|
||||
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, err.to_string())));
|
||||
}
|
||||
}
|
||||
|
||||
// Demonstrate direct Rust interaction with the Rhai-exposed flow functionality
|
||||
println!("\nDirect Rust interaction with Rhai-exposed flow functionality");
|
||||
println!("----------------------------------------------------------");
|
||||
|
||||
// Create a new scope
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Create a new flow using the Rhai function
|
||||
let result = engine.eval::<Flow>("new_flow(0, \"Direct Rust Flow\")");
|
||||
match result {
|
||||
Ok(mut flow) => {
|
||||
println!("Created flow from Rust: {} (ID: {})", flow.name, flow.get_id());
|
||||
|
||||
// Set flow status using the builder pattern
|
||||
flow = flow.status("active".to_string());
|
||||
println!("Set flow status to: {}", flow.status);
|
||||
|
||||
// Create a new flow step using the Rhai function
|
||||
let result = engine.eval::<FlowStep>("new_flow_step(0, 1)");
|
||||
|
||||
match result {
|
||||
Ok(mut step) => {
|
||||
println!("Created flow step from Rust: Step Order {} (ID: {})",
|
||||
step.step_order, step.get_id());
|
||||
|
||||
// Set step description
|
||||
step = step.description("Direct Rust Step".to_string());
|
||||
println!("Set step description to: {}",
|
||||
step.description.clone().unwrap_or_else(|| "None".to_string()));
|
||||
|
||||
// Create a signature requirement using the Rhai function
|
||||
let result = engine.eval::<SignatureRequirement>(
|
||||
"new_signature_requirement(0, 1, \"Direct Rust Signer\", \"Please sign this document\")"
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(req) => {
|
||||
println!("Created signature requirement from Rust: Public Key {} (ID: {})",
|
||||
req.public_key, req.get_id());
|
||||
|
||||
// Add the step to the flow using the builder pattern
|
||||
flow = flow.add_step(step);
|
||||
println!("Added step to flow. Flow now has {} steps", flow.steps.len());
|
||||
|
||||
// Save the flow to the database using the Rhai function
|
||||
let save_flow_script = "fn save_it(f) { return db::save_flow(f); }";
|
||||
let save_flow_ast = engine.compile(save_flow_script).unwrap();
|
||||
let result = engine.call_fn::<Flow>(&mut scope, &save_flow_ast, "save_it", (flow,));
|
||||
match result {
|
||||
Ok(saved_flow) => {
|
||||
println!("Saved flow to database with ID: {}", saved_flow.get_id());
|
||||
},
|
||||
Err(err) => eprintln!("Error saving flow: {}", err),
|
||||
}
|
||||
|
||||
// Save the signature requirement to the database using the Rhai function
|
||||
let save_req_script = "fn save_it(r) { return db::save_signature_requirement(r); }";
|
||||
let save_req_ast = engine.compile(save_req_script).unwrap();
|
||||
let result = engine.call_fn::<SignatureRequirement>(&mut scope, &save_req_ast, "save_it", (req,));
|
||||
match result {
|
||||
Ok(saved_req) => {
|
||||
println!("Saved signature requirement to database with ID: {}", saved_req.get_id());
|
||||
},
|
||||
Err(err) => eprintln!("Error saving signature requirement: {}", err),
|
||||
}
|
||||
},
|
||||
Err(err) => eprintln!("Error creating signature requirement: {}", err),
|
||||
}
|
||||
},
|
||||
Err(err) => eprintln!("Error creating flow step: {}", err),
|
||||
}
|
||||
},
|
||||
Err(err) => eprintln!("Error creating flow: {}", err),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
// flow_script.rhai
|
||||
// Example Rhai script for working with Flow models
|
||||
|
||||
// Constants for Flow status
|
||||
const STATUS_DRAFT = "draft";
|
||||
const STATUS_ACTIVE = "active";
|
||||
const STATUS_COMPLETED = "completed";
|
||||
const STATUS_CANCELLED = "cancelled";
|
||||
|
||||
// Create a new flow using builder pattern
|
||||
let my_flow = new_flow(0, "flow-123");
|
||||
name(my_flow, "Document Approval Flow");
|
||||
status(my_flow, STATUS_DRAFT);
|
||||
|
||||
print(`Created flow: ${get_flow_name(my_flow)} (ID: ${get_flow_id(my_flow)})`);
|
||||
print(`Status: ${get_flow_status(my_flow)}`);
|
||||
|
||||
// Create flow steps using builder pattern
|
||||
let step1 = new_flow_step(0, 1);
|
||||
description(step1, "Initial review by legal team");
|
||||
status(step1, STATUS_DRAFT);
|
||||
|
||||
let step2 = new_flow_step(0, 2);
|
||||
description(step2, "Approval by department head");
|
||||
status(step2, STATUS_DRAFT);
|
||||
|
||||
let step3 = new_flow_step(0, 3);
|
||||
description(step3, "Final signature by CEO");
|
||||
status(step3, STATUS_DRAFT);
|
||||
|
||||
// Create signature requirements using builder pattern
|
||||
let req1 = new_signature_requirement(0, get_flow_step_id(step1), "legal@example.com", "Please review this document");
|
||||
signed_by(req1, "Legal Team");
|
||||
status(req1, STATUS_DRAFT);
|
||||
|
||||
let req2 = new_signature_requirement(0, get_flow_step_id(step2), "dept@example.com", "Department approval needed");
|
||||
signed_by(req2, "Department Head");
|
||||
status(req2, STATUS_DRAFT);
|
||||
|
||||
let req3 = new_signature_requirement(0, get_flow_step_id(step3), "ceo@example.com", "Final approval required");
|
||||
signed_by(req3, "CEO");
|
||||
status(req3, STATUS_DRAFT);
|
||||
|
||||
print(`Created flow steps with signature requirements`);
|
||||
|
||||
// Add steps to the flow
|
||||
let flow_with_steps = my_flow;
|
||||
add_step(flow_with_steps, step1);
|
||||
add_step(flow_with_steps, step2);
|
||||
add_step(flow_with_steps, step3);
|
||||
|
||||
print(`Added steps to flow. Flow now has ${get_flow_steps(flow_with_steps).len()} steps`);
|
||||
|
||||
// Activate the flow
|
||||
let active_flow = flow_with_steps;
|
||||
status(active_flow, STATUS_ACTIVE);
|
||||
print(`Updated flow status to: ${get_flow_status(active_flow)}`);
|
||||
|
||||
// Save the flow to the database
|
||||
let saved_flow = db::save_flow(active_flow);
|
||||
print(`Flow saved to database with ID: ${get_flow_id(saved_flow)}`);
|
||||
|
||||
// Save signature requirements to the database
|
||||
let saved_req1 = db::save_signature_requirement(req1);
|
||||
let saved_req2 = db::save_signature_requirement(req2);
|
||||
let saved_req3 = db::save_signature_requirement(req3);
|
||||
print(`Signature requirements saved to database with IDs: ${get_signature_requirement_id(saved_req1)}, ${get_signature_requirement_id(saved_req2)}, ${get_signature_requirement_id(saved_req3)}`);
|
||||
|
||||
// Retrieve the flow from the database
|
||||
let retrieved_flow = db::get_flow_by_id(get_flow_id(saved_flow));
|
||||
print(`Retrieved flow: ${get_flow_name(retrieved_flow)}`);
|
||||
print(`It has ${get_flow_steps(retrieved_flow).len()} steps`);
|
||||
|
||||
// Complete the flow
|
||||
let completed_flow = retrieved_flow;
|
||||
status(completed_flow, STATUS_COMPLETED);
|
||||
print(`Updated retrieved flow status to: ${get_flow_status(completed_flow)}`);
|
||||
|
||||
// Save the updated flow
|
||||
db::save_flow(completed_flow);
|
||||
print("Updated flow saved to database");
|
||||
|
||||
// List all flows in the database
|
||||
let all_flows = db::list_flows();
|
||||
print("\nListing all flows in database:");
|
||||
let flow_count = 0;
|
||||
for flow in all_flows {
|
||||
print(` - Flow: ${get_flow_name(flow)} (ID: ${get_flow_id(flow)})`);
|
||||
flow_count += 1;
|
||||
}
|
||||
print(`Total flows: ${flow_count}`);
|
||||
|
||||
// List all signature requirements
|
||||
let all_reqs = db::list_signature_requirements();
|
||||
print("\nListing all signature requirements in database:");
|
||||
let req_count = 0;
|
||||
for req in all_reqs {
|
||||
print(` - Requirement for step ${get_signature_requirement_flow_step_id(req)} (ID: ${get_signature_requirement_id(req)})`);
|
||||
req_count += 1;
|
||||
}
|
||||
print(`Total signature requirements: ${req_count}`);
|
||||
|
||||
// Clean up - delete the flow
|
||||
db::delete_flow(get_flow_id(completed_flow));
|
||||
print(`Deleted flow with ID: ${get_flow_id(completed_flow)}`);
|
||||
|
||||
// Clean up - delete signature requirements
|
||||
db::delete_signature_requirement(get_signature_requirement_id(saved_req1));
|
||||
db::delete_signature_requirement(get_signature_requirement_id(saved_req2));
|
||||
db::delete_signature_requirement(get_signature_requirement_id(saved_req3));
|
||||
print("Deleted all signature requirements");
|
@ -1,72 +0,0 @@
|
||||
use rhai::{Engine, AST, Scope};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use heromodels::db::hero::OurDB;
|
||||
|
||||
// Export the mock database module
|
||||
pub mod mock_db;
|
||||
|
||||
pub fn create_heromodels_engine(db: Arc<OurDB>) -> Engine {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Configure engine settings
|
||||
engine.set_max_expr_depths(128, 128);
|
||||
engine.set_max_string_size(10 * 1024 * 1024); // 10 MB
|
||||
engine.set_max_array_size(10 * 1024); // 10K elements
|
||||
engine.set_max_map_size(10 * 1024); // 10K elements
|
||||
|
||||
// Register all heromodels Rhai modules
|
||||
register_all_modules(&mut engine, db);
|
||||
|
||||
engine
|
||||
}
|
||||
|
||||
/// Register all heromodels Rhai modules with the engine
|
||||
pub fn register_all_modules(engine: &mut Engine, db: Arc<OurDB>) {
|
||||
// Register the calendar module if the feature is enabled
|
||||
#[cfg(feature = "calendar")]
|
||||
heromodels::models::calendar::register_calendar_rhai_module(engine, db.clone());
|
||||
|
||||
// Register the flow module if the feature is enabled
|
||||
#[cfg(feature = "flow")]
|
||||
heromodels::models::flow::register_flow_rhai_module(engine, db.clone());
|
||||
|
||||
// // Register the finance module if the feature is enabled
|
||||
// #[cfg(feature = "finance")]
|
||||
// heromodels::models::finance::register_finance_rhai_module(engine, db.clone());
|
||||
|
||||
// Register the legal module if the feature is enabled
|
||||
#[cfg(feature = "legal")]
|
||||
heromodels::models::legal::register_legal_rhai_module(engine, db.clone());
|
||||
|
||||
// Register the projects module if the feature is enabled
|
||||
#[cfg(feature = "projects")]
|
||||
heromodels::models::projects::register_projects_rhai_module(engine, db.clone());
|
||||
|
||||
// Register the biz module if the feature is enabled
|
||||
#[cfg(feature = "biz")]
|
||||
heromodels::models::biz::register_biz_rhai_module(engine, db.clone());
|
||||
|
||||
println!("Heromodels Rhai modules registered successfully.");
|
||||
}
|
||||
|
||||
/// Evaluate a Rhai script file
|
||||
pub fn eval_file(engine: &Engine, file_path: &str) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
engine.eval_file::<rhai::Dynamic>(file_path.into())
|
||||
}
|
||||
|
||||
/// Evaluate a Rhai script string
|
||||
pub fn eval_script(engine: &Engine, script: &str) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
engine.eval::<rhai::Dynamic>(script)
|
||||
}
|
||||
|
||||
/// Compile a Rhai script to AST for repeated execution
|
||||
pub fn compile_script(engine: &Engine, script: &str) -> Result<AST, Box<rhai::EvalAltResult>> {
|
||||
Ok(engine.compile(script)?)
|
||||
}
|
||||
|
||||
/// Run a compiled Rhai script AST
|
||||
pub fn run_ast(engine: &Engine, ast: &AST, scope: &mut Scope) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
engine.eval_ast_with_scope(scope, ast)
|
||||
}
|
@ -1,315 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use std::env;
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::db::{Db, Collection}; // Import both Db and Collection traits
|
||||
use heromodels::models::calendar::{Calendar, Event, Attendee, AttendanceStatus};
|
||||
use heromodels_core::Model; // Import Model trait to use build method
|
||||
use chrono::Utc;
|
||||
use heromodels::models::userexample::User;
|
||||
|
||||
// Import finance models
|
||||
use heromodels::models::finance::account::Account;
|
||||
use heromodels::models::finance::asset::{Asset, AssetType};
|
||||
use heromodels::models::finance::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus};
|
||||
|
||||
// Conditionally import other modules based on features
|
||||
#[cfg(feature = "flow")]
|
||||
use heromodels::models::flow::{Flow, FlowStep, SignatureRequirement};
|
||||
|
||||
#[cfg(feature = "legal")]
|
||||
use heromodels::models::legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus};
|
||||
|
||||
#[cfg(feature = "projects")]
|
||||
use heromodels::models::projects::{Project, Status as ProjectStatus, Priority, ItemType};
|
||||
|
||||
/// Create a mock in-memory database for examples
|
||||
pub fn create_mock_db() -> Arc<OurDB> {
|
||||
// Create a temporary directory for the database files
|
||||
let temp_dir = env::temp_dir().join("engine_examples");
|
||||
std::fs::create_dir_all(&temp_dir).expect("Failed to create temp directory");
|
||||
|
||||
// Create a new OurDB instance with reset=true to ensure it's clean
|
||||
let db = OurDB::new(temp_dir, true).expect("Failed to create OurDB instance");
|
||||
|
||||
Arc::new(db)
|
||||
}
|
||||
|
||||
/// Seed the mock database with some initial data for all modules
|
||||
pub fn seed_mock_db(db: Arc<OurDB>) {
|
||||
// Seed calendar data
|
||||
seed_calendar_data(db.clone());
|
||||
|
||||
// Seed finance data
|
||||
seed_finance_data(db.clone());
|
||||
|
||||
// Seed flow data if the feature is enabled
|
||||
#[cfg(feature = "flow")]
|
||||
seed_flow_data(db.clone());
|
||||
|
||||
// Seed legal data if the feature is enabled
|
||||
#[cfg(feature = "legal")]
|
||||
seed_legal_data(db.clone());
|
||||
|
||||
// Seed projects data if the feature is enabled
|
||||
#[cfg(feature = "projects")]
|
||||
seed_projects_data(db.clone());
|
||||
|
||||
println!("Mock database seeded with initial data for all enabled modules.");
|
||||
}
|
||||
|
||||
/// Seed the mock database with calendar data
|
||||
fn seed_calendar_data(db: Arc<OurDB>) {
|
||||
// Create a calendar
|
||||
let mut calendar = Calendar::new(None, "Work Calendar".to_string());
|
||||
calendar.description = Some("My work schedule".to_string());
|
||||
|
||||
// Store the calendar in the database
|
||||
let (calendar_id, updated_calendar) = db.collection::<Calendar>()
|
||||
.expect("Failed to get Calendar collection")
|
||||
.set(&calendar)
|
||||
.expect("Failed to store calendar");
|
||||
|
||||
|
||||
// Create an event
|
||||
let now = Utc::now().timestamp();
|
||||
let end_time = now + 3600; // Add 1 hour in seconds
|
||||
|
||||
// Use the builder pattern for Event
|
||||
let event = Event::new()
|
||||
.title("Team Meeting".to_string())
|
||||
.reschedule(now, end_time)
|
||||
.location("Conference Room A".to_string())
|
||||
.description("Weekly sync".to_string())
|
||||
// .add_attendee(Attendee::new(1))
|
||||
// .add_attendee(Attendee::new(2))
|
||||
.build();
|
||||
|
||||
// // Add attendees to the event using the builder pattern
|
||||
// let attendee1 = Attendee::new(1);
|
||||
// let attendee2 = Attendee::new(2);
|
||||
|
||||
// // Add attendees using the builder pattern
|
||||
// event = event.add_attendee(attendee1);
|
||||
// event = event.add_attendee(attendee2);
|
||||
|
||||
// Call build and capture the returned value
|
||||
// let event = event.build();
|
||||
|
||||
// Store the event in the database first to get its ID
|
||||
let (event_id, updated_event) = db.collection()
|
||||
.expect("Failed to get Event collection")
|
||||
.set(&event)
|
||||
.expect("Failed to store event");
|
||||
|
||||
// Add the event ID to the calendar
|
||||
calendar = calendar.add_event(event_id as i64);
|
||||
|
||||
// Store the calendar in the database
|
||||
let (calendar_id, updated_calendar) = db.collection::<Calendar>()
|
||||
.expect("Failed to get Calendar collection")
|
||||
.set(&calendar)
|
||||
.expect("Failed to store calendar");
|
||||
|
||||
println!("Mock database seeded with calendar data:");
|
||||
println!(" - Added calendar: {} (ID: {})", updated_calendar.name, updated_calendar.base_data.id);
|
||||
println!(" - Added event: {} (ID: {})", updated_event.title, updated_event.base_data.id);
|
||||
}
|
||||
|
||||
/// Seed the mock database with flow data
|
||||
#[cfg(feature = "flow")]
|
||||
fn seed_flow_data(db: Arc<OurDB>) {
|
||||
// Create a flow
|
||||
let mut flow = Flow::new(0, "Document Approval".to_string());
|
||||
|
||||
// Set flow properties using the builder pattern
|
||||
flow = flow.status("draft".to_string());
|
||||
flow = flow.name("Document Approval Flow".to_string());
|
||||
|
||||
// Create flow steps
|
||||
let mut step1 = FlowStep::new(0, 1);
|
||||
step1 = step1.description("Initial review by legal team".to_string());
|
||||
step1 = step1.status("pending".to_string());
|
||||
|
||||
let mut step2 = FlowStep::new(0, 2);
|
||||
step2 = step2.description("Approval by department head".to_string());
|
||||
step2 = step2.status("pending".to_string());
|
||||
|
||||
// Add signature requirements
|
||||
let mut req1 = SignatureRequirement::new(0, 1, "Legal Team".to_string(), "Please review this document".to_string());
|
||||
let mut req2 = SignatureRequirement::new(0, 2, "Department Head".to_string(), "Please approve this document".to_string());
|
||||
|
||||
// Add steps to flow
|
||||
flow = flow.add_step(step1);
|
||||
flow = flow.add_step(step2);
|
||||
|
||||
// Store in the database
|
||||
let (_, updated_flow) = db.collection::<Flow>()
|
||||
.expect("Failed to get Flow collection")
|
||||
.set(&flow)
|
||||
.expect("Failed to store flow");
|
||||
|
||||
// Store signature requirements in the database
|
||||
let (_, updated_req1) = db.collection::<SignatureRequirement>()
|
||||
.expect("Failed to get SignatureRequirement collection")
|
||||
.set(&req1)
|
||||
.expect("Failed to store signature requirement");
|
||||
|
||||
let (_, updated_req2) = db.collection::<SignatureRequirement>()
|
||||
.expect("Failed to get SignatureRequirement collection")
|
||||
.set(&req2)
|
||||
.expect("Failed to store signature requirement");
|
||||
|
||||
println!("Mock database seeded with flow data:");
|
||||
println!(" - Added flow: {} (ID: {})", updated_flow.name, updated_flow.base_data.id);
|
||||
println!(" - Added {} steps", updated_flow.steps.len());
|
||||
println!(" - Added signature requirements with IDs: {} and {}",
|
||||
updated_req1.base_data.id, updated_req2.base_data.id);
|
||||
}
|
||||
|
||||
/// Seed the mock database with legal data
|
||||
#[cfg(feature = "legal")]
|
||||
fn seed_legal_data(db: Arc<OurDB>) {
|
||||
// Create a contract
|
||||
let mut contract = Contract::new(None, "Service Agreement".to_string());
|
||||
contract.description = Some("Agreement for software development services".to_string());
|
||||
contract.status = ContractStatus::Draft;
|
||||
|
||||
// Create a revision
|
||||
let revision = ContractRevision::new(
|
||||
None,
|
||||
"Initial draft".to_string(),
|
||||
"https://example.com/contract/v1".to_string(),
|
||||
);
|
||||
|
||||
// Create signers
|
||||
let signer1 = ContractSigner::new(None, 1, "Client".to_string());
|
||||
let signer2 = ContractSigner::new(None, 2, "Provider".to_string());
|
||||
|
||||
// Add revision and signers to contract
|
||||
contract.add_revision(revision);
|
||||
contract.add_signer(signer1);
|
||||
contract.add_signer(signer2);
|
||||
|
||||
// Store in the database
|
||||
let (_, updated_contract) = db.collection::<Contract>()
|
||||
.expect("Failed to get Contract collection")
|
||||
.set(&contract)
|
||||
.expect("Failed to store contract");
|
||||
|
||||
println!("Mock database seeded with legal data:");
|
||||
println!(" - Added contract: {} (ID: {})", updated_contract.name, updated_contract.base_data.id);
|
||||
println!(" - Added {} revisions and {} signers",
|
||||
updated_contract.revisions.len(),
|
||||
updated_contract.signers.len());
|
||||
}
|
||||
|
||||
/// Seed the mock database with projects data
|
||||
#[cfg(feature = "projects")]
|
||||
fn seed_projects_data(db: Arc<OurDB>) {
|
||||
// Create a project
|
||||
let mut project = Project::new(None, "Website Redesign".to_string());
|
||||
project.description = Some("Redesign the company website".to_string());
|
||||
project.status = ProjectStatus::InProgress;
|
||||
project.priority = Priority::High;
|
||||
|
||||
// Add members and tags
|
||||
project.add_member_id(1);
|
||||
project.add_member_id(2);
|
||||
project.add_tag("design".to_string());
|
||||
project.add_tag("web".to_string());
|
||||
|
||||
// Store in the database
|
||||
let (_, updated_project) = db.collection::<Project>()
|
||||
.expect("Failed to get Project collection")
|
||||
.set(&project)
|
||||
.expect("Failed to store project");
|
||||
|
||||
println!("Mock database seeded with projects data:");
|
||||
println!(" - Added project: {} (ID: {})", updated_project.name, updated_project.base_data.id);
|
||||
println!(" - Status: {}, Priority: {}", updated_project.status, updated_project.priority);
|
||||
println!(" - Added {} members and {} tags",
|
||||
updated_project.member_ids.len(),
|
||||
updated_project.tags.len());
|
||||
}
|
||||
/// Seed the mock database with finance data
|
||||
fn seed_finance_data(db: Arc<OurDB>) {
|
||||
// Create a user account
|
||||
let mut account = Account::new()
|
||||
.name("Demo Account")
|
||||
.user_id(1)
|
||||
.description("Demo trading account")
|
||||
.ledger("ethereum")
|
||||
.address("0x1234567890abcdef1234567890abcdef12345678")
|
||||
.pubkey("0xabcdef1234567890abcdef1234567890abcdef12");
|
||||
|
||||
// Store the account in the database
|
||||
let (account_id, updated_account) = db.collection::<Account>()
|
||||
.expect("Failed to get Account collection")
|
||||
.set(&account)
|
||||
.expect("Failed to store account");
|
||||
|
||||
// Create an ERC20 token asset
|
||||
let token_asset = Asset::new()
|
||||
.name("HERO Token")
|
||||
.description("Herocode governance token")
|
||||
.amount(1000.0)
|
||||
.address("0x9876543210abcdef9876543210abcdef98765432")
|
||||
.asset_type(AssetType::Erc20)
|
||||
.decimals(18);
|
||||
|
||||
// Store the token asset in the database
|
||||
let (token_id, updated_token) = db.collection::<Asset>()
|
||||
.expect("Failed to get Asset collection")
|
||||
.set(&token_asset)
|
||||
.expect("Failed to store token asset");
|
||||
|
||||
// Create an NFT asset
|
||||
let nft_asset = Asset::new()
|
||||
.name("Herocode #1")
|
||||
.description("Unique digital collectible")
|
||||
.amount(1.0)
|
||||
.address("0xabcdef1234567890abcdef1234567890abcdef12")
|
||||
.asset_type(AssetType::Erc721)
|
||||
.decimals(0);
|
||||
|
||||
// Store the NFT asset in the database
|
||||
let (nft_id, updated_nft) = db.collection::<Asset>()
|
||||
.expect("Failed to get Asset collection")
|
||||
.set(&nft_asset)
|
||||
.expect("Failed to store NFT asset");
|
||||
|
||||
// Add assets to the account
|
||||
account = updated_account.add_asset(token_id);
|
||||
account = account.add_asset(nft_id);
|
||||
|
||||
// Update the account in the database
|
||||
let (_, updated_account) = db.collection::<Account>()
|
||||
.expect("Failed to get Account collection")
|
||||
.set(&account)
|
||||
.expect("Failed to store updated account");
|
||||
|
||||
// Create a listing for the NFT
|
||||
let listing = Listing::new()
|
||||
.seller_id(account_id)
|
||||
.asset_id(nft_id)
|
||||
.price(0.5)
|
||||
.currency("ETH")
|
||||
.listing_type(ListingType::Auction)
|
||||
.title("Rare Herocode NFT".to_string())
|
||||
.description("One of a kind digital collectible".to_string())
|
||||
.image_url(Some("hcttps://example.com/nft/1.png".to_string()))
|
||||
.add_tag("rare".to_string())
|
||||
.add_tag("collectible".to_string());
|
||||
|
||||
// Store the listing in the database
|
||||
let (listing_id, updated_listing) = db.collection::<Listing>()
|
||||
.expect("Failed to get Listing collection")
|
||||
.set(&listing)
|
||||
.expect("Failed to store listing");
|
||||
|
||||
println!("Mock database seeded with finance data:");
|
||||
println!(" - Added account: {} (ID: {})", updated_account.name, updated_account.base_data.id);
|
||||
println!(" - Added token asset: {} (ID: {})", updated_token.name, updated_token.base_data.id);
|
||||
println!(" - Added NFT asset: {} (ID: {})", updated_nft.name, updated_nft.base_data.id);
|
||||
println!(" - Added listing: {} (ID: {})", updated_listing.title, updated_listing.base_data.id);
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
/// Seed the mock database with finance data
|
||||
fn seed_finance_data(db: Arc<OurDB>) {
|
||||
// Create a user account
|
||||
let mut account = Account::new()
|
||||
.name("Demo Account")
|
||||
.user_id(1)
|
||||
.description("Demo trading account")
|
||||
.ledger("ethereum")
|
||||
.address("0x1234567890abcdef1234567890abcdef12345678")
|
||||
.pubkey("0xabcdef1234567890abcdef1234567890abcdef12");
|
||||
|
||||
// Store the account in the database
|
||||
let (account_id, updated_account) = db.collection::<Account>()
|
||||
.expect("Failed to get Account collection")
|
||||
.set(&account)
|
||||
.expect("Failed to store account");
|
||||
|
||||
// Create an ERC20 token asset
|
||||
let token_asset = Asset::new()
|
||||
.name("HERO Token")
|
||||
.description("Herocode governance token")
|
||||
.amount(1000.0)
|
||||
.address("0x9876543210abcdef9876543210abcdef98765432")
|
||||
.asset_type(AssetType::Erc20)
|
||||
.decimals(18);
|
||||
|
||||
// Store the token asset in the database
|
||||
let (token_id, updated_token) = db.collection::<Asset>()
|
||||
.expect("Failed to get Asset collection")
|
||||
.set(&token_asset)
|
||||
.expect("Failed to store token asset");
|
||||
|
||||
// Create an NFT asset
|
||||
let nft_asset = Asset::new()
|
||||
.name("Herocode #1")
|
||||
.description("Unique digital collectible")
|
||||
.amount(1.0)
|
||||
.address("0xabcdef1234567890abcdef1234567890abcdef12")
|
||||
.asset_type(AssetType::Erc721)
|
||||
.decimals(0);
|
||||
|
||||
// Store the NFT asset in the database
|
||||
let (nft_id, updated_nft) = db.collection::<Asset>()
|
||||
.expect("Failed to get Asset collection")
|
||||
.set(&nft_asset)
|
||||
.expect("Failed to store NFT asset");
|
||||
|
||||
// Add assets to the account
|
||||
account = updated_account.add_asset(token_id);
|
||||
account = account.add_asset(nft_id);
|
||||
|
||||
// Update the account in the database
|
||||
let (_, updated_account) = db.collection::<Account>()
|
||||
.expect("Failed to get Account collection")
|
||||
.set(&account)
|
||||
.expect("Failed to store updated account");
|
||||
|
||||
// Create a listing for the NFT
|
||||
let listing = Listing::new()
|
||||
.seller_id(account_id)
|
||||
.asset_id(nft_id)
|
||||
.price(0.5)
|
||||
.currency("ETH")
|
||||
.listing_type(ListingType::Auction)
|
||||
.title(Some("Rare Herocode NFT".to_string()))
|
||||
.description(Some("One of a kind digital collectible".to_string()))
|
||||
.image_url(Some("https://example.com/nft/1.png".to_string()))
|
||||
.add_tag("rare".to_string())
|
||||
.add_tag("collectible".to_string());
|
||||
|
||||
// Store the listing in the database
|
||||
let (listing_id, updated_listing) = db.collection::<Listing>()
|
||||
.expect("Failed to get Listing collection")
|
||||
.set(&listing)
|
||||
.expect("Failed to store listing");
|
||||
|
||||
println!("Mock database seeded with finance data:");
|
||||
println!(" - Added account: {} (ID: {})", updated_account.name, updated_account.base_data.id);
|
||||
println!(" - Added token asset: {} (ID: {})", updated_token.name, updated_token.base_data.id);
|
||||
println!(" - Added NFT asset: {} (ID: {})", updated_nft.name, updated_nft.base_data.id);
|
||||
println!(" - Added listing: {} (ID: {})", updated_listing.title.unwrap_or_default(), updated_listing.base_data.id);
|
||||
}
|
1
rhai_client/.gitignore
vendored
1
rhai_client/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/target
|
@ -1,17 +0,0 @@
|
||||
[package]
|
||||
name = "rhai_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
redis = { version = "0.25.0", features = ["tokio-comp"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "1.6", features = ["v4", "serde"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
log = "0.4"
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] } # For async main in examples, and general async
|
||||
|
||||
[dev-dependencies] # For examples later
|
||||
env_logger = "0.10"
|
||||
rhai = "1.18.0" # For examples that might need to show engine setup
|
@ -1,212 +0,0 @@
|
||||
use chrono::Utc;
|
||||
use log::{debug, info, warn, error}; // Added error
|
||||
use redis::AsyncCommands;
|
||||
use tokio::time::{sleep, Instant}; // For polling with timeout
|
||||
use std::time::Duration;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value; // For client_rpc_id, though not directly used by this client's submit method
|
||||
use uuid::Uuid;
|
||||
|
||||
const REDIS_TASK_DETAILS_PREFIX: &str = "rhai_task_details:";
|
||||
const REDIS_QUEUE_PREFIX: &str = "rhai_tasks:";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct RhaiTaskDetails {
|
||||
pub script: String,
|
||||
pub status: String, // "pending", "processing", "completed", "error"
|
||||
#[serde(rename = "clientRpcId")]
|
||||
pub client_rpc_id: Option<Value>, // Kept for compatibility with worker/server, but optional for client
|
||||
pub output: Option<String>,
|
||||
pub error: Option<String>, // Renamed from error_message for consistency
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
#[serde(rename = "updatedAt")]
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RhaiClientError {
|
||||
RedisError(redis::RedisError),
|
||||
SerializationError(serde_json::Error),
|
||||
Timeout(String), // task_id that timed out
|
||||
TaskNotFound(String), // task_id not found after submission (should be rare)
|
||||
}
|
||||
|
||||
impl From<redis::RedisError> for RhaiClientError {
|
||||
fn from(err: redis::RedisError) -> Self {
|
||||
RhaiClientError::RedisError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for RhaiClientError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
RhaiClientError::SerializationError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RhaiClientError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
RhaiClientError::RedisError(e) => write!(f, "Redis error: {}", e),
|
||||
RhaiClientError::SerializationError(e) => write!(f, "Serialization error: {}", e),
|
||||
RhaiClientError::Timeout(task_id) => write!(f, "Timeout waiting for task {} to complete", task_id),
|
||||
RhaiClientError::TaskNotFound(task_id) => write!(f, "Task {} not found after submission", task_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RhaiClientError {}
|
||||
|
||||
pub struct RhaiClient {
|
||||
redis_client: redis::Client,
|
||||
}
|
||||
|
||||
impl RhaiClient {
|
||||
pub fn new(redis_url: &str) -> Result<Self, RhaiClientError> {
|
||||
let client = redis::Client::open(redis_url)?;
|
||||
Ok(Self { redis_client: client })
|
||||
}
|
||||
|
||||
pub async fn submit_script(
|
||||
&self,
|
||||
circle_name: &str,
|
||||
script: String,
|
||||
client_rpc_id: Option<Value>, // Optional: if the caller has an RPC ID to associate
|
||||
) -> Result<String, RhaiClientError> {
|
||||
let mut conn = self.redis_client.get_multiplexed_async_connection().await?;
|
||||
|
||||
let task_id = Uuid::new_v4().to_string();
|
||||
let now = Utc::now();
|
||||
|
||||
let task_details = RhaiTaskDetails {
|
||||
script,
|
||||
status: "pending".to_string(),
|
||||
client_rpc_id,
|
||||
output: None,
|
||||
error: None,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
};
|
||||
|
||||
let task_key = format!("{}{}", REDIS_TASK_DETAILS_PREFIX, task_id);
|
||||
let queue_key = format!("{}{}", REDIS_QUEUE_PREFIX, circle_name.replace(" ", "_").to_lowercase());
|
||||
|
||||
debug!(
|
||||
"Submitting task_id: {} for circle: {} to queue: {}. Details: {:?}",
|
||||
task_id, circle_name, queue_key, task_details
|
||||
);
|
||||
|
||||
// Using HSET_MULTIPLE for efficiency if redis-rs supports it directly for struct fields.
|
||||
// Otherwise, individual HSETs are fine.
|
||||
// For simplicity and directness with redis-rs async, individual HSETs are used here.
|
||||
conn.hset::<_, _, _, ()>(&task_key, "script", &task_details.script).await?;
|
||||
conn.hset::<_, _, _, ()>(&task_key, "status", &task_details.status).await?;
|
||||
if let Some(rpc_id_val) = &task_details.client_rpc_id {
|
||||
conn.hset::<_, _, _, ()>(&task_key, "clientRpcId", serde_json::to_string(rpc_id_val)?).await?;
|
||||
} else {
|
||||
// Ensure the field exists even if null, or decide if it should be omitted
|
||||
conn.hset::<_, _, _, ()>(&task_key, "clientRpcId", Value::Null.to_string()).await?;
|
||||
}
|
||||
conn.hset::<_, _, _, ()>(&task_key, "createdAt", task_details.created_at.to_rfc3339()).await?;
|
||||
conn.hset::<_, _, _, ()>(&task_key, "updatedAt", task_details.updated_at.to_rfc3339()).await?;
|
||||
// output and error fields are initially None, so they might not be set here or set as empty strings/null
|
||||
|
||||
conn.lpush::<_, _, ()>(&queue_key, &task_id).await?;
|
||||
|
||||
Ok(task_id)
|
||||
}
|
||||
|
||||
// Optional: A method to check task status, similar to what circle_server_ws polling does.
|
||||
// This could be useful for a client that wants to poll for results itself.
|
||||
pub async fn get_task_status(&self, task_id: &str) -> Result<Option<RhaiTaskDetails>, RhaiClientError> {
|
||||
let mut conn = self.redis_client.get_multiplexed_async_connection().await?;
|
||||
let task_key = format!("{}{}", REDIS_TASK_DETAILS_PREFIX, task_id);
|
||||
|
||||
let result_map: Option<std::collections::HashMap<String, String>> = conn.hgetall(&task_key).await?;
|
||||
|
||||
match result_map {
|
||||
Some(map) => {
|
||||
// Reconstruct RhaiTaskDetails from HashMap
|
||||
// This is a simplified reconstruction; ensure all fields are handled robustly
|
||||
let details = RhaiTaskDetails {
|
||||
script: map.get("script").cloned().unwrap_or_default(),
|
||||
status: map.get("status").cloned().unwrap_or_default(),
|
||||
client_rpc_id: map.get("clientRpcId")
|
||||
.and_then(|s| serde_json::from_str(s).ok())
|
||||
.or(Some(Value::Null)), // Default to Value::Null if missing or parse error
|
||||
output: map.get("output").cloned(),
|
||||
error: map.get("error").cloned(),
|
||||
created_at: map.get("createdAt")
|
||||
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
|
||||
.map(|dt| dt.with_timezone(&Utc))
|
||||
.unwrap_or_else(Utc::now), // Provide a default
|
||||
updated_at: map.get("updatedAt")
|
||||
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
|
||||
.map(|dt| dt.with_timezone(&Utc))
|
||||
.unwrap_or_else(Utc::now), // Provide a default
|
||||
};
|
||||
Ok(Some(details))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn submit_script_and_await_result(
|
||||
&self,
|
||||
circle_name: &str,
|
||||
script: String,
|
||||
client_rpc_id: Option<Value>,
|
||||
timeout: Duration,
|
||||
poll_interval: Duration,
|
||||
) -> Result<RhaiTaskDetails, RhaiClientError> {
|
||||
let task_id = self.submit_script(circle_name, script, client_rpc_id).await?;
|
||||
info!("Task {} submitted. Polling for result with timeout {:?}...", task_id, timeout);
|
||||
|
||||
let start_time = Instant::now();
|
||||
loop {
|
||||
if start_time.elapsed() > timeout {
|
||||
warn!("Timeout waiting for task {}", task_id);
|
||||
return Err(RhaiClientError::Timeout(task_id.clone()));
|
||||
}
|
||||
|
||||
match self.get_task_status(&task_id).await {
|
||||
Ok(Some(details)) => {
|
||||
debug!("Polled task {}: status = {}", task_id, details.status);
|
||||
if details.status == "completed" || details.status == "error" {
|
||||
info!("Task {} finished with status: {}", task_id, details.status);
|
||||
return Ok(details);
|
||||
}
|
||||
// else status is "pending" or "processing", continue polling
|
||||
}
|
||||
Ok(None) => {
|
||||
// This case should ideally not happen if submit_script succeeded and worker is running,
|
||||
// unless the task details were manually deleted from Redis.
|
||||
warn!("Task {} not found during polling. This might indicate an issue.", task_id);
|
||||
// Depending on desired robustness, could retry a few times or return an error immediately.
|
||||
// For now, let it continue polling up to timeout, or return a specific error.
|
||||
// If it persists, it's effectively a timeout or a lost task.
|
||||
// Let's consider it a lost task if it's not found after a short while post-submission.
|
||||
if start_time.elapsed() > Duration::from_secs(5) { // Arbitrary short duration
|
||||
return Err(RhaiClientError::TaskNotFound(task_id.clone()));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// Log error but continue polling unless it's a critical Redis error
|
||||
error!("Error polling task {}: {}. Will retry.", task_id, e);
|
||||
}
|
||||
}
|
||||
sleep(poll_interval).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// use super::*;
|
||||
// Basic tests can be added later, especially once examples are in place.
|
||||
// For now, ensuring it compiles is the priority.
|
||||
#[test]
|
||||
fn it_compiles() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
@ -11,6 +11,10 @@ path = "src/lib.rs"
|
||||
name = "rhai_engine"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "test_dynamic_loading"
|
||||
path = "examples/loadscripts/test_dynamic_loading.rs"
|
||||
|
||||
[dependencies]
|
||||
rhai = "1.12.0"
|
||||
chrono = "0.4"
|
@ -1,4 +1,4 @@
|
||||
// Math utility functions for Terra templates
|
||||
// Math utility functions for Tera templates
|
||||
|
||||
// Format a number with commas as thousands separators
|
||||
fn format_number(num) {
|
@ -1,4 +1,4 @@
|
||||
// String manipulation functions that will be exposed to Terra templates
|
||||
// String manipulation functions that will be exposed to Tera templates
|
||||
|
||||
// Capitalize the first letter of each word
|
||||
fn capitalize(text) {
|
98
rhai_engine/examples/loadscripts/test_dynamic_loading.rs
Normal file
98
rhai_engine/examples/loadscripts/test_dynamic_loading.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use std::collections::HashMap;
|
||||
use rhai::Dynamic;
|
||||
use rhai_engine::ScriptManager;
|
||||
use std::path::Path;
|
||||
|
||||
/// Test the dynamic loading of Rhai scripts and functions
|
||||
pub fn main() -> Result<(), String> {
|
||||
println!("\n=== TESTING DYNAMIC FUNCTION LOADING ===\n");
|
||||
|
||||
// Create a new ScriptManager
|
||||
let mut script_manager = ScriptManager::new();
|
||||
|
||||
println!("Loading Rhai scripts from scripts directory...");
|
||||
|
||||
// Get the scripts directory path
|
||||
let scripts_dir = Path::new("examples/loadscripts/scripts");
|
||||
|
||||
// Use the ScriptManager to load all scripts from the directory
|
||||
let loaded_scripts = script_manager.load_scripts_from_directory(scripts_dir)?;
|
||||
|
||||
// Print loaded scripts
|
||||
println!("\nLoaded scripts:");
|
||||
for script in &loaded_scripts {
|
||||
println!(" - {}", script);
|
||||
}
|
||||
|
||||
// Get all available functions using the get_function_names method
|
||||
let function_names = script_manager.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:");
|
||||
|
||||
//TODO: should not start with string_utils we want capitalize as function usable as is
|
||||
script_manager.run( "string_utils:capitalize(hello world);")?;
|
||||
script_manager.run( "math_utils:format_number(1234567);")?;
|
||||
|
||||
|
||||
// Test evaluating a complex script
|
||||
println!("\nEvaluating a complex script:");
|
||||
let complex_script = r#"
|
||||
let sentence = "Count the words in this sentence";
|
||||
let count = test_utils:count_words(sentence);
|
||||
print("Word count: " + truncate("count",1));
|
||||
count * 2 // Return double the word count
|
||||
"#;
|
||||
|
||||
// Evaluate the script
|
||||
println!("```\n{}\n```", complex_script);
|
||||
match script_manager.eval::<Dynamic>(complex_script) {
|
||||
Ok(result) => println!(" Result: {}", result),
|
||||
Err(e) => println!(" Error: {}", e)
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
# Product Catalog
|
||||
|
||||
{% let products = get_products() %}
|
||||
|
||||
## All Products
|
||||
|
||||
{% for product in products %}
|
||||
### {{ product.name }}
|
||||
|
||||
**Price:** {{ format_price(product.price) }}
|
||||
|
||||
#### Features:
|
||||
{% for feature in product.features %}
|
||||
- {{ feature }}
|
||||
{% endfor %}
|
||||
|
||||
*Status: {% if product.available %}✅ In Stock{% else %}❌ Out of Stock{% endif %}*
|
||||
|
||||
---
|
||||
{% endfor %}
|
||||
|
||||
## Available Products
|
||||
|
||||
{% let available_products = get_available_products() %}
|
||||
*Available Products: {{ available_products.len() }}*
|
||||
|
||||
{% for product in available_products %}
|
||||
### {{ product.name }}
|
||||
|
||||
**Price:** {{ format_price(product.price) }}
|
||||
|
||||
---
|
||||
{% endfor %}
|
||||
|
||||
## Total Catalog Value
|
||||
|
||||
**Total Value:** {{ format_price(calculate_total_price()) }}
|
||||
|
||||
## User Information
|
||||
|
||||
{% let user = get_user() %}
|
||||
|
||||
**Name:** {{ user.name }}
|
||||
**Email:** {{ user.email }}
|
||||
**Role:** {{ user.role }}
|
||||
**Theme:** {{ user.settings.theme }}
|
||||
|
||||
### Order History
|
||||
|
||||
{% for order in user.orders %}
|
||||
- **{{ order.id }}** ({{ order.date }}) - {{ format_price(order.total) }}
|
||||
{% endfor %}
|
||||
|
||||
## All Product Features
|
||||
|
||||
{% let features = get_all_features() %}
|
||||
{% for feature in features %}
|
||||
- {{ feature }}
|
||||
{% endfor %}
|
2
rhai_engine/examples/simple/producttemplates/readme.md
Normal file
2
rhai_engine/examples/simple/producttemplates/readme.md
Normal file
@ -0,0 +1,2 @@
|
||||
the data objects which need to be filled into product_catalog.tera come from ../scripts/
|
||||
|
@ -0,0 +1,146 @@
|
||||
// Data objects for Tera template integration
|
||||
// This script defines complex objects that can be accessed from Tera 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user