1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-06-09 16:59:34 +00:00

refactor attribute parsing to comply with #26

This commit is contained in:
Luro02 2020-01-26 13:11:57 +01:00
parent e156f6e3fd
commit a777f74cfa
No known key found for this signature in database
GPG key ID: B66FD4F74501A9CF
10 changed files with 140 additions and 136 deletions

View file

@ -1,148 +1,144 @@
use std::collections::HashMap; use core::iter::FusedIterator;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use crate::Error;
#[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AttributePairs(HashMap<String, String>); pub(crate) struct AttributePairs<'a> {
string: &'a str,
impl AttributePairs { index: usize,
pub fn new() -> Self { Self::default() }
} }
impl Deref for AttributePairs { impl<'a> AttributePairs<'a> {
type Target = HashMap<String, String>; pub const fn new(string: &'a str) -> Self { Self { string, index: 0 } }
fn deref(&self) -> &Self::Target { &self.0 }
} }
impl DerefMut for AttributePairs { impl<'a> Iterator for AttributePairs<'a> {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } type Item = (&'a str, &'a str);
}
impl IntoIterator for AttributePairs { fn next(&mut self) -> Option<Self::Item> {
type IntoIter = ::std::collections::hash_map::IntoIter<String, String>; // return `None`, if there are no more chars
type Item = (String, String); self.string.as_bytes().get(self.index + 1)?;
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } 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;
impl<'a> IntoIterator for &'a AttributePairs { // advance the index to the 2nd char after the end of the key
type IntoIter = ::std::collections::hash_map::Iter<'a, String, String>; // (this will skip the `=`)
type Item = (&'a String, &'a String); self.index = end + 1;
fn into_iter(self) -> Self::IntoIter { self.0.iter() } core::str::from_utf8(&self.string.as_bytes()[start..end]).unwrap()
} };
impl FromStr for AttributePairs { let value = {
type Err = Error; let start = self.index;
let mut end = 0;
fn from_str(input: &str) -> Result<Self, Self::Err> { // find the end of the value by searching for `,`.
let mut result = Self::new(); // 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;
for line in split(input, ',') { if *item == b'"' {
let pair = split(line.trim(), '='); inside_quotes = !inside_quotes;
} else if *item == b',' && !inside_quotes {
if pair.len() < 2 { self.index += 1;
continue; end -= 1;
} break;
let key = pair[0].trim().to_uppercase();
let value = pair[1].trim().to_string();
if value.is_empty() {
continue;
}
result.insert(key.trim().to_string(), value.trim().to_string());
}
#[cfg(test)] // this is very useful, when a test fails!
dbg!(&result);
Ok(result)
}
}
fn split(value: &str, terminator: char) -> Vec<String> {
let mut result = vec![];
let mut inside_quotes = false;
let mut temp_string = String::with_capacity(1024);
for c in value.chars() {
match c {
'"' => {
inside_quotes = !inside_quotes;
temp_string.push(c);
}
k if (k == terminator) => {
if inside_quotes {
temp_string.push(c);
} else {
result.push(temp_string);
temp_string = String::with_capacity(1024);
} }
} }
_ => {
temp_string.push(c); 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;
} }
} }
}
result.push(temp_string);
result (remaining, Some(remaining))
}
} }
impl<'a> ExactSizeIterator for AttributePairs<'a> {}
impl<'a> FusedIterator for AttributePairs<'a> {}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
fn test_parser() { fn test_attributes() {
let pairs = "FOO=BAR,BAR=\"baz,qux\",ABC=12.3" let mut attributes = AttributePairs::new("KEY=VALUE,PAIR=YES");
.parse::<AttributePairs>() assert_eq!((2, Some(2)), attributes.size_hint());
.unwrap(); 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 iterator = pairs.iter(); let mut attributes = AttributePairs::new("garbage");
assert!(iterator.any(|(k, v)| k == "FOO" && "BAR" == v)); assert_eq!((0, Some(0)), attributes.size_hint());
assert_eq!(None, attributes.next());
let mut iterator = pairs.iter(); let mut attributes = AttributePairs::new("KEY=,=VALUE,=,");
assert!(iterator.any(|(k, v)| k == "BAR" && v == "\"baz,qux\"")); 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());
let mut iterator = pairs.iter(); // test quotes:
assert!(iterator.any(|(k, v)| k == "ABC" && v == "12.3")); 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());
let mut pairs = AttributePairs::new(); // test with chars, that are larger, than 1 byte
pairs.insert("FOO".to_string(), "BAR".to_string()); let mut attributes = AttributePairs::new(
"LANGUAGE=\"fre\",\
assert_eq!("FOO=BAR,VAL".parse::<AttributePairs>().unwrap(), pairs); NAME=\"Français\",\
} AUTOSELECT=YES",
#[test]
fn test_iterator() {
let mut attrs = AttributePairs::new();
attrs.insert("key_01".to_string(), "value_01".to_string());
attrs.insert("key_02".to_string(), "value_02".to_string());
let mut iterator = attrs.iter();
assert!(iterator.any(|(k, v)| k == "key_01" && v == "value_01"));
let mut iterator = attrs.iter();
assert!(iterator.any(|(k, v)| k == "key_02" && v == "value_02"));
}
#[test]
fn test_into_iter() {
let mut map = HashMap::new();
map.insert("k".to_string(), "v".to_string());
let mut attrs = AttributePairs::new();
attrs.insert("k".to_string(), "v".to_string());
assert_eq!(
attrs.into_iter().collect::<Vec<_>>(),
map.into_iter().collect::<Vec<_>>()
); );
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);
} }
} }

View file

@ -162,8 +162,8 @@ impl FromStr for ExtXIFrameStreamInf {
let mut uri = None; let mut uri = None;
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in AttributePairs::new(input) {
if let "URI" = key.as_str() { if key == "URI" {
uri = Some(unquote(value)); uri = Some(unquote(value));
} }
} }

View file

@ -733,8 +733,8 @@ impl FromStr for ExtXMedia {
let mut builder = Self::builder(); let mut builder = Self::builder();
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in AttributePairs::new(input) {
match key.as_str() { match key {
"TYPE" => { "TYPE" => {
builder.media_type(value.parse::<MediaType>()?); builder.media_type(value.parse::<MediaType>()?);
} }
@ -832,12 +832,12 @@ mod test {
{ {
ExtXMedia::builder() ExtXMedia::builder()
.media_type(MediaType::Audio) .media_type(MediaType::Audio)
.uri("fre/prog_index.m3u8")
.group_id("audio") .group_id("audio")
.language("fre") .language("fre")
.name("Français") .name("Français")
.is_autoselect(true)
.is_default(false) .is_default(false)
.uri("fre/prog_index.m3u8") .is_autoselect(true)
.build() .build()
.unwrap(), .unwrap(),
"#EXT-X-MEDIA:\ "#EXT-X-MEDIA:\

View file

@ -275,8 +275,8 @@ impl FromStr for ExtXSessionData {
let mut uri = None; let mut uri = None;
let mut language = None; let mut language = None;
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in AttributePairs::new(input) {
match key.as_str() { match key {
"DATA-ID" => data_id = Some(unquote(value)), "DATA-ID" => data_id = Some(unquote(value)),
"VALUE" => session_value = Some(unquote(value)), "VALUE" => session_value = Some(unquote(value)),
"URI" => uri = Some(unquote(value)), "URI" => uri = Some(unquote(value)),

View file

@ -342,8 +342,8 @@ impl FromStr for ExtXStreamInf {
let mut subtitles = None; let mut subtitles = None;
let mut closed_captions = None; let mut closed_captions = None;
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in AttributePairs::new(input) {
match key.as_str() { match key {
"FRAME-RATE" => frame_rate = Some((value.parse())?), "FRAME-RATE" => frame_rate = Some((value.parse())?),
"AUDIO" => audio = Some(unquote(value)), "AUDIO" => audio = Some(unquote(value)),
"SUBTITLES" => subtitles = Some(unquote(value)), "SUBTITLES" => subtitles = Some(unquote(value)),

View file

@ -714,8 +714,8 @@ impl FromStr for ExtXDateRange {
let mut client_attributes = BTreeMap::new(); let mut client_attributes = BTreeMap::new();
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in AttributePairs::new(input) {
match key.as_str() { match key {
"ID" => id = Some(unquote(value)), "ID" => id = Some(unquote(value)),
"CLASS" => class = Some(unquote(value)), "CLASS" => class = Some(unquote(value)),
"START-DATE" => start_date = Some(unquote(value)), "START-DATE" => start_date = Some(unquote(value)),

View file

@ -168,8 +168,8 @@ impl FromStr for ExtXMap {
let mut uri = None; let mut uri = None;
let mut range = None; let mut range = None;
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in AttributePairs::new(input) {
match key.as_str() { match key {
"URI" => uri = Some(unquote(value)), "URI" => uri = Some(unquote(value)),
"BYTERANGE" => { "BYTERANGE" => {
range = Some(unquote(value).parse()?); range = Some(unquote(value).parse()?);

View file

@ -133,8 +133,8 @@ impl FromStr for ExtXStart {
let mut time_offset = None; let mut time_offset = None;
let mut precise = false; let mut precise = false;
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in AttributePairs::new(input) {
match key.as_str() { match key {
"TIME-OFFSET" => time_offset = Some((value.parse())?), "TIME-OFFSET" => time_offset = Some((value.parse())?),
"PRECISE" => precise = (parse_yes_or_no(value))?, "PRECISE" => precise = (parse_yes_or_no(value))?,
_ => { _ => {

View file

@ -293,10 +293,18 @@ impl FromStr for DecryptionKey {
let mut key_format = None; let mut key_format = None;
let mut key_format_versions = None; let mut key_format_versions = None;
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in AttributePairs::new(input) {
match key.as_str() { match key {
"METHOD" => method = Some(value.parse().map_err(Error::strum)?), "METHOD" => method = Some(value.parse().map_err(Error::strum)?),
"URI" => uri = Some(unquote(value)), "URI" => {
let unquoted_uri = unquote(value);
if unquoted_uri.trim().is_empty() {
uri = None;
} else {
uri = Some(unquoted_uri);
}
}
"IV" => iv = Some(value.parse()?), "IV" => iv = Some(value.parse()?),
"KEYFORMAT" => key_format = Some(value.parse()?), "KEYFORMAT" => key_format = Some(value.parse()?),
"KEYFORMATVERSIONS" => key_format_versions = Some(value.parse().unwrap()), "KEYFORMATVERSIONS" => key_format_versions = Some(value.parse().unwrap()),

View file

@ -268,8 +268,8 @@ impl FromStr for StreamInf {
let mut hdcp_level = None; let mut hdcp_level = None;
let mut video = None; let mut video = None;
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in AttributePairs::new(input) {
match key.as_str() { match key {
"BANDWIDTH" => bandwidth = Some(value.parse::<u64>().map_err(Error::parse_int)?), "BANDWIDTH" => bandwidth = Some(value.parse::<u64>().map_err(Error::parse_int)?),
"AVERAGE-BANDWIDTH" => { "AVERAGE-BANDWIDTH" => {
average_bandwidth = Some(value.parse::<u64>().map_err(Error::parse_int)?) average_bandwidth = Some(value.parse::<u64>().map_err(Error::parse_int)?)