Add parser for playlist command

This commit is contained in:
kageru 2020-07-23 21:03:13 +02:00
parent 1e39119e09
commit 6bf85ca664
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2

View File

@ -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<Item = &'a str>, T: de::DeserializeOwned>(input: I) -> MpdResult<T> {
let mut map: HashMap<String, String> = HashMap::new();
for line in input {
@ -34,19 +35,22 @@ pub fn parse_response<'a, I: Iterator<Item = &'a str>, 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<Item = &'a str>, T: de::DeserializeOwned>(input: I, first_key: &'b str) -> MpdResult<Vec<T>> {
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<Item = &'a str>, 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<Item = &'a str>>(input: I) -> MpdResult<Vec<&'a str>> {
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",
]
),
}
}
}