use std::fmt::Debug;
use std::str::FromStr;
use radix_trie::Trie;
use crate::util::completion_keys;
pub enum CommandStep<C: Command> {
Continue(C::Action, C::Context),
Stop(C::Action, C::Context),
Again(String),
}
#[derive(thiserror::Error, Debug, Eq, PartialEq)]
pub enum CommandError {
#[error("Invalid command: {0}")]
InvalidCommand(String),
#[error("Invalid argument")]
InvalidArgument,
#[error("Invalid range")]
InvalidRange,
#[error("Failed to parse command: {0}")]
ParseFailed(String),
#[error("Error: {0}")]
Error(String),
}
pub type CommandResult<C> = Result<CommandStep<C>, CommandError>;
pub trait ParsedCommand: Debug + FromStr<Err = String> {
fn name(&self) -> String;
}
pub trait Command: Clone {
type Parsed: ParsedCommand;
type Action;
type Context;
type CommandContext: From<Self::Context>;
fn name(&self) -> String;
fn aliases(&self) -> Vec<String>;
fn exec(&self, cmd: Self::Parsed, ctx: &mut Self::CommandContext) -> CommandResult<Self>;
}
pub trait DefaultCommands<C: Command>: Default {
fn setup(self, machine: &mut CommandMachine<C>);
}
#[derive(Debug)]
pub struct CommandMachine<C: Command> {
names: Trie<String, C>,
aliases: Trie<String, C>,
last_cmd: String,
}
impl<C: Command> CommandMachine<C> {
pub fn new() -> Self {
let names = Trie::new();
let aliases = Trie::new();
let last_cmd = "".to_string();
CommandMachine { names, aliases, last_cmd }
}
pub fn add_command(&mut self, cmd: C) {
for alias in cmd.aliases().into_iter() {
self.aliases.insert(alias, cmd.clone());
}
self.names.insert(cmd.name(), cmd);
}
pub fn complete_name(&self, prefix: &str) -> Vec<String> {
completion_keys(&self.names, prefix)
}
pub fn complete_aliases(&self, prefix: &str) -> Vec<String> {
completion_keys(&self.aliases, prefix)
}
pub fn get(&self, name: &str) -> Result<&C, CommandError> {
if let Some(m) = self.names.get(name) {
Ok(m)
} else if let Some(m) = self.aliases.get(name) {
Ok(m)
} else {
Err(CommandError::InvalidCommand(name.into()))
}
}
pub fn get_last_command(&self) -> String {
self.last_cmd.clone()
}
pub fn input_cmd<T: Into<String>>(
&mut self,
input: T,
ctx: C::Context,
) -> Result<Vec<(C::Action, C::Context)>, CommandError> {
let mut input: String = input.into();
let mut results = Vec::new();
let mut ctx = C::CommandContext::from(ctx);
self.last_cmd = input.clone();
loop {
let cmd = C::Parsed::from_str(&input).map_err(CommandError::ParseFailed)?;
let name = cmd.name();
if name.is_empty() {
return Ok(results);
}
match self.get(&name)?.exec(cmd, &mut ctx)? {
CommandStep::Continue(act, c) => {
results.push((act, c));
return Ok(results);
},
CommandStep::Stop(act, c) => {
results.push((act, c));
return Ok(results);
},
CommandStep::Again(next) => {
input = next;
},
}
}
}
}