Module registry

Module registry 

Source
Expand description

Command registry module

This module provides the central registry for storing and managing commands. The registry maintains the mapping between command names/aliases and their definitions and handlers.

§Architecture

The registry serves as a lookup table for the executor:

Configuration → Registry → Executor
    (YAML)        (Store)    (Execute)

§Flow

  1. Initialization: Commands are registered during application startup
  2. Lookup: During execution, the executor queries the registry
  3. Dispatch: The registry returns the appropriate handler

§Design Principles

§Separation of Concerns

The registry separates:

  • Definition (from config module): What the command accepts
  • Implementation (from executor module): What the command does
  • Lookup (this module): How to find commands

§Efficient Lookup

The registry uses HashMaps for O(1) lookup by:

  • Command name
  • Command alias

This ensures fast command resolution even with many registered commands.

§Validation

The registry validates during registration:

  • No duplicate command names
  • No duplicate aliases
  • No conflicts between names and aliases

§Quick Start

use dynamic_cli::registry::CommandRegistry;
use dynamic_cli::config::schema::CommandDefinition;
use dynamic_cli::executor::CommandHandler;
use std::collections::HashMap;

// 1. Create a registry
let mut registry = CommandRegistry::new();

// 2. Define a command
let definition = CommandDefinition {
    name: "hello".to_string(),
    aliases: vec!["hi".to_string()],
    description: "Say hello".to_string(),
    required: false,
    arguments: vec![],
    options: vec![],
    implementation: "hello_handler".to_string(),
};

// 3. Create a handler
struct HelloCommand;
impl CommandHandler for HelloCommand {
    fn execute(
        &self,
        _ctx: &mut dyn dynamic_cli::context::ExecutionContext,
        args: &HashMap<String, String>,
    ) -> dynamic_cli::Result<()> {
        let default_world = "World".to_string();
        let name = args.get("name").unwrap_or(&default_world);
        println!("Hello, {}!", name);
        Ok(())
    }
}

// 4. Register the command
registry.register(definition, Box::new(HelloCommand))?;

// 5. Use the registry
if let Some(handler) = registry.get_handler("hello") {
    // Execute the command
}

// Works with aliases too!
if let Some(handler) = registry.get_handler("hi") {
    // Same handler
}

§Examples

§Basic Registration

use dynamic_cli::registry::CommandRegistry;

let mut registry = CommandRegistry::new();

registry.register(definition, Box::new(TestCmd))?;

§Command with Multiple Aliases

let definition = CommandDefinition {
    name: "simulate".to_string(),
    aliases: vec![
        "sim".to_string(),
        "run".to_string(),
        "exec".to_string(),
    ],
    description: "Run a simulation".to_string(),
    required: false,
    arguments: vec![],
    options: vec![],
    implementation: "simulate_handler".to_string(),
};

registry.register(definition, Box::new(SimCmd))?;

// All these work:
assert!(registry.contains("simulate"));
assert!(registry.contains("sim"));
assert!(registry.contains("run"));
assert!(registry.contains("exec"));

§Listing All Commands

// Get all commands for help text
for cmd in registry.list_commands() {
    println!("{}: {}", cmd.name, cmd.description);
}

§Error Handling

// Try to register duplicate
let result = registry.register(def2, Box::new(TestCmd));

match result {
    Err(DynamicCliError::Registry(RegistryError::DuplicateRegistration { name })) => {
        eprintln!("Command '{}' already registered", name);
    }
    _ => {}
}

§Integration with Other Modules

§With Config Module

The registry stores CommandDefinition from the config module:

use dynamic_cli::config::loader::load_config;
use dynamic_cli::registry::CommandRegistry;

let config = load_config("commands.yaml")?;
let mut registry = CommandRegistry::new();

for cmd_def in config.commands {
    let handler = create_handler(&cmd_def.implementation);
    registry.register(cmd_def, handler)?;
}

§With Executor Module

The executor queries the registry to find handlers:

use dynamic_cli::registry::CommandRegistry;

fn execute_command(
    registry: &CommandRegistry,
    command_name: &str,
    context: &mut dyn ExecutionContext,
    args: &HashMap<String, String>,
) -> Result<()> {
    let handler = registry.get_handler(command_name)
        .ok_or_else(|| anyhow::anyhow!("Unknown command"))?;
     
    handler.execute(context, args)
}

§Thread Safety

The registry is designed for setup-once, use-many pattern:

use std::sync::Arc;

// Setup phase (single-threaded)
let mut registry = CommandRegistry::new();
// ... register commands ...

// Usage phase (can be multi-threaded)
let registry = Arc::new(registry);
let registry_clone = registry.clone();

std::thread::spawn(move || {
    // Safe to use in multiple threads
    if let Some(handler) = registry_clone.get_handler("test") {
        // ...
    }
});

Re-exports§

pub use command_registry::CommandRegistry;

Modules§

command_registry
Command registry implementation