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}