use std::collections::HashMap;
use crate::CliError;
#[derive(Debug, Clone)]
pub struct CommandSignature {
pub name: String,
pub description: String,
pub usage: String,
}
pub trait Command: Send + Sync {
fn signature(&self) -> CommandSignature;
fn execute(&self, args: &[String]) -> Result<i32, CliError>;
}
pub struct Console {
commands: HashMap<String, Box<dyn Command>>,
}
impl Console {
pub fn new() -> Self {
Self {
commands: HashMap::new(),
}
}
pub fn register(&mut self, command: Box<dyn Command>) -> &mut Self {
let name = command.signature().name;
self.commands.insert(name, command);
self
}
pub async fn run(&self, args: Vec<String>) -> Result<i32, CliError> {
if args.len() >= 2 {
if let Some(command) = self.commands.get(&args[1]) {
let cmd_args: &[String] = &args[2..];
return command.execute(cmd_args);
}
}
crate::run(args).await
}
pub fn list(&self) -> Vec<CommandSignature> {
self.commands.values().map(|cmd| cmd.signature()).collect()
}
pub fn print_list(&self) {
println!("Available commands:");
let mut signatures = self.list();
signatures.sort_by(|a, b| a.name.cmp(&b.name));
for sig in signatures {
println!(" {:<20} {}", sig.name, sig.description);
}
}
}
impl Default for Console {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct HelloCommand;
impl Command for HelloCommand {
fn signature(&self) -> CommandSignature {
CommandSignature {
name: "hello".to_string(),
description: "Print hello world".to_string(),
usage: "sz-rust hello".to_string(),
}
}
fn execute(&self, _args: &[String]) -> Result<i32, CliError> {
println!("Hello, World!");
Ok(0)
}
}
struct EchoCommand;
impl Command for EchoCommand {
fn signature(&self) -> CommandSignature {
CommandSignature {
name: "echo".to_string(),
description: "Echo arguments".to_string(),
usage: "sz-rust echo <args...>".to_string(),
}
}
fn execute(&self, args: &[String]) -> Result<i32, CliError> {
println!("{}", args.join(" "));
Ok(0)
}
}
#[test]
fn test_register_and_list() {
let mut console = Console::new();
console.register(Box::new(HelloCommand));
let commands = console.list();
assert_eq!(commands.len(), 1);
assert_eq!(commands[0].name, "hello");
assert_eq!(commands[0].description, "Print hello world");
assert_eq!(commands[0].usage, "sz-rust hello");
}
#[test]
fn test_register_multiple_commands() {
let mut console = Console::new();
console
.register(Box::new(HelloCommand))
.register(Box::new(EchoCommand));
let commands = console.list();
assert_eq!(commands.len(), 2);
}
#[tokio::test]
async fn test_run_custom_command() {
let mut console = Console::new();
console.register(Box::new(HelloCommand));
let result = console
.run(vec!["sz-rust".to_string(), "hello".to_string()])
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[tokio::test]
async fn test_run_custom_command_with_args() {
let mut console = Console::new();
console.register(Box::new(EchoCommand));
let result = console
.run(vec![
"sz-rust".to_string(),
"echo".to_string(),
"foo".to_string(),
"bar".to_string(),
])
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[tokio::test]
async fn test_run_unknown_command_falls_through() {
let console = Console::new();
let result = console
.run(vec!["sz-rust".to_string(), "cache:clear".to_string()])
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_run_no_args_falls_through() {
let console = Console::new();
let result = console.run(vec!["sz-rust".to_string()]).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_console_default_is_empty() {
let console = Console::default();
assert!(console.list().is_empty());
}
#[test]
fn test_register_overwrites_same_name() {
let mut console = Console::new();
console.register(Box::new(HelloCommand));
console.register(Box::new(EchoCommand));
let commands = console.list();
assert_eq!(commands.len(), 2);
}
}