diff --git a/src/lib.rs b/src/lib.rs index 463c5ab..1fe413e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub use structs::{Position, State, Stats, Status, Track, UnitResponse}; /// some unprintable character to separate repeated keys const SEPARATOR: char = '\x02'; +/// Parse an interator of string slices into `T`. pub fn parse_response<'a, I: Iterator, T: de::DeserializeOwned>(input: I) -> MpdResult { let mut map: HashMap = HashMap::new(); for line in input { @@ -34,19 +35,22 @@ pub fn parse_response<'a, I: Iterator, T: de::DeserializeOwned>( Ok(envy::from_iter(map).unwrap()) } +/// Parse an iterator of string slices into a vector of `T`, splitting at `first_key`. +/// One possible use for this is the `playlistinfo` command which returns all items in the current +/// playlist, where the `file: ` key denotes the start of a new item. pub fn parse_response_vec<'a, 'b, I: Iterator, T: de::DeserializeOwned>(input: I, first_key: &'b str) -> MpdResult> { input .peekable() .batching(|it| { let mut v = match it.next() { - // if the playlist is empty or we’ve reached the end, - // the response is only the `OK` line. + // if the response is empty or we’ve reached the end, + // we receive only the `OK` line. Some("OK") => return None, // otherwise this is the file attribute and thus the first key-value of this track Some(s) => vec![s], _ => return None, }; - // Only peek here in case we encounter the `file:` line which we still need for the next track. + // Only peek here in case we encounter the first key (e.g. `file:`) line which we still need for the next track. while let Some(l) = it.peek() { if l.starts_with(first_key) { return Some(v); @@ -62,6 +66,21 @@ pub fn parse_response_vec<'a, 'b, I: Iterator, T: de::Deserializ .collect() } +/// Parse the `playlist` command, a list of key-value pairs, as a vector of filenames. +/// The playlist index of each item is *not* included because, if needed, +/// it can easily be added with `.enumerate()`. +pub fn parse_playlist<'a, I: Iterator>(input: I) -> MpdResult> { + input + // `iter.scan((), |(), item| predicate(item))` is equivalent to map_while(predicate), + // but the latter isn’t stabilized (yet). + .scan((), |(), s| match s.splitn(2, ' ').next_tuple() { + Some(("OK", _)) | None => None, // Covers OK with message and without + Some(("ACK", err)) => Some(Err(Error::from_str(err))), + Some((_, filename)) => Some(Ok(filename)), + }) + .collect() +} + #[cfg(test)] mod tests { use super::*; @@ -96,7 +115,7 @@ OK"; title: Some("American Pie".to_string()), track: Some(Position { item_position: 1, - total_items: None, + total_items: None, }), duration: Some(Duration::from_secs_f64(512.380)), pos: 1367, @@ -152,11 +171,11 @@ OK"#; title: Some("Symphonie No. 41 en ut majeur, K. 551 \"Jupiter\": I. Allegro Vivace".to_string()), disc: Some(Position { item_position: 1, - total_items: Some(1), + total_items: Some(1), }), track: Some(Position { item_position: 6, - total_items: Some(6), + total_items: Some(6), }), duration: Some(Duration::from_secs_f64(682.840)), pos: 3439, @@ -215,7 +234,7 @@ OK"; ); } #[test] - fn de_playlist_test() { + fn de_playlistinfo_test() { let input_str = "file: 137 A New World.mp3 Last-Modified: 2018-03-07T13:11:43Z Artist: Arata Iiyoshi @@ -294,13 +313,13 @@ OK"; assert_eq!( s, Stats { - uptime: Duration::from_secs(23691), - playtime: Duration::from_secs(11288), - artists: 2841, - albums: 2455, - songs: 40322, + uptime: Duration::from_secs(23691), + playtime: Duration::from_secs(11288), + artists: 2841, + albums: 2455, + songs: 40322, db_playtime: Duration::from_secs(11620284), - db_update: 1588433046, + db_update: 1588433046, } ); } @@ -318,4 +337,29 @@ OK"; Err(error::Error::from_str(r#"[2@0] {consume} wrong number of arguments for "consume""#)) ); } + + #[test] + fn parse_playlist_test() { + let input = "0:file: Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/01 Opening Screen.flac +1:file: Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/02 Wolfstack Lights.flac +2:file: Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/03 Submergio Viol.flac +3:file: Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/08 Dark Sailing.flac +4:file: Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/17 The Sea Does Not Forgive.flac +5:file: Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/18 Hope Is an Anchor.flac"; + + match parse_playlist(input.lines()) { + Err(_) => panic!("Should not be an error"), + Ok(tracks) => assert_eq!( + tracks, + vec![ + "Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/01 Opening Screen.flac", + "Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/02 Wolfstack Lights.flac", + "Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/03 Submergio Viol.flac", + "Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/08 Dark Sailing.flac", + "Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/17 The Sea Does Not Forgive.flac", + "Brent Barkman & Maribeth Solomon/Sunless Sea (2015)/18 Hope Is an Anchor.flac", + ] + ), + } + } }