use std::{
collections::{BTreeMap, HashMap},
sync::Arc,
};
use super::{Command, StrategyError};
pub trait SubcommandCatalog {
fn subcommands(&self) -> Vec<Command>;
}
pub trait CommandStrategy: Send + Sync {
fn execute(
&self,
options: Vec<String>,
arguments: HashMap<String, String>,
subcommands: Vec<String>,
) -> Result<(), StrategyError>;
fn subcommand_catalog(&self) -> Option<&dyn SubcommandCatalog> {
None
}
}
pub struct FunctionStrategy<F>
where
F: Fn(Vec<String>, HashMap<String, String>, Vec<String>) -> Result<(), StrategyError>
+ Send
+ Sync,
{
runner: F,
}
impl<F> FunctionStrategy<F>
where
F: Fn(Vec<String>, HashMap<String, String>, Vec<String>) -> Result<(), StrategyError>
+ Send
+ Sync,
{
pub fn new(runner: F) -> Self {
Self { runner }
}
}
impl<F> CommandStrategy for FunctionStrategy<F>
where
F: Fn(Vec<String>, HashMap<String, String>, Vec<String>) -> Result<(), StrategyError>
+ Send
+ Sync,
{
fn execute(
&self,
options: Vec<String>,
arguments: HashMap<String, String>,
subcommands: Vec<String>,
) -> Result<(), StrategyError> {
(self.runner)(options, arguments, subcommands)
}
}
#[derive(Default)]
pub struct SubcommandRouter {
children: BTreeMap<String, Command>,
aliases: BTreeMap<String, String>,
}
impl SubcommandRouter {
pub fn new() -> Self {
Self::default()
}
pub fn register(mut self, command: Command) -> Self {
self.register_mut(command);
self
}
pub fn register_mut(&mut self, command: Command) -> &mut Self {
for alias in &command.metadata.aliases {
self.aliases
.insert(alias.clone(), command.metadata.name.clone());
}
self.children.insert(command.metadata.name.clone(), command);
self
}
fn resolve(&self, token: &str) -> Option<Command> {
if let Some(command) = self.children.get(token) {
return Some(command.clone());
}
self.aliases
.get(token)
.and_then(|canonical| self.children.get(canonical))
.cloned()
}
fn available_subcommands(&self) -> String {
self.children
.keys()
.cloned()
.collect::<Vec<String>>()
.join(", ")
}
}
impl SubcommandCatalog for SubcommandRouter {
fn subcommands(&self) -> Vec<Command> {
self.children.values().cloned().collect()
}
}
impl CommandStrategy for SubcommandRouter {
fn execute(
&self,
_options: Vec<String>,
_arguments: HashMap<String, String>,
subcommands: Vec<String>,
) -> Result<(), StrategyError> {
let Some(subcommand_name) = subcommands.first() else {
return Err(StrategyError::invalid_arguments(format!(
"missing subcommand. available: {}",
self.available_subcommands()
)));
};
let command = self.resolve(subcommand_name).ok_or_else(|| {
StrategyError::invalid_arguments(format!(
"unknown subcommand '{subcommand_name}'. available: {}",
self.available_subcommands()
))
})?;
command.execute(subcommands[1..].to_vec())
}
fn subcommand_catalog(&self) -> Option<&dyn SubcommandCatalog> {
Some(self)
}
}
pub(crate) struct FallbackSubcommandStrategy {
strategy: Arc<dyn CommandStrategy>,
router: SubcommandRouter,
}
impl FallbackSubcommandStrategy {
pub(crate) fn new(strategy: Arc<dyn CommandStrategy>, router: SubcommandRouter) -> Self {
Self { strategy, router }
}
}
impl CommandStrategy for FallbackSubcommandStrategy {
fn execute(
&self,
options: Vec<String>,
arguments: HashMap<String, String>,
subcommands: Vec<String>,
) -> Result<(), StrategyError> {
if subcommands.is_empty() {
return self.strategy.execute(options, arguments, subcommands);
}
self.router.execute(options, arguments, subcommands)
}
fn subcommand_catalog(&self) -> Option<&dyn SubcommandCatalog> {
Some(self)
}
}
impl SubcommandCatalog for FallbackSubcommandStrategy {
fn subcommands(&self) -> Vec<Command> {
let mut subcommands = BTreeMap::new();
for command in self.router.subcommands() {
subcommands.insert(command.metadata.name.clone(), command);
}
if let Some(catalog) = self.strategy.subcommand_catalog() {
for command in catalog.subcommands() {
subcommands
.entry(command.metadata.name.clone())
.or_insert(command);
}
}
subcommands.into_values().collect()
}
}