herosal-heroscript

Crates.ioherosal-heroscript
lib.rsherosal-heroscript
version0.1.1
created_at2025-12-13 11:18:07.486465+00
updated_at2025-12-14 13:35:46.992983+00
descriptionHeroScript - A defensive configuration language for safe system orchestration
homepage
repositoryhttps://github.com/threefoldtech/sal
max_upload_size
id1982820
size203,270
kristof de spiegeleer (despiegk)

documentation

README

HeroScript (sal-heroscript)

A defensive configuration language for safe, predictable system orchestration.

Installation

Add this to your Cargo.toml:

[dependencies]
sal-heroscript = { git = "https://forge.ourworld.tf/geomind_research/herolib_rust.git", package = "sal-heroscript" }

Then in your Rust code:

use sal_heroscript::{PlayBook, PlayBookNewArgs, FindArgs};

Overview

HeroScript is designed for defining actions, configurations, and workflows without the risks associated with general-purpose scripting languages. Values are treated as data, never executed as code.

Key Features:

  • Defensive by Design - No arbitrary code execution, shell injection, or dynamic evaluation
  • Declarative Actions - Define what should happen through structured actions
  • Type-Safe Parameters - All parameters are parsed and validated before execution
  • Audit Trail - Every action has a defined actor, name, and parameters

Syntax

Actions are defined with the !!actor.action_name prefix, followed by parameters.

Compact Form (single line)

!!person.define name:'John Doe' email:'john@example.com' active:true
!!payment.add amount:100 currency:'USD'

Expanded Form (multiline)

!!person.define
    name: 'John Doe'
    email: 'john@example.com'
    age: 30
    active: true

!!server.configure
    host: 'localhost'
    port: 8080
    ssl: true

Comments

// This comment attaches to the next action
!!person.define name:'John'

Usage

Basic Parsing

use sal_heroscript::{PlayBook, PlayBookNewArgs, FindArgs};

let script = r#"
    !!person.define name:'John Doe' email:'john@example.com'
    !!person.define name:'Jane Doe' email:'jane@example.com'
"#;

let playbook = PlayBook::new(PlayBookNewArgs {
    text: script.to_string(),
    ..Default::default()
}).unwrap();

// Find all person.define actions
let actions = playbook.find(&FindArgs {
    filter: "person.define".to_string(),
    ..Default::default()
}).unwrap();

for action in actions {
    let name = action.params.get("name").unwrap();
    let email = action.params.get("email").unwrap();
    println!("Person: {} <{}>", name, email);
}

Getting Single Actions

// Get exactly one action (errors if 0 or multiple)
let config = playbook.get("server.configure")?;

// Check existence
if playbook.exists("server.configure") {
    // ...
}

// Check if exactly one exists
if playbook.exists_once("payment.configure") {
    // ...
}

Glob Filtering

// Find all payment actions (payment.add, payment.configure, etc.)
let payment_actions = playbook.find(&FindArgs {
    filter: "payment.*".to_string(),
    ..Default::default()
})?;

// Find all configure actions across actors
let configs = playbook.find(&FindArgs {
    filter: "*.configure".to_string(),
    ..Default::default()
})?;

// Multiple filters (comma-separated)
let results = playbook.find(&FindArgs {
    filter: "payment.add,notification.send".to_string(),
    ..Default::default()
})?;

Processing Actions

let mut playbook = PlayBook::new(PlayBookNewArgs {
    text: script.to_string(),
    ..Default::default()
})?;

// Get mutable reference and mark done
let action = playbook.get_mut("payment.configure")?;
// ... process action ...
action.mark_done();

// Ensure all actions were processed
playbook.empty_check()?;

Loading from Files

// Load from a single .hero file
let playbook = PlayBook::new(PlayBookNewArgs {
    path: "/path/to/config.hero".to_string(),
    ..Default::default()
})?;

// Load all .hero files from a directory
let playbook = PlayBook::new(PlayBookNewArgs {
    path: "/path/to/configs/".to_string(),
    ..Default::default()
})?;

Parameter Types

String Values

let name = params.get("name")?;                    // Required
let name = params.get_default("name", "default");  // With default
let name = params.get_opt("name");                 // Returns Option<&str>

Integer Values

let port = params.get_int("port")?;                      // i32
let port = params.get_int_default("port", 8080)?;        // With default
let count = params.get_u32("count")?;                    // u32
let id = params.get_u64("id")?;                          // u64

Float Values

let ratio = params.get_float("ratio")?;                  // f64
let ratio = params.get_float_default("ratio", 1.0)?;     // With default
let pct = params.get_percentage("threshold")?;           // "75%" -> 0.75

Boolean Values

// Returns true if missing (default true behavior)
let enabled = params.get_default_true("enabled");

// Returns false if missing (default false behavior)
let debug = params.get_default_false("debug");

// Explicit boolean parsing
let active = params.get_bool("active")?;

Boolean true values: true, 1, y, yes, or empty string Boolean false values: false, 0, n, no

List Values

// Parse comma-separated values: "items:a,b,c"
let items = params.get_list("items")?;  // vec!["a", "b", "c"]

Positional Arguments

// In: "!!action.name debug verbose key:value"
// "debug" and "verbose" are positional arguments

if params.exists_arg("debug") {
    // Enable debug mode
}

Existence Checks

if params.exists("optional_key") {
    // Key exists with a value
}

if params.exists_arg("flag") {
    // Positional argument exists
}

Direct Parameter Parsing

Parse parameters without a full playbook:

use sal_heroscript::parse_params;

let params = parse_params("name:'John' age:30 debug")?;
assert_eq!(params.get("name")?, "John");
assert_eq!(params.get_int("age")?, 30);
assert!(params.exists_arg("debug"));

Serialization

Convert actions back to HeroScript format:

// Single action
let heroscript = action.heroscript();
let oneline = action.to_oneline();

// Entire playbook
let script = playbook.heroscript(false);  // false = exclude done actions

Testing

Run tests with:

cargo test -p sal-heroscript

Run a specific test:

cargo test -p sal-heroscript test_basic_workflow
Commit count: 0

cargo fmt