1use std::borrow::Cow;
7use std::cmp::Ordering;
8use std::ops::Deref;
9
10use crate::{Action, utf8};
11
12use anyhow::Result;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16#[derive(Clone, Debug, Default)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23pub struct Cmdline<'a>(Cow<'a, [u8]>);
24
25pub type CmdlineOwned = Cmdline<'static>;
27
28impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for Cmdline<'a> {
29 fn from(input: &'a T) -> Self {
33 Self(Cow::Borrowed(input.as_ref()))
34 }
35}
36
37impl Deref for Cmdline<'_> {
38 type Target = [u8];
39
40 fn deref(&self) -> &Self::Target {
41 &self.0
42 }
43}
44
45impl<'a, T> AsRef<T> for Cmdline<'a>
46where
47 T: ?Sized,
48 <Cmdline<'a> as Deref>::Target: AsRef<T>,
49{
50 fn as_ref(&self) -> &T {
51 self.deref().as_ref()
52 }
53}
54
55impl From<Vec<u8>> for CmdlineOwned {
56 fn from(input: Vec<u8>) -> Self {
58 Self(Cow::Owned(input))
59 }
60}
61
62#[derive(Debug)]
66pub struct CmdlineIter<'a>(CmdlineIterBytes<'a>);
67
68impl<'a> Iterator for CmdlineIter<'a> {
69 type Item = Parameter<'a>;
70
71 fn next(&mut self) -> Option<Self::Item> {
72 self.0.next().and_then(Parameter::parse_internal)
73 }
74}
75
76#[derive(Debug)]
80pub struct CmdlineIterBytes<'a>(&'a [u8]);
81
82impl<'a> Iterator for CmdlineIterBytes<'a> {
83 type Item = &'a [u8];
84
85 fn next(&mut self) -> Option<Self::Item> {
86 let input = self.0.trim_ascii_start();
87
88 if input.is_empty() {
89 self.0 = input;
90 return None;
91 }
92
93 let mut in_quotes = false;
94 let end = input.iter().position(move |c| {
95 if *c == b'"' {
96 in_quotes = !in_quotes;
97 }
98 !in_quotes && c.is_ascii_whitespace()
99 });
100
101 let end = end.unwrap_or(input.len());
102 let (param, rest) = input.split_at(end);
103 self.0 = rest;
104
105 Some(param)
106 }
107}
108
109impl<'a> Cmdline<'a> {
110 pub fn new() -> CmdlineOwned {
114 Cmdline::default()
115 }
116
117 pub fn from_proc() -> Result<Self> {
121 Ok(Self(Cow::Owned(std::fs::read("/proc/cmdline")?)))
122 }
123
124 pub fn iter(&'a self) -> CmdlineIter<'a> {
130 CmdlineIter(self.iter_bytes())
131 }
132
133 pub fn iter_bytes(&self) -> CmdlineIterBytes<'_> {
138 CmdlineIterBytes(&self.0)
139 }
140
141 pub fn iter_utf8(&'a self) -> impl Iterator<Item = utf8::Parameter<'a>> {
144 self.iter()
145 .filter_map(|p| utf8::Parameter::try_from(p).ok())
146 }
147
148 pub fn find<T: AsRef<[u8]> + ?Sized>(&'a self, key: &T) -> Option<Parameter<'a>> {
153 let key = ParameterKey(key.as_ref());
154 self.iter().find(|p| p.key == key)
155 }
156
157 pub fn find_utf8<T: AsRef<[u8]> + ?Sized>(
166 &'a self,
167 key: &T,
168 ) -> Result<Option<utf8::Parameter<'a>>> {
169 let bytes = match self.find(key.as_ref()) {
170 Some(p) => p,
171 None => return Ok(None),
172 };
173
174 Ok(Some(utf8::Parameter::try_from(bytes)?))
175 }
176
177 pub fn find_all_starting_with<T: AsRef<[u8]> + ?Sized>(
181 &'a self,
182 prefix: &'a T,
183 ) -> impl Iterator<Item = Parameter<'a>> + 'a {
184 self.iter()
185 .filter(move |p| p.key.0.starts_with(prefix.as_ref()))
186 }
187
188 pub fn value_of<T: AsRef<[u8]> + ?Sized>(&'a self, key: &T) -> Option<&'a [u8]> {
193 self.find(&key).and_then(|p| p.value)
194 }
195
196 pub fn require_value_of<T: AsRef<[u8]> + ?Sized>(&'a self, key: &T) -> Result<&'a [u8]> {
200 let key = key.as_ref();
201 self.value_of(key).ok_or_else(|| {
202 let key = String::from_utf8_lossy(key);
203 anyhow::anyhow!("Failed to find kernel argument '{key}'")
204 })
205 }
206
207 pub fn add(&mut self, param: &Parameter) -> Action {
220 for p in self.iter() {
222 if p == *param {
223 return Action::Existed;
225 }
226 }
227
228 let self_mut = self.0.to_mut();
230 if self_mut
231 .last()
232 .filter(|v| !v.is_ascii_whitespace())
233 .is_some()
234 {
235 self_mut.push(b' ');
236 }
237 self_mut.extend_from_slice(param.parameter);
238 Action::Added
239 }
240
241 pub fn add_or_modify(&mut self, param: &Parameter) -> Action {
254 let mut new_params = Vec::new();
255 let mut modified = false;
256 let mut seen_key = false;
257
258 for p in self.iter() {
259 if p.key == param.key {
260 if !seen_key {
261 if p != *param {
264 modified = true;
265 }
266 new_params.push(param.parameter);
267 } else {
268 modified = true;
271 }
272 seen_key = true;
273 } else {
274 new_params.push(p.parameter);
275 }
276 }
277
278 if !seen_key {
279 let self_mut = self.0.to_mut();
281 if self_mut
282 .last()
283 .filter(|v| !v.is_ascii_whitespace())
284 .is_some()
285 {
286 self_mut.push(b' ');
287 }
288 self_mut.extend_from_slice(param.parameter);
289 return Action::Added;
290 }
291 if modified {
292 self.0 = Cow::Owned(new_params.join(b" ".as_slice()));
293 Action::Modified
294 } else {
295 Action::Existed
297 }
298 }
299
300 pub fn remove(&mut self, key: &ParameterKey) -> bool {
304 let mut removed = false;
305 let mut new_params = Vec::new();
306
307 for p in self.iter() {
308 if p.key == *key {
309 removed = true;
310 } else {
311 new_params.push(p.parameter);
312 }
313 }
314
315 if removed {
316 self.0 = Cow::Owned(new_params.join(b" ".as_slice()));
317 }
318
319 removed
320 }
321
322 pub fn remove_exact(&mut self, param: &Parameter) -> bool {
327 let mut removed = false;
328 let mut new_params = Vec::new();
329
330 for p in self.iter() {
331 if p == *param {
332 removed = true;
333 } else {
334 new_params.push(p.parameter);
335 }
336 }
337
338 if removed {
339 self.0 = Cow::Owned(new_params.join(b" ".as_slice()));
340 }
341
342 removed
343 }
344
345 #[cfg(test)]
346 pub(crate) fn is_owned(&self) -> bool {
347 matches!(self.0, Cow::Owned(_))
348 }
349
350 #[cfg(test)]
351 pub(crate) fn is_borrowed(&self) -> bool {
352 matches!(self.0, Cow::Borrowed(_))
353 }
354}
355
356impl<'a> IntoIterator for &'a Cmdline<'a> {
357 type Item = Parameter<'a>;
358 type IntoIter = CmdlineIter<'a>;
359
360 fn into_iter(self) -> Self::IntoIter {
361 self.iter()
362 }
363}
364
365impl<'a, 'other> Extend<Parameter<'other>> for Cmdline<'a> {
366 fn extend<T: IntoIterator<Item = Parameter<'other>>>(&mut self, iter: T) {
367 for param in iter {
375 self.add(¶m);
376 }
377 }
378}
379
380impl PartialEq for Cmdline<'_> {
381 fn eq(&self, other: &Self) -> bool {
382 let mut our_params = self.iter().collect::<Vec<_>>();
383 our_params.sort();
384 let mut their_params = other.iter().collect::<Vec<_>>();
385 their_params.sort();
386
387 our_params == their_params
388 }
389}
390
391impl Eq for Cmdline<'_> {}
392
393#[derive(Clone, Debug)]
397pub struct ParameterKey<'a>(pub(crate) &'a [u8]);
398
399impl Deref for ParameterKey<'_> {
400 type Target = [u8];
401
402 fn deref(&self) -> &Self::Target {
403 self.0
404 }
405}
406
407impl<'a, T> AsRef<T> for ParameterKey<'a>
408where
409 T: ?Sized,
410 <ParameterKey<'a> as Deref>::Target: AsRef<T>,
411{
412 fn as_ref(&self) -> &T {
413 self.deref().as_ref()
414 }
415}
416
417impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for ParameterKey<'a> {
418 fn from(s: &'a T) -> Self {
419 Self(s.as_ref())
420 }
421}
422
423impl ParameterKey<'_> {
424 fn iter(&self) -> impl Iterator<Item = u8> + use<'_> {
427 self.0
428 .iter()
429 .map(|&c: &u8| if c == b'-' { b'_' } else { c })
430 }
431}
432
433impl PartialEq for ParameterKey<'_> {
434 fn eq(&self, other: &Self) -> bool {
439 self.iter().eq(other.iter())
440 }
441}
442
443impl Eq for ParameterKey<'_> {}
444
445impl Ord for ParameterKey<'_> {
446 fn cmp(&self, other: &Self) -> Ordering {
447 self.iter().cmp(other.iter())
448 }
449}
450
451impl PartialOrd for ParameterKey<'_> {
452 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
453 Some(self.cmp(other))
454 }
455}
456
457#[derive(Clone, Debug)]
459pub struct Parameter<'a> {
460 parameter: &'a [u8],
462 key: ParameterKey<'a>,
464 value: Option<&'a [u8]>,
466}
467
468impl<'a> Parameter<'a> {
469 pub fn parse<T: AsRef<[u8]> + ?Sized>(input: &'a T) -> Option<Self> {
479 CmdlineIterBytes(input.as_ref())
480 .next()
481 .and_then(Self::parse_internal)
482 }
483
484 fn parse_internal(input: &'a [u8]) -> Option<Self> {
489 let dequoted_input = input.strip_prefix(b"\"").unwrap_or(input);
491 let dequoted_input = dequoted_input.strip_suffix(b"\"").unwrap_or(dequoted_input);
492
493 let equals = dequoted_input.iter().position(|b| *b == b'=');
494
495 match equals {
496 None => Some(Self {
497 parameter: input,
498 key: ParameterKey(dequoted_input),
499 value: None,
500 }),
501 Some(i) => {
502 let (key, mut value) = dequoted_input.split_at(i);
503 let key = ParameterKey(key);
504
505 value = &value[1..];
508
509 value = value.strip_prefix(b"\"").unwrap_or(value);
514
515 Some(Self {
516 parameter: input,
517 key,
518 value: Some(value),
519 })
520 }
521 }
522 }
523
524 pub fn key(&self) -> ParameterKey<'a> {
526 self.key.clone()
527 }
528
529 pub fn value(&self) -> Option<&'a [u8]> {
531 self.value
532 }
533}
534
535impl PartialEq for Parameter<'_> {
536 fn eq(&self, other: &Self) -> bool {
537 self.key == other.key && self.value == other.value
539 }
540}
541
542impl Eq for Parameter<'_> {}
543
544impl Ord for Parameter<'_> {
545 fn cmp(&self, other: &Self) -> Ordering {
546 self.key.cmp(&other.key).then(self.value.cmp(&other.value))
547 }
548}
549
550impl PartialOrd for Parameter<'_> {
551 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
552 Some(self.cmp(other))
553 }
554}
555
556impl Deref for Parameter<'_> {
557 type Target = [u8];
558
559 fn deref(&self) -> &Self::Target {
560 self.parameter
561 }
562}
563
564impl<'a, T> AsRef<T> for Parameter<'a>
565where
566 T: ?Sized,
567 <Parameter<'a> as Deref>::Target: AsRef<T>,
568{
569 fn as_ref(&self) -> &T {
570 self.deref().as_ref()
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577
578 fn param(s: &str) -> Parameter<'_> {
580 Parameter::parse(s.as_bytes()).unwrap()
581 }
582
583 fn param_utf8(s: &str) -> utf8::Parameter<'_> {
584 utf8::Parameter::parse(s).unwrap()
585 }
586
587 #[test]
588 fn test_parameter_parse() {
589 let p = Parameter::parse(b"foo").unwrap();
590 assert_eq!(p.key.0, b"foo");
591 assert_eq!(p.value, None);
592
593 let p = Parameter::parse(b"foo=bar baz").unwrap();
595 assert_eq!(p.key.0, b"foo");
596 assert_eq!(p.value, Some(b"bar".as_slice()));
597
598 assert!(Parameter::parse(b"").is_none());
600 assert!(Parameter::parse(b" ").is_none());
601 }
602
603 #[test]
604 fn test_parameter_simple() {
605 let switch = param("foo");
606 assert_eq!(switch.key.0, b"foo");
607 assert_eq!(switch.value, None);
608
609 let kv = param("bar=baz");
610 assert_eq!(kv.key.0, b"bar");
611 assert_eq!(kv.value, Some(b"baz".as_slice()));
612 }
613
614 #[test]
615 fn test_parameter_quoted() {
616 let p = param("foo=\"quoted value\"");
617 assert_eq!(p.value, Some(b"quoted value".as_slice()));
618
619 let p = param("foo=\"unclosed quotes");
620 assert_eq!(p.value, Some(b"unclosed quotes".as_slice()));
621
622 let p = param("foo=trailing_quotes\"");
623 assert_eq!(p.value, Some(b"trailing_quotes".as_slice()));
624
625 let outside_quoted = param("\"foo=quoted value\"");
626 let value_quoted = param("foo=\"quoted value\"");
627 assert_eq!(outside_quoted, value_quoted);
628 }
629
630 #[test]
631 fn test_parameter_extra_whitespace() {
632 let p = param(" foo=bar ");
633 assert_eq!(p.key.0, b"foo");
634 assert_eq!(p.value, Some(b"bar".as_slice()));
635 }
636
637 #[test]
638 fn test_parameter_internal_key_whitespace() {
639 let p = Parameter::parse("foo bar=baz".as_bytes()).unwrap();
641 assert_eq!(p.key.0, b"foo");
642 assert_eq!(p.value, None);
643 }
644
645 #[test]
646 fn test_parameter_pathological() {
647 let p = param("\"foo\"=bar");
653 assert_eq!(p.key.0, b"foo\"");
654 assert_eq!(p.value, Some(b"bar".as_slice()));
655 assert_ne!(p, param("foo=bar"));
657
658 let p = param("\"foo=\"bar");
661 assert_eq!(p.key.0, b"foo");
662 assert_eq!(p.value, Some(b"bar".as_slice()));
663 assert_eq!(p, param("foo=bar"));
665
666 let p = param("foo=\"internal\"quotes\"are\"ok\"");
668 assert_eq!(p.value, Some(b"internal\"quotes\"are\"ok".as_slice()));
669
670 let non_utf8_byte = b"\xff";
672 #[allow(invalid_from_utf8)]
673 let failed_conversion = str::from_utf8(non_utf8_byte);
674 assert!(failed_conversion.is_err());
675 let mut p = b"foo=".to_vec();
676 p.push(non_utf8_byte[0]);
677 let p = Parameter::parse(&p).unwrap();
678 assert_eq!(p.value, Some(non_utf8_byte.as_slice()));
679 }
680
681 #[test]
682 fn test_parameter_equality() {
683 let foo = param("foo");
685 let bar = param("foobar");
686 assert_ne!(foo, bar);
687 assert_ne!(bar, foo);
688
689 let dashes = param("a-delimited-param");
691 let underscores = param("a_delimited_param");
692 assert_eq!(dashes, underscores);
693
694 let dashes = param("a-delimited-param=same_values");
696 let underscores = param("a_delimited_param=same_values");
697 assert_eq!(dashes, underscores);
698
699 let dashes = param("a-delimited-param=different_values");
701 let underscores = param("a_delimited_param=DiFfErEnT_valUEZ");
702 assert_ne!(dashes, underscores);
703
704 let switch = param("same_key");
706 let keyvalue = param("same_key=but_with_a_value");
707 assert_ne!(switch, keyvalue);
708 }
709
710 #[test]
711 fn test_kargs_simple() {
712 let kargs = Cmdline::from(b"foo=bar,bar2 baz=fuz wiz".as_slice());
715 let mut iter = kargs.iter();
716
717 assert_eq!(iter.next(), Some(param("foo=bar,bar2")));
718 assert_eq!(iter.next(), Some(param("baz=fuz")));
719 assert_eq!(iter.next(), Some(param("wiz")));
720 assert_eq!(iter.next(), None);
721
722 assert_eq!(kargs.find("foo").unwrap().value.unwrap(), b"bar,bar2");
724 assert!(kargs.find("nothing").is_none());
725 }
726
727 #[test]
728 fn test_cmdline_default() {
729 let kargs: Cmdline = Default::default();
730 assert_eq!(kargs.iter().next(), None);
731 }
732
733 #[test]
734 fn test_cmdline_new() {
735 let kargs = Cmdline::new();
736 assert_eq!(kargs.iter().next(), None);
737 assert!(kargs.is_owned());
738
739 let _static_kargs: CmdlineOwned = Cmdline::new();
741 }
742
743 #[test]
744 fn test_kargs_iter_utf8() {
745 let kargs = Cmdline::from(b"foo=bar,bar2 \xff baz=fuz bad=oh\xffno wiz");
746 let mut iter = kargs.iter_utf8();
747
748 assert_eq!(iter.next(), Some(param_utf8("foo=bar,bar2")));
749 assert_eq!(iter.next(), Some(param_utf8("baz=fuz")));
750 assert_eq!(iter.next(), Some(param_utf8("wiz")));
751 assert_eq!(iter.next(), None);
752 }
753
754 #[test]
755 fn test_kargs_find_utf8() {
756 let kargs = Cmdline::from(b"foo=bar,bar2 \xff baz=fuz bad=oh\xffno wiz");
757
758 assert_eq!(
760 kargs.find_utf8("foo").unwrap().unwrap().value().unwrap(),
761 "bar,bar2"
762 );
763
764 assert!(kargs.find_utf8("nothing").unwrap().is_none());
766
767 let p = kargs.find_utf8("bad");
769 assert_eq!(
770 p.unwrap_err().to_string(),
771 "Parameter value is not valid UTF-8"
772 );
773 }
774
775 #[test]
776 fn test_kargs_from_proc() {
777 let kargs = Cmdline::from_proc().unwrap();
778
779 assert!(kargs.iter().count() > 0);
783 }
784
785 #[test]
786 fn test_kargs_find_dash_hyphen() {
787 let kargs = Cmdline::from(b"a-b=1 a_b=2".as_slice());
788 let p = kargs.find("a_b").unwrap();
790 assert_eq!(p.key.0, b"a-b");
791 assert_eq!(p.value.unwrap(), b"1");
792 let p = kargs.find("a-b").unwrap();
793 assert_eq!(p.key.0, b"a-b");
794 assert_eq!(p.value.unwrap(), b"1");
795
796 let kargs = Cmdline::from(b"a_b=2 a-b=1".as_slice());
797 let p = kargs.find("a_b").unwrap();
799 assert_eq!(p.key.0, b"a_b");
800 assert_eq!(p.value.unwrap(), b"2");
801 let p = kargs.find("a-b").unwrap();
802 assert_eq!(p.key.0, b"a_b");
803 assert_eq!(p.value.unwrap(), b"2");
804 }
805
806 #[test]
807 fn test_kargs_extra_whitespace() {
808 let kargs = Cmdline::from(b" foo=bar baz=fuz wiz ".as_slice());
809 let mut iter = kargs.iter();
810
811 assert_eq!(iter.next(), Some(param("foo=bar")));
812 assert_eq!(iter.next(), Some(param("baz=fuz")));
813 assert_eq!(iter.next(), Some(param("wiz")));
814 assert_eq!(iter.next(), None);
815 }
816
817 #[test]
818 fn test_value_of() {
819 let kargs = Cmdline::from(b"foo=bar baz=qux switch".as_slice());
820
821 assert_eq!(kargs.value_of("foo"), Some(b"bar".as_slice()));
823 assert_eq!(kargs.value_of("baz"), Some(b"qux".as_slice()));
824
825 assert_eq!(kargs.value_of("switch"), None);
827
828 assert_eq!(kargs.value_of("missing"), None);
830
831 let kargs = Cmdline::from(b"dash-key=value1 under_key=value2".as_slice());
833 assert_eq!(kargs.value_of("dash_key"), Some(b"value1".as_slice()));
834 assert_eq!(kargs.value_of("under-key"), Some(b"value2".as_slice()));
835 }
836
837 #[test]
838 fn test_require_value_of() {
839 let kargs = Cmdline::from(b"foo=bar baz=qux switch".as_slice());
840
841 assert_eq!(kargs.require_value_of("foo").unwrap(), b"bar");
843 assert_eq!(kargs.require_value_of("baz").unwrap(), b"qux");
844
845 let err = kargs.require_value_of("switch").unwrap_err();
847 assert!(
848 err.to_string()
849 .contains("Failed to find kernel argument 'switch'")
850 );
851
852 let err = kargs.require_value_of("missing").unwrap_err();
854 assert!(
855 err.to_string()
856 .contains("Failed to find kernel argument 'missing'")
857 );
858
859 let kargs = Cmdline::from(b"dash-key=value1 under_key=value2".as_slice());
861 assert_eq!(kargs.require_value_of("dash_key").unwrap(), b"value1");
862 assert_eq!(kargs.require_value_of("under-key").unwrap(), b"value2");
863 }
864
865 #[test]
866 fn test_find_all() {
867 let kargs =
868 Cmdline::from(b"foo=bar rd.foo=a rd.bar=b rd.baz rd.qux=c notrd.val=d".as_slice());
869 let mut rd_args: Vec<_> = kargs.find_all_starting_with(b"rd.".as_slice()).collect();
870 rd_args.sort_by(|a, b| a.key.0.cmp(b.key.0));
871 assert_eq!(rd_args.len(), 4);
872 assert_eq!(rd_args[0], param("rd.bar=b"));
873 assert_eq!(rd_args[1], param("rd.baz"));
874 assert_eq!(rd_args[2], param("rd.foo=a"));
875 assert_eq!(rd_args[3], param("rd.qux=c"));
876 }
877
878 #[test]
879 fn test_add() {
880 let mut kargs = Cmdline::from(b"console=tty0 console=ttyS1");
881
882 assert!(matches!(kargs.add(¶m("console=ttyS2")), Action::Added));
884 let mut iter = kargs.iter();
885 assert_eq!(iter.next(), Some(param("console=tty0")));
886 assert_eq!(iter.next(), Some(param("console=ttyS1")));
887 assert_eq!(iter.next(), Some(param("console=ttyS2")));
888 assert_eq!(iter.next(), None);
889
890 assert!(matches!(
892 kargs.add(¶m("console=ttyS1")),
893 Action::Existed
894 ));
895 iter = kargs.iter();
896 assert_eq!(iter.next(), Some(param("console=tty0")));
897 assert_eq!(iter.next(), Some(param("console=ttyS1")));
898 assert_eq!(iter.next(), Some(param("console=ttyS2")));
899 assert_eq!(iter.next(), None);
900
901 assert!(matches!(kargs.add(¶m("quiet")), Action::Added));
903 iter = kargs.iter();
904 assert_eq!(iter.next(), Some(param("console=tty0")));
905 assert_eq!(iter.next(), Some(param("console=ttyS1")));
906 assert_eq!(iter.next(), Some(param("console=ttyS2")));
907 assert_eq!(iter.next(), Some(param("quiet")));
908 assert_eq!(iter.next(), None);
909 }
910
911 #[test]
912 fn test_add_empty_cmdline() {
913 let mut kargs = Cmdline::from(b"");
914 assert!(matches!(kargs.add(¶m("foo")), Action::Added));
915 assert_eq!(kargs.0, b"foo".as_slice());
916 }
917
918 #[test]
919 fn test_add_or_modify() {
920 let mut kargs = Cmdline::from(b"foo=bar");
921
922 assert!(matches!(kargs.add_or_modify(¶m("baz")), Action::Added));
924 let mut iter = kargs.iter();
925 assert_eq!(iter.next(), Some(param("foo=bar")));
926 assert_eq!(iter.next(), Some(param("baz")));
927 assert_eq!(iter.next(), None);
928
929 assert!(matches!(
931 kargs.add_or_modify(¶m("foo=fuz")),
932 Action::Modified
933 ));
934 iter = kargs.iter();
935 assert_eq!(iter.next(), Some(param("foo=fuz")));
936 assert_eq!(iter.next(), Some(param("baz")));
937 assert_eq!(iter.next(), None);
938
939 assert!(matches!(
942 kargs.add_or_modify(¶m("foo=fuz")),
943 Action::Existed
944 ));
945 iter = kargs.iter();
946 assert_eq!(iter.next(), Some(param("foo=fuz")));
947 assert_eq!(iter.next(), Some(param("baz")));
948 assert_eq!(iter.next(), None);
949 }
950
951 #[test]
952 fn test_add_or_modify_empty_cmdline() {
953 let mut kargs = Cmdline::from(b"");
954 assert!(matches!(kargs.add_or_modify(¶m("foo")), Action::Added));
955 assert_eq!(kargs.0, b"foo".as_slice());
956 }
957
958 #[test]
959 fn test_add_or_modify_duplicate_parameters() {
960 let mut kargs = Cmdline::from(b"a=1 a=2");
961 assert!(matches!(
962 kargs.add_or_modify(¶m("a=3")),
963 Action::Modified
964 ));
965 let mut iter = kargs.iter();
966 assert_eq!(iter.next(), Some(param("a=3")));
967 assert_eq!(iter.next(), None);
968 }
969
970 #[test]
971 fn test_remove() {
972 let mut kargs = Cmdline::from(b"foo bar baz");
973
974 assert!(kargs.remove(&"bar".into()));
976 let mut iter = kargs.iter();
977 assert_eq!(iter.next(), Some(param("foo")));
978 assert_eq!(iter.next(), Some(param("baz")));
979 assert_eq!(iter.next(), None);
980
981 assert!(!kargs.remove(&"missing".into()));
983 iter = kargs.iter();
984 assert_eq!(iter.next(), Some(param("foo")));
985 assert_eq!(iter.next(), Some(param("baz")));
986 assert_eq!(iter.next(), None);
987 }
988
989 #[test]
990 fn test_remove_duplicates() {
991 let mut kargs = Cmdline::from(b"a=1 b=2 a=3");
992 assert!(kargs.remove(&"a".into()));
993 let mut iter = kargs.iter();
994 assert_eq!(iter.next(), Some(param("b=2")));
995 assert_eq!(iter.next(), None);
996 }
997
998 #[test]
999 fn test_remove_exact() {
1000 let mut kargs = Cmdline::from(b"foo foo=bar foo=baz");
1001
1002 assert!(kargs.remove_exact(¶m("foo=bar")));
1004 let mut iter = kargs.iter();
1005 assert_eq!(iter.next(), Some(param("foo")));
1006 assert_eq!(iter.next(), Some(param("foo=baz")));
1007 assert_eq!(iter.next(), None);
1008
1009 assert!(!kargs.remove_exact(¶m("foo=wuz")));
1011 iter = kargs.iter();
1012 assert_eq!(iter.next(), Some(param("foo")));
1013 assert_eq!(iter.next(), Some(param("foo=baz")));
1014 assert_eq!(iter.next(), None);
1015 }
1016
1017 #[test]
1018 fn test_extend() {
1019 let mut kargs = Cmdline::from(b"foo=bar baz");
1020 let other = Cmdline::from(b"qux=quux foo=updated");
1021
1022 kargs.extend(&other);
1023
1024 drop(other);
1027
1028 let mut iter = kargs.iter();
1031 assert_eq!(iter.next(), Some(param("foo=bar")));
1032 assert_eq!(iter.next(), Some(param("baz")));
1033 assert_eq!(iter.next(), Some(param("qux=quux")));
1034 assert_eq!(iter.next(), Some(param("foo=updated")));
1035 assert_eq!(iter.next(), None);
1036 }
1037
1038 #[test]
1039 fn test_extend_empty() {
1040 let mut kargs = Cmdline::from(b"");
1041 let other = Cmdline::from(b"foo=bar baz");
1042
1043 kargs.extend(&other);
1044
1045 let mut iter = kargs.iter();
1046 assert_eq!(iter.next(), Some(param("foo=bar")));
1047 assert_eq!(iter.next(), Some(param("baz")));
1048 assert_eq!(iter.next(), None);
1049 }
1050
1051 #[test]
1052 fn test_into_iterator() {
1053 let kargs = Cmdline::from(b"foo=bar baz=qux wiz");
1054 let params: Vec<_> = (&kargs).into_iter().collect();
1055
1056 assert_eq!(params.len(), 3);
1057 assert_eq!(params[0], param("foo=bar"));
1058 assert_eq!(params[1], param("baz=qux"));
1059 assert_eq!(params[2], param("wiz"));
1060 }
1061
1062 #[test]
1063 fn test_iter_bytes_simple() {
1064 let kargs = Cmdline::from(b"foo bar baz");
1065 let params: Vec<_> = kargs.iter_bytes().collect();
1066
1067 assert_eq!(params.len(), 3);
1068 assert_eq!(params[0], b"foo");
1069 assert_eq!(params[1], b"bar");
1070 assert_eq!(params[2], b"baz");
1071 }
1072
1073 #[test]
1074 fn test_iter_bytes_with_values() {
1075 let kargs = Cmdline::from(b"foo=bar baz=qux wiz");
1076 let params: Vec<_> = kargs.iter_bytes().collect();
1077
1078 assert_eq!(params.len(), 3);
1079 assert_eq!(params[0], b"foo=bar");
1080 assert_eq!(params[1], b"baz=qux");
1081 assert_eq!(params[2], b"wiz");
1082 }
1083
1084 #[test]
1085 fn test_iter_bytes_with_quotes() {
1086 let kargs = Cmdline::from(b"foo=\"bar baz\" qux");
1087 let params: Vec<_> = kargs.iter_bytes().collect();
1088
1089 assert_eq!(params.len(), 2);
1090 assert_eq!(params[0], b"foo=\"bar baz\"");
1091 assert_eq!(params[1], b"qux");
1092 }
1093
1094 #[test]
1095 fn test_iter_bytes_extra_whitespace() {
1096 let kargs = Cmdline::from(b" foo bar ");
1097 let params: Vec<_> = kargs.iter_bytes().collect();
1098
1099 assert_eq!(params.len(), 2);
1100 assert_eq!(params[0], b"foo");
1101 assert_eq!(params[1], b"bar");
1102 }
1103
1104 #[test]
1105 fn test_iter_bytes_empty() {
1106 let kargs = Cmdline::from(b"");
1107 let params: Vec<_> = kargs.iter_bytes().collect();
1108
1109 assert_eq!(params.len(), 0);
1110 }
1111
1112 #[test]
1113 fn test_cmdline_eq() {
1114 assert_eq!(
1118 Cmdline::from("foo bar-with-delim=\"with spaces\""),
1119 Cmdline::from("\"bar_with_delim=with spaces\" foo")
1120 );
1121
1122 assert_ne!(Cmdline::from("foo"), Cmdline::from("foo foo"));
1126 assert_ne!(Cmdline::from("foo foo"), Cmdline::from("foo"));
1127
1128 assert_ne!(Cmdline::from("a a b"), Cmdline::from("a b b"));
1130 }
1131}