Skip to main content

az_rs/
cmd.rs

1use crate::api::{metadata, ApiManager};
2use crate::arg::CliInput;
3use clap::{command, Arg, Command};
4
5pub fn cmd() -> Command {
6    cmd_base().subcommand(cmd_api_stub())
7}
8
9fn cmd_base() -> Command {
10    command!()
11        .subcommand_required(true)
12        .arg_required_else_help(true)
13        .disable_help_subcommand(true)
14}
15
16fn cmd_api_stub() -> Command {
17    cmd_api_base().disable_help_flag(true).arg(
18        Arg::new("args")
19            .num_args(0..)
20            .trailing_var_arg(true)
21            .allow_hyphen_values(true)
22            .hide(true),
23    )
24}
25
26fn cmd_api_base_real() -> Command {
27    cmd_api_base()
28        .subcommand_required(true)
29        .arg_required_else_help(true)
30}
31
32pub fn cmd_api_base() -> Command {
33    Command::new("api").about("Directly invoke the Azure API primitives.")
34}
35
36pub fn cmd_api(api_manager: &ApiManager, input: &CliInput) -> Command {
37    let pos_args = input.pos_args();
38
39    // No positional argument specified, list the rps
40    if pos_args.is_empty() {
41        return cmd_base().subcommand(
42            cmd_api_base_real().subcommands(api_manager.list_rps().iter().map(Command::new)),
43        );
44    }
45    let rp = pos_args.first().unwrap();
46    match api_manager.read_metadata(rp) {
47        Ok(metadata) => {
48            let mut args = pos_args.iter();
49            let mut command_names = vec![];
50
51            // Construct a fake command group here to initiate the following while loop
52            let mut cg = metadata::CommandGroup {
53                name: rp.to_string(),
54                command_groups: Some(metadata.command_groups),
55                ..metadata::CommandGroup::default()
56            };
57
58            let mut c: Option<metadata::Command> = None;
59
60            while let Some(arg) = args.next() {
61                command_names.push(arg.to_string());
62
63                if let Some(v) = cg
64                    .command_groups
65                    .clone()
66                    .and_then(|cgs| cgs.iter().find(|cg| cg.name.as_str() == *arg).cloned())
67                {
68                    cg = v;
69                } else if let Some(v) = cg
70                    .commands
71                    .iter()
72                    .find(|c| c.name.as_str() == *arg)
73                    .cloned()
74                {
75                    // Stop once we meet a command.
76                    // It can happen that there are still remaining positional arguments here, we
77                    // tolerate them here as there is no obvious way to handle it correctly during
78                    // constructing clap::Command.
79                    c = Some(v);
80                    break;
81                }
82            }
83
84            let mut command_names_rev = command_names.iter().rev();
85            let mut cmd = Command::new(command_names_rev.next().unwrap());
86            if let Some(c) = c {
87                // Construct the last command name as a Command, which contains args
88                cmd = cmd.args(build_args(&c.arg_groups));
89            } else {
90                // Construct the last command name as a CommandGroup, which contains commands and potential
91                // command groups
92                cmd = cmd
93                    .subcommands(cg.commands.iter().map(|c| Command::new(c.name.clone())))
94                    .subcommand_required(true)
95                    .arg_required_else_help(true);
96                if let Some(cgs) = cg.command_groups {
97                    cmd = cmd.subcommands(cgs.iter().map(|c| Command::new(c.name.clone())));
98                }
99            }
100            for name in command_names_rev {
101                cmd = Command::new(name.clone())
102                    .subcommand(cmd)
103                    .subcommand_required(true)
104                    .arg_required_else_help(true);
105            }
106            cmd_base().subcommand(cmd_api_base_real().subcommand(cmd))
107        }
108        Err(err) => {
109            dbg!("subcommand construction failed: {}", err);
110            cmd_base().subcommand(cmd_api_base_real().subcommand(Command::new(rp.to_string())))
111        }
112    }
113}
114
115fn build_args(arg_groups: &Vec<metadata::ArgGroup>) -> Vec<Arg> {
116    let mut out = vec![];
117    arg_groups
118        .iter()
119        .for_each(|ag| out.extend(ag.args.iter().map(build_arg)));
120    out
121}
122
123fn build_arg(arg: &metadata::Arg) -> Arg {
124    // The options of one argument can have 0/N short, 0/N long.
125    // We reagard the first short(prefered)/long as the name.
126    let mut short: Option<char> = None;
127    let mut short_aliases = vec![];
128    let mut long: Option<String> = None;
129    let mut long_aliases = vec![];
130    arg.options.iter().for_each(|opt| {
131        if opt.len() == 1 {
132            let c = opt.chars().next().unwrap();
133            if short.is_none() {
134                short = Some(c);
135            } else {
136                short_aliases.push(c);
137            }
138        } else {
139            if long.is_none() {
140                long = Some(opt.clone());
141            } else {
142                long_aliases.push(opt.clone());
143            }
144        }
145    });
146    let mut out = Arg::new(arg.var.clone())
147        .value_name("value")
148        .visible_short_aliases(short_aliases)
149        .visible_aliases(long_aliases);
150    if let Some(short) = short {
151        out = out.short(short);
152    }
153    if let Some(long) = long {
154        out = out.long(long);
155    }
156    if let Some(required) = arg.required {
157        out = out.required(required);
158    }
159    if let Some(help) = &arg.help {
160        out = out.help(help.short.clone());
161    }
162    out
163}
164
165#[test]
166fn verify_cmd() {
167    cmd().debug_assert();
168}