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