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
//! A module for the commands portion of shi.
//!
//! This module includes all command-related functionality and interfaces for using shi.

use crate::Result;

// TODO: We should be re-exporting these _from_ the command module. They should be submodules
// underneath the command module.
pub mod echo;
pub mod exit;
pub mod help;
pub mod helptree;
pub mod history;

pub use echo::*;
pub use exit::*;
pub use help::*;
pub use helptree::*;
pub use history::*;

pub mod example {
    pub use super::echo::EchoCommand;
}

pub(crate) mod builtin {
    pub use super::exit::ExitCommand;
    pub use super::help::HelpCommand;
    pub use super::helptree::HelpTreeCommand;
    pub use super::history::HistoryCommand;
}

pub mod parent;
pub use parent::ParentCommand;

pub mod basic;
pub use basic::BasicCommand;

/// Command represents all and any command that should exist in shi. It represents a clear
/// bifurcation: a command is either a `Leaf` or a `Parent` command.
///
/// This refers to tree terminology; a command either has no children (subcommands) or it does.
/// Thus, it is either a Leaf command or a `Parent` command, respectively.
pub enum Command<'a, S> {
    /// A command that has no sub commands. Conforms to `BaseCommand`. Executes, unlike `Parent`.
    Leaf(Box<dyn BaseCommand<State = S> + 'a>),
    // TODO: Do we want to make Parent commands a trait?
    /// A command that has sub commands. A `ParentCommand` does not execute.
    Parent(ParentCommand<'a, S>),
}

impl<'a, S> Command<'a, S> {
    // TODO: We should make this more ergonomic to use... Perhaps add `new_basic_leaf()`? At the
    // very least, we should shorten this to `leaf()`?
    /// Creates a new `Leaf` `Command` from the given command.
    pub fn new_leaf<C>(child_cmd: C) -> Self
    where
        C: BaseCommand<State = S> + 'a,
    {
        Self::Leaf(Box::new(child_cmd))
    }

    /// Creates a new `Parent` `Command` from the given vector of sub commands.
    pub fn new_parent(name: &'a str, sub_cmds: Vec<Command<'a, S>>) -> Self {
        Self::Parent(ParentCommand::new(name, sub_cmds))
    }
}

impl<'a, S> BaseCommand for Command<'a, S> {
    type State = S;

    fn name(&self) -> &str {
        match self {
            Self::Leaf(cmd) => cmd.name(),
            Self::Parent(parent_cmd) => parent_cmd.name(),
        }
    }

    fn validate_args(&self, args: &[String]) -> Result<()> {
        match self {
            Self::Leaf(cmd) => cmd.validate_args(args),
            Self::Parent(parent_cmd) => parent_cmd.validate_args(args),
        }
    }

    fn execute(&self, state: &mut Self::State, args: &[String]) -> Result<String> {
        match self {
            Self::Leaf(cmd) => cmd.execute(state, args),
            Self::Parent(parent_cmd) => parent_cmd.execute(state, args),
        }
    }

    fn help(&self) -> String {
        match self {
            Self::Leaf(cmd) => cmd.help(),
            Self::Parent(parent_cmd) => parent_cmd.help(),
        }
    }
}

/// Completion represents the result of an autocompletion for command arguments.
///
/// There are two cases that case occur:
/// * `PartialArgCompletion` - The last argument is partially typed and can be completed to full.
/// PartialArgCompletion contains the suffix which, when append to the partial argument,
/// provides the full argument.
/// * `Possibilities` - The arguments are complete, and there are guesses as to what the next
/// argument could be.
/// * `Nothing` - There are no completions to provide, either because there is no
/// autocompletion, or because the command and its arguments are complete already.
#[derive(Debug, PartialEq)]
pub enum Completion {
    PartialArgCompletion(Vec<String>),
    Possibilities(Vec<String>),
    Nothing,
}

/// BaseCommand is the lower-level command trait. It covers many of the behaviors one would expect
/// from a shell command, e.g., a name (`name()`) or execution (`execute()`).
///
/// It is generic over a `State`, `S`, expected to be bound to its containing `Shell`.
///
/// As it may aid in understanding: builtins are in fact `BaseCommand`'s, where `State = Shell<T>`.
pub trait BaseCommand {
    /// The State of the command. Expected to be bound to a containing `Shell`.
    type State;

    /// Returns the name of the command. This is equivalent to how the command would be invoked.
    fn name(&self) -> &str;

    // TODO: This may be better removed and implied to implementors to include in execute()'s body.
    //       Alternatively, this could return a type that execute takes instead of args.
    // TODO: Perhaps this should take the state, so that validation can occur w.r.t. the state.
    /// Validates the given arguments, returning a `Result<()>` indicating the result of
    /// validation.
    ///
    /// # Arguments
    /// `args` - The arguments to validate.
    fn validate_args(&self, args: &[String]) -> Result<()>;

    // TODO: Execute should probably be returning something better than a Result<String>.
    // TODO: Execute should probably have &mut self.
    /// Executes the command.
    ///
    /// # Arguments
    /// `state` - The state to execute with.
    /// `args` - The arguments to the command invocation.
    ///
    /// # Returns
    /// `Result<String>` - The result of the execution of this command. If successful, returns a
    /// String that represents the output of the command.
    fn execute(&self, state: &mut Self::State, args: &[String]) -> Result<String>;

    /// Autocompletes a command, given arguments.
    ///
    /// The default implementation provides no autocompletion.
    ///
    /// # Arguments
    /// `args` - The arguments to autocomplete.
    /// `trailing_space` - A boolean to indicate if there is a trailing space at the end of the
    /// line where a user has asked for completion.
    ///
    /// # Returns
    /// `Completion` - The completion result.
    fn autocomplete(&self, _args: Vec<&str>, _trailing_space: bool) -> Completion {
        Completion::Nothing
    }

    /// Returns a String representing the help text of this command.
    /// By default, returns nothing.
    ///
    /// Expected to be relatively brief.
    fn help(&self) -> String {
        "".to_string()
    }
}