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}