use chrono::{DateTime, UTC}; use rustc_serialize::base64::{STANDARD, ToBase64}; use rustc_serialize::json::Json as RustcJson; use std::collections::BTreeMap; use std::io::{Read, Seek}; use {builder, EventReader, PlistEvent, Result}; #[derive(Clone, Debug, PartialEq)] pub enum Plist { Array(Vec), Dictionary(BTreeMap), Boolean(bool), Data(Vec), Date(DateTime), Real(f64), Integer(i64), String(String), } impl Plist { pub fn read(reader: R) -> Result { let reader = EventReader::new(reader); Plist::from_events(reader) } pub fn from_events(events: T) -> Result where T: IntoIterator> { let iter = events.into_iter(); let builder = builder::Builder::new(iter); builder.build() } pub fn into_events(self) -> Vec { let mut events = Vec::new(); self.into_events_inner(&mut events); events } fn into_events_inner(self, events: &mut Vec) { match self { Plist::Array(array) => { events.push(PlistEvent::StartArray(Some(array.len() as u64))); for value in array.into_iter() { value.into_events_inner(events); } events.push(PlistEvent::EndArray); } Plist::Dictionary(dict) => { events.push(PlistEvent::StartDictionary(Some(dict.len() as u64))); for (key, value) in dict.into_iter() { events.push(PlistEvent::StringValue(key)); value.into_events_inner(events); } events.push(PlistEvent::EndDictionary); } Plist::Boolean(value) => events.push(PlistEvent::BooleanValue(value)), Plist::Data(value) => events.push(PlistEvent::DataValue(value)), Plist::Date(value) => events.push(PlistEvent::DateValue(value)), Plist::Real(value) => events.push(PlistEvent::RealValue(value)), Plist::Integer(value) => events.push(PlistEvent::IntegerValue(value)), Plist::String(value) => events.push(PlistEvent::StringValue(value)), } } pub fn into_rustc_serialize_json(self) -> RustcJson { match self { Plist::Array(value) => { RustcJson::Array(value.into_iter().map(|p| p.into_rustc_serialize_json()).collect()) } Plist::Dictionary(value) => { RustcJson::Object(value.into_iter() .map(|(k, v)| (k, v.into_rustc_serialize_json())) .collect()) } Plist::Boolean(value) => RustcJson::Boolean(value), Plist::Data(value) => RustcJson::String(value.to_base64(STANDARD)), Plist::Date(value) => RustcJson::String(value.to_rfc3339()), Plist::Real(value) => RustcJson::F64(value), Plist::Integer(value) => RustcJson::I64(value), Plist::String(value) => RustcJson::String(value), } } /// If the `Plist` is an Array, returns the associated Vec. /// Returns None otherwise. pub fn as_array(&self) -> Option<&Vec> { match self { &Plist::Array(ref array) => Some(array), _ => None, } } /// If the `Plist` is an Array, returns the associated mutable Vec. /// Returns None otherwise. pub fn as_array_mut(&mut self) -> Option<&mut Vec> { match self { &mut Plist::Array(ref mut array) => Some(array), _ => None, } } /// If the `Plist` is a Dictionary, returns the associated BTreeMap. /// Returns None otherwise. pub fn as_dictionary(&self) -> Option<&BTreeMap> { match self { &Plist::Dictionary(ref map) => Some(map), _ => None, } } /// If the `Plist` is a Dictionary, returns the associated mutable BTreeMap. /// Returns None otherwise. pub fn as_dictionary_mut(&mut self) -> Option<&mut BTreeMap> { match self { &mut Plist::Dictionary(ref mut map) => Some(map), _ => None, } } /// If the `Plist` is a Boolean, returns the associated bool. /// Returns None otherwise. pub fn as_boolean(&self) -> Option { match self { &Plist::Boolean(v) => Some(v), _ => None, } } /// If the `Plist` is a Data, returns the underlying Vec. /// Returns None otherwise. /// /// This method consumes the `Plist`. If this is not desired, please use /// `as_data` method. pub fn into_data(self) -> Option> { match self { Plist::Data(data) => Some(data), _ => None, } } /// If the `Plist` is a Data, returns the associated Vec. /// Returns None otherwise. pub fn as_data(&self) -> Option<&[u8]> { match self { &Plist::Data(ref data) => Some(data), _ => None, } } /// If the `Plist` is a Date, returns the associated DateTime. /// Returns None otherwise. pub fn as_date(&self) -> Option> { match self { &Plist::Date(date) => Some(date), _ => None, } } /// If the `Plist` is a Real, returns the associated f64. /// Returns None otherwise. pub fn as_real(&self) -> Option { match self { &Plist::Real(v) => Some(v), _ => None, } } /// If the `Plist` is an Integer, returns the associated i64. /// Returns None otherwise. pub fn as_integer(&self) -> Option { match self { &Plist::Integer(v) => Some(v), _ => None, } } /// If the `Plist` is a String, returns the underlying String. /// Returns None otherwise. /// /// This method consumes the `Plist`. If this is not desired, please use /// `as_string` method. pub fn into_string(self) -> Option { match self { Plist::String(v) => Some(v), _ => None, } } /// If the `Plist` is a String, returns the associated str. /// Returns None otherwise. pub fn as_string(&self) -> Option<&str> { match self { &Plist::String(ref v) => Some(v), _ => None, } } } impl From> for Plist { fn from(from: Vec) -> Plist { Plist::Array(from) } } impl From> for Plist { fn from(from: BTreeMap) -> Plist { Plist::Dictionary(from) } } impl From for Plist { fn from(from: bool) -> Plist { Plist::Boolean(from) } } impl<'a> From<&'a bool> for Plist { fn from(from: &'a bool) -> Plist { Plist::Boolean(*from) } } impl From> for Plist { fn from(from: DateTime) -> Plist { Plist::Date(from) } } impl<'a> From<&'a DateTime> for Plist { fn from(from: &'a DateTime) -> Plist { Plist::Date(from.clone()) } } impl From for Plist { fn from(from: f64) -> Plist { Plist::Real(from) } } impl From for Plist { fn from(from: f32) -> Plist { Plist::Real(from as f64) } } impl From for Plist { fn from(from: i64) -> Plist { Plist::Integer(from) } } impl From for Plist { fn from(from: i32) -> Plist { Plist::Integer(from as i64) } } impl From for Plist { fn from(from: i16) -> Plist { Plist::Integer(from as i64) } } impl From for Plist { fn from(from: i8) -> Plist { Plist::Integer(from as i64) } } impl From for Plist { fn from(from: u32) -> Plist { Plist::Integer(from as i64) } } impl From for Plist { fn from(from: u16) -> Plist { Plist::Integer(from as i64) } } impl From for Plist { fn from(from: u8) -> Plist { Plist::Integer(from as i64) } } impl<'a> From<&'a f64> for Plist { fn from(from: &'a f64) -> Plist { Plist::Real(*from) } } impl<'a> From<&'a f32> for Plist { fn from(from: &'a f32) -> Plist { Plist::Real(*from as f64) } } impl<'a> From<&'a i64> for Plist { fn from(from: &'a i64) -> Plist { Plist::Integer(*from) } } impl<'a> From<&'a i32> for Plist { fn from(from: &'a i32) -> Plist { Plist::Integer(*from as i64) } } impl<'a> From<&'a i16> for Plist { fn from(from: &'a i16) -> Plist { Plist::Integer(*from as i64) } } impl<'a> From<&'a i8> for Plist { fn from(from: &'a i8) -> Plist { Plist::Integer(*from as i64) } } impl<'a> From<&'a u32> for Plist { fn from(from: &'a u32) -> Plist { Plist::Integer(*from as i64) } } impl<'a> From<&'a u16> for Plist { fn from(from: &'a u16) -> Plist { Plist::Integer(*from as i64) } } impl<'a> From<&'a u8> for Plist { fn from(from: &'a u8) -> Plist { Plist::Integer(*from as i64) } } impl From for Plist { fn from(from: String) -> Plist { Plist::String(from) } } impl<'a> From<&'a str> for Plist { fn from(from: &'a str) -> Plist { Plist::String(from.into()) } } #[cfg(test)] mod tests { use super::Plist; #[test] fn test_plist_access() { use std::collections::BTreeMap; use chrono::*; let vec = vec![Plist::Real(0.0)]; let mut array = Plist::Array(vec.clone()); assert_eq!(array.as_array(), Some(&vec.clone())); assert_eq!(array.as_array_mut(), Some(&mut vec.clone())); let mut map = BTreeMap::new(); map.insert("key1".to_owned(), Plist::String("value1".to_owned())); let mut dict = Plist::Dictionary(map.clone()); assert_eq!(dict.as_dictionary(), Some(&map.clone())); assert_eq!(dict.as_dictionary_mut(), Some(&mut map.clone())); assert_eq!(Plist::Boolean(true).as_boolean(), Some(true)); let slice: &[u8] = &[1, 2, 3]; assert_eq!(Plist::Data(slice.to_vec()).as_data(), Some(slice)); assert_eq!(Plist::Data(slice.to_vec()).into_data(), Some(slice.to_vec())); let date: DateTime = UTC::now(); assert_eq!(Plist::Date(date).as_date(), Some(date)); assert_eq!(Plist::Real(0.0).as_real(), Some(0.0)); assert_eq!(Plist::Integer(1).as_integer(), Some(1)); assert_eq!(Plist::String("2".to_owned()).as_string(), Some("2")); assert_eq!(Plist::String("t".to_owned()).into_string(), Some("t".to_owned())); } }