shared/extensions/
commands.rs1use 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}