1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-06-02 08:22:01 +00:00
hls_m3u8/src/attribute.rs
2020-02-06 17:02:44 +01:00

145 lines
4.7 KiB
Rust

use core::iter::FusedIterator;
#[derive(Clone, Debug)]
pub(crate) struct AttributePairs<'a> {
string: &'a str,
index: usize,
}
impl<'a> AttributePairs<'a> {
pub const fn new(string: &'a str) -> Self { Self { string, index: 0 } }
}
impl<'a> Iterator for AttributePairs<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
// return `None`, if there are no more chars
self.string.as_bytes().get(self.index + 1)?;
let key = {
// the position in the string:
let start = self.index;
// the key ends at an `=`:
let end = self
.string
.bytes()
.skip(self.index)
.position(|i| i == b'=')?
+ start;
// advance the index to the 2nd char after the end of the key
// (this will skip the `=`)
self.index = end + 1;
core::str::from_utf8(&self.string.as_bytes()[start..end]).unwrap()
};
let value = {
let start = self.index;
let mut end = 0;
// find the end of the value by searching for `,`.
// it should ignore `,` that are inside double quotes.
let mut inside_quotes = false;
while let Some(item) = self.string.as_bytes().get(start + end) {
end += 1;
if *item == b'"' {
inside_quotes = !inside_quotes;
} else if *item == b',' && !inside_quotes {
self.index += 1;
end -= 1;
break;
}
}
self.index += end;
end += start;
core::str::from_utf8(&self.string.as_bytes()[start..end]).unwrap()
};
Some((key.trim(), value.trim()))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let mut remaining = 0;
// each `=` in the remaining str is an iteration
// this also ignores `=` inside quotes!
let mut inside_quotes = false;
for c in self.string.as_bytes().iter().skip(self.index) {
if *c == b'=' && !inside_quotes {
remaining += 1;
} else if *c == b'"' {
inside_quotes = !inside_quotes;
}
}
(remaining, Some(remaining))
}
}
impl<'a> ExactSizeIterator for AttributePairs<'a> {}
impl<'a> FusedIterator for AttributePairs<'a> {}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_attributes() {
let mut attributes = AttributePairs::new("KEY=VALUE,PAIR=YES");
assert_eq!((2, Some(2)), attributes.size_hint());
assert_eq!(Some(("KEY", "VALUE")), attributes.next());
assert_eq!((1, Some(1)), attributes.size_hint());
assert_eq!(Some(("PAIR", "YES")), attributes.next());
assert_eq!((0, Some(0)), attributes.size_hint());
assert_eq!(None, attributes.next());
let mut attributes = AttributePairs::new("garbage");
assert_eq!((0, Some(0)), attributes.size_hint());
assert_eq!(None, attributes.next());
let mut attributes = AttributePairs::new("KEY=,=VALUE,=,");
assert_eq!((3, Some(3)), attributes.size_hint());
assert_eq!(Some(("KEY", "")), attributes.next());
assert_eq!((2, Some(2)), attributes.size_hint());
assert_eq!(Some(("", "VALUE")), attributes.next());
assert_eq!((1, Some(1)), attributes.size_hint());
assert_eq!(Some(("", "")), attributes.next());
assert_eq!((0, Some(0)), attributes.size_hint());
assert_eq!(None, attributes.next());
// test quotes:
let mut attributes = AttributePairs::new("KEY=\"VALUE,\",");
assert_eq!((1, Some(1)), attributes.size_hint());
assert_eq!(Some(("KEY", "\"VALUE,\"")), attributes.next());
assert_eq!((0, Some(0)), attributes.size_hint());
assert_eq!(None, attributes.next());
// test with chars, that are larger, than 1 byte
let mut attributes = AttributePairs::new(
"LANGUAGE=\"fre\",\
NAME=\"Français\",\
AUTOSELECT=YES",
);
assert_eq!(Some(("LANGUAGE", "\"fre\"")), attributes.next());
assert_eq!(Some(("NAME", "\"Français\"")), attributes.next());
assert_eq!(Some(("AUTOSELECT", "YES")), attributes.next());
}
#[test]
fn test_parser() {
let mut pairs = AttributePairs::new("FOO=BAR,BAR=\"baz,qux\",ABC=12.3");
assert_eq!(pairs.next(), Some(("FOO", "BAR")));
assert_eq!(pairs.next(), Some(("BAR", "\"baz,qux\"")));
assert_eq!(pairs.next(), Some(("ABC", "12.3")));
assert_eq!(pairs.next(), None);
}
}