parse nested ICS objects

This commit is contained in:
Astro 2019-10-10 18:27:39 +02:00
parent 11dab8a049
commit b20594deac
3 changed files with 88 additions and 13 deletions

View File

@ -15,6 +15,7 @@ pub type Props = Vec<(String, String)>;
pub struct Object { pub struct Object {
pub name: String, pub name: String,
pub content: HashMap<String, (Props, String)>, pub content: HashMap<String, (Props, String)>,
pub children: Vec<Box<Object>>,
} }
impl<'a> GetValue<'a, &'a str> for Object { 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 { impl<'a> GetValue<'a, NaiveDateTime> for Object {
fn get(&self, key: &'_ str) -> Option<NaiveDateTime> { fn get(&self, key: &'_ str) -> Option<NaiveDateTime> {
let s = self.get(key)?; let s = self.get(key)?;

View File

@ -5,22 +5,20 @@ use super::tokenizer::{Tokenizer, Token};
pub struct Parser { pub struct Parser {
tokenizer: Tokenizer, tokenizer: Tokenizer,
object_name: Option<String>,
current_key: Option<String>, current_key: Option<String>,
current_prop: Option<String>, current_prop: Option<String>,
props: Props, props: Props,
content: HashMap<String, (Props, String)>, objects: Vec<Object>,
} }
impl Parser { impl Parser {
pub fn new() -> Self { pub fn new() -> Self {
Parser { Parser {
tokenizer: Tokenizer::new(), tokenizer: Tokenizer::new(),
object_name: None,
current_key: None, current_key: None,
current_prop: None, current_prop: None,
props: vec![], props: vec![],
content: HashMap::new(), objects: vec![],
} }
} }
@ -31,8 +29,7 @@ impl Parser {
let current_key = &mut self.current_key; let current_key = &mut self.current_key;
let current_prop = &mut self.current_prop; let current_prop = &mut self.current_prop;
let props = &mut self.props; let props = &mut self.props;
let object_name = &mut self.object_name; let objects = &mut self.objects;
let content = &mut self.content;
self.tokenizer.feed(input, |token| { self.tokenizer.feed(input, |token| {
match token { match token {
Token::Key(key) => *current_key = Some(key), Token::Key(key) => *current_key = Some(key),
@ -48,15 +45,29 @@ impl Parser {
} }
if compare(current_key, "BEGIN") { if compare(current_key, "BEGIN") {
*object_name = Some(value); objects.push(Object {
content.clear(); name: value,
content: HashMap::new(),
children: vec![],
});
} else if compare(current_key, "END") { } else if compare(current_key, "END") {
let object_name = replace(object_name, None); while objects.len() > 1 {
let content = replace(content, HashMap::new()); let object = objects.pop().unwrap();
object_name.map(|name| f(Object { name, content })); let end = value == object.name;
} else { 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 props = replace(props, vec![]);
let key = replace(current_key, None); 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))); key.map(|key| content.insert(key, (props, value)));
} }
} }
@ -90,6 +101,66 @@ END:VEVENT
.cloned() .cloned()
.map(|(k, v)| (k.to_owned(), (vec![], v.to_owned()))) .map(|(k, v)| (k.to_owned(), (vec![], v.to_owned())))
.collect(), .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![],
})],
})); }));
} }
} }

View File

@ -141,8 +141,11 @@ impl Resources {
len if len > 0 => { len if len > 0 => {
let data = &buf[..len]; let data = &buf[..len];
p.feed(data, |obj| { p.feed(data, |obj| {
let dbg = format!("{:?}", obj);
if let Some(event) = obj_to_event(cal_opts.url.clone(), obj) { if let Some(event) = obj_to_event(cal_opts.url.clone(), obj) {
events.push(event); events.push(event);
} else {
println!("ignore {}", dbg);
} }
}); });
} }
@ -156,7 +159,6 @@ impl Resources {
.filter(schema::events::dsl::calendar.eq(cal_opts.url)) .filter(schema::events::dsl::calendar.eq(cal_opts.url))
.execute(&db)?; .execute(&db)?;
for event in events { for event in events {
// println!("insert {:?}", event);
diesel::insert_into(schema::events::dsl::events) diesel::insert_into(schema::events::dsl::events)
.values(&event) .values(&event)
.execute(&db)?; .execute(&db)?;