Skip to main content

shared/extensions/
commands.rs

1use clap::{Arg, ArgMatches, Args, Command};
2use std::{collections::HashMap, pin::Pin, sync::Arc};
3
4pub type ExecutorFunc = dyn Fn(
5        Option<Arc<crate::env::Env>>,
6        ArgMatches,
7    ) -> Pin<Box<dyn Future<Output = Result<i32, anyhow::Error>>>>
8    + Send;
9
10pub enum CommandMapEntry {
11    Command(Box<ExecutorFunc>),
12    Group(HashMap<&'static str, CommandMapEntry>),
13}
14
15pub struct CliCommandGroupBuilder {
16    command: Command,
17    map: HashMap<&'static str, CommandMapEntry>,
18}
19
20impl CliCommandGroupBuilder {
21    pub fn new(name: &'static str, about: &'static str) -> Self {
22        Self {
23            command: Command::new(name)
24                .version(crate::full_version())
25                .arg(
26                    Arg::new("debug")
27                        .help("pass in order to run in debug mode")
28                        .num_args(0)
29                        .short('d')
30                        .long("debug")
31                        .default_value("false")
32                        .value_parser(clap::value_parser!(bool))
33                        .global(true)
34                        .required(false),
35                )
36                .about(about),
37            map: HashMap::new(),
38        }
39    }
40
41    pub fn get_command(&self) -> Command {
42        self.command.clone()
43    }
44
45    pub fn get_matches(&mut self) -> ArgMatches {
46        self.command.get_matches_mut()
47    }
48
49    pub fn print_help(&mut self) {
50        let _ = self.command.print_long_help();
51    }
52
53    pub fn match_command(
54        &self,
55        command: String,
56        arg_matches: ArgMatches,
57    ) -> Option<(&ExecutorFunc, ArgMatches)> {
58        let mut current_map = &self.map;
59        let mut current_matches = arg_matches;
60        let mut current_command = command;
61
62        loop {
63            let entry = current_map.get(current_command.as_str())?;
64
65            match entry {
66                CommandMapEntry::Command(executor) => {
67                    return Some((executor, current_matches));
68                }
69                CommandMapEntry::Group(submap) => {
70                    let (subcommand_name, subcommand_matches) =
71                        current_matches.remove_subcommand()?;
72
73                    current_map = submap;
74                    current_matches = subcommand_matches;
75                    current_command = subcommand_name;
76                }
77            }
78        }
79    }
80
81    pub fn add_group<F: FnOnce(CliCommandGroupBuilder) -> CliCommandGroupBuilder>(
82        mut self,
83        name: &'static str,
84        about: &'static str,
85        callback: F,
86    ) -> Self {
87        let subgroup = CliCommandGroupBuilder::new(name, about);
88        let subgroup = callback(subgroup);
89
90        self.command = self.command.subcommand(subgroup.command);
91        self.map.insert(name, CommandMapEntry::Group(subgroup.map));
92
93        self
94    }
95
96    pub fn add_command<A: Args>(
97        mut self,
98        name: &'static str,
99        about: &'static str,
100        cli_command: impl CliCommand<A>,
101    ) -> Self {
102        let command = cli_command.get_command(Command::new(name).about(about));
103        let command = A::augment_args(command);
104
105        self.command = self.command.subcommand(command);
106        self.map
107            .insert(name, CommandMapEntry::Command(cli_command.get_executor()));
108
109        self
110    }
111}
112
113pub trait CliCommand<A: Args> {
114    fn get_command(&self, command: Command) -> Command;
115    fn get_executor(self) -> Box<ExecutorFunc>;
116}