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;
                },
            }
        }
    }
}