]> git.proxmox.com Git - proxmox-backup.git/blob - src/getopts.rs
getopt: let parse_arguments() take a slice of AsRef<str>
[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
61 let mut errors = ParameterError::new();
62
63 let properties = &schema.properties;
64
65 let mut data: Vec<(String, String)> = vec![];
66 let mut rest: Vec<String> = vec![];
67
68 let mut pos = 0;
69
70 while pos < args.len() {
71 match parse_argument(args[pos].as_ref()) {
72 RawArgument::Separator => {
73 break;
74 }
75 RawArgument::Option { name, value } => {
76 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 { can_default = true; }
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) { next_is_bool = true; }
99 }
100 }
101
102 if want_bool {
103 if next_is_bool {
104 pos += 1;
105 data.push((name, args[pos].as_ref().to_string()));
106 } else if can_default {
107 data.push((name, "true".to_string()));
108 } else {
109 errors.push(format_err!("parameter '{}': {}", name,
110 "missing boolean value."));
111 }
112
113 } else {
114
115 if next_is_argument {
116 pos += 1;
117 data.push((name, args[pos].as_ref().to_string()));
118 } else {
119 errors.push(format_err!("parameter '{}': {}", name,
120 "missing parameter value."));
121 }
122 }
123 }
124 Some(v) => {
125 data.push((name, v));
126 }
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 for i in 0..arg_param.len() {
143 if rest.len() > i {
144 data.push((arg_param[i].to_string(), rest[i].clone()));
145 } else {
146 errors.push(format_err!("missing argument '{}'", arg_param[i]));
147 }
148 }
149
150 if errors.len() > 0 { return Err(errors); }
151
152 if arg_param.len() > 0 {
153 rest = rest[arg_param.len()..].to_vec();
154 }
155
156 let options = parse_parameter_strings(&data, schema, true)?;
157
158 Ok((options,rest))
159 }
160
161
162 #[test]
163 fn test_boolean_arg() {
164
165 let schema = ObjectSchema::new("Parameters:")
166 .required(
167 "enable", BooleanSchema::new("Enable")
168 );
169
170 let mut variants: Vec<(Vec<&str>, bool)> = vec![];
171 variants.push((vec!["-enable"], true));
172 variants.push((vec!["-enable=1"], true));
173 variants.push((vec!["-enable", "yes"], true));
174 variants.push((vec!["-enable", "Yes"], true));
175 variants.push((vec!["--enable", "1"], true));
176 variants.push((vec!["--enable", "ON"], true));
177 variants.push((vec!["--enable", "true"], true));
178
179 variants.push((vec!["--enable", "0"], false));
180 variants.push((vec!["--enable", "no"], false));
181 variants.push((vec!["--enable", "off"], false));
182 variants.push((vec!["--enable", "false"], false));
183
184 for (args, expect) in variants {
185 let res = parse_arguments(&args, &vec![], &schema);
186 assert!(res.is_ok());
187 if let Ok((options, rest)) = res {
188 assert!(options["enable"] == expect);
189 assert!(rest.len() == 0);
190 }
191 }
192 }
193
194 #[test]
195 fn test_argument_paramenter() {
196
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 }