clishe/
shell.rs

1use ::rustyline::config::Configurer;
2
3impl<C, R, A> ::clap::Parser for crate::Shell<C, R, A>
4    where A: ::clap::Parser + crate::Command<C, R>
5{}
6
7impl<C, R, A> ::clap::Args for crate::Shell<C, R, A>
8    where A: ::clap::Parser + crate::Command<C, R>
9{
10    fn augment_args<'b>(cmd: ::clap::Command<'b>) -> ::clap::Command<'b> {
11        cmd
12    }
13
14    fn augment_args_for_update<'b>(cmd: ::clap::Command<'b>) -> ::clap::Command<'b> {
15        cmd
16    }
17}
18
19impl<C, R, A> ::clap::IntoApp for crate::Shell<C, R, A>
20    where A: ::clap::Parser + crate::Command<C, R>,
21{
22    fn into_app<'b>() -> ::clap::App<'b> {
23        // TODO: the app should feature a clap `about`, but it does not look
24        // like this one is being forwarded/considered by the parent
25        ::clap::App::new("shell").about("Try out this CLI in a shell!")
26    }
27
28    fn into_app_for_update<'b>() -> ::clap::App<'b> {
29        Self::into_app()
30    }
31}
32
33impl<C, R, A> ::clap::FromArgMatches for crate::Shell<C, R, A>
34    where A: ::clap::Parser + crate::Command<C, R>,
35{
36    fn from_arg_matches(_matches: &::clap::ArgMatches) -> Result<Self, ::clap::Error> {
37        Ok(Self{
38            _phda: ::std::marker::PhantomData::<A>,
39            _phdc: ::std::marker::PhantomData::<C>,
40            _phdr: ::std::marker::PhantomData::<R>,
41        })
42    }
43
44    fn update_from_arg_matches(
45        &mut self,
46        _matches: &::clap::ArgMatches,
47    ) -> Result<(), ::clap::Error> {
48        Ok(())
49    }
50}
51
52impl<C, R, A> crate::Command<C, R> for crate::Shell<C, R, A>
53    where A: ::clap::Parser + crate::Command<C, R>,
54{
55    fn run(self, ctx: &mut C) -> ::anyhow::Result<R> {
56        let mut rl = ::rustyline::Editor::<()>::new()?;
57        rl.set_completion_type(::rustyline::CompletionType::List);
58        rl.set_edit_mode(::rustyline::EditMode::Vi);
59
60        let mut last_res = Err(::anyhow::Error::msg("no result available (no command ran)"));
61        loop {
62            // generate prompt, parse args
63            let args = match ::shellwords::split(rl.readline("> ")?.trim_end()) {
64                Err(_) => { eprintln!("mismatched quotes"); continue },
65                Ok(args) => args,
66            };
67
68            // exit or run command
69            if is_asking_to_exit(&args) {
70                break last_res; // exit
71            } else {
72                // TODO: have to prepend with "shell" because i do not
73                // find the NoBinaryName setting in the new clap v3 beta...
74                // the main problem here is that "shell" appears in the help message
75                let args = ::std::iter::once("shell".to_owned()).chain(args.into_iter());
76                match A::try_parse_from(args) {
77                    Err(err) => eprintln!("{}", err), // TODO: better error printing
78                    Ok(app) => {
79                        last_res = match app.run(ctx) {
80                            Err(err) => { eprintln!("{}", err); Err(err) }
81                            Ok(res) => Ok(res),
82                        }
83                    }
84                }
85            }
86        }
87    }
88}
89
90fn is_asking_to_exit(args: &Vec<String>) -> bool {
91    args.len() == 1 && (args[0] == "exit" || args[0] == "quit" || args[0] == "q")
92}