repl_framework/repl.rs
1#![warn(clippy::all)]
2use crate::utils::{self, parse, FnPtr};
3use std::{collections::HashMap, io, io::Write};
4/// Main Repl Struct that contains all logic for the crate
5#[derive(Debug, Clone)]
6pub struct Repl<'a, T> {
7 /// struct to store data, used as an argument in all functions
8 pub data: T,
9 /// prompt that is displayed before asking for input
10 prompt: &'a str,
11 /// command used to exit. defaults to "exit"
12 exit: &'a str,
13 /// text displayed when repl is closed: defaults to "repl terminated"
14 exit_message: &'a str,
15 /// text displayed when repl gets an empty line as argument
16 empty_argument_message: &'a str,
17 /// text displayed when repl gets an unknown command as argument
18 unknown_command_message: &'a str,
19 /// hashmap that stores functions
20 functions: HashMap<&'a str, FnPtr<T>>,
21 /// parser function for input
22 parser_fn: fn(String) -> Vec<String>,
23}
24impl<T: Default> Default for Repl<'_, T> {
25 /// builds a new Repl from the given data
26 ///
27 /// # Examples
28 ///
29 /// ```rust
30 /// use repl_framework::Repl;
31 /// let repl: Repl<()> = Repl::default();
32 /// ```
33 #[inline]
34 fn default() -> Self {
35 Repl::new(Default::default())
36 }
37}
38
39impl<'a, T> Repl<'a, T> {
40 /// builds a new Repl from the given data
41 ///
42 /// # Examples
43 ///
44 /// ```rust
45 /// use repl_framework::Repl;
46 /// let repl: Repl<()> = Repl::new(());
47 /// ```
48 #[inline]
49 pub fn new(data: T) -> Self {
50 Self {
51 data,
52 prompt: ">>>",
53 exit: "exit",
54 exit_message: "repl terminated",
55 functions: HashMap::new(),
56 empty_argument_message: "",
57 unknown_command_message: "",
58 parser_fn: parse,
59 }
60 }
61 /// runs the repl
62 /// functions which have command `""` will be called if none of the other commands are not called.
63 ///
64 /// # Examples
65 ///
66 /// ```rust, no_run
67 /// use repl_framework::Repl;
68 /// Repl::default().with_function("", |_: &mut (), b| println!("{:?}", b)).run();
69 /// ```
70 ///
71 /// # Errors
72 /// - reading from stdin fails
73 /// - flushing stdout fails
74 pub fn run(&mut self) -> io::Result<()> {
75 loop {
76 let arguments = self.get_input()?;
77 if arguments.is_empty() {
78 println!("{}", self.empty_argument_message);
79 } else if arguments.concat() == self.exit {
80 println!("{}", self.exit_message);
81 break;
82 } else if self.functions.contains_key(arguments[0].as_str()) {
83 self.functions[arguments[0].as_str()](
84 &mut self.data,
85 arguments[1..arguments.len()].to_vec(),
86 );
87 } else if self.functions.contains_key("") {
88 self.functions[""](&mut self.data, arguments[0..arguments.len()].to_vec());
89 } else {
90 println!("{}", self.unknown_command_message);
91 }
92 }
93 Ok(())
94 }
95 /// builds a new Repl from the given data, use for compatibility reasons only, the new parser is better in pretty much every way possible
96 ///
97 /// # Examples
98 ///
99 /// ```rust
100 /// use repl_framework::Repl;
101 /// let repl: Repl<()> = Repl::new_with_depreciated_parser(());
102 /// ```
103 #[inline]
104 pub fn new_with_depreciated_parser(data: T) -> Self {
105 Self {
106 data,
107 prompt: ">>>",
108 exit: "exit",
109 exit_message: "repl terminated",
110 functions: HashMap::new(),
111 empty_argument_message: "",
112 unknown_command_message: "",
113 parser_fn: |buf| {
114 buf.trim_end_matches('\n')
115 .trim_end_matches('\r')
116 .split_ascii_whitespace()
117 .map(|f| f.to_owned())
118 .collect()
119 },
120 }
121 }
122 /// same as `take_arg`, but returns the argument instead of storing it in self.argument
123 ///
124 /// # Errors
125 /// this function returns an error if
126 /// - reading from stdin fails
127 /// - flushing stdout fails
128 pub fn get_input(&self) -> io::Result<Vec<String>> {
129 print!("{}", &self.prompt);
130 // flushing stdout because print! doesn't do it by default
131 io::stdout().flush()?;
132 let mut buf = String::new();
133 io::stdin().read_line(&mut buf)?;
134 Ok((self.parser_fn)(buf.trim().to_owned()))
135 }
136 /// builder style method for adding a function.
137 /// this function is chainable, use `add_function` if you don't want it to be chainable.
138 ///
139 /// # Example
140 ///
141 /// ```rust
142 /// use repl_framework::Repl;
143 /// let repl: Repl<()> = Repl::default().with_function("hello", hello);
144 ///
145 /// fn hello(_: &mut (), _: Vec<String>) {
146 /// println!("hello");
147 /// }
148 /// ```
149 #[inline]
150 pub fn with_function(mut self, name: &'a str, func: fn(&mut T, Vec<String>)) -> Self {
151 self.add_function(name, func);
152 self
153 }
154 /// builder style method for changing the parser.
155 /// this function is chainable, use `set_parser` if you don't want it to be chainable.
156 ///
157 /// # Examples
158 ///
159 /// ```rust
160 /// use repl_framework::Repl;
161 /// let repl: Repl<()> = Repl::default().with_parser(|raw| vec![raw]);
162 /// ```
163 #[inline]
164 pub fn with_parser(mut self, parser: fn(String) -> Vec<String>) -> Self {
165 self.set_parser(parser);
166 self
167 }
168
169 /// builder style method for changing the data.
170 /// this function is chainable, use `set_data` if you don't want it to be chainable.
171 ///
172 /// # Examples
173 ///
174 /// ```rust
175 /// use repl_framework::Repl;
176 /// let repl: Repl<()> = Repl::default().with_data(());
177 /// ```
178 #[inline]
179 pub fn with_data(mut self, data: T) -> Self {
180 self.set_data(data);
181 self
182 }
183 /// builder style method for changing the prompt.
184 /// this function is chainable, use `set_prompt` if you don't want it to be chainable.
185 ///
186 /// # Examples
187 ///
188 /// ```rust
189 /// use repl_framework::Repl;
190 /// let repl: Repl<()> = Repl::default().with_prompt("+>");
191 /// // the repl will display +> after every message now, instead of the default ">>>"
192 /// ```
193 #[inline]
194 pub fn with_prompt(mut self, prompt: &'a str) -> Self {
195 self.set_prompt(prompt);
196 self
197 }
198 /// builder style method for changing the exit command.
199 /// this function is chainable, use `set_exit_command` if you don't want it to be chainable.
200 ///
201 /// # Examples
202 ///
203 /// ```rust
204 /// use repl_framework::Repl;
205 /// let repl: Repl<()> = Repl::default().with_exit_command("close");
206 /// // the repl will close if you give "close" as input now
207 /// ```
208 #[inline]
209 pub fn with_exit_command(mut self, exit: &'a str) -> Self {
210 self.set_exit_command(exit);
211 self
212 }
213 /// builder style method for changing the exit message.
214 /// this function is chainable, use `set_exit_message` if you don't want it to be chainable.
215 ///
216 /// # Examples
217 ///
218 /// ```rust
219 /// use repl_framework::Repl;
220 /// let repl: Repl<()> = Repl::default().with_exit_message("repl closed!");
221 /// // the repl will display "repl closed!" on termination
222 /// ```
223 #[inline]
224 pub fn with_exit_message(mut self, exit_message: &'a str) -> Self {
225 self.set_exit_message(exit_message);
226 self
227 }
228 /// builder style method for changing the exit message.
229 /// this function is chainable, use `set_exit_message` if you don't want it to be chainable.
230 ///
231 /// # Examples
232 ///
233 /// ```rust
234 /// use repl_framework::Repl;
235 /// let repl: Repl<()> = Repl::default().with_empty_argument_message("empty arg :(");
236 /// // the repl will display "empty arg :(" on termination
237 /// ```
238 #[inline]
239 pub fn with_empty_argument_message(mut self, empty_argument_message: &'a str) -> Self {
240 self.set_empty_argument_message(empty_argument_message);
241 self
242 }
243 /// adds function to Repl, not chainable, use `with_function` if you want chaining
244 ///
245 /// # Examples
246 ///
247 /// ```rust
248 /// use repl_framework::Repl;
249 /// let mut repl: Repl<()> = Repl::default();
250 /// repl.add_function("hello", hello);
251 /// fn hello(_: &mut (), _: Vec<String>) {
252 /// println!("Hello!")
253 /// }
254 /// ```
255 #[inline]
256 pub fn add_function(&mut self, name: &'a str, func: fn(&mut T, Vec<String>)) {
257 self.functions.insert(name, utils::FnPtr(func));
258 }
259 /// sets data to argument, NOT chainable, use `with_data` if you want chaining.
260 ///
261 /// # Examples
262 ///
263 /// ```rust
264 /// use repl_framework::Repl;
265 /// let mut repl: Repl<i32> = Repl::default();
266 /// repl.set_data(100);
267 /// ```
268 #[inline]
269 pub fn set_data(&mut self, data: T) {
270 self.data = data;
271 }
272 /// sets prompt to argument, NOT chainable, use `with_prompt` if you want chaining.
273 ///
274 /// # Examples
275 ///
276 /// ```rust
277 /// use repl_framework::Repl;
278 /// let mut repl: Repl<()> = Repl::default();
279 /// repl.set_prompt(":>");
280 ///
281 /// ```
282 #[inline]
283 pub fn set_prompt(&mut self, prompt: &'a str) {
284 self.prompt = prompt;
285 }
286 /// sets exit command to argument, NOT chainable, use `with_exit_command` if you want chaining.
287 ///
288 /// # Examples
289 ///
290 /// ```rust
291 /// use repl_framework::Repl;
292 /// let mut repl: Repl<()> = Repl::default();
293 /// repl.set_exit_command("close!");
294 /// ```
295 #[inline]
296 pub fn set_exit_command(&mut self, exit: &'a str) {
297 self.exit = exit;
298 }
299 /// sets exit message to argument, NOT chainable, use `with_exit_message` if you want chaining.
300 ///
301 /// # Examples
302 ///
303 /// ```rust
304 /// use repl_framework::Repl;
305 /// let mut repl: Repl<()> = Repl::default();
306 /// repl.set_exit_message("bye!");
307 /// ```
308 #[inline]
309 pub fn set_exit_message(&mut self, exit_message: &'a str) {
310 self.exit_message = exit_message;
311 }
312 /// sets exit message to argument, NOT chainable, use `with_exit_message` if you want chaining.
313 ///
314 /// # Examples
315 ///
316 /// ```rust
317 /// use repl_framework::Repl;
318 /// let mut repl: Repl<()> = Repl::default();
319 /// repl.set_empty_argument_message("empty argument list!");
320 /// ```
321 #[inline]
322 pub fn set_empty_argument_message(&mut self, empty_argument_message: &'a str) {
323 self.empty_argument_message = empty_argument_message;
324 }
325 /// sets parser function to argument, NOT chainable, use `with_parser` if you want chaining.
326 ///
327 /// # Examples
328 ///
329 /// ```rust
330 /// use repl_framework::Repl;
331 /// let mut repl: Repl<i32> = Repl::default();
332 /// repl.set_parser(|raw| vec![raw]);
333 /// ```
334 #[inline]
335 pub fn set_parser(&mut self, parser: fn(String) -> Vec<String>) {
336 self.parser_fn = parser;
337 }
338}