]> git.proxmox.com Git - proxmox-backup.git/blob - src/cli/command.rs
cleanup parameter types
[proxmox-backup.git] / src / cli / command.rs
1 use failure::*;
2 use std::collections::HashMap;
3 use std::collections::HashSet;
4
5 use crate::api::schema::*;
6 use crate::api::router::*;
7 use crate::api::config::*;
8 use crate::getopts;
9
10 pub fn print_cli_usage() {
11
12 eprintln!("Usage: TODO");
13 }
14
15 fn handle_simple_command(cli_cmd: &CliCommand, args: Vec<String>) -> Result<(), Error> {
16
17 let (params, rest) = getopts::parse_arguments(
18 &args, &cli_cmd.arg_param, &cli_cmd.info.parameters)?;
19
20 if !rest.is_empty() {
21 bail!("got additional arguments: {:?}", rest);
22 }
23
24 let res = (cli_cmd.info.handler)(params, &cli_cmd.info)?;
25
26 println!("Result: {}", serde_json::to_string_pretty(&res).unwrap());
27
28 Ok(())
29 }
30
31 fn find_command<'a>(def: &'a CliCommandMap, name: &str) -> Option<&'a CommandLineInterface> {
32
33 if let Some(sub_cmd) = def.commands.get(name) {
34 return Some(sub_cmd);
35 };
36
37 let mut matches: Vec<&str> = vec![];
38
39 for cmd in def.commands.keys() {
40 if cmd.starts_with(name) {
41 matches.push(cmd); }
42 }
43
44 if matches.len() != 1 { return None; }
45
46 if let Some(sub_cmd) = def.commands.get(matches[0]) {
47 return Some(sub_cmd);
48 };
49
50 None
51 }
52
53 fn handle_nested_command(def: &CliCommandMap, mut args: Vec<String>) -> Result<(), Error> {
54
55 if args.len() < 1 {
56 let mut cmds: Vec<&String> = def.commands.keys().collect();
57 cmds.sort();
58
59 let list = cmds.iter().fold(String::new(),|mut s,item| {
60 if !s.is_empty() { s+= ", "; }
61 s += item;
62 s
63 });
64
65 bail!("expected command argument, but no command specified.\nPossible commands: {}", list);
66 }
67
68 let command = args.remove(0);
69
70 let sub_cmd = match find_command(def, &command) {
71 Some(cmd) => cmd,
72 None => bail!("no such command '{}'", command),
73 };
74
75 match sub_cmd {
76 CommandLineInterface::Simple(cli_cmd) => {
77 handle_simple_command(cli_cmd, args)?;
78 }
79 CommandLineInterface::Nested(map) => {
80 handle_nested_command(map, args)?;
81 }
82 }
83
84 Ok(())
85 }
86
87 fn print_property_completion(schema: &Schema, arg: &str) {
88 // fixme: implement completion functions
89 if let Schema::String(StringSchema { format: Some(format), ..} ) = schema {
90 if let ApiStringFormat::Enum(list) = format.as_ref() {
91 for value in list {
92 if value.starts_with(arg) {
93 println!("{}", value);
94 }
95 }
96 return;
97 }
98 }
99 println!("");
100 }
101
102 fn record_done_arguments(done: &mut HashSet<String>, parameters: &ObjectSchema, list: &[String]) {
103
104 for arg in list {
105 if arg.starts_with("--") && arg.len() > 2 {
106 let prop_name = arg[2..].to_owned();
107 if let Some((_, schema)) = parameters.properties.get::<str>(&prop_name) {
108 match schema.as_ref() {
109 Schema::Array(_) => { /* do nothing */ }
110 _ => { done.insert(prop_name); }
111 }
112 }
113 }
114 }
115 }
116
117 fn print_simple_completion(
118 cli_cmd: &CliCommand,
119 done: &mut HashSet<String>,
120 arg_param: &[&str],
121 mut args: Vec<String>,
122 ) {
123 // fixme: arg_param, fixed_param
124 //eprintln!("COMPL: {:?} {:?} {}", arg_param, args, args.len());
125
126 if !arg_param.is_empty() {
127 let prop_name = arg_param[0];
128 done.insert(prop_name.into());
129 if args.len() > 1 {
130 args.remove(0);
131 print_simple_completion(cli_cmd, done, &arg_param[1..], args);
132 return;
133 }
134 if let Some((_, schema)) = cli_cmd.info.parameters.properties.get(prop_name) {
135 if args.is_empty() {
136 print_property_completion(schema, "");
137 } else {
138 print_property_completion(schema, &args[0]);
139 }
140 }
141 return;
142 }
143 if args.is_empty() { return; }
144
145 record_done_arguments(done, &cli_cmd.info.parameters, &args);
146
147 let prefix = args.pop().unwrap(); // match on last arg
148
149 // complete option-name or option-value ?
150 if !prefix.starts_with("-") && args.len() > 0 {
151 let last = &args[args.len()-1];
152 if last.starts_with("--") && last.len() > 2 {
153 let prop_name = &last[2..];
154 if let Some((_, schema)) = cli_cmd.info.parameters.properties.get(prop_name) {
155 print_property_completion(schema, &prefix);
156 }
157 return;
158 }
159 }
160
161 for (name, (optional, schema)) in &cli_cmd.info.parameters.properties {
162 if done.contains(*name) { continue; }
163 let option = String::from("--") + name;
164 if option.starts_with(&prefix) {
165 println!("{}", option);
166 }
167 }
168 }
169
170 fn print_nested_completion(def: &CommandLineInterface, mut args: Vec<String>) {
171
172 match def {
173 CommandLineInterface::Simple(cli_cmd) => {
174 let mut done = HashSet::new();
175 let fixed: Vec<String> = cli_cmd.fixed_param.iter().map(|s| s.to_string()).collect();
176 record_done_arguments(&mut done, &cli_cmd.info.parameters, &fixed);
177 print_simple_completion(cli_cmd, &mut done, &cli_cmd.arg_param, args);
178 return;
179 }
180 CommandLineInterface::Nested(map) => {
181 if args.is_empty() {
182 for cmd in map.commands.keys() {
183 println!("{}", cmd);
184 }
185 return;
186 }
187 let first = args.remove(0);
188 if let Some(sub_cmd) = map.commands.get(&first) {
189 print_nested_completion(sub_cmd, args);
190 return;
191 }
192 for cmd in map.commands.keys() {
193 if cmd.starts_with(&first) {
194 println!("{}", cmd);
195 }
196 }
197 }
198 }
199 }
200
201 pub fn print_bash_completion(def: &CommandLineInterface) {
202
203 let comp_point: usize = match std::env::var("COMP_POINT") {
204 Ok(val) => {
205 match usize::from_str_radix(&val, 10) {
206 Ok(i) => i,
207 Err(e) => return,
208 }
209 }
210 Err(e) => return,
211 };
212
213 let cmdline = match std::env::var("COMP_LINE") {
214 Ok(val) => val[0..comp_point].to_owned(),
215 Err(e) => return,
216 };
217
218
219 let mut args = match shellwords::split(&cmdline) {
220 Ok(v) => v,
221 Err(_) => return,
222 };
223
224 args.remove(0); //no need for program name
225
226 if cmdline.ends_with(char::is_whitespace) {
227 //eprintln!("CMDLINE {:?}", cmdline);
228 args.push("".into());
229 }
230
231 //eprintln!("COMP_ARGS {:?}", args);
232
233 print_nested_completion(def, args);
234 }
235
236 pub fn run_cli_command(def: &CommandLineInterface) -> Result<(), Error> {
237
238 let args: Vec<String> = std::env::args().skip(1).collect();
239
240 if !args.is_empty() && args[0] == "bashcomplete" {
241 print_bash_completion(def);
242 return Ok(());
243 }
244
245 match def {
246 CommandLineInterface::Simple(cli_cmd) => handle_simple_command(cli_cmd, args),
247 CommandLineInterface::Nested(map) => handle_nested_command(map, args),
248 }
249 }
250
251 pub struct CliCommand {
252 pub info: ApiMethod,
253 pub arg_param: Vec<&'static str>,
254 pub fixed_param: Vec<&'static str>,
255 }
256
257 impl CliCommand {
258
259 pub fn new(info: ApiMethod) -> Self {
260 Self { info, arg_param: vec![], fixed_param: vec![] }
261 }
262
263 pub fn arg_param(mut self, names: Vec<&'static str>) -> Self {
264 self.arg_param = names;
265 self
266 }
267
268 pub fn fixed_param(mut self, args: Vec<&'static str>) -> Self {
269 self.fixed_param = args;
270 self
271 }
272 }
273
274 pub struct CliCommandMap {
275 pub commands: HashMap<String, CommandLineInterface>,
276 }
277
278 impl CliCommandMap {
279
280 pub fn new() -> Self {
281 Self { commands: HashMap:: new() }
282 }
283
284 pub fn insert<S: Into<String>>(mut self, name: S, cli: CommandLineInterface) -> Self {
285 self.commands.insert(name.into(), cli);
286 self
287 }
288 }
289
290 pub enum CommandLineInterface {
291 Simple(CliCommand),
292 Nested(CliCommandMap),
293 }
294
295 impl From<CliCommand> for CommandLineInterface {
296 fn from(cli_cmd: CliCommand) -> Self {
297 CommandLineInterface::Simple(cli_cmd)
298 }
299 }
300
301 impl From<CliCommandMap> for CommandLineInterface {
302 fn from(list: CliCommandMap) -> Self {
303 CommandLineInterface::Nested(list)
304 }
305 }