| Crates.io | goblin-engine |
| lib.rs | goblin-engine |
| version | 0.1.0 |
| created_at | 2025-10-29 19:51:08.565807+00 |
| updated_at | 2025-10-29 19:51:08.565807+00 |
| description | A high-performance async workflow engine for executing scripts in planned sequences with dependency resolution |
| homepage | https://github.com/wsb1994/goblin-rs |
| repository | https://github.com/wsb1994/goblin-rs |
| max_upload_size | |
| id | 1907264 |
| size | 141,875 |
A high-performance, async workflow engine for executing scripts in planned sequences, written in Rust. This is a complete reimplementation and architectural improvement of the original Python-based goblin workflow engine.
Goblin Engine allows you to:
| Aspect | Original Python | New Rust Implementation |
|---|---|---|
| Performance | Synchronous execution | Fully async/concurrent execution |
| Type Safety | Runtime validation | Compile-time type safety |
| Error Handling | Exception-based | Result-based with rich error types |
| Concurrency | Threading with locks | Lock-free concurrent data structures |
| Memory Management | Garbage collected | Zero-cost abstractions, no GC overhead |
| Configuration | Basic TOML parsing | Full validation with defaults |
| Testing | Limited test coverage | Comprehensive unit tests |
| Dependency Management | Basic topological sort | Advanced cycle detection |
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Engine โโโโโบโ Executor โโโโโบโ Script โ
โ (Orchestrator)โ โ (Runs Commands) โ โ (Configuration) โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ โ
โผ โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโผโโโโโโโโโ
โ Plan โโโโโบโ Step โโโโโบโ StepInput โ
โ (Workflow) โ โ (Single Task) โ โ (Input Types) โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
Script)pub struct Script {
pub name: String, // Unique identifier
pub command: String, // Command to execute
pub timeout: Duration, // Execution timeout
pub test_command: Option<String>, // Optional test command
pub require_test: bool, // Whether to run test before execution
pub path: PathBuf, // Working directory
}
TOML Configuration (goblin.toml):
name = "example_script"
command = "deno run --allow-all main.ts"
timeout = 500 # seconds
test_command = "deno test"
require_test = false
Plan)pub struct Plan {
pub name: String, // Plan identifier
pub steps: Vec<Step>, // Ordered execution steps
}
pub struct Step {
pub name: String, // Step identifier
pub function: String, // Script to execute
pub inputs: Vec<StepInput>, // Input arguments
pub timeout: Option<Duration>, // Override timeout
}
TOML Configuration (plan file):
name = "example_plan"
[[steps]]
name = "step_one"
function = "script_name"
inputs = ["default_input"]
timeout = 1000
[[steps]]
name = "step_two"
function = "another_script"
inputs = ["step_one", "literal_value"]
StepInput)The engine supports three types of step inputs:
Literal Values: Plain string values
inputs = ["hello world", "static_value"]
Step References: Output from previous steps
inputs = ["previous_step_name"]
Templates: String interpolation with step outputs
inputs = ["Processing {step1} with {step2}"]
GoblinError)pub enum GoblinError {
ScriptNotFound { name: String },
PlanNotFound { name: String },
ScriptExecutionFailed { script: String, message: String },
ScriptTimeout { script: String, timeout: Duration },
TestFailed { script: String },
ConfigError { message: String },
InvalidStepConfig { message: String },
CircularDependency { plan: String },
MissingDependency { step: String, dependency: String },
// ... IO and serialization errors
}
git clone <repository>
cd goblin-engine
cargo build --release
cargo install --path .
# or
cargo install goblin-engine
# Initialize a new project
goblin init [directory]
# List available scripts and plans
goblin scripts
goblin plans
# Execute a single script
goblin run-script <script_name> [args...]
# Execute a plan
goblin run-plan <plan_name> --input "default input"
# Validate configuration
goblin validate
# Show statistics
goblin stats
# Generate sample configuration
goblin config > goblin.toml
goblin.toml)# Directory paths
scripts_dir = "./scripts"
plans_dir = "./plans"
# Execution settings
default_timeout = 500
require_tests = false
# Global environment variables
[environment]
API_KEY = "secret_key"
[logging]
level = "info"
stdout = true
file = "./goblin.log"
timestamps = true
[execution]
max_concurrent = 4
fail_fast = true
cleanup_temp_files = true
use goblin_engine::{Engine, EngineConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create and configure engine
let config = EngineConfig::from_file("goblin.toml")?;
let engine = Engine::new()
.with_scripts_dir(config.scripts_dir.unwrap());
// Auto-discover scripts
engine.auto_discover_scripts()?;
// Execute a plan
let context = engine.execute_plan("my_plan", Some("input".to_string())).await?;
println!("Execution completed in {:?}", context.elapsed());
for (step, result) in context.results {
println!("{}: {}", step, result);
}
Ok(())
}
project/
โโโ goblin.toml # Main configuration
โโโ scripts/ # Script definitions
โ โโโ script1/
โ โ โโโ goblin.toml # Script config
โ โ โโโ main.py # Implementation
โ โ โโโ test.sh # Optional test
โ โโโ script2/
โ โโโ goblin.toml
โ โโโ main.ts
โโโ plans/ # Execution plans
โโโ plan1.toml
โโโ plan2.toml
Python (goblin.toml):
name = "example"
command = "python main.py"
timeout = 500
test_command = "python -m pytest"
require_test = false
Rust (same format):
name = "example"
command = "python main.py"
timeout = 500
test_command = "python -m pytest"
require_test = false
Python (plan.toml):
name = "old_plan"
[[steps]]
name = "step1"
function = "hello_world" # Note: function field
inputs = ["default_input"]
Rust (plan.toml):
name = "new_plan"
[[steps]]
name = "step1"
function = "hello_world" # Same format supported
inputs = ["default_input"]
# Run all tests
cargo test
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test test_plan_execution
Generate and view API documentation:
cargo doc --open
Executor Trait#[async_trait]
pub trait Executor {
async fn execute_script(&self, script: &Script, args: &[String]) -> Result<ExecutionResult>;
async fn run_test(&self, script: &Script) -> Result<bool>;
}
Implement custom executors for different environments (Docker, remote execution, etc.).
Engine::new() - Create engine with default executorEngine::with_executor() - Create with custom executorauto_discover_scripts() - Find and load scripts from directoryexecute_plan() - Execute a workflow planexecute_script() - Execute single scriptPlan::from_toml_file() - Load plan from fileget_execution_order() - Resolve dependency ordervalidate() - Check for cycles and missing dependenciesThe engine automatically resolves step dependencies using topological sorting:
[[steps]]
name = "fetch_data"
inputs = ["default_input"]
[[steps]]
name = "process_data"
inputs = ["fetch_data"]
[[steps]]
name = "save_results"
inputs = ["process_data", "config_value"]
Execution order: fetch_data โ process_data โ save_results
Use previous step outputs in later steps:
[[steps]]
name = "get_user"
inputs = ["user_id"]
[[steps]]
name = "send_email"
inputs = ["Hello {get_user}, welcome!"]
The engine prevents infinite loops:
# This will fail validation
[[steps]]
name = "step_a"
inputs = ["step_b"]
[[steps]]
name = "step_b"
inputs = ["step_a"]
git checkout -b feature/amazing-feature)cargo test and cargo clippygit commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License - see the LICENSE file for details.
Performance Note: The Rust implementation shows 5-10x performance improvements over the Python version for typical workflows, with significantly better memory usage and concurrent execution capabilities.