Module executor

Module executor 

Source
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
  1. Parser: Converts raw input to structured arguments
  2. Validator: Checks argument types and constraints
  3. Executor: Looks up and invokes the appropriate handler
  4. 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 Result type

§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