use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::str::FromStr; use chrono::{DateTime, NaiveDate, NaiveDateTime, Local, Utc}; mod tokenizer; mod parser; pub use parser::Parser; pub trait GetValue<'a, R: 'a> { fn get(&'a self, key: &'_ str) -> Option; } pub type Props = Vec<(String, String)>; #[derive(Debug, PartialEq)] pub struct Object { pub name: String, pub content: HashMap>, pub children: Vec>, } impl<'a> GetValue<'a, &'a str> for Object { fn get(&'a self, key: &'_ str) -> Option<&'a str> { self.content.get(key) .and_then(|pairs| pairs.first().map(|(_props, value)| value.as_str())) } } impl<'a> GetValue<'a, &'a (Props, String)> for Object { fn get(&'a self, key: &'_ str) -> Option<&'a (Props, String)> { self.content.get(key) .and_then(|pairs| pairs.first()) } } impl<'a> GetValue<'a, &'a [(Props, String)]> for Object { fn get(&'a self, key: &'_ str) -> Option<&'a [(Props, String)]> { self.content.get(key) .map(|data| data.as_slice()) } } impl<'a> GetValue<'a, String> for Object { fn get(&self, key: &'_ str) -> Option { self.content.get(key) .and_then(|pairs| pairs.first().map(|(_props, value)| value.clone())) } } // TODO: TZID, offset impl<'a> GetValue<'a, Timestamp> for Object { fn get(&self, key: &'_ str) -> Option { let s = self.get(key)?; Timestamp::from_str(s) .map_err(|e| println!("Cannot parse date: {:?}", e)) .ok() } } impl Object { pub fn fmt_rrule(&self) -> Result { let rrule_keys = [ "DTSTART", "RRULE", "RDATE", "EXRULE", "EXDATE", ].iter().cloned().collect::>(); let mut s = String::new(); for (name, values) in &self.content { if ! rrule_keys.contains(name.as_str()) { continue; } for (props, value) in values { write!(s, "{}", name)?; for (key, prop) in props { write!(s, ";{}={}", key, prop)?; } writeln!(s, ":{}", value)?; } } Ok(s) } } #[derive(Debug, Clone)] pub enum Timestamp { Date(NaiveDate), DateTime(NaiveDateTime), } impl FromStr for Timestamp { type Err = String; fn from_str(s: &'_ str) -> Result { NaiveDateTime::parse_from_str(s, "%Y%m%dT%H%M%SZ") .map(|time| DateTime::::from_utc(time, Utc) .with_timezone(&Local) .naive_local() ) .or_else(|_| NaiveDateTime::parse_from_str(s, "%Y%m%dT%H%M%S")) .map(Timestamp::DateTime) .or_else(|_| NaiveDate::parse_from_str(s, "%Y%m%d") .map(Timestamp::Date) ) .map_err(|e| format!("Cannot parse date: {:?}", e)) } } impl Timestamp { pub fn or_hms(self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { match self { Timestamp::Date(date) => date.and_hms(hour, min, sec), Timestamp::DateTime(date_time) => date_time, } } }