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                .arg(
25                    Arg::new("debug")
26                        .help("pass in order to run in debug mode")
27                        .num_args(0)
28                        .short('d')
29                        .long("debug")
30                        .default_value("false")
31                        .value_parser(clap::value_parser!(bool))
32                        .global(true)
33                        .required(false),
34                )
35                .about(about),
36            map: HashMap::new(),
37        }
38    }
39
40    pub fn get_matches(&mut self) -> ArgMatches {
41        self.command.get_matches_mut()
42    }
43
44    pub fn print_help(&mut self) {
45        let _ = self.command.print_long_help();
46    }
47
48    pub fn match_command(
49        &self,
50        command: String,
51        arg_matches: ArgMatches,
52    ) -> Option<(&ExecutorFunc, ArgMatches)> {
53        let mut current_map = &self.map;
54        let mut current_matches = arg_matches;
55        let mut current_command = command;
56
57        loop {
58            let entry = current_map.get(current_command.as_str())?;
59
60            match entry {
61                CommandMapEntry::Command(executor) => {
62                    return Some((executor, current_matches));
63                }
64                CommandMapEntry::Group(submap) => {
65                    let (subcommand_name, subcommand_matches) =
66                        current_matches.remove_subcommand()?;
67
68                    current_map = submap;
69                    current_matches = subcommand_matches;
70                    current_command = subcommand_name;
71                }
72            }
73        }
74    }
75
76    pub fn add_group<F: FnOnce(CliCommandGroupBuilder) -> CliCommandGroupBuilder>(
77        mut self,
78        name: &'static str,
79        about: &'static str,
80        callback: F,
81    ) -> Self {
82        let subgroup = CliCommandGroupBuilder::new(name, about);
83        let subgroup = callback(subgroup);
84
85        self.command = self.command.subcommand(subgroup.command);
86        self.map.insert(name, CommandMapEntry::Group(subgroup.map));
87
88        self
89    }
90
91    pub fn add_command<A: Args>(
92        mut self,
93        name: &'static str,
94        about: &'static str,
95        cli_command: impl CliCommand<A>,
96    ) -> Self {
97        let command = cli_command.get_command(Command::new(name).about(about));
98        let command = A::augment_args(command);
99
100        self.command = self.command.subcommand(command);
101        self.map
102            .insert(name, CommandMapEntry::Command(cli_command.get_executor()));
103
104        self
105    }
106}
107
108pub trait CliCommand<A: Args> {
109    fn get_command(&self, command: Command) -> Command;
110    fn get_executor(self) -> Box<ExecutorFunc>;
111}