use crate::context::ExecutionContext;
use crate::error::{display_error, DynamicCliError, Result};
use crate::parser::CliParser;
use crate::registry::CommandRegistry;
use std::process;
pub struct CliInterface {
registry: CommandRegistry,
context: Box<dyn ExecutionContext>,
}
impl CliInterface {
pub fn new(registry: CommandRegistry, context: Box<dyn ExecutionContext>) -> Self {
Self { registry, context }
}
pub fn run(mut self, args: Vec<String>) -> Result<()> {
if args.is_empty() {
return Err(DynamicCliError::Parse(
crate::error::ParseError::InvalidSyntax {
details: "No command specified".to_string(),
hint: Some("Try 'help' to see available commands".to_string()),
},
));
}
let command_name = &args[0];
let resolved_name = self.registry.resolve_name(command_name).ok_or_else(|| {
crate::error::ParseError::unknown_command_with_suggestions(
command_name,
&self
.registry
.list_commands()
.iter()
.map(|cmd| cmd.name.clone())
.collect::<Vec<_>>(),
)
})?;
let definition = self.registry.get_definition(resolved_name).ok_or_else(|| {
DynamicCliError::Registry(crate::error::RegistryError::missing_handler(resolved_name))
})?;
let parser = CliParser::new(definition);
let parsed_args = parser.parse(&args[1..])?;
let handler = self.registry.get_handler(resolved_name).ok_or_else(|| {
DynamicCliError::Execution(crate::error::ExecutionError::handler_not_found(
resolved_name,
&definition.implementation,
))
})?;
handler.execute(&mut *self.context, &parsed_args)?;
Ok(())
}
pub fn run_and_exit(self, args: Vec<String>) -> ! {
match self.run(args) {
Ok(()) => process::exit(0),
Err(e) => {
display_error(&e);
let exit_code = match e {
DynamicCliError::Parse(_) => 2,
DynamicCliError::Validation(_) => 2,
DynamicCliError::Execution(_) => 1,
_ => 3,
};
process::exit(exit_code);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::schema::{ArgumentDefinition, ArgumentType, CommandDefinition};
use std::collections::HashMap;
#[derive(Default)]
struct TestContext {
executed_command: Option<String>,
}
impl ExecutionContext for TestContext {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
struct TestHandler {
name: String,
}
impl crate::executor::CommandHandler for TestHandler {
fn execute(
&self,
context: &mut dyn ExecutionContext,
_args: &HashMap<String, String>,
) -> Result<()> {
let ctx = crate::context::downcast_mut::<TestContext>(context)
.expect("Failed to downcast context");
ctx.executed_command = Some(self.name.clone());
Ok(())
}
}
fn create_test_registry() -> CommandRegistry {
let mut registry = CommandRegistry::new();
let cmd_def = CommandDefinition {
name: "test".to_string(),
aliases: vec!["t".to_string()],
description: "Test command".to_string(),
required: false,
arguments: vec![],
options: vec![],
implementation: "test_handler".to_string(),
};
let handler = Box::new(TestHandler {
name: "test".to_string(),
});
registry
.register(cmd_def, handler)
.expect("Failed to register command");
registry
}
#[test]
fn test_cli_interface_creation() {
let registry = create_test_registry();
let context = Box::new(TestContext::default());
let _cli = CliInterface::new(registry, context);
}
#[test]
fn test_cli_run_simple_command() {
let registry = create_test_registry();
let context = Box::new(TestContext::default());
let cli = CliInterface::new(registry, context);
let result = cli.run(vec!["test".to_string()]);
assert!(result.is_ok());
}
#[test]
fn test_cli_run_with_alias() {
let registry = create_test_registry();
let context = Box::new(TestContext::default());
let cli = CliInterface::new(registry, context);
let result = cli.run(vec!["t".to_string()]);
assert!(result.is_ok());
}
#[test]
fn test_cli_empty_args() {
let registry = create_test_registry();
let context = Box::new(TestContext::default());
let cli = CliInterface::new(registry, context);
let result = cli.run(vec![]);
assert!(result.is_err());
match result.unwrap_err() {
DynamicCliError::Parse(crate::error::ParseError::InvalidSyntax { .. }) => {}
other => panic!("Expected InvalidSyntax error, got: {:?}", other),
}
}
#[test]
fn test_cli_unknown_command() {
let registry = create_test_registry();
let context = Box::new(TestContext::default());
let cli = CliInterface::new(registry, context);
let result = cli.run(vec!["unknown".to_string()]);
assert!(result.is_err());
match result.unwrap_err() {
DynamicCliError::Parse(crate::error::ParseError::UnknownCommand { .. }) => {}
other => panic!("Expected UnknownCommand error, got: {:?}", other),
}
}
#[test]
fn test_cli_command_with_args() {
let mut registry = CommandRegistry::new();
let cmd_def = CommandDefinition {
name: "greet".to_string(),
aliases: vec![],
description: "Greet someone".to_string(),
required: false,
arguments: vec![ArgumentDefinition {
name: "name".to_string(),
arg_type: ArgumentType::String,
required: true,
description: "Name to greet".to_string(),
validation: vec![],
}],
options: vec![],
implementation: "greet_handler".to_string(),
};
struct GreetHandler;
impl crate::executor::CommandHandler for GreetHandler {
fn execute(
&self,
_context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()> {
assert_eq!(args.get("name"), Some(&"Alice".to_string()));
Ok(())
}
}
registry.register(cmd_def, Box::new(GreetHandler)).unwrap();
let context = Box::new(TestContext::default());
let cli = CliInterface::new(registry, context);
let result = cli.run(vec!["greet".to_string(), "Alice".to_string()]);
assert!(result.is_ok());
}
}