]>
Commit | Line | Data |
---|---|---|
845901f4 DM |
1 | use crate::api::schema::*; |
2 | ||
3 | use failure::*; | |
00c908df | 4 | |
bfb1d69a | 5 | use serde_json::Value; |
845901f4 DM |
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 { | |
ad8a98f7 | 15 | let bytes = arg.as_bytes(); |
845901f4 | 16 | |
ad8a98f7 | 17 | let length = bytes.len(); |
845901f4 | 18 | |
ad8a98f7 WB |
19 | if length < 2 || bytes[0] != b'-' { |
20 | return RawArgument::Argument { | |
21 | value: arg.to_string(), | |
22 | }; | |
23 | } | |
c78bcf07 | 24 | |
ad8a98f7 | 25 | let mut first = 1; |
845901f4 | 26 | |
ad8a98f7 WB |
27 | if bytes[1] == b'-' { |
28 | if length == 2 { | |
29 | return RawArgument::Separator; | |
30 | } | |
31 | first = 2; | |
32 | } | |
845901f4 | 33 | |
ad8a98f7 WB |
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 | }; | |
7556cab4 | 46 | } |
845901f4 DM |
47 | } |
48 | ||
ad8a98f7 WB |
49 | return RawArgument::Option { |
50 | name: unsafe { arg.get_unchecked(first..).to_string() }, | |
51 | value: None, | |
52 | }; | |
845901f4 DM |
53 | } |
54 | ||
4d0ea997 WB |
55 | pub fn parse_arguments<T: AsRef<str>>( |
56 | args: &[T], | |
73f29c34 | 57 | arg_param: &Vec<&'static str>, |
a653882d | 58 | schema: &ObjectSchema, |
7d95c10d | 59 | ) -> Result<(Value, Vec<String>), ParameterError> { |
0c9ce2bb | 60 | let mut errors = ParameterError::new(); |
845901f4 | 61 | |
a653882d | 62 | let properties = &schema.properties; |
845901f4 DM |
63 | |
64 | let mut data: Vec<(String, String)> = vec![]; | |
65 | let mut rest: Vec<String> = vec![]; | |
66 | ||
67 | let mut pos = 0; | |
68 | ||
be45ccd2 | 69 | while pos < args.len() { |
4d0ea997 | 70 | match parse_argument(args[pos].as_ref()) { |
99da3a07 | 71 | RawArgument::Separator => { |
2482c095 | 72 | break; |
99da3a07 | 73 | } |
03fb8951 WB |
74 | RawArgument::Option { name, value } => match value { |
75 | None => { | |
76 | let mut want_bool = false; | |
77 | let mut can_default = false; | |
379ea0ed | 78 | if let Some((_optional, param_schema)) = properties.get::<str>(&name) { |
03fb8951 WB |
79 | if let Schema::Boolean(boolean_schema) = param_schema.as_ref() { |
80 | want_bool = true; | |
81 | if let Some(default) = boolean_schema.default { | |
2767c5d3 WB |
82 | if default == false { |
83 | can_default = true; | |
84 | } | |
03fb8951 WB |
85 | } else { |
86 | can_default = true; | |
00c908df | 87 | } |
99da3a07 | 88 | } |
03fb8951 | 89 | } |
2770fbf3 | 90 | |
03fb8951 WB |
91 | let mut next_is_argument = false; |
92 | let mut next_is_bool = false; | |
2770fbf3 | 93 | |
03fb8951 | 94 | if (pos + 1) < args.len() { |
7d95c10d WB |
95 | let next = args[pos + 1].as_ref(); |
96 | if let RawArgument::Argument { value: _ } = parse_argument(next) { | |
03fb8951 | 97 | next_is_argument = true; |
2767c5d3 WB |
98 | if let Ok(_) = parse_boolean(next) { |
99 | next_is_bool = true; | |
100 | } | |
99da3a07 | 101 | } |
03fb8951 | 102 | } |
2770fbf3 | 103 | |
03fb8951 WB |
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 { | |
7d95c10d | 109 | data.push((name, "true".to_string())); |
99da3a07 | 110 | } else { |
03fb8951 WB |
111 | errors.push(format_err!("parameter '{}': {}", name, |
112 | "missing boolean value.")); | |
113 | } | |
99da3a07 | 114 | |
03fb8951 WB |
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.")); | |
845901f4 | 123 | } |
99da3a07 | 124 | } |
03fb8951 WB |
125 | } |
126 | Some(v) => { | |
127 | data.push((name, v)); | |
845901f4 | 128 | } |
7ebb1733 | 129 | }, |
99da3a07 WB |
130 | RawArgument::Argument { value } => { |
131 | rest.push(value); | |
845901f4 DM |
132 | } |
133 | } | |
134 | ||
135 | pos += 1; | |
845901f4 DM |
136 | } |
137 | ||
4d0ea997 WB |
138 | rest.reserve(args.len() - pos); |
139 | for i in &args[pos..] { | |
140 | rest.push(i.as_ref().to_string()); | |
141 | } | |
2482c095 | 142 | |
a19f223d DM |
143 | for i in 0..arg_param.len() { |
144 | if rest.len() > i { | |
73f29c34 | 145 | data.push((arg_param[i].to_string(), rest[i].clone())); |
a19f223d DM |
146 | } else { |
147 | errors.push(format_err!("missing argument '{}'", arg_param[i])); | |
148 | } | |
149 | } | |
150 | ||
2767c5d3 WB |
151 | if errors.len() > 0 { |
152 | return Err(errors); | |
153 | } | |
845901f4 | 154 | |
a19f223d DM |
155 | if arg_param.len() > 0 { |
156 | rest = rest[arg_param.len()..].to_vec(); | |
157 | } | |
158 | ||
0c9ce2bb DM |
159 | let options = parse_parameter_strings(&data, schema, true)?; |
160 | ||
7d95c10d | 161 | Ok((options, rest)) |
845901f4 | 162 | } |
2770fbf3 | 163 | |
2770fbf3 DM |
164 | #[test] |
165 | fn test_boolean_arg() { | |
7edeec7b DM |
166 | let schema = ObjectSchema::new("Parameters:") |
167 | .required( | |
168 | "enable", BooleanSchema::new("Enable") | |
7edeec7b | 169 | ); |
2770fbf3 | 170 | |
c78bcf07 DM |
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 { | |
4d0ea997 | 186 | let res = parse_arguments(&args, &vec![], &schema); |
2770fbf3 DM |
187 | assert!(res.is_ok()); |
188 | if let Ok((options, rest)) = res { | |
c78bcf07 | 189 | assert!(options["enable"] == expect); |
2770fbf3 DM |
190 | assert!(rest.len() == 0); |
191 | } | |
192 | } | |
a19f223d | 193 | } |
2770fbf3 | 194 | |
a19f223d DM |
195 | #[test] |
196 | fn test_argument_paramenter() { | |
7edeec7b | 197 | let schema = ObjectSchema::new("Parameters:") |
768e0109 DM |
198 | .required("enable", BooleanSchema::new("Enable.")) |
199 | .required("storage", StringSchema::new("Storage.")); | |
a19f223d DM |
200 | |
201 | let args = vec!["-enable", "local"]; | |
4d0ea997 | 202 | let res = parse_arguments(&args, &vec!["storage"], &schema); |
a19f223d DM |
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 | } | |
2770fbf3 | 209 | } |