Web-based Calendar Aggregator
https://ticker.c3d2.de/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
3.1 KiB
114 lines
3.1 KiB
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, |
|
} |
|
} |
|
}
|
|
|