diff --git a/src/line.rs b/src/line.rs index 0641bc5..4cd76bf 100644 --- a/src/line.rs +++ b/src/line.rs @@ -1,87 +1,74 @@ use std::fmt; +use std::ops::{Deref, DerefMut}; use std::str::FromStr; use crate::tags; use crate::types::SingleLineString; use crate::Error; -#[derive(Debug)] -pub struct Lines<'a> { - input: &'a str, -} -impl<'a> Lines<'a> { - pub const fn new(input: &'a str) -> Self { - Lines { input } +#[derive(Debug, Default)] +pub struct Lines(Vec); + +impl Lines { + pub fn new() -> Self { + Self::default() } +} - fn read_line(&mut self) -> crate::Result> { - let mut end = self.input.len(); - let mut next_start = self.input.len(); - let mut adjust = 0; - let mut next_line_of_ext_x_stream_inf = false; +impl FromStr for Lines { + type Err = Error; - for (i, c) in self.input.char_indices() { - match c { - '\n' => { - if !next_line_of_ext_x_stream_inf - && self.input.starts_with(tags::ExtXStreamInf::PREFIX) - { - next_line_of_ext_x_stream_inf = true; - adjust = 0; - continue; - } - next_start = i + 1; - end = i - adjust; - break; - } - '\r' => { - adjust = 1; - } - _ => { - if c.is_control() { - return Err(Error::invalid_input()); - } - adjust = 0; - } + fn from_str(input: &str) -> Result { + let mut result = Lines::new(); + + for line in input.lines() { + // ignore empty lines + if line.trim().len() == 0 { + continue; } - } - let raw_line = &self.input[..end]; - let line = if raw_line.is_empty() { - Line::Blank - } else if raw_line.starts_with("#EXT") { - Line::Tag((raw_line.parse())?) - } else if raw_line.starts_with('#') { - Line::Comment(raw_line) - } else { - let uri = SingleLineString::new(raw_line)?; - Line::Uri(uri) - }; + let pline = { + if line.starts_with("#EXT") { + Line::Tag(line.parse()?) + } else if line.starts_with("#") { + continue; // ignore comments + } else { + Line::Uri(SingleLineString::new(line)?) + } + }; - self.input = &self.input[next_start..]; - Ok(line) - } -} -impl<'a> Iterator for Lines<'a> { - type Item = crate::Result>; - - fn next(&mut self) -> Option { - if self.input.is_empty() { - return None; + result.push(pline); } - match self.read_line() { - Err(e) => Some(Err(e)), - Ok(line) => Some(Ok(line)), - } + Ok(result) + } +} + +impl IntoIterator for Lines { + type Item = Line; + type IntoIter = ::std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Deref for Lines { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Lines { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } -#[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq, Eq)] -pub enum Line<'a> { - Blank, - Comment(&'a str), +pub enum Line { Tag(Tag), Uri(SingleLineString), } diff --git a/src/master_playlist.rs b/src/master_playlist.rs index 5ce9a38..85ba8fe 100644 --- a/src/master_playlist.rs +++ b/src/master_playlist.rs @@ -9,7 +9,7 @@ use crate::tags::{ ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, MasterPlaylistTag, }; use crate::types::{ClosedCaptions, MediaType, ProtocolVersion}; -use crate::{Error, Result}; +use crate::Error; /// Master playlist builder. #[derive(Debug, Clone)] @@ -69,7 +69,7 @@ impl MasterPlaylistBuilder { } /// Builds a `MasterPlaylist` instance. - pub fn finish(self) -> Result { + pub fn finish(self) -> crate::Result { let required_version = self.required_version(); let specified_version = self.version.unwrap_or(required_version); @@ -116,7 +116,7 @@ impl MasterPlaylistBuilder { .expect("Never fails") } - fn validate_stream_inf_tags(&self) -> Result<()> { + fn validate_stream_inf_tags(&self) -> crate::Result<()> { let mut has_none_closed_captions = false; for t in &self.stream_inf_tags { if let Some(group_id) = t.audio() { @@ -159,7 +159,7 @@ impl MasterPlaylistBuilder { Ok(()) } - fn validate_i_frame_stream_inf_tags(&self) -> Result<()> { + fn validate_i_frame_stream_inf_tags(&self) -> crate::Result<()> { for t in &self.i_frame_stream_inf_tags { if let Some(group_id) = t.video() { if !self.check_media_group(MediaType::Video, group_id) { @@ -171,7 +171,7 @@ impl MasterPlaylistBuilder { Ok(()) } - fn validate_session_data_tags(&self) -> Result<()> { + fn validate_session_data_tags(&self) -> crate::Result<()> { let mut set = HashSet::new(); for t in &self.session_data_tags { if !set.insert((t.data_id(), t.language())) { @@ -182,7 +182,7 @@ impl MasterPlaylistBuilder { Ok(()) } - fn validate_session_key_tags(&self) -> Result<()> { + fn validate_session_key_tags(&self) -> crate::Result<()> { let mut set = HashSet::new(); for t in &self.session_key_tags { if !set.insert(t.key()) { @@ -294,11 +294,11 @@ impl fmt::Display for MasterPlaylist { impl FromStr for MasterPlaylist { type Err = Error; - fn from_str(s: &str) -> Result { + + fn from_str(input: &str) -> Result { let mut builder = MasterPlaylistBuilder::new(); - for (i, line) in Lines::new(s).enumerate() { - match (line)? { - Line::Blank | Line::Comment(_) => {} + for (i, line) in input.parse::()?.into_iter().enumerate() { + match line { Line::Tag(tag) => { if i == 0 { if tag != Tag::ExtM3u(ExtM3u) { @@ -373,3 +373,11 @@ impl FromStr for MasterPlaylist { builder.finish() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parser() {} +} diff --git a/src/media_playlist.rs b/src/media_playlist.rs index 0b09225..a4074b6 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -337,9 +337,8 @@ impl MediaPlaylistOptions { let mut segment = MediaSegmentBuilder::new(); let mut has_partial_segment = false; let mut has_discontinuity_tag = false; - for (i, line) in Lines::new(m3u8).enumerate() { - match (line)? { - Line::Blank | Line::Comment(_) => {} + for (i, line) in m3u8.parse::()?.into_iter().enumerate() { + match line { Line::Tag(tag) => { if i == 0 { if tag != Tag::ExtM3u(ExtM3u) {