]> git.proxmox.com Git - proxmox-backup.git/blob - src/cli/getopts.rs
858a56bc415f30bb3519df585a2b4d5d5406c101
[proxmox-backup.git] / src / cli / getopts.rs
1 use failure::*;
2 use serde_json::Value;
3
4 use proxmox::api::schema::*;
5
6 #[derive(Debug)]
7 enum RawArgument {
8 Separator,
9 Argument { value: String },
10 Option { name: String, value: Option<String> },
11 }
12
13 fn parse_argument(arg: &str) -> RawArgument {
14 let bytes = arg.as_bytes();
15
16 let length = bytes.len();
17
18 if length < 2 || bytes[0] != b'-' {
19 return RawArgument::Argument {
20 value: arg.to_string(),
21 };
22 }
23
24 let mut first = 1;
25
26 if bytes[1] == b'-' {
27 if length == 2 {
28 return RawArgument::Separator;
29 }
30 first = 2;
31 }
32
33 for start in first..length {
34 if bytes[start] == b'=' {
35 // Since we take a &str, we know the contents of it are valid utf8.
36 // Since bytes[start] == b'=', we know the byte beginning at start is a single-byte
37 // code pointer. We also know that 'first' points exactly after a single-byte code
38 // point as it points to the first byte after a hyphen.
39 // Therefore we know arg[first..start] is valid utf-8, therefore it is safe to use
40 // get_unchecked() to speed things up.
41 return RawArgument::Option {
42 name: unsafe { arg.get_unchecked(first..start).to_string() },
43 value: Some(unsafe { arg.get_unchecked((start + 1)..).to_string() }),
44 };
45 }
46 }
47
48 RawArgument::Option {
49 name: unsafe { arg.get_unchecked(first..).to_string() },
50 value: None,
51 }
52 }
53
54 /// parse as many arguments as possible into a Vec<String, String>. This does not
55 /// verify the schema.
56 /// Returns parsed data and the rest as separate array
57 pub (crate) fn parse_argument_list<T: AsRef<str>>(
58 args: &[T],
59 schema: &ObjectSchema,
60 errors: &mut ParameterError,
61 ) -> (Vec<(String, String)>, Vec<String>) {
62
63 let mut data: Vec<(String, String)> = vec![];
64 let mut rest: Vec<String> = vec![];
65
66 let mut pos = 0;
67
68 while pos < args.len() {
69 match parse_argument(args[pos].as_ref()) {
70 RawArgument::Separator => {
71 break;
72 }
73 RawArgument::Option { name, value } => match value {
74 None => {
75 let mut want_bool = false;
76 let mut can_default = false;
77 if let Some((_optional, param_schema)) = schema.lookup(&name) {
78 if let Schema::Boolean(boolean_schema) = param_schema {
79 want_bool = true;
80 if let Some(default) = boolean_schema.default {
81 if default == false {
82 can_default = true;
83 }
84 } else {
85 can_default = true;
86 }
87 }
88 }
89
90 let mut next_is_argument = false;
91 let mut next_is_bool = false;
92
93 if (pos + 1) < args.len() {
94 let next = args[pos + 1].as_ref();
95 if let RawArgument::Argument { .. } = parse_argument(next) {
96 next_is_argument = true;
97 if let Ok(_) = parse_boolean(next) {
98 next_is_bool = true;
99 }
100 }
101 }
102
103 if want_bool {
104 if next_is_bool {
105 pos += 1;
106 data.push((name, args[pos].as_ref().to_string()));
107 } else if can_default {
108 data.push((name, "true".to_string()));
109 } else {
110 errors.push(format_err!("parameter '{}': {}", name,
111 "missing boolean value."));
112 }
113
114 } else if next_is_argument {
115 pos += 1;
116 data.push((name, args[pos].as_ref().to_string()));
117 } else {
118 errors.push(format_err!("parameter '{}': {}", name,
119 "missing parameter value."));
120 }
121 }
122 Some(v) => {
123 data.push((name, v));
124 }
125 },
126 RawArgument::Argument { value } => {
127 rest.push(value);
128 }
129 }
130
131 pos += 1;
132 }
133
134 rest.reserve(args.len() - pos);
135 for i in &args[pos..] {
136 rest.push(i.as_ref().to_string());
137 }
138
139 (data, rest)
140 }
141
142 /// Parses command line arguments using a `Schema`
143 ///
144 /// Returns parsed options as json object, together with the
145 /// list of additional command line arguments.
146 pub fn parse_arguments<T: AsRef<str>>(
147 args: &[T],
148 arg_param: &Vec<&'static str>,
149 schema: &ObjectSchema,
150 ) -> Result<(Value, Vec<String>), ParameterError> {
151 let mut errors = ParameterError::new();
152
153 // first check if all arg_param exists in schema
154
155 let mut last_arg_param_is_optional = false;
156 let mut last_arg_param_is_array = false;
157
158 for i in 0..arg_param.len() {
159 let name = arg_param[i];
160 if let Some((optional, param_schema)) = schema.lookup(&name) {
161 if i == arg_param.len() -1 {
162 last_arg_param_is_optional = optional;
163 if let Schema::Array(_) = param_schema {
164 last_arg_param_is_array = true;
165 }
166 } else if optional {
167 panic!("positional argument '{}' may not be optional", name);
168 }
169 } else {
170 panic!("no such property '{}' in schema", name);
171 }
172 }
173
174 let (mut data, mut rest) = parse_argument_list(args, schema, &mut errors);
175
176 for i in 0..arg_param.len() {
177
178 let name = arg_param[i];
179 let is_last_arg_param = i == (arg_param.len() - 1);
180
181 if rest.len() == 0 {
182 if !(is_last_arg_param && last_arg_param_is_optional) {
183 errors.push(format_err!("missing argument '{}'", name));
184 }
185 } else if is_last_arg_param && last_arg_param_is_array {
186 for value in rest {
187 data.push((name.to_string(), value));
188 }
189 rest = vec![];
190 } else {
191 data.push((name.to_string(), rest.remove(0)));
192 }
193 }
194
195 if errors.len() > 0 {
196 return Err(errors);
197 }
198
199 let options = parse_parameter_strings(&data, schema, true)?;
200
201 Ok((options, rest))
202 }
203
204 #[test]
205 fn test_boolean_arg() {
206
207 const PARAMETERS: ObjectSchema = ObjectSchema::new(
208 "Parameters:",
209 &[ ("enable", false, &BooleanSchema::new("Enable").schema()) ],
210 );
211
212 let mut variants: Vec<(Vec<&str>, bool)> = vec![];
213 variants.push((vec!["-enable"], true));
214 variants.push((vec!["-enable=1"], true));
215 variants.push((vec!["-enable", "yes"], true));
216 variants.push((vec!["-enable", "Yes"], true));
217 variants.push((vec!["--enable", "1"], true));
218 variants.push((vec!["--enable", "ON"], true));
219 variants.push((vec!["--enable", "true"], true));
220
221 variants.push((vec!["--enable", "0"], false));
222 variants.push((vec!["--enable", "no"], false));
223 variants.push((vec!["--enable", "off"], false));
224 variants.push((vec!["--enable", "false"], false));
225
226 for (args, expect) in variants {
227 let res = parse_arguments(&args, &vec![], &PARAMETERS);
228 assert!(res.is_ok());
229 if let Ok((options, rest)) = res {
230 assert!(options["enable"] == expect);
231 assert!(rest.len() == 0);
232 }
233 }
234 }
235
236 #[test]
237 fn test_argument_paramenter() {
238
239 const PARAMETERS: ObjectSchema = ObjectSchema::new(
240 "Parameters:",
241 &[
242 ("enable", false, &BooleanSchema::new("Enable.").schema()),
243 ("storage", false, &StringSchema::new("Storage.").schema()),
244 ],
245 );
246
247 let args = vec!["-enable", "local"];
248 let res = parse_arguments(&args, &vec!["storage"], &PARAMETERS);
249 assert!(res.is_ok());
250 if let Ok((options, rest)) = res {
251 assert!(options["enable"] == true);
252 assert!(options["storage"] == "local");
253 assert!(rest.len() == 0);
254 }
255 }