1#![cfg_attr(not(feature = "std"), no_std)]
12#![cfg(not(feature = "std"))]
13extern crate alloc;
14
15#[cfg(not(feature = "std"))]
16use alloc::string::String;
17#[cfg(not(feature = "std"))]
18use alloc::vec::Vec;
19
20pub type CmdlineValue = Vec<Option<String>>;
22pub type CmdlineParam = (String, CmdlineValue);
24pub type Cmdline = Vec<CmdlineParam>;
26
27trait VecOfParamsAsDict {
29 fn get<'a>(&'a mut self, key: &String) -> Option<&'a mut CmdlineValue>;
30 fn take_from_key(&mut self, key: &str) -> Option<CmdlineValue>;
31 fn entry_or_insert<'a>(&'a mut self, key: String, insert: CmdlineValue)
32 -> &'a mut CmdlineValue;
33}
34
35impl VecOfParamsAsDict for Cmdline {
36 fn get<'a>(&'a mut self, key: &String) -> Option<&'a mut CmdlineValue> {
37 for item in self {
38 if &item.0 == key {
39 return Some(&mut item.1);
40 }
41 }
42 None
43 }
44
45 fn take_from_key(&mut self, key: &str) -> Option<CmdlineValue> {
46 match { self.iter().position(|x| &x.0 == key) } {
47 Some(pos) => Some(self.remove(pos).1),
48 None => None,
49 }
50 }
51
52 fn entry_or_insert<'a>(
53 &'a mut self,
54 key: String,
55 insert: CmdlineValue,
56 ) -> &'a mut CmdlineValue {
57 let pos = { self.iter().position(|(k, _)| k == &key) };
58
59 match pos {
60 Some(index) => &mut self[index].1,
61 None => {
62 self.push((key, insert));
63 let len = { self.len() };
64 &mut self[len - 1].1
65 }
66 }
67 }
68}
69
70pub trait CmdlineContent {
72 fn parse(buffer: &str) -> Result<Cmdline, (usize, &'static str)>;
87 fn render(&self) -> Result<String, &'static str>;
97
98 fn add_param(&mut self, key: String, value: Option<String>);
104}
105
106impl CmdlineContent for Cmdline {
107 fn parse(buffer: &str) -> Result<Cmdline, (usize, &'static str)> {
108 #[derive(Debug)]
109 enum Scope {
110 InValueQuoted,
111 InValueUnquoted,
112 InKey,
113 InEqual,
114 InSpace,
115 }
116
117 let mut key = String::new();
118 let mut value = String::new();
119
120 let mut result = Cmdline::new();
121 let mut scope = Scope::InSpace;
122
123 let mut i: usize = 0;
124 for c in buffer.chars() {
125 match c {
126 ' ' => match scope {
127 Scope::InValueQuoted => {
128 value.push(c);
129 }
130 Scope::InValueUnquoted => {
131 result.add_param(key.drain(..).collect(), Some(value.drain(..).collect()));
132 scope = Scope::InSpace;
133 }
134 Scope::InSpace => {}
135 Scope::InEqual => {
136 return Err((i, "empty parameter value"));
137 }
138 Scope::InKey => {
139 result.add_param(key.drain(..).collect(), None);
140 }
141 },
142 '"' => match scope {
143 Scope::InValueQuoted => {
144 scope = Scope::InValueUnquoted;
145 }
146 Scope::InEqual => {
147 scope = Scope::InValueQuoted;
148 }
149 Scope::InKey => {
150 return Err((i, "quote in parameter name"));
151 }
152 Scope::InValueUnquoted => {
153 scope = Scope::InValueQuoted;
154 }
155 Scope::InSpace => {
156 return Err((i, "quote after unquoted space"));
157 }
158 },
159 '=' => match scope {
160 Scope::InKey => {
161 scope = Scope::InEqual;
162 }
163 Scope::InValueQuoted | Scope::InValueUnquoted => {
164 value.push(c);
165 }
166 Scope::InEqual => {
167 scope = Scope::InValueUnquoted;
168 value.push(c)
169 }
170 Scope::InSpace => {
171 return Err((i, "equals after space"));
172 }
173 },
174 _ => match scope {
175 Scope::InKey => {
176 key.push(c);
177 }
178 Scope::InValueQuoted => {
179 value.push(c);
180 }
181 Scope::InValueUnquoted => {
182 value.push(c);
183 }
184 Scope::InSpace => {
185 scope = Scope::InKey;
186 key.push(c);
187 }
188 Scope::InEqual => {
189 scope = Scope::InValueUnquoted;
190 value.push(c);
191 }
192 },
193 };
194 i += 1;
195 }
196
197 match scope {
198 Scope::InKey => {
199 result.add_param(key.drain(..).collect(), None);
200 }
201 Scope::InValueQuoted => {
202 return Err((i, "unclosed quote in parameter value"));
203 }
204 Scope::InValueUnquoted => {
205 result.add_param(key.drain(..).collect(), Some(value.drain(..).collect()))
206 }
207 Scope::InEqual => {
208 return Err((i, "empty parameter value"));
209 }
210 Scope::InSpace => {}
211 }
212
213 Ok(result)
214 }
215
216 fn add_param(&mut self, key: String, value: Option<String>) {
217 let vec = self.entry_or_insert(key, Vec::new());
219 vec.push(value);
220 }
221
222 fn render(&self) -> Result<String, &'static str> {
223 let mut render = String::new();
224 for (param, values) in self {
225 for value in values {
226 match value {
227 Some(value) => {
228 render.push_str(¶m);
229 render.push('=');
230 if value.contains('"') {
231 return Err("cannot escape quote character");
232 }
233 if value.contains(' ') {
234 render.push('"');
235 render.push_str(&value);
236 render.push('"');
237 } else {
238 render.push_str(&value);
239 }
240 }
241 _ => {
242 render.push_str(¶m);
243 }
244 }
245 render.push(' ');
246 }
247 }
248 render.pop();
249
250 Ok(render)
251 }
252}
253
254#[cfg(test)]
255mod cmdline_tests {
256 use super::*;
257 #[cfg(not(feature = "std"))]
258 use alloc::vec;
259
260 #[test]
261 fn cmdline_parse_test() {
262 let test = "a=test";
263 assert_eq!(
264 Cmdline::parse(test),
265 Ok(vec![(String::from("a"), vec![Some(String::from("test"))])])
266 );
267
268 let test = "a=te\"s\"t";
269 assert_eq!(
270 Cmdline::parse(test),
271 Ok(vec![(String::from("a"), vec![Some(String::from("test"))])])
272 );
273
274 let test = "a b c";
275 assert_eq!(
276 Cmdline::parse(test),
277 Ok(vec![
278 (String::from("a"), vec![None]),
279 (String::from("b"), vec![None]),
280 (String::from("c"), vec![None])
281 ])
282 );
283
284 let test = "a=test a a=test2 c a=test3";
285 assert_eq!(
286 Cmdline::parse(test),
287 Ok(vec![
288 (
289 String::from("a"),
290 vec![
291 Some(String::from("test")),
292 None,
293 Some(String::from("test2")),
294 Some(String::from("test3"))
295 ]
296 ),
297 (String::from("c"), vec![None])
298 ])
299 );
300
301 let test = "a=3 =asd";
302 assert!(Cmdline::parse(test).is_err());
303
304 let test = "a=3 b= ";
305 assert!(Cmdline::parse(test).is_err());
306
307 let test = "a=3 b=";
308 assert!(Cmdline::parse(test).is_err());
309
310 let test = "\"quoted param\"=should_error";
311 assert!(Cmdline::parse(test).is_err());
312
313 let test = "quot\"ed param\"=should_error";
314 assert!(Cmdline::parse(test).is_err());
315
316 let test = "arg1 \"quoted param\"=should_error";
317 assert!(Cmdline::parse(test).is_err());
318
319 let test = "param=\"unclosed quote";
320 assert!(Cmdline::parse(test).is_err());
321 }
322}