diff --git a/src/ics/mod.rs b/src/ics/mod.rs index 4fa9d54..634302f 100644 --- a/src/ics/mod.rs +++ b/src/ics/mod.rs @@ -15,6 +15,7 @@ pub type Props = Vec<(String, String)>; pub struct Object { pub name: String, pub content: HashMap, + pub children: Vec>, } impl<'a> GetValue<'a, &'a str> for Object { @@ -31,6 +32,7 @@ impl<'a> GetValue<'a, String> for Object { } } +// TODO: TZID, offset impl<'a> GetValue<'a, NaiveDateTime> for Object { fn get(&self, key: &'_ str) -> Option { let s = self.get(key)?; diff --git a/src/ics/parser.rs b/src/ics/parser.rs index 3ca89ba..f673ce0 100644 --- a/src/ics/parser.rs +++ b/src/ics/parser.rs @@ -5,22 +5,20 @@ use super::tokenizer::{Tokenizer, Token}; pub struct Parser { tokenizer: Tokenizer, - object_name: Option, current_key: Option, current_prop: Option, props: Props, - content: HashMap, + objects: Vec, } impl Parser { pub fn new() -> Self { Parser { tokenizer: Tokenizer::new(), - object_name: None, current_key: None, current_prop: None, props: vec![], - content: HashMap::new(), + objects: vec![], } } @@ -31,8 +29,7 @@ impl Parser { let current_key = &mut self.current_key; let current_prop = &mut self.current_prop; let props = &mut self.props; - let object_name = &mut self.object_name; - let content = &mut self.content; + let objects = &mut self.objects; self.tokenizer.feed(input, |token| { match token { Token::Key(key) => *current_key = Some(key), @@ -48,15 +45,29 @@ impl Parser { } if compare(current_key, "BEGIN") { - *object_name = Some(value); - content.clear(); + objects.push(Object { + name: value, + content: HashMap::new(), + children: vec![], + }); } else if compare(current_key, "END") { - let object_name = replace(object_name, None); - let content = replace(content, HashMap::new()); - object_name.map(|name| f(Object { name, content })); - } else { + while objects.len() > 1 { + let object = objects.pop().unwrap(); + let end = value == object.name; + let objects_len = objects.len(); + objects[objects_len - 1].children.push(Box::new(object)); + if end { + break; + } + } + if objects.len() == 1 { + f(objects.remove(0)); + } + } else if objects.len() > 0 { let props = replace(props, vec![]); let key = replace(current_key, None); + let objects_len = objects.len(); + let content = &mut objects[objects_len - 1].content; key.map(|key| content.insert(key, (props, value))); } } @@ -90,6 +101,66 @@ END:VEVENT .cloned() .map(|(k, v)| (k.to_owned(), (vec![], v.to_owned()))) .collect(), + children: vec![], + })); + } + + #[test] + fn parse_props() { + let mut p = Parser::new(); + let mut obj = None; + p.feed(b"BEGIN:VEVENT +SUMMARY:Test event +DTSTART;TZID=Europe/Berlin:19700101 +END:VEVENT + +", |o| { + assert!(obj.is_none()); + obj = Some(o); +}); + assert_eq!(obj, Some(Object { + name: "VEVENT".to_owned(), + content: [ + ("SUMMARY".to_owned(), (vec![], "Test event".to_owned())), + ("DTSTART".to_owned(), (vec![("TZID".to_owned(), "Europe/Berlin".to_owned())], "19700101".to_owned())) + ].iter().cloned().collect(), + children: vec![], + })); + } + + #[test] + fn parse_nested() { + let mut p = Parser::new(); + let mut obj = None; + p.feed(b"BEGIN:VEVENT +SUMMARY:Test event +DTSTART:19700101 +BEGIN:VALARM +ACTION:NONE +END:VALARM +END:VEVENT + +", |o| { + assert!(obj.is_none()); + obj = Some(o); +}); + assert_eq!(obj, Some(Object { + name: "VEVENT".to_owned(), + content: [("SUMMARY", "Test event"), + ("DTSTART", "19700101")] + .iter() + .cloned() + .map(|(k, v)| (k.to_owned(), (vec![], v.to_owned()))) + .collect(), + children: vec![Box::new(Object { + name: "VALARM".to_owned(), + content: [("ACTION", "NONE")] + .iter() + .cloned() + .map(|(k, v)| (k.to_owned(), (vec![], v.to_owned()))) + .collect(), + children: vec![], + })], })); } } diff --git a/src/main.rs b/src/main.rs index d7cd5f1..8e76c17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,8 +141,11 @@ impl Resources { len if len > 0 => { let data = &buf[..len]; p.feed(data, |obj| { + let dbg = format!("{:?}", obj); if let Some(event) = obj_to_event(cal_opts.url.clone(), obj) { events.push(event); + } else { + println!("ignore {}", dbg); } }); } @@ -156,7 +159,6 @@ impl Resources { .filter(schema::events::dsl::calendar.eq(cal_opts.url)) .execute(&db)?; for event in events { - // println!("insert {:?}", event); diesel::insert_into(schema::events::dsl::events) .values(&event) .execute(&db)?;