| Crates.io | herosal-heroscript |
| lib.rs | herosal-heroscript |
| version | 0.1.1 |
| created_at | 2025-12-13 11:18:07.486465+00 |
| updated_at | 2025-12-14 13:35:46.992983+00 |
| description | HeroScript - A defensive configuration language for safe system orchestration |
| homepage | |
| repository | https://github.com/threefoldtech/sal |
| max_upload_size | |
| id | 1982820 |
| size | 203,270 |
sal-heroscript)A defensive configuration language for safe, predictable system orchestration.
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};
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:
Actions are defined with the !!actor.action_name prefix, followed by parameters.
!!person.define name:'John Doe' email:'john@example.com' active:true
!!payment.add amount:100 currency:'USD'
!!person.define
name: 'John Doe'
email: 'john@example.com'
age: 30
active: true
!!server.configure
host: 'localhost'
port: 8080
ssl: true
// This comment attaches to the next action
!!person.define name:'John'
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);
}
// 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") {
// ...
}
// 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()
})?;
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()?;
// 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()
})?;
let name = params.get("name")?; // Required
let name = params.get_default("name", "default"); // With default
let name = params.get_opt("name"); // Returns Option<&str>
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
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
// 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
// Parse comma-separated values: "items:a,b,c"
let items = params.get_list("items")?; // vec!["a", "b", "c"]
// In: "!!action.name debug verbose key:value"
// "debug" and "verbose" are positional arguments
if params.exists_arg("debug") {
// Enable debug mode
}
if params.exists("optional_key") {
// Key exists with a value
}
if params.exists_arg("flag") {
// Positional argument exists
}
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"));
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
Run tests with:
cargo test -p sal-heroscript
Run a specific test:
cargo test -p sal-heroscript test_basic_workflow