#![cfg_attr(not(feature = "std"), no_std)]
#![cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
pub type CmdlineValue = Vec<Option<String>>;
pub type CmdlineParam = (String, CmdlineValue);
pub type Cmdline = Vec<CmdlineParam>;
trait VecOfParamsAsDict {
fn get<'a>(&'a mut self, key: &String) -> Option<&'a mut CmdlineValue>;
fn take_from_key(&mut self, key: &str) -> Option<CmdlineValue>;
fn entry_or_insert<'a>(&'a mut self, key: String, insert: CmdlineValue)
-> &'a mut CmdlineValue;
}
impl VecOfParamsAsDict for Cmdline {
fn get<'a>(&'a mut self, key: &String) -> Option<&'a mut CmdlineValue> {
for item in self {
if &item.0 == key {
return Some(&mut item.1);
}
}
None
}
fn take_from_key(&mut self, key: &str) -> Option<CmdlineValue> {
match { self.iter().position(|x| &x.0 == key) } {
Some(pos) => Some(self.remove(pos).1),
None => None,
}
}
fn entry_or_insert<'a>(
&'a mut self,
key: String,
insert: CmdlineValue,
) -> &'a mut CmdlineValue {
let pos = { self.iter().position(|(k, _)| k == &key) };
match pos {
Some(index) => &mut self[index].1,
None => {
self.push((key, insert));
let len = { self.len() };
&mut self[len - 1].1
}
}
}
}
pub trait CmdlineContent {
fn parse(buffer: &str) -> Result<Cmdline, (usize, &'static str)>;
fn render(&self) -> Result<String, &'static str>;
fn add_param(&mut self, key: String, value: Option<String>);
}
impl CmdlineContent for Cmdline {
fn parse(buffer: &str) -> Result<Cmdline, (usize, &'static str)> {
#[derive(Debug)]
enum Scope {
InValueQuoted,
InValueUnquoted,
InKey,
InEqual,
InSpace,
}
let mut key = String::new();
let mut value = String::new();
let mut result = Cmdline::new();
let mut scope = Scope::InSpace;
let mut i: usize = 0;
for c in buffer.chars() {
match c {
' ' => match scope {
Scope::InValueQuoted => {
value.push(c);
}
Scope::InValueUnquoted => {
result.add_param(key.drain(..).collect(), Some(value.drain(..).collect()));
scope = Scope::InSpace;
}
Scope::InSpace => {}
Scope::InEqual => {
return Err((i, "empty parameter value"));
}
Scope::InKey => {
result.add_param(key.drain(..).collect(), None);
}
},
'"' => match scope {
Scope::InValueQuoted => {
scope = Scope::InValueUnquoted;
}
Scope::InEqual => {
scope = Scope::InValueQuoted;
}
Scope::InKey => {
return Err((i, "quote in parameter name"));
}
Scope::InValueUnquoted => {
scope = Scope::InValueQuoted;
}
Scope::InSpace => {
return Err((i, "quote after unquoted space"));
}
},
'=' => match scope {
Scope::InKey => {
scope = Scope::InEqual;
}
Scope::InValueQuoted | Scope::InValueUnquoted => {
value.push(c);
}
Scope::InEqual => {
scope = Scope::InValueUnquoted;
value.push(c)
}
Scope::InSpace => {
return Err((i, "equals after space"));
}
},
_ => match scope {
Scope::InKey => {
key.push(c);
}
Scope::InValueQuoted => {
value.push(c);
}
Scope::InValueUnquoted => {
value.push(c);
}
Scope::InSpace => {
scope = Scope::InKey;
key.push(c);
}
Scope::InEqual => {
scope = Scope::InValueUnquoted;
value.push(c);
}
},
};
i += 1;
}
match scope {
Scope::InKey => {
result.add_param(key.drain(..).collect(), None);
}
Scope::InValueQuoted => {
return Err((i, "unclosed quote in parameter value"));
}
Scope::InValueUnquoted => {
result.add_param(key.drain(..).collect(), Some(value.drain(..).collect()))
}
Scope::InEqual => {
return Err((i, "empty parameter value"));
}
Scope::InSpace => {}
}
Ok(result)
}
fn add_param(&mut self, key: String, value: Option<String>) {
let vec = self.entry_or_insert(key, Vec::new());
vec.push(value);
}
fn render(&self) -> Result<String, &'static str> {
let mut render = String::new();
for (param, values) in self {
for value in values {
match value {
Some(value) => {
render.push_str(¶m);
render.push('=');
if value.contains('"') {
return Err("cannot escape quote character");
}
if value.contains(' ') {
render.push('"');
render.push_str(&value);
render.push('"');
} else {
render.push_str(&value);
}
}
_ => {
render.push_str(¶m);
}
}
render.push(' ');
}
}
render.pop();
Ok(render)
}
}
#[cfg(test)]
mod cmdline_tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[test]
fn cmdline_parse_test() {
let test = "a=test";
assert_eq!(
Cmdline::parse(test),
Ok(vec![(String::from("a"), vec![Some(String::from("test"))])])
);
let test = "a=te\"s\"t";
assert_eq!(
Cmdline::parse(test),
Ok(vec![(String::from("a"), vec![Some(String::from("test"))])])
);
let test = "a b c";
assert_eq!(
Cmdline::parse(test),
Ok(vec![
(String::from("a"), vec![None]),
(String::from("b"), vec![None]),
(String::from("c"), vec![None])
])
);
let test = "a=test a a=test2 c a=test3";
assert_eq!(
Cmdline::parse(test),
Ok(vec![
(
String::from("a"),
vec![
Some(String::from("test")),
None,
Some(String::from("test2")),
Some(String::from("test3"))
]
),
(String::from("c"), vec![None])
])
);
let test = "a=3 =asd";
assert!(Cmdline::parse(test).is_err());
let test = "a=3 b= ";
assert!(Cmdline::parse(test).is_err());
let test = "a=3 b=";
assert!(Cmdline::parse(test).is_err());
let test = "\"quoted param\"=should_error";
assert!(Cmdline::parse(test).is_err());
let test = "quot\"ed param\"=should_error";
assert!(Cmdline::parse(test).is_err());
let test = "arg1 \"quoted param\"=should_error";
assert!(Cmdline::parse(test).is_err());
let test = "param=\"unclosed quote";
assert!(Cmdline::parse(test).is_err());
}
}