2 releases
| 0.1.1 | Dec 14, 2025 |
|---|---|
| 0.1.0 | Dec 13, 2025 |
#9 in #playbook
Used in 3 crates
(2 directly)
160KB
3.5K
SLoC
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
Dependencies
~6–18MB
~196K SLoC