use std::borrow::Cow;
use std::cmp::Ordering;
use std::ops::Deref;
use crate::{Action, utf8};
use anyhow::Result;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Cmdline<'a>(Cow<'a, [u8]>);
pub type CmdlineOwned = Cmdline<'static>;
impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for Cmdline<'a> {
fn from(input: &'a T) -> Self {
Self(Cow::Borrowed(input.as_ref()))
}
}
impl Deref for Cmdline<'_> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a, T> AsRef<T> for Cmdline<'a>
where
T: ?Sized,
<Cmdline<'a> as Deref>::Target: AsRef<T>,
{
fn as_ref(&self) -> &T {
self.deref().as_ref()
}
}
impl From<Vec<u8>> for CmdlineOwned {
fn from(input: Vec<u8>) -> Self {
Self(Cow::Owned(input))
}
}
#[derive(Debug)]
pub struct CmdlineIter<'a>(CmdlineIterBytes<'a>);
impl<'a> Iterator for CmdlineIter<'a> {
type Item = Parameter<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().and_then(Parameter::parse_internal)
}
}
#[derive(Debug)]
pub struct CmdlineIterBytes<'a>(&'a [u8]);
impl<'a> Iterator for CmdlineIterBytes<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
let input = self.0.trim_ascii_start();
if input.is_empty() {
self.0 = input;
return None;
}
let mut in_quotes = false;
let end = input.iter().position(move |c| {
if *c == b'"' {
in_quotes = !in_quotes;
}
!in_quotes && c.is_ascii_whitespace()
});
let end = end.unwrap_or(input.len());
let (param, rest) = input.split_at(end);
self.0 = rest;
Some(param)
}
}
impl<'a> Cmdline<'a> {
pub fn new() -> CmdlineOwned {
Cmdline::default()
}
pub fn from_proc() -> Result<Self> {
Ok(Self(Cow::Owned(std::fs::read("/proc/cmdline")?)))
}
pub fn iter(&'a self) -> CmdlineIter<'a> {
CmdlineIter(self.iter_bytes())
}
pub fn iter_bytes(&self) -> CmdlineIterBytes<'_> {
CmdlineIterBytes(&self.0)
}
pub fn iter_utf8(&'a self) -> impl Iterator<Item = utf8::Parameter<'a>> {
self.iter()
.filter_map(|p| utf8::Parameter::try_from(p).ok())
}
pub fn find<T: AsRef<[u8]> + ?Sized>(&'a self, key: &T) -> Option<Parameter<'a>> {
let key = ParameterKey(key.as_ref());
self.iter().find(|p| p.key == key)
}
pub fn find_utf8<T: AsRef<[u8]> + ?Sized>(
&'a self,
key: &T,
) -> Result<Option<utf8::Parameter<'a>>> {
let bytes = match self.find(key.as_ref()) {
Some(p) => p,
None => return Ok(None),
};
Ok(Some(utf8::Parameter::try_from(bytes)?))
}
pub fn find_all_starting_with<T: AsRef<[u8]> + ?Sized>(
&'a self,
prefix: &'a T,
) -> impl Iterator<Item = Parameter<'a>> + 'a {
self.iter()
.filter(move |p| p.key.0.starts_with(prefix.as_ref()))
}
pub fn value_of<T: AsRef<[u8]> + ?Sized>(&'a self, key: &T) -> Option<&'a [u8]> {
self.find(&key).and_then(|p| p.value)
}
pub fn require_value_of<T: AsRef<[u8]> + ?Sized>(&'a self, key: &T) -> Result<&'a [u8]> {
let key = key.as_ref();
self.value_of(key).ok_or_else(|| {
let key = String::from_utf8_lossy(key);
anyhow::anyhow!("Failed to find kernel argument '{key}'")
})
}
pub fn add(&mut self, param: &Parameter) -> Action {
for p in self.iter() {
if p == *param {
return Action::Existed;
}
}
let self_mut = self.0.to_mut();
if self_mut
.last()
.filter(|v| !v.is_ascii_whitespace())
.is_some()
{
self_mut.push(b' ');
}
self_mut.extend_from_slice(param.parameter);
Action::Added
}
pub fn add_or_modify(&mut self, param: &Parameter) -> Action {
let mut new_params = Vec::new();
let mut modified = false;
let mut seen_key = false;
for p in self.iter() {
if p.key == param.key {
if !seen_key {
if p != *param {
modified = true;
}
new_params.push(param.parameter);
} else {
modified = true;
}
seen_key = true;
} else {
new_params.push(p.parameter);
}
}
if !seen_key {
let self_mut = self.0.to_mut();
if self_mut
.last()
.filter(|v| !v.is_ascii_whitespace())
.is_some()
{
self_mut.push(b' ');
}
self_mut.extend_from_slice(param.parameter);
return Action::Added;
}
if modified {
self.0 = Cow::Owned(new_params.join(b" ".as_slice()));
Action::Modified
} else {
Action::Existed
}
}
pub fn remove(&mut self, key: &ParameterKey) -> bool {
let mut removed = false;
let mut new_params = Vec::new();
for p in self.iter() {
if p.key == *key {
removed = true;
} else {
new_params.push(p.parameter);
}
}
if removed {
self.0 = Cow::Owned(new_params.join(b" ".as_slice()));
}
removed
}
pub fn remove_exact(&mut self, param: &Parameter) -> bool {
let mut removed = false;
let mut new_params = Vec::new();
for p in self.iter() {
if p == *param {
removed = true;
} else {
new_params.push(p.parameter);
}
}
if removed {
self.0 = Cow::Owned(new_params.join(b" ".as_slice()));
}
removed
}
#[cfg(test)]
pub(crate) fn is_owned(&self) -> bool {
matches!(self.0, Cow::Owned(_))
}
#[cfg(test)]
pub(crate) fn is_borrowed(&self) -> bool {
matches!(self.0, Cow::Borrowed(_))
}
}
impl<'a> IntoIterator for &'a Cmdline<'a> {
type Item = Parameter<'a>;
type IntoIter = CmdlineIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, 'other> Extend<Parameter<'other>> for Cmdline<'a> {
fn extend<T: IntoIterator<Item = Parameter<'other>>>(&mut self, iter: T) {
for param in iter {
self.add(¶m);
}
}
}
impl PartialEq for Cmdline<'_> {
fn eq(&self, other: &Self) -> bool {
let mut our_params = self.iter().collect::<Vec<_>>();
our_params.sort();
let mut their_params = other.iter().collect::<Vec<_>>();
their_params.sort();
our_params == their_params
}
}
impl Eq for Cmdline<'_> {}
#[derive(Clone, Debug)]
pub struct ParameterKey<'a>(pub(crate) &'a [u8]);
impl Deref for ParameterKey<'_> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<'a, T> AsRef<T> for ParameterKey<'a>
where
T: ?Sized,
<ParameterKey<'a> as Deref>::Target: AsRef<T>,
{
fn as_ref(&self) -> &T {
self.deref().as_ref()
}
}
impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for ParameterKey<'a> {
fn from(s: &'a T) -> Self {
Self(s.as_ref())
}
}
impl ParameterKey<'_> {
fn iter(&self) -> impl Iterator<Item = u8> + use<'_> {
self.0
.iter()
.map(|&c: &u8| if c == b'-' { b'_' } else { c })
}
}
impl PartialEq for ParameterKey<'_> {
fn eq(&self, other: &Self) -> bool {
self.iter().eq(other.iter())
}
}
impl Eq for ParameterKey<'_> {}
impl Ord for ParameterKey<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.iter().cmp(other.iter())
}
}
impl PartialOrd for ParameterKey<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug)]
pub struct Parameter<'a> {
parameter: &'a [u8],
key: ParameterKey<'a>,
value: Option<&'a [u8]>,
}
impl<'a> Parameter<'a> {
pub fn parse<T: AsRef<[u8]> + ?Sized>(input: &'a T) -> Option<Self> {
CmdlineIterBytes(input.as_ref())
.next()
.and_then(Self::parse_internal)
}
fn parse_internal(input: &'a [u8]) -> Option<Self> {
let dequoted_input = input.strip_prefix(b"\"").unwrap_or(input);
let dequoted_input = dequoted_input.strip_suffix(b"\"").unwrap_or(dequoted_input);
let equals = dequoted_input.iter().position(|b| *b == b'=');
match equals {
None => Some(Self {
parameter: input,
key: ParameterKey(dequoted_input),
value: None,
}),
Some(i) => {
let (key, mut value) = dequoted_input.split_at(i);
let key = ParameterKey(key);
value = &value[1..];
value = value.strip_prefix(b"\"").unwrap_or(value);
Some(Self {
parameter: input,
key,
value: Some(value),
})
}
}
}
pub fn key(&self) -> ParameterKey<'a> {
self.key.clone()
}
pub fn value(&self) -> Option<&'a [u8]> {
self.value
}
}
impl PartialEq for Parameter<'_> {
fn eq(&self, other: &Self) -> bool {
self.key == other.key && self.value == other.value
}
}
impl Eq for Parameter<'_> {}
impl Ord for Parameter<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.key.cmp(&other.key).then(self.value.cmp(&other.value))
}
}
impl PartialOrd for Parameter<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Deref for Parameter<'_> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.parameter
}
}
impl<'a, T> AsRef<T> for Parameter<'a>
where
T: ?Sized,
<Parameter<'a> as Deref>::Target: AsRef<T>,
{
fn as_ref(&self) -> &T {
self.deref().as_ref()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn param(s: &str) -> Parameter<'_> {
Parameter::parse(s.as_bytes()).unwrap()
}
fn param_utf8(s: &str) -> utf8::Parameter<'_> {
utf8::Parameter::parse(s).unwrap()
}
#[test]
fn test_parameter_parse() {
let p = Parameter::parse(b"foo").unwrap();
assert_eq!(p.key.0, b"foo");
assert_eq!(p.value, None);
let p = Parameter::parse(b"foo=bar baz").unwrap();
assert_eq!(p.key.0, b"foo");
assert_eq!(p.value, Some(b"bar".as_slice()));
assert!(Parameter::parse(b"").is_none());
assert!(Parameter::parse(b" ").is_none());
}
#[test]
fn test_parameter_simple() {
let switch = param("foo");
assert_eq!(switch.key.0, b"foo");
assert_eq!(switch.value, None);
let kv = param("bar=baz");
assert_eq!(kv.key.0, b"bar");
assert_eq!(kv.value, Some(b"baz".as_slice()));
}
#[test]
fn test_parameter_quoted() {
let p = param("foo=\"quoted value\"");
assert_eq!(p.value, Some(b"quoted value".as_slice()));
let p = param("foo=\"unclosed quotes");
assert_eq!(p.value, Some(b"unclosed quotes".as_slice()));
let p = param("foo=trailing_quotes\"");
assert_eq!(p.value, Some(b"trailing_quotes".as_slice()));
let outside_quoted = param("\"foo=quoted value\"");
let value_quoted = param("foo=\"quoted value\"");
assert_eq!(outside_quoted, value_quoted);
}
#[test]
fn test_parameter_extra_whitespace() {
let p = param(" foo=bar ");
assert_eq!(p.key.0, b"foo");
assert_eq!(p.value, Some(b"bar".as_slice()));
}
#[test]
fn test_parameter_internal_key_whitespace() {
let p = Parameter::parse("foo bar=baz".as_bytes()).unwrap();
assert_eq!(p.key.0, b"foo");
assert_eq!(p.value, None);
}
#[test]
fn test_parameter_pathological() {
let p = param("\"foo\"=bar");
assert_eq!(p.key.0, b"foo\"");
assert_eq!(p.value, Some(b"bar".as_slice()));
assert_ne!(p, param("foo=bar"));
let p = param("\"foo=\"bar");
assert_eq!(p.key.0, b"foo");
assert_eq!(p.value, Some(b"bar".as_slice()));
assert_eq!(p, param("foo=bar"));
let p = param("foo=\"internal\"quotes\"are\"ok\"");
assert_eq!(p.value, Some(b"internal\"quotes\"are\"ok".as_slice()));
let non_utf8_byte = b"\xff";
#[allow(invalid_from_utf8)]
let failed_conversion = str::from_utf8(non_utf8_byte);
assert!(failed_conversion.is_err());
let mut p = b"foo=".to_vec();
p.push(non_utf8_byte[0]);
let p = Parameter::parse(&p).unwrap();
assert_eq!(p.value, Some(non_utf8_byte.as_slice()));
}
#[test]
fn test_parameter_equality() {
let foo = param("foo");
let bar = param("foobar");
assert_ne!(foo, bar);
assert_ne!(bar, foo);
let dashes = param("a-delimited-param");
let underscores = param("a_delimited_param");
assert_eq!(dashes, underscores);
let dashes = param("a-delimited-param=same_values");
let underscores = param("a_delimited_param=same_values");
assert_eq!(dashes, underscores);
let dashes = param("a-delimited-param=different_values");
let underscores = param("a_delimited_param=DiFfErEnT_valUEZ");
assert_ne!(dashes, underscores);
let switch = param("same_key");
let keyvalue = param("same_key=but_with_a_value");
assert_ne!(switch, keyvalue);
}
#[test]
fn test_kargs_simple() {
let kargs = Cmdline::from(b"foo=bar,bar2 baz=fuz wiz".as_slice());
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo=bar,bar2")));
assert_eq!(iter.next(), Some(param("baz=fuz")));
assert_eq!(iter.next(), Some(param("wiz")));
assert_eq!(iter.next(), None);
assert_eq!(kargs.find("foo").unwrap().value.unwrap(), b"bar,bar2");
assert!(kargs.find("nothing").is_none());
}
#[test]
fn test_cmdline_default() {
let kargs: Cmdline = Default::default();
assert_eq!(kargs.iter().next(), None);
}
#[test]
fn test_cmdline_new() {
let kargs = Cmdline::new();
assert_eq!(kargs.iter().next(), None);
assert!(kargs.is_owned());
let _static_kargs: CmdlineOwned = Cmdline::new();
}
#[test]
fn test_kargs_iter_utf8() {
let kargs = Cmdline::from(b"foo=bar,bar2 \xff baz=fuz bad=oh\xffno wiz");
let mut iter = kargs.iter_utf8();
assert_eq!(iter.next(), Some(param_utf8("foo=bar,bar2")));
assert_eq!(iter.next(), Some(param_utf8("baz=fuz")));
assert_eq!(iter.next(), Some(param_utf8("wiz")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_kargs_find_utf8() {
let kargs = Cmdline::from(b"foo=bar,bar2 \xff baz=fuz bad=oh\xffno wiz");
assert_eq!(
kargs.find_utf8("foo").unwrap().unwrap().value().unwrap(),
"bar,bar2"
);
assert!(kargs.find_utf8("nothing").unwrap().is_none());
let p = kargs.find_utf8("bad");
assert_eq!(
p.unwrap_err().to_string(),
"Parameter value is not valid UTF-8"
);
}
#[test]
fn test_kargs_from_proc() {
let kargs = Cmdline::from_proc().unwrap();
assert!(kargs.iter().count() > 0);
}
#[test]
fn test_kargs_find_dash_hyphen() {
let kargs = Cmdline::from(b"a-b=1 a_b=2".as_slice());
let p = kargs.find("a_b").unwrap();
assert_eq!(p.key.0, b"a-b");
assert_eq!(p.value.unwrap(), b"1");
let p = kargs.find("a-b").unwrap();
assert_eq!(p.key.0, b"a-b");
assert_eq!(p.value.unwrap(), b"1");
let kargs = Cmdline::from(b"a_b=2 a-b=1".as_slice());
let p = kargs.find("a_b").unwrap();
assert_eq!(p.key.0, b"a_b");
assert_eq!(p.value.unwrap(), b"2");
let p = kargs.find("a-b").unwrap();
assert_eq!(p.key.0, b"a_b");
assert_eq!(p.value.unwrap(), b"2");
}
#[test]
fn test_kargs_extra_whitespace() {
let kargs = Cmdline::from(b" foo=bar baz=fuz wiz ".as_slice());
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo=bar")));
assert_eq!(iter.next(), Some(param("baz=fuz")));
assert_eq!(iter.next(), Some(param("wiz")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_value_of() {
let kargs = Cmdline::from(b"foo=bar baz=qux switch".as_slice());
assert_eq!(kargs.value_of("foo"), Some(b"bar".as_slice()));
assert_eq!(kargs.value_of("baz"), Some(b"qux".as_slice()));
assert_eq!(kargs.value_of("switch"), None);
assert_eq!(kargs.value_of("missing"), None);
let kargs = Cmdline::from(b"dash-key=value1 under_key=value2".as_slice());
assert_eq!(kargs.value_of("dash_key"), Some(b"value1".as_slice()));
assert_eq!(kargs.value_of("under-key"), Some(b"value2".as_slice()));
}
#[test]
fn test_require_value_of() {
let kargs = Cmdline::from(b"foo=bar baz=qux switch".as_slice());
assert_eq!(kargs.require_value_of("foo").unwrap(), b"bar");
assert_eq!(kargs.require_value_of("baz").unwrap(), b"qux");
let err = kargs.require_value_of("switch").unwrap_err();
assert!(
err.to_string()
.contains("Failed to find kernel argument 'switch'")
);
let err = kargs.require_value_of("missing").unwrap_err();
assert!(
err.to_string()
.contains("Failed to find kernel argument 'missing'")
);
let kargs = Cmdline::from(b"dash-key=value1 under_key=value2".as_slice());
assert_eq!(kargs.require_value_of("dash_key").unwrap(), b"value1");
assert_eq!(kargs.require_value_of("under-key").unwrap(), b"value2");
}
#[test]
fn test_find_all() {
let kargs =
Cmdline::from(b"foo=bar rd.foo=a rd.bar=b rd.baz rd.qux=c notrd.val=d".as_slice());
let mut rd_args: Vec<_> = kargs.find_all_starting_with(b"rd.".as_slice()).collect();
rd_args.sort_by(|a, b| a.key.0.cmp(b.key.0));
assert_eq!(rd_args.len(), 4);
assert_eq!(rd_args[0], param("rd.bar=b"));
assert_eq!(rd_args[1], param("rd.baz"));
assert_eq!(rd_args[2], param("rd.foo=a"));
assert_eq!(rd_args[3], param("rd.qux=c"));
}
#[test]
fn test_add() {
let mut kargs = Cmdline::from(b"console=tty0 console=ttyS1");
assert!(matches!(kargs.add(¶m("console=ttyS2")), Action::Added));
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("console=tty0")));
assert_eq!(iter.next(), Some(param("console=ttyS1")));
assert_eq!(iter.next(), Some(param("console=ttyS2")));
assert_eq!(iter.next(), None);
assert!(matches!(
kargs.add(¶m("console=ttyS1")),
Action::Existed
));
iter = kargs.iter();
assert_eq!(iter.next(), Some(param("console=tty0")));
assert_eq!(iter.next(), Some(param("console=ttyS1")));
assert_eq!(iter.next(), Some(param("console=ttyS2")));
assert_eq!(iter.next(), None);
assert!(matches!(kargs.add(¶m("quiet")), Action::Added));
iter = kargs.iter();
assert_eq!(iter.next(), Some(param("console=tty0")));
assert_eq!(iter.next(), Some(param("console=ttyS1")));
assert_eq!(iter.next(), Some(param("console=ttyS2")));
assert_eq!(iter.next(), Some(param("quiet")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_add_empty_cmdline() {
let mut kargs = Cmdline::from(b"");
assert!(matches!(kargs.add(¶m("foo")), Action::Added));
assert_eq!(kargs.0, b"foo".as_slice());
}
#[test]
fn test_add_or_modify() {
let mut kargs = Cmdline::from(b"foo=bar");
assert!(matches!(kargs.add_or_modify(¶m("baz")), Action::Added));
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo=bar")));
assert_eq!(iter.next(), Some(param("baz")));
assert_eq!(iter.next(), None);
assert!(matches!(
kargs.add_or_modify(¶m("foo=fuz")),
Action::Modified
));
iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo=fuz")));
assert_eq!(iter.next(), Some(param("baz")));
assert_eq!(iter.next(), None);
assert!(matches!(
kargs.add_or_modify(¶m("foo=fuz")),
Action::Existed
));
iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo=fuz")));
assert_eq!(iter.next(), Some(param("baz")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_add_or_modify_empty_cmdline() {
let mut kargs = Cmdline::from(b"");
assert!(matches!(kargs.add_or_modify(¶m("foo")), Action::Added));
assert_eq!(kargs.0, b"foo".as_slice());
}
#[test]
fn test_add_or_modify_duplicate_parameters() {
let mut kargs = Cmdline::from(b"a=1 a=2");
assert!(matches!(
kargs.add_or_modify(¶m("a=3")),
Action::Modified
));
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("a=3")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_remove() {
let mut kargs = Cmdline::from(b"foo bar baz");
assert!(kargs.remove(&"bar".into()));
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo")));
assert_eq!(iter.next(), Some(param("baz")));
assert_eq!(iter.next(), None);
assert!(!kargs.remove(&"missing".into()));
iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo")));
assert_eq!(iter.next(), Some(param("baz")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_remove_duplicates() {
let mut kargs = Cmdline::from(b"a=1 b=2 a=3");
assert!(kargs.remove(&"a".into()));
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("b=2")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_remove_exact() {
let mut kargs = Cmdline::from(b"foo foo=bar foo=baz");
assert!(kargs.remove_exact(¶m("foo=bar")));
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo")));
assert_eq!(iter.next(), Some(param("foo=baz")));
assert_eq!(iter.next(), None);
assert!(!kargs.remove_exact(¶m("foo=wuz")));
iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo")));
assert_eq!(iter.next(), Some(param("foo=baz")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_extend() {
let mut kargs = Cmdline::from(b"foo=bar baz");
let other = Cmdline::from(b"qux=quux foo=updated");
kargs.extend(&other);
drop(other);
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo=bar")));
assert_eq!(iter.next(), Some(param("baz")));
assert_eq!(iter.next(), Some(param("qux=quux")));
assert_eq!(iter.next(), Some(param("foo=updated")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_extend_empty() {
let mut kargs = Cmdline::from(b"");
let other = Cmdline::from(b"foo=bar baz");
kargs.extend(&other);
let mut iter = kargs.iter();
assert_eq!(iter.next(), Some(param("foo=bar")));
assert_eq!(iter.next(), Some(param("baz")));
assert_eq!(iter.next(), None);
}
#[test]
fn test_into_iterator() {
let kargs = Cmdline::from(b"foo=bar baz=qux wiz");
let params: Vec<_> = (&kargs).into_iter().collect();
assert_eq!(params.len(), 3);
assert_eq!(params[0], param("foo=bar"));
assert_eq!(params[1], param("baz=qux"));
assert_eq!(params[2], param("wiz"));
}
#[test]
fn test_iter_bytes_simple() {
let kargs = Cmdline::from(b"foo bar baz");
let params: Vec<_> = kargs.iter_bytes().collect();
assert_eq!(params.len(), 3);
assert_eq!(params[0], b"foo");
assert_eq!(params[1], b"bar");
assert_eq!(params[2], b"baz");
}
#[test]
fn test_iter_bytes_with_values() {
let kargs = Cmdline::from(b"foo=bar baz=qux wiz");
let params: Vec<_> = kargs.iter_bytes().collect();
assert_eq!(params.len(), 3);
assert_eq!(params[0], b"foo=bar");
assert_eq!(params[1], b"baz=qux");
assert_eq!(params[2], b"wiz");
}
#[test]
fn test_iter_bytes_with_quotes() {
let kargs = Cmdline::from(b"foo=\"bar baz\" qux");
let params: Vec<_> = kargs.iter_bytes().collect();
assert_eq!(params.len(), 2);
assert_eq!(params[0], b"foo=\"bar baz\"");
assert_eq!(params[1], b"qux");
}
#[test]
fn test_iter_bytes_extra_whitespace() {
let kargs = Cmdline::from(b" foo bar ");
let params: Vec<_> = kargs.iter_bytes().collect();
assert_eq!(params.len(), 2);
assert_eq!(params[0], b"foo");
assert_eq!(params[1], b"bar");
}
#[test]
fn test_iter_bytes_empty() {
let kargs = Cmdline::from(b"");
let params: Vec<_> = kargs.iter_bytes().collect();
assert_eq!(params.len(), 0);
}
#[test]
fn test_cmdline_eq() {
assert_eq!(
Cmdline::from("foo bar-with-delim=\"with spaces\""),
Cmdline::from("\"bar_with_delim=with spaces\" foo")
);
assert_ne!(Cmdline::from("foo"), Cmdline::from("foo foo"));
assert_ne!(Cmdline::from("foo foo"), Cmdline::from("foo"));
assert_ne!(Cmdline::from("a a b"), Cmdline::from("a b b"));
}
}