115 lines
3.1 KiB
Rust
115 lines
3.1 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
use std::fmt::Write;
|
|
use std::str::FromStr;
|
|
use chrono::{NaiveDate, NaiveDateTime};
|
|
|
|
mod tokenizer;
|
|
mod parser;
|
|
pub use parser::Parser;
|
|
|
|
pub trait GetValue<'a, R: 'a> {
|
|
fn get(&'a self, key: &'_ str) -> Option<R>;
|
|
}
|
|
|
|
pub type Props = Vec<(String, String)>;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct Object {
|
|
pub name: String,
|
|
pub content: HashMap<String, Vec<(Props, String)>>,
|
|
pub children: Vec<Box<Object>>,
|
|
}
|
|
|
|
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<String> {
|
|
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<Timestamp> {
|
|
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<String, std::fmt::Error> {
|
|
let rrule_keys = [
|
|
"DTSTART",
|
|
"RRULE", "RDATE",
|
|
"EXRULE", "EXDATE",
|
|
].iter().cloned().collect::<HashSet<_>>();
|
|
|
|
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<Self, Self::Err> {
|
|
NaiveDateTime::parse_from_str(s, "%Y%m%dT%H%M%SZ")
|
|
.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,
|
|
}
|
|
}
|
|
}
|