parse nested ICS objects
This commit is contained in:
parent
11dab8a049
commit
b20594deac
|
@ -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)?;
|
||||||
|
|
|
@ -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![],
|
||||||
|
})],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
Loading…
Reference in New Issue