]> git.proxmox.com Git - proxmox-backup.git/blob - src/getopts.rs
1293deecec85ddae64e3aabec0d890fa562d8802
[proxmox-backup.git] / src / 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 return RawArgument::Option {
50 name: unsafe { arg.get_unchecked(first..).to_string() },
51 value: None,
52 };
53 }
54
55 pub fn parse_arguments<T: AsRef<str>>(
56 args: &[T],
57 arg_param: &Vec<&'static str>,
58 schema: &ObjectSchema,
59 ) -> Result<(Value, Vec<String>), ParameterError> {
60 let mut errors = ParameterError::new();
61
62 let properties = &schema.properties;
63
64 let mut data: Vec<(String, String)> = vec![];
65 let mut rest: Vec<String> = vec![];
66
67 let mut pos = 0;
68
69 while pos < args.len() {
70 match parse_argument(args[pos].as_ref()) {
71 RawArgument::Separator => {
72 break;
73 }
74 RawArgument::Option { name, value } => match value {
75 None => {
76 let mut want_bool = false;
77 let mut can_default = false;
78 if let Some((_optional, param_schema)) = properties.get::<str>(&name) {
79 if let Schema::Boolean(boolean_schema) = param_schema.as_ref() {
80 want_bool = true;
81 if let Some(default) = boolean_schema.default {
82 if default == false {
83 can_default = true;
84 }
85 } else {
86 can_default = true;
87 }
88 }
89 }
90
91 let mut next_is_argument = false;
92 let mut next_is_bool = false;
93
94 if (pos + 1) < args.len() {
95 let next = args[pos + 1].as_ref();
96 if let RawArgument::Argument { value: _ } = parse_argument(next) {
97 next_is_argument = true;
98 if let Ok(_) = parse_boolean(next) {
99 next_is_bool = true;
100 }
101 }
102 }
103
104 if want_bool {
105 if next_is_bool {
106 pos += 1;
107 data.push((name, args[pos].as_ref().to_string()));
108 } else if can_default {
109 data.push((name, "true".to_string()));
110 } else {
111 errors.push(format_err!("parameter '{}': {}", name,
112 "missing boolean value."));
113 }
114
115 } else {
116
117 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 }
126 Some(v) => {
127 data.push((name, v));
128 }
129 },
130 RawArgument::Argument { value } => {
131 rest.push(value);
132 }
133 }
134
135 pos += 1;
136 }
137
138 rest.reserve(args.len() - pos);
139 for i in &args[pos..] {
140 rest.push(i.as_ref().to_string());
141 }
142
143 for i in 0..arg_param.len() {
144 if rest.len() > i {
145 data.push((arg_param[i].to_string(), rest[i].clone()));
146 } else {
147 errors.push(format_err!("missing argument '{}'", arg_param[i]));
148 }
149 }
150
151 if errors.len() > 0 {
152 return Err(errors);
153 }
154
155 if arg_param.len() > 0 {
156 rest = rest[arg_param.len()..].to_vec();
157 }
158
159 let options = parse_parameter_strings(&data, schema, true)?;
160
161 Ok((options, rest))
162 }
163
164 #[test]
165 fn test_boolean_arg() {
166 let schema = ObjectSchema::new("Parameters:")
167 .required(
168 "enable", BooleanSchema::new("Enable")
169 );
170
171 let mut variants: Vec<(Vec<&str>, bool)> = vec![];
172 variants.push((vec!["-enable"], true));
173 variants.push((vec!["-enable=1"], true));
174 variants.push((vec!["-enable", "yes"], true));
175 variants.push((vec!["-enable", "Yes"], true));
176 variants.push((vec!["--enable", "1"], true));
177 variants.push((vec!["--enable", "ON"], true));
178 variants.push((vec!["--enable", "true"], true));
179
180 variants.push((vec!["--enable", "0"], false));
181 variants.push((vec!["--enable", "no"], false));
182 variants.push((vec!["--enable", "off"], false));
183 variants.push((vec!["--enable", "false"], false));
184
185 for (args, expect) in variants {
186 let res = parse_arguments(&args, &vec![], &schema);
187 assert!(res.is_ok());
188 if let Ok((options, rest)) = res {
189 assert!(options["enable"] == expect);
190 assert!(rest.len() == 0);
191 }
192 }
193 }
194
195 #[test]
196 fn test_argument_paramenter() {
197 let schema = ObjectSchema::new("Parameters:")
198 .required("enable", BooleanSchema::new("Enable."))
199 .required("storage", StringSchema::new("Storage."));
200
201 let args = vec!["-enable", "local"];
202 let res = parse_arguments(&args, &vec!["storage"], &schema);
203 assert!(res.is_ok());
204 if let Ok((options, rest)) = res {
205 assert!(options["enable"] == true);
206 assert!(options["storage"] == "local");
207 assert!(rest.len() == 0);
208 }
209 }