Add parser for playlist
command
This commit is contained in:
parent
1e39119e09
commit
6bf85ca664
70
src/lib.rs
70
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<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",
|
||||
]
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user