From 8253e4b5de3182254ad2d093c64870e607b9bcab Mon Sep 17 00:00:00 2001 From: kageru Date: Sat, 20 Jun 2020 22:50:33 +0200 Subject: [PATCH] convert to library --- Cargo.lock | 73 +++++++++++++++++++++++++++++++++++ Cargo.toml | 4 +- rustfmt.toml | 8 ++++ src/lib.rs | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 87 ----------------------------------------- src/structs.rs | 16 ++++---- 6 files changed, 194 insertions(+), 96 deletions(-) create mode 100644 Cargo.lock create mode 100644 rustfmt.toml create mode 100644 src/lib.rs delete mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e9dedfb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,73 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "envy" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f938a4abd5b75fe3737902dbc2e79ca142cc1526827a9e40b829a086758531a9" +dependencies = [ + "serde", +] + +[[package]] +name = "mparsed" +version = "0.1.0" +dependencies = [ + "envy", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/Cargo.toml b/Cargo.toml index 7e4d2a2..fcca626 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,9 @@ version = "0.1.0" authors = ["kageru "] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] serde = { version = "1.0", features = ["derive"] } #itertools = "0.9" envy = "0.4" + +[lib] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..0ddf519 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,8 @@ +newline_style = "Unix" +max_width = 140 +tab_spaces = 2 +imports_layout = "Horizontal" +merge_imports = true +struct_field_align_threshold = 25 +where_single_line = true +edition = "2018" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3b5c470 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,102 @@ +use serde::{de, de::value::MapDeserializer, forward_to_deserialize_any}; +use std::collections::HashMap; +mod error; +use error::{Error, MpdResult}; +mod structs; +pub use structs::Track; + +/// some unprintable character to separate repeated keys +const SEPARATOR: char = '\x02'; + +struct MPDeserializer<'de, Iter: Iterator> { + inner: MapDeserializer<'de, Iter, Error>, +} + +impl<'de, Iter: Iterator> de::Deserializer<'de> for MPDeserializer<'de, Iter> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> MpdResult + where V: de::Visitor<'de> { + self.deserialize_map(visitor) + } + + fn deserialize_map(self, visitor: V) -> MpdResult + where V: de::Visitor<'de> { + visitor.visit_map(self.inner) + } + + forward_to_deserialize_any! { + bool char str string bytes byte_buf unit unit_struct seq + tuple tuple_struct struct identifier ignored_any + i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 option newtype_struct enum + } +} + +pub fn deserialize_response<'a, I: Iterator, T: de::Deserialize<'a>>(input: I) -> Result { + let mut map: HashMap = HashMap::new(); + for line in input { + if line.starts_with("OK") { + break; + } else if let Some(message) = line.strip_prefix("ACK") { + return Err(Error::from_str(message.trim())); + } + 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()); + // Eventually, I’d like to use my own deserializer instead of envy + // let deser = MPDeserializer { + // inner: MapDeserializer::new(map.into_iter()), + // }; + // Song::deserialize(deser) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn track_de_test() { + let input_str = "file: American Pie.flac +Last-Modified: 2019-12-16T21:38:54Z +Album: American Pie +Artist: Don McLean +Date: 1979 +Genre: Rock +Title: American Pie +Track: 1 +Time: 512 +duration: 512.380 +Pos: 1367 +Id: 1368 +OK"; + let t = deserialize_response::<'_, _, Track>(input_str.lines()).unwrap(); + assert_eq!( + t, + Track { + file: "American Pie.flac".to_string(), + last_modified: Some("2019-12-16T21:38:54Z".to_string()), + album: Some("American Pie".to_string()), + artist: Some("Don McLean".to_string()), + date: Some(1979), + genre: Some("Rock".to_string()), + title: Some("American Pie".to_string()), + track: Some(1), + duration: std::time::Duration::from_secs_f64(512.380), + pos: 1367, + id: 1368, + ..Track::default() + } + ); + } +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 55b09d5..0000000 --- a/src/main.rs +++ /dev/null @@ -1,87 +0,0 @@ -use serde::de; -use serde::de::value::MapDeserializer; -use serde::forward_to_deserialize_any; -use std::collections::HashMap; -mod error; -use error::{Error, MpdResult}; -mod structs; -pub use structs::Track; - -/// some unprintable character to separate repeated keys -const SEPARATOR: char = '\x02'; - -struct MPDeserializer<'de, Iter: Iterator> { - inner: MapDeserializer<'de, Iter, Error>, -} - -impl<'de, Iter: Iterator> de::Deserializer<'de> - for MPDeserializer<'de, Iter> -{ - type Error = Error; - - fn deserialize_any(self, visitor: V) -> MpdResult - where - V: de::Visitor<'de>, - { - self.deserialize_map(visitor) - } - - fn deserialize_map(self, visitor: V) -> MpdResult - where - V: de::Visitor<'de>, - { - visitor.visit_map(self.inner) - } - - forward_to_deserialize_any! { - bool char str string bytes byte_buf unit unit_struct seq - tuple tuple_struct struct identifier ignored_any - i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 option newtype_struct enum - } -} - -fn deserialize_response<'a, I: Iterator>(input: I) -> Result { - let mut map: HashMap = HashMap::new(); - for line in input { - if line.starts_with("OK") { - break; - } else if let Some(message) = line.strip_prefix("ACK") { - return Err(Error::from_str(message.trim())); - } - 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()); - // Eventually, I’d like to use my own deserializer instead of envy - // let deser = MPDeserializer { - // inner: MapDeserializer::new(map.into_iter()), - // }; - // Song::deserialize(deser) -} - -fn main() { - 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()); - println!("{:?}", s); -} diff --git a/src/structs.rs b/src/structs.rs index c319887..50a0983 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -2,7 +2,7 @@ use helpers::*; use serde::Deserialize; use std::time::Duration; -#[derive(Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug, Default, PartialEq)] pub struct Track { pub file: String, pub artist_sort: Option, @@ -18,18 +18,20 @@ pub struct Track { pub track: Option, pub album: Option, pub artist: Option, - pub pos: Option, - pub id: Option, + pub pos: u32, + pub id: u32, // TODO: use proper time here + #[serde(rename = "last-modified")] pub last_modified: Option, - pub original_date: Option, - pub time: Option, + pub original_date: Option, + // probably not needed. it’s just the duration but as an int + // pub time: u16, pub format: Option, #[serde(deserialize_with = "de_time_float")] pub duration: Duration, pub label: Option, - pub date: Option, - pub disc: Option, + pub date: Option, + pub disc: Option, pub musicbraiz_trackid: Option, pub musicbrainz_albumid: Option, pub musicbrainz_albumartistid: Option,