pub mod hooks;
pub mod policy;
pub mod stdlib;
use anyhow::{anyhow, Result};
use bundcore::bundcore::Bund;
use parking_lot::RwLock;
use rust_dynamic::value::Value;
use std::sync::OnceLock;
use crate::store::Store;
use policy::Policy;
static ADAM: OnceLock<RwLock<Bund>> = OnceLock::new();
static ACTIVE_STORE: OnceLock<Store> = OnceLock::new();
static POLICY: OnceLock<Policy> = OnceLock::new();
pub fn init_adam() -> Result<()> {
if ADAM.get().is_some() {
return Ok(());
}
let mut bund = Bund::new();
stdlib::register_ink_stdlib(&mut bund.vm)
.map_err(|e| anyhow!("register ink stdlib: {e}"))?;
let p = POLICY.get().cloned().unwrap_or_default();
if !p.is_open() {
policy::apply_policy(&mut bund.vm, &p)
.map_err(|e| anyhow!("apply policy: {e}"))?;
}
if !p.bootstrap.trim().is_empty() {
if let Err(e) = bund.eval(p.bootstrap.clone()) {
tracing::warn!(
target: "inkhaven::scripting",
"bootstrap script failed: {}",
e
);
}
}
load_store_scripts(&mut bund);
let _ = ADAM.set(RwLock::new(bund));
Ok(())
}
fn load_store_scripts(bund: &mut Bund) {
let Some(store) = active_store() else { return };
let hierarchy = match crate::store::hierarchy::Hierarchy::load(store) {
Ok(h) => h,
Err(e) => {
tracing::warn!(
target: "inkhaven::scripting",
"load_store_scripts: hierarchy load failed: {}",
e
);
return;
}
};
for node in hierarchy.iter() {
if node.kind != crate::store::NodeKind::Script {
continue;
}
let bytes = match store.get_content(node.id) {
Ok(Some(b)) => b,
Ok(None) => continue,
Err(e) => {
tracing::warn!(
target: "inkhaven::scripting",
"script {} read failed: {}",
node.id,
e
);
continue;
}
};
let body = String::from_utf8_lossy(&bytes).into_owned();
if body.trim().is_empty() {
continue;
}
if let Err(e) = bund.eval(body) {
tracing::warn!(
target: "inkhaven::scripting",
"script `{}` ({}) eval failed: {}",
node.title,
node.id,
e
);
}
}
}
pub(crate) fn with_adam<F, R>(f: F) -> Option<R>
where
F: FnOnce(&mut Bund) -> R,
{
let adam = ADAM.get()?;
let mut guard = adam.write();
Some(f(&mut guard))
}
pub fn set_policy(policy: Policy) {
let _ = POLICY.set(policy);
}
pub fn register_active_store(store: Store) {
let _ = ACTIVE_STORE.set(store);
}
pub fn configure(policy: Policy, store: Store) {
set_policy(policy);
register_active_store(store);
}
pub fn active_store() -> Option<&'static Store> {
ACTIVE_STORE.get()
}
#[derive(Debug, Default)]
pub struct EvalOutput {
pub stdout: String,
pub top: Option<Value>,
}
pub fn eval(code: &str) -> Result<EvalOutput> {
init_adam()?;
let adam = ADAM.get().ok_or_else(|| anyhow!("Adam VM missing after init"))?;
let _ = stdlib::io::drain_print_buffer();
let mut guard = adam.write();
guard
.eval(code)
.map_err(|e| anyhow!("bund eval failed: {e}"))?;
let top = guard.vm.stack.pull();
drop(guard);
let stdout = stdlib::io::drain_print_buffer();
Ok(EvalOutput { stdout, top })
}
pub fn format_value(v: &Value) -> String {
if let Ok(s) = v.clone().cast_string() {
return s;
}
if let Ok(i) = v.clone().cast_int() {
return i.to_string();
}
if let Ok(f) = v.clone().cast_float() {
return f.to_string();
}
if let Ok(b) = v.clone().cast_bool() {
return b.to_string();
}
let j = value_to_json(v);
serde_json::to_string_pretty(&j).unwrap_or_else(|_| j.to_string())
}
fn value_to_json(v: &Value) -> serde_json::Value {
if let Ok(s) = v.clone().cast_string() {
return serde_json::Value::String(s);
}
if let Ok(i) = v.clone().cast_int() {
return serde_json::Value::from(i);
}
if let Ok(f) = v.clone().cast_float() {
return serde_json::Value::from(f);
}
if let Ok(b) = v.clone().cast_bool() {
return serde_json::Value::Bool(b);
}
if let Ok(list) = v.clone().cast_list() {
return serde_json::Value::Array(list.iter().map(value_to_json).collect());
}
if let Ok(dict) = v.clone().cast_dict() {
let mut m = serde_json::Map::new();
for (k, val) in dict.iter() {
m.insert(k.clone(), value_to_json(val));
}
return serde_json::Value::Object(m);
}
serde_json::Value::Null
}