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
|
/// some unprintable character to separate repeated keys
|
||||||
const SEPARATOR: char = '\x02';
|
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> {
|
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();
|
let mut map: HashMap<String, String> = HashMap::new();
|
||||||
for line in input {
|
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())
|
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>> {
|
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
|
input
|
||||||
.peekable()
|
.peekable()
|
||||||
.batching(|it| {
|
.batching(|it| {
|
||||||
let mut v = match it.next() {
|
let mut v = match it.next() {
|
||||||
// if the playlist is empty or we’ve reached the end,
|
// if the response is empty or we’ve reached the end,
|
||||||
// the response is only the `OK` line.
|
// we receive only the `OK` line.
|
||||||
Some("OK") => return None,
|
Some("OK") => return None,
|
||||||
// otherwise this is the file attribute and thus the first key-value of this track
|
// otherwise this is the file attribute and thus the first key-value of this track
|
||||||
Some(s) => vec![s],
|
Some(s) => vec![s],
|
||||||
_ => return None,
|
_ => 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() {
|
while let Some(l) = it.peek() {
|
||||||
if l.starts_with(first_key) {
|
if l.starts_with(first_key) {
|
||||||
return Some(v);
|
return Some(v);
|
||||||
|
@ -62,6 +66,21 @@ pub fn parse_response_vec<'a, 'b, I: Iterator<Item = &'a str>, T: de::Deserializ
|
||||||
.collect()
|
.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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -96,7 +115,7 @@ OK";
|
||||||
title: Some("American Pie".to_string()),
|
title: Some("American Pie".to_string()),
|
||||||
track: Some(Position {
|
track: Some(Position {
|
||||||
item_position: 1,
|
item_position: 1,
|
||||||
total_items: None,
|
total_items: None,
|
||||||
}),
|
}),
|
||||||
duration: Some(Duration::from_secs_f64(512.380)),
|
duration: Some(Duration::from_secs_f64(512.380)),
|
||||||
pos: 1367,
|
pos: 1367,
|
||||||
|
@ -152,11 +171,11 @@ OK"#;
|
||||||
title: Some("Symphonie No. 41 en ut majeur, K. 551 \"Jupiter\": I. Allegro Vivace".to_string()),
|
title: Some("Symphonie No. 41 en ut majeur, K. 551 \"Jupiter\": I. Allegro Vivace".to_string()),
|
||||||
disc: Some(Position {
|
disc: Some(Position {
|
||||||
item_position: 1,
|
item_position: 1,
|
||||||
total_items: Some(1),
|
total_items: Some(1),
|
||||||
}),
|
}),
|
||||||
track: Some(Position {
|
track: Some(Position {
|
||||||
item_position: 6,
|
item_position: 6,
|
||||||
total_items: Some(6),
|
total_items: Some(6),
|
||||||
}),
|
}),
|
||||||
duration: Some(Duration::from_secs_f64(682.840)),
|
duration: Some(Duration::from_secs_f64(682.840)),
|
||||||
pos: 3439,
|
pos: 3439,
|
||||||
|
@ -215,7 +234,7 @@ OK";
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn de_playlist_test() {
|
fn de_playlistinfo_test() {
|
||||||
let input_str = "file: 137 A New World.mp3
|
let input_str = "file: 137 A New World.mp3
|
||||||
Last-Modified: 2018-03-07T13:11:43Z
|
Last-Modified: 2018-03-07T13:11:43Z
|
||||||
Artist: Arata Iiyoshi
|
Artist: Arata Iiyoshi
|
||||||
|
@ -294,13 +313,13 @@ OK";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
s,
|
s,
|
||||||
Stats {
|
Stats {
|
||||||
uptime: Duration::from_secs(23691),
|
uptime: Duration::from_secs(23691),
|
||||||
playtime: Duration::from_secs(11288),
|
playtime: Duration::from_secs(11288),
|
||||||
artists: 2841,
|
artists: 2841,
|
||||||
albums: 2455,
|
albums: 2455,
|
||||||
songs: 40322,
|
songs: 40322,
|
||||||
db_playtime: Duration::from_secs(11620284),
|
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""#))
|
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