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_matches(&mut self) -> ArgMatches {
42        self.command.get_matches_mut()
43    }
44
45    pub fn print_help(&mut self) {
46        let _ = self.command.print_long_help();
47    }
48
49    pub fn match_command(
50        &self,
51        command: String,
52        arg_matches: ArgMatches,
53    ) -> Option<(&ExecutorFunc, ArgMatches)> {
54        let mut current_map = &self.map;
55        let mut current_matches = arg_matches;
56        let mut current_command = command;
57
58        loop {
59            let entry = current_map.get(current_command.as_str())?;
60
61            match entry {
62                CommandMapEntry::Command(executor) => {
63                    return Some((executor, current_matches));
64                }
65                CommandMapEntry::Group(submap) => {
66                    let (subcommand_name, subcommand_matches) =
67                        current_matches.remove_subcommand()?;
68
69                    current_map = submap;
70                    current_matches = subcommand_matches;
71                    current_command = subcommand_name;
72                }
73            }
74        }
75    }
76
77    pub fn add_group<F: FnOnce(CliCommandGroupBuilder) -> CliCommandGroupBuilder>(
78        mut self,
79        name: &'static str,
80        about: &'static str,
81        callback: F,
82    ) -> Self {
83        let subgroup = CliCommandGroupBuilder::new(name, about);
84        let subgroup = callback(subgroup);
85
86        self.command = self.command.subcommand(subgroup.command);
87        self.map.insert(name, CommandMapEntry::Group(subgroup.map));
88
89        self
90    }
91
92    pub fn add_command<A: Args>(
93        mut self,
94        name: &'static str,
95        about: &'static str,
96        cli_command: impl CliCommand<A>,
97    ) -> Self {
98        let command = cli_command.get_command(Command::new(name).about(about));
99        let command = A::augment_args(command);
100
101        self.command = self.command.subcommand(command);
102        self.map
103            .insert(name, CommandMapEntry::Command(cli_command.get_executor()));
104
105        self
106    }
107}
108
109pub trait CliCommand<A: Args> {
110    fn get_command(&self, command: Command) -> Command;
111    fn get_executor(self) -> Box<ExecutorFunc>;
112}