1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
//! # Tracking and processing for commands
//!
//! ## Overview
//!
//! This module contains components to help consumers map commands into actions.
//!
use std::fmt::Debug;
use std::str::FromStr;
use radix_trie::Trie;
use crate::util::completion_keys;
/// Result from executing a single command in a sequence.
pub enum CommandStep<C: Command> {
/// Return an action, and continue onto the next part of the command sequence.
Continue(C::Action, C::Context),
/// Return an action, and skip anything else in the command sequence.
Stop(C::Action, C::Context),
/// Process the given string as a command.
Again(String),
}
/// Errors that can be encountered during command processing.
#[derive(thiserror::Error, Debug, Eq, PartialEq)]
pub enum CommandError {
/// Error for unmapped commands.
#[error("Invalid command: {0}")]
InvalidCommand(String),
/// Error for bad command arguments.
#[error("Invalid argument")]
InvalidArgument,
/// Error for bad ranges.
#[error("Invalid range")]
InvalidRange,
/// Error for command parse failures.
#[error("Failed to parse command: {0}")]
ParseFailed(String),
/// Generic error.
#[error("Error: {0}")]
Error(String),
}
/// Result type for individual, mapped commands.
pub type CommandResult<C> = Result<CommandStep<C>, CommandError>;
/// Trait for result type of parsing commands.
pub trait ParsedCommand: Debug + FromStr<Err = String> {
/// Get the name of the command being executed.
fn name(&self) -> String;
}
/// Trait for mapped commands.
pub trait Command: Clone {
/// Result of parsing a command string.
type Parsed: ParsedCommand;
/// Result of running a command.
type Action;
/// Context provided with each command string.
type Context;
/// Context to be passed to [Command::exec].
type CommandContext: From<Self::Context>;
/// The primary name to map this command under.
fn name(&self) -> String;
/// Additional names to map this command under.
fn aliases(&self) -> Vec<String>;
/// Execute this command.
fn exec(&self, cmd: Self::Parsed, ctx: &mut Self::CommandContext) -> CommandResult<Self>;
}
/// A collection of default commands.
pub trait DefaultCommands<C: Command>: Default {
/// Insert the commands contained by this object into a [CommandMachine].
fn setup(self, machine: &mut CommandMachine<C>);
}
/// Track mapped commands and handle their execution.
#[derive(Debug)]
pub struct CommandMachine<C: Command> {
names: Trie<String, C>,
aliases: Trie<String, C>,
last_cmd: String,
}
impl<C: Command> CommandMachine<C> {
/// Create a new instance.
pub fn new() -> Self {
let names = Trie::new();
let aliases = Trie::new();
let last_cmd = "".to_string();
CommandMachine { names, aliases, last_cmd }
}
/// Map a command under its names.
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);
}
/// Generate a list of completion candidates for command names.
pub fn complete_name(&self, prefix: &str) -> Vec<String> {
completion_keys(&self.names, prefix)
}
/// Generate a list of completion candidates for command aliases.
pub fn complete_aliases(&self, prefix: &str) -> Vec<String> {
completion_keys(&self.aliases, prefix)
}
/// Get the previously executed command.
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()))
}
}
/// Get the previously executed command.
pub fn get_last_command(&self) -> String {
self.last_cmd.clone()
}
/// Parse and execute a command string.
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) => {
// XXX: need to support processing the next command.
results.push((act, c));
return Ok(results);
},
CommandStep::Stop(act, c) => {
results.push((act, c));
return Ok(results);
},
CommandStep::Again(next) => {
input = next;
},
}
}
}
}