Expand description
Command execution module
This module provides the core functionality for executing commands in the
dynamic-cli framework. It defines the CommandHandler trait that all
command implementations must satisfy.
§Module Organization
traits: Core trait definitions (CommandHandler)command_executor(future): Executor logic for running commands
§Architecture
The execution flow in dynamic-cli follows this pattern:
User Input → Parser → Validator → Executor → Command Handler
↓
Context- Parser: Converts raw input to structured arguments
- Validator: Checks argument types and constraints
- Executor: Looks up and invokes the appropriate handler
- Handler: Executes the command logic with access to context
§Design Philosophy
§Object Safety
The module is designed around object-safe traits to enable dynamic dispatch. This allows:
- Runtime registration of commands
- Storing heterogeneous handlers in collections
- Plugin-style architecture where handlers are loaded dynamically
§Thread Safety
All types are Send + Sync to support:
- Multi-threaded CLI applications
- Concurrent command execution (future enhancement)
- Safe shared access to the command registry
§Simplicity
The API is intentionally kept simple:
- Arguments are passed as
HashMap<String, String> - Context is accessed through trait objects
- Error handling uses the framework’s standard
Resulttype
§Quick Start
use std::collections::HashMap;
use dynamic_cli::error::ExecutionError;
use dynamic_cli::executor::CommandHandler;
use dynamic_cli::context::ExecutionContext;
use dynamic_cli::Result;
// 1. Define your context
#[derive(Default)]
struct AppContext {
counter: i32,
}
impl ExecutionContext for AppContext {
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}
// 2. Implement a command handler
struct IncrementCommand;
impl CommandHandler for IncrementCommand {
fn execute(
&self,
context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()> {
let ctx = dynamic_cli::context::downcast_mut::<AppContext>(context)
.ok_or_else(|| ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type")))?;
let amount: i32 = args.get("amount")
.and_then(|s| s.parse().ok())
.unwrap_or(1);
ctx.counter += amount;
println!("Counter is now: {}", ctx.counter);
Ok(())
}
}
// 3. Use the handler
let handler = IncrementCommand;
let mut context = AppContext::default();
let mut args = HashMap::new();
args.insert("amount".to_string(), "5".to_string());
handler.execute(&mut context, &args)?;
assert_eq!(context.counter, 5);§Examples
§Basic Command
use std::collections::HashMap;
use dynamic_cli::executor::CommandHandler;
use dynamic_cli::context::ExecutionContext;
use dynamic_cli::Result;
struct EchoCommand;
impl CommandHandler for EchoCommand {
fn execute(
&self,
_context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()> {
if let Some(message) = args.get("message") {
println!("{}", message);
}
Ok(())
}
}§Command with Validation
use std::collections::HashMap;
use dynamic_cli::error::ExecutionError;
use dynamic_cli::executor::CommandHandler;
use dynamic_cli::context::ExecutionContext;
use dynamic_cli::DynamicCliError::Execution;
use dynamic_cli::Result;
struct DivideCommand;
impl CommandHandler for DivideCommand {
fn execute(
&self,
_context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> dynamic_cli::Result<()> {
let denom = args.get("denominator")
.ok_or_else(|| {
ExecutionError::CommandFailed(
anyhow::anyhow!("Missing Denominator"))})?;
let value: f64 = denom.parse()
.map_err(|_| {
ExecutionError::CommandFailed(
anyhow::anyhow!("Invalid Denominator"))})?;
if value == 0.0 {
return Err(ExecutionError::CommandFailed(
anyhow::anyhow!("Cannot divide by zero")).into());
}
Ok(())
}
}§Stateful Command
use std::collections::HashMap;
use dynamic_cli::error::ExecutionError;
use dynamic_cli::executor::CommandHandler;
use dynamic_cli::context::ExecutionContext;
use dynamic_cli::Result;
#[derive(Default)]
struct FileContext {
current_file: Option<String>,
}
impl ExecutionContext for FileContext {
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}
struct OpenCommand;
impl CommandHandler for OpenCommand {
fn execute(
&self,
context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()> {
let ctx = dynamic_cli::context::downcast_mut::<FileContext>(context)
.ok_or_else(|| ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type")))?;
let filename = args.get("file")
.ok_or_else(|| { ExecutionError::CommandFailed(anyhow::anyhow!("Missing file argument"))})?;
ctx.current_file = Some(filename.clone());
println!("Opened: {}", filename);
Ok(())
}
}§Advanced Usage
§Dynamic Command Registration
Commands can be registered dynamically at runtime using trait objects:
use std::collections::HashMap;
use dynamic_cli::executor::CommandHandler;
// Store commands in a registry
struct CommandRegistry {
handlers: HashMap<String, Box<dyn CommandHandler>>,
}
impl CommandRegistry {
fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
fn register(&mut self, name: String, handler: Box<dyn CommandHandler>) {
self.handlers.insert(name, handler);
}
fn get(&self, name: &str) -> Option<&Box<dyn CommandHandler>> {
self.handlers.get(name)
}
}§Error Handling Pattern
use std::collections::HashMap;
use dynamic_cli::executor::CommandHandler;
use dynamic_cli::context::ExecutionContext;
use dynamic_cli::error::ExecutionError;
use dynamic_cli::Result;
struct FileCommand;
impl CommandHandler for FileCommand {
fn execute(
&self,
_context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()> {
let path = args.get("path")
.ok_or_else(|| { ExecutionError::CommandFailed(anyhow::anyhow!("Missing path argument"))})?;
// Wrap application errors in ExecutionError
std::fs::read_to_string(path)
.map_err(|e| ExecutionError::CommandFailed(
anyhow::anyhow!("Failed to read file: {}", e)
))?;
Ok(())
}
}Re-exports§
pub use traits::CommandHandler;
Modules§
- traits
- Command handler trait and related types