Add parser for listfiles
command
This commit is contained in:
parent
d8d152ba80
commit
0fb1912c71
|
@ -1,5 +1,14 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
|
@ -42,12 +51,24 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd7d4bd64732af4bf3a67f367c27df8520ad7e230c5817b8ff485864d80242b9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
|
||||
[[package]]
|
||||
name = "mparsed"
|
||||
version = "0.1.0"
|
||||
|
@ -55,6 +76,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"envy",
|
||||
"itertools",
|
||||
"regex",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -95,6 +117,24 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.114"
|
||||
|
@ -126,6 +166,15 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
|
|
|
@ -14,5 +14,6 @@ serde = { version = "1.0.114", features = ["derive"] }
|
|||
itertools = "0.9.0"
|
||||
envy = "0.4.1"
|
||||
chrono = { version = "0.4.13", features = ["serde"] }
|
||||
regex = "1.3.9"
|
||||
|
||||
[lib]
|
||||
|
|
99
src/lib.rs
99
src/lib.rs
|
@ -4,12 +4,37 @@ mod error;
|
|||
use error::{Error, MpdResult};
|
||||
use itertools::Itertools;
|
||||
mod structs;
|
||||
pub use structs::{Position, State, Stats, Status, Track, UnitResponse};
|
||||
// TODO: If std::str::pattern::Pattern ever gets stabilized,
|
||||
// use that instead of depending on the Regex crate
|
||||
use regex::Regex;
|
||||
pub use structs::{File, Position, State, Stats, Status, Track, UnitResponse};
|
||||
|
||||
/// some unprintable character to separate repeated keys
|
||||
// some unprintable character to separate repeated keys
|
||||
const SEPARATOR: char = '\x02';
|
||||
|
||||
/// Parse an interator of string slices into `T`.
|
||||
/// Parse an interator of string slices into `T`,
|
||||
/// returning Ok(T) if the data could be deserialized and the MPD response ended with `OK` or
|
||||
/// `Error` if the deserialization failed or MPD sent an error message.
|
||||
///
|
||||
/// ```
|
||||
/// # use mparsed::{Track, parse_response};
|
||||
/// # use std::time::Duration;
|
||||
/// let response = "file: 01 Track.flac
|
||||
/// Last-Modified: 2018-03-07T13:11:43Z
|
||||
/// duration: 123.45
|
||||
/// Pos: 1
|
||||
/// Id: 2
|
||||
/// OK";
|
||||
/// let parsed: Track = parse_response(response.lines()).unwrap();
|
||||
///
|
||||
/// assert_eq!(parsed.file, String::from("01 Track.flac"));
|
||||
/// assert_eq!(parsed.duration, Some(Duration::from_secs_f64(123.45)));
|
||||
/// ```
|
||||
///
|
||||
/// For responses that contain multiple instances of a struct (like playlists), see
|
||||
/// [`parse_response_vec`].
|
||||
///
|
||||
/// [`parse_response_vec`]: fn.parse_response_vec.html
|
||||
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 {
|
||||
|
@ -38,7 +63,25 @@ pub fn parse_response<'a, I: Iterator<Item = &'a str>, T: de::DeserializeOwned>(
|
|||
/// 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>> {
|
||||
///
|
||||
/// # `first_key` as Regex
|
||||
/// In some cases, like the `listfiles` command, there are multiple options for `first_key`, so a
|
||||
/// proper regex must be specified.
|
||||
/// ```
|
||||
/// # use regex::Regex;
|
||||
/// # use mparsed::{parse_response_vec, File};
|
||||
/// let response = "file: A track.flac
|
||||
/// size: 123456
|
||||
/// Last-Modified: 2019-12-17T08:51:37Z
|
||||
/// directory: A directory
|
||||
/// Last-Modified: 2015-01-30T14:53:03Z
|
||||
/// OK";
|
||||
/// let files: Vec<File> = parse_response_vec(response.lines(), Regex::new("^(file|directory): ").unwrap()).unwrap();
|
||||
///
|
||||
/// assert_eq!(files[0].name, String::from("A track.flac"));
|
||||
/// assert!(files[1].is_directory());
|
||||
/// ```
|
||||
pub fn parse_response_vec<'a, 'b, I: Iterator<Item = &'a str>, T: de::DeserializeOwned>(input: I, first_key: Regex) -> MpdResult<Vec<T>> {
|
||||
input
|
||||
.peekable()
|
||||
.batching(|it| {
|
||||
|
@ -52,7 +95,7 @@ pub fn parse_response_vec<'a, 'b, I: Iterator<Item = &'a str>, T: de::Deserializ
|
|||
};
|
||||
// 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) {
|
||||
if first_key.is_match(l) {
|
||||
return Some(v);
|
||||
}
|
||||
if l.starts_with("OK") {
|
||||
|
@ -260,7 +303,7 @@ duration: 50.155
|
|||
Pos: 1002
|
||||
Id: 6367
|
||||
OK";
|
||||
let queue = parse_response_vec(input_str.lines(), "file:");
|
||||
let queue = parse_response_vec(input_str.lines(), Regex::new("^file:").unwrap());
|
||||
let first_track = Track {
|
||||
file: "137 A New World.mp3".into(),
|
||||
title: Some("A New World".into()),
|
||||
|
@ -295,7 +338,7 @@ OK";
|
|||
])
|
||||
);
|
||||
|
||||
let queue = parse_response_vec("OK".lines(), "file:");
|
||||
let queue = parse_response_vec("OK".lines(), Regex::new("^file:").unwrap());
|
||||
assert_eq!(queue, Ok(Vec::<Track>::new()));
|
||||
}
|
||||
|
||||
|
@ -365,8 +408,46 @@ OK";
|
|||
|
||||
let input = "ACK [] {playlistinfo} something went wrong";
|
||||
match parse_playlist(input.lines()) {
|
||||
Ok(_) => panic!("Should have failed"),
|
||||
Err(e) => assert_eq!(e.message, "[] {playlistinfo} something went wrong")
|
||||
Ok(_) => panic!("Should have failed"),
|
||||
Err(e) => assert_eq!(e.message, "[] {playlistinfo} something went wrong"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_list_test() {
|
||||
let input = "file: 11 奏(かなで)(original スキマスイッチ).flac
|
||||
size: 18948052
|
||||
Last-Modified: 2019-12-17T08:51:37Z
|
||||
directory: Scans
|
||||
Last-Modified: 2015-01-30T14:53:03Z
|
||||
file: 15 風ハ旅スル(スマートフォンゲーム「風パズル 黒猫と白猫の夢見た世界」テーマ曲).flac
|
||||
size: 13058417
|
||||
Last-Modified: 2019-12-17T08:51:41Z
|
||||
OK";
|
||||
let parsed: Vec<File> = parse_response_vec(input.lines(), Regex::new("^(file:|directory:)").unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
vec![
|
||||
File {
|
||||
name: "11 奏(かなで)(original スキマスイッチ).flac".into(),
|
||||
last_modified: DateTime::parse_from_rfc3339("2019-12-17T08:51:37Z").unwrap(),
|
||||
size: 18948052
|
||||
},
|
||||
File {
|
||||
name: "Scans".into(),
|
||||
last_modified: DateTime::parse_from_rfc3339("2015-01-30T14:53:03Z").unwrap(),
|
||||
size: 0
|
||||
},
|
||||
File {
|
||||
name: "15 風ハ旅スル(スマートフォンゲーム「風パズル 黒猫と白猫の夢見た世界」テーマ曲).flac".into(),
|
||||
last_modified: DateTime::parse_from_rfc3339("2019-12-17T08:51:41Z").unwrap(),
|
||||
size: 13058417
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
parsed.iter().map(|f| f.is_directory()).collect::<Vec<_>>(),
|
||||
vec![false, true, false],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,22 @@ pub struct Status {
|
|||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct File {
|
||||
#[serde(alias = "directory", rename = "file")]
|
||||
pub name: String,
|
||||
#[serde(rename = "last-modified")]
|
||||
pub last_modified: DateTime<FixedOffset>,
|
||||
#[serde(default)]
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
impl File {
|
||||
pub fn is_directory(&self) -> bool {
|
||||
self.size == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug, PartialEq)]
|
||||
pub enum State {
|
||||
Stop,
|
||||
|
|
Loading…
Reference in New Issue
Block a user