use envy for deserialization
This commit is contained in:
parent
faf9589706
commit
d4f86ee393
@ -7,6 +7,6 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
pest = "2.1"
|
||||
pest_derive = "2.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
#itertools = "0.9"
|
||||
envy = "0.4"
|
||||
|
11
src/error.rs
11
src/error.rs
@ -1,9 +1,6 @@
|
||||
use pest;
|
||||
use serde::de;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::Rule;
|
||||
|
||||
pub type MpdResult<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -11,14 +8,6 @@ pub struct Error {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl From<pest::error::Error<Rule>> for Error {
|
||||
fn from(err: pest::error::Error<Rule>) -> Self {
|
||||
Error {
|
||||
message: err.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl de::Error for Error {
|
||||
fn custom<T: Display>(msg: T) -> Self {
|
||||
Error {
|
||||
|
184
src/main.rs
184
src/main.rs
@ -1,21 +1,91 @@
|
||||
#[macro_use]
|
||||
extern crate pest_derive;
|
||||
use pest::Parser;
|
||||
use serde::de;
|
||||
use serde::de::value::MapDeserializer;
|
||||
use serde::forward_to_deserialize_any;
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
mod error;
|
||||
use error::MpdResult;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[grammar = "response.pest"]
|
||||
struct MpdParser;
|
||||
|
||||
struct MPDeserializer<'de, Iter: Iterator<Item = (&'de str, &'de str)>> {
|
||||
inner: MapDeserializer<'de, Iter, error::Error>,
|
||||
}
|
||||
|
||||
/// Deserialize time from an integer that represents the seconds.
|
||||
/// mpd uses int for the database stats (e.g. total time played).
|
||||
fn de_time_int<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
u64::deserialize(deserializer).map(Duration::from_secs)
|
||||
}
|
||||
|
||||
/// Deserialize time from a float that represents the seconds.
|
||||
/// mpd uses floats for the current status (e.g. time elapsed in song).
|
||||
fn de_time_float<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
f64::deserialize(deserializer)
|
||||
.map(Duration::from_secs_f64)
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
/// mpd uses bints (0 or 1) to represent booleans,
|
||||
/// so we need a special parser for those.
|
||||
fn de_bint<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
match u8::deserialize(deserializer)? {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
n => Err(de::Error::invalid_value(
|
||||
de::Unexpected::Unsigned(n as u64),
|
||||
&"zero or one",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct Track {
|
||||
pub file: String,
|
||||
pub artist_sort: Option<String>,
|
||||
pub album_artist: Option<String>,
|
||||
pub album_sort: Option<String>,
|
||||
pub album_artist_sort: Option<String>,
|
||||
#[serde(deserialize_with = "string_or_vec")]
|
||||
#[serde(default)]
|
||||
#[serde(rename = "performer")]
|
||||
pub performers: Vec<String>,
|
||||
pub genre: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub track: Option<u32>,
|
||||
pub album: Option<String>,
|
||||
pub artist: Option<String>,
|
||||
pub pos: Option<u32>,
|
||||
pub id: Option<u32>,
|
||||
// TODO: use proper time here
|
||||
pub last_modified: Option<String>,
|
||||
pub original_date: Option<String>,
|
||||
pub time: Option<String>,
|
||||
pub format: Option<String>,
|
||||
#[serde(deserialize_with = "de_time_float")]
|
||||
pub duration: Option<Duration>,
|
||||
pub label: Option<String>,
|
||||
pub date: Option<String>,
|
||||
pub disc: Option<u32>,
|
||||
pub musicbraiz_trackid: Option<String>,
|
||||
pub musicbrainz_albumid: Option<String>,
|
||||
pub musicbrainz_albumartistid: Option<String>,
|
||||
pub musicbrainz_artistid: Option<String>,
|
||||
pub musicbraiz_releasetrackid: Option<String>,
|
||||
pub composer: Option<String>,
|
||||
}
|
||||
|
||||
// some unprintable character
|
||||
const SEPARATOR: char = '\x02';
|
||||
|
||||
impl<'de, Iter: Iterator<Item = (&'de str, &'de str)>> de::Deserializer<'de>
|
||||
for MPDeserializer<'de, Iter>
|
||||
{
|
||||
@ -42,41 +112,73 @@ impl<'de, Iter: Iterator<Item = (&'de str, &'de str)>> de::Deserializer<'de>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct Song {
|
||||
title: String,
|
||||
artist: String,
|
||||
fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
struct StringOrVec();
|
||||
|
||||
impl<'de> de::Visitor<'de> for StringOrVec {
|
||||
type Value = Vec<String>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("string or list of strings")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
Ok(value
|
||||
.split(SEPARATOR)
|
||||
.map(std::string::String::from)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(StringOrVec())
|
||||
}
|
||||
|
||||
fn deserialize_response<'a, I: Iterator<Item = &'a str>>(input: I) -> Result<Track, error::Error> {
|
||||
let mut map: std::collections::HashMap<String, String> = std::collections::HashMap::new();
|
||||
for line in input {
|
||||
if let Some(_) = line.strip_prefix("OK") {
|
||||
break;
|
||||
} else if let Some(message) = line.strip_prefix("ACK") {
|
||||
return Err(error::Error {
|
||||
message: message.trim().to_string(),
|
||||
});
|
||||
}
|
||||
let mut fields = line.splitn(2, ": ");
|
||||
match (fields.next(), fields.next()) {
|
||||
(Some(k), Some(v)) => {
|
||||
if let Some(existing) = map.get_mut(k) {
|
||||
existing.push(SEPARATOR);
|
||||
existing.push_str(v);
|
||||
} else {
|
||||
map.insert(k.to_string(), v.to_string());
|
||||
}
|
||||
}
|
||||
_ => panic!("invalid response line: {:?}", line),
|
||||
}
|
||||
}
|
||||
return Ok(envy::from_iter(map).unwrap());
|
||||
// let deser = MPDeserializer {
|
||||
// inner: MapDeserializer::new(map.into_iter()),
|
||||
// };
|
||||
// Song::deserialize(deser)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input_var: &str = "Title: A song 星
|
||||
Artist: A name
|
||||
OK mpd 0.21.23";
|
||||
|
||||
let mut map = std::collections::HashMap::new();
|
||||
let parser = MpdParser::parse(Rule::response, input_var)
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap();
|
||||
for line in parser.into_inner() {
|
||||
match line.as_rule() {
|
||||
Rule::kv => {
|
||||
let mut fields = line.into_inner();
|
||||
map.insert(
|
||||
fields.next().unwrap().as_str(),
|
||||
fields.next().unwrap().as_str(),
|
||||
);
|
||||
}
|
||||
Rule::ok => break,
|
||||
Rule::err => panic!("received error response"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
dbg!(&map);
|
||||
let deser = MPDeserializer {
|
||||
inner: MapDeserializer::new(map.into_iter()),
|
||||
};
|
||||
let s = Song::deserialize(deser);
|
||||
println!("{:?}", s);
|
||||
let input_str = "file: Youtube Rip/VVVVVV Medley.flac
|
||||
Last-Modified: 2018-03-07T13:18:01Z
|
||||
Album: VVVVVV OST / PPPPPP
|
||||
Artist: FamilyJules7x
|
||||
Composer: Magnus Pålsson
|
||||
Date: 2014
|
||||
Genre: Video Game Music
|
||||
Title: VVVVVV Medley
|
||||
Time: 433
|
||||
duration: 432.727
|
||||
Pos: 1548
|
||||
Id: 1549";
|
||||
let s = deserialize_response(input_str.lines());
|
||||
dbg!(s);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user