command_engine/
engine.rs

1use super::*;
2use std::collections::HashMap;
3use std::fmt::{Debug, Formatter};
4use crate::{Error, Instruction};
5
6/// Engine is the main part that can be implemented in any way.
7///
8/// This is a default Engine which acts as a container for the Commands which it can also execute
9/// based on the raw input.
10///
11/// You can disable default features to ignore this default Engine implementation and create your own.
12///
13/// To use the async version enable `async` feature.
14///
15/// Example:
16/// ```rust
17/// let mut engine = Engine::new();
18/// engine.add(/* your command here */);
19///
20/// let x = engine.execute(/* your input here */);
21/// ```
22#[repr(transparent)]
23pub struct Engine<Output> {
24    commands: HashMap<&'static str, Box<dyn Command<Output=Output>>>,
25}
26
27impl<Output: 'static> Engine<Output> {
28    /// Creates a new empty Engine.
29    pub fn new() -> Self {
30        Default::default()
31    }
32
33    /// Adds a new Command.
34    ///
35    /// Each structure added need to implement `Command` trait and will be transformed into a trait object.
36    ///
37    /// If Command with the same caller already exists in the Engine it will be overwritten.
38    pub fn insert<T: Command<Output=Output>>(&mut self, command: T) {
39        let _ = self.commands.insert(
40            command.caller(),
41            Box::new(command)
42        );
43    }
44
45    /// Removes Command based on its caller.
46    ///
47    /// If the Command was present in the Engine it will be returned.
48    pub fn remove(&mut self, caller: impl AsRef<str>) -> Option<Box<dyn Command<Output=Output>>> {
49        self.commands.remove(caller.as_ref())
50    }
51
52    /// Checks if there are any Commands in the Engine.
53    pub fn is_empty(&self) -> bool {
54        self.commands.is_empty()
55    }
56
57    /// Based on the given `input` the Engine will choose related Command and trigger its `on_execute`
58    /// method with the Instruction created from the input.
59    ///
60    /// This function can fail if the `input` is not in a valid Instruction format, or if Engine
61    /// failed to find any related Command.
62    #[cfg(feature = "async")]
63    pub async fn execute(&self, input: impl AsRef<str>) -> Result<Output, Error> {
64        let instruction = Instruction::new(input.as_ref())?;
65
66        let command = self
67            .commands
68            .get(instruction.caller)
69            .ok_or_else(|| Error::EngineCommandNotFound)?;
70
71        let output = command.on_execute(instruction).await;
72        Ok(output)
73    }
74
75    /// Based on the given `input` the Engine will choose related Command and trigger its `on_execute`
76    /// method with the Instruction created from the input.
77    ///
78    /// This function can fail if the `input` is not in a valid Instruction format, or if Engine
79    /// failed to find any related Command.
80    #[cfg(not(feature = "async"))]
81    pub fn execute(&self, input: impl AsRef<str>) -> Result<Output, Error> {
82        let instruction = Instruction::new(input.as_ref())?;
83
84        let command = self
85            .commands
86            .get(instruction.caller)
87            .ok_or_else(|| Error::EngineCommandNotFound)?;
88
89        let output = command.on_execute(instruction);
90        Ok(output)
91    }
92}
93
94impl<T> Default for Engine<T> {
95    fn default() -> Self {
96        Self {
97            commands: HashMap::new(),
98        }
99    }
100}
101
102impl<T> Debug for Engine<T> {
103    fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
104        let entries = self
105            .commands
106            .iter()
107            .map(|(caller, _)| *caller);
108
109        fmt.debug_list().entries(entries).finish()
110    }
111}