cloop/
shell.rs

1use crate::input_parser::InputParser;
2use crate::input_reader::{InputReader, InputResult};
3
4pub type ShellResult = anyhow::Result<ShellAction>;
5
6pub enum ShellAction {
7    Continue,
8    UpdatePrompt(String),
9    Exit,
10}
11
12pub trait Handler<A, T> {
13    fn handle(&self, args: A, context: &mut T) -> ShellResult;
14}
15
16/// A handler that takes a parser and a context and returns a ShellResult
17/// This is a convenience implementation for closures
18/// It allows you to pass a closure that takes a parser and a context and returns a ShellResult
19/// as a handler to the Shell struct
20impl<A, T, F> Handler<A, T> for F
21where
22    F: Fn(A, &mut T) -> ShellResult,
23{
24    fn handle(&self, args: A, context: &mut T) -> ShellResult {
25        self(args, context)
26    }
27}
28
29pub struct Shell<T, I: InputReader, P: InputParser<T>, H: Handler<P::Output, T>> {
30    prompt: String,
31    context: T,
32    input_reader: I,
33    input_parser: P,
34    handler: H,
35}
36
37impl<T, I: InputReader, P: InputParser<T>, H: Handler<P::Output, T>> Shell<T, I, P, H> {
38    pub fn new(
39        prompt: impl std::fmt::Display,
40        context: T,
41        input_reader: I,
42        input_parser: P,
43        handler: H,
44    ) -> Self {
45        Self {
46            prompt: prompt.to_string(),
47            context,
48            input_reader,
49            input_parser,
50            handler,
51        }
52    }
53
54    pub fn run(&mut self) -> anyhow::Result<()> {
55        loop {
56            let input_res = self.input_reader.read(&self.prompt)?;
57            match input_res {
58                InputResult::Input(input) => {
59                    let res = self.handle_input(&input);
60                    match res {
61                        Ok(shell_res) => match shell_res {
62                            Ok(ShellAction::Continue) => continue,
63                            Ok(ShellAction::UpdatePrompt(prompt)) => {
64                                self.prompt = prompt;
65                                continue;
66                            }
67                            Ok(ShellAction::Exit) => break,
68                            Err(e) => println!("{}", e),
69                        },
70                        Err(e) => println!("{}", e),
71                    }
72                }
73                InputResult::Interrupted => continue,
74                InputResult::Eof => break,
75            }
76        }
77        Ok(())
78    }
79
80    fn handle_input(&mut self, input: &str) -> Result<ShellResult, String> {
81        // Here we are using the parser to parse the arguments before handing them to the handler
82        // so that in case of a parsing error, we can early return and print the error message
83        let args = self
84            .input_parser
85            .parse(input, &mut self.context)
86            .map_err(|e| e.to_string())?;
87
88        let res = self.handler.handle(args, &mut self.context);
89
90        Ok(res)
91    }
92}