use std::fs;
use std::io::BufReader;
use ical::IcalParser;
use ical::parser::ical::component::{
IcalCalendar, IcalEvent, IcalJournal, IcalTodo,
};
use ical::property::Property;
use serde_json::{Map, Value, json};
use crate::{Error, Job, Result};
pub fn ics_to_json(job: &Job) -> Result<()> {
let file = fs::File::open(job.input)?;
let reader = BufReader::new(file);
let parser = IcalParser::new(reader);
let mut calendars: Vec<Value> = Vec::new();
for cal in parser {
let cal = cal.map_err(|e| Error::other(format!("ics parse: {e}")))?;
calendars.push(build_calendar(&cal));
}
let value = if calendars.len() == 1 {
calendars.into_iter().next().unwrap()
} else {
Value::Array(calendars)
};
let out = serde_json::to_string_pretty(&value)
.map_err(|e| Error::other(format!("json serialize: {e}")))?;
fs::write(job.output, out + "\n")?;
Ok(())
}
fn build_calendar(cal: &IcalCalendar) -> Value {
let mut obj = Map::new();
obj.insert("properties".into(), properties_json(&cal.properties));
let events: Vec<Value> = cal.events.iter().map(build_event).collect();
if !events.is_empty() {
obj.insert("events".into(), Value::Array(events));
}
let todos: Vec<Value> = cal.todos.iter().map(build_todo).collect();
if !todos.is_empty() {
obj.insert("todos".into(), Value::Array(todos));
}
let journals: Vec<Value> = cal.journals.iter().map(build_journal).collect();
if !journals.is_empty() {
obj.insert("journals".into(), Value::Array(journals));
}
Value::Object(obj)
}
fn build_event(ev: &IcalEvent) -> Value {
let mut obj = Map::new();
obj.insert("properties".into(), properties_json(&ev.properties));
if !ev.alarms.is_empty() {
let alarms: Vec<Value> = ev
.alarms
.iter()
.map(|a| json!({ "properties": properties_json(&a.properties) }))
.collect();
obj.insert("alarms".into(), Value::Array(alarms));
}
Value::Object(obj)
}
fn build_todo(todo: &IcalTodo) -> Value {
let mut obj = Map::new();
obj.insert("properties".into(), properties_json(&todo.properties));
if !todo.alarms.is_empty() {
let alarms: Vec<Value> = todo
.alarms
.iter()
.map(|a| json!({ "properties": properties_json(&a.properties) }))
.collect();
obj.insert("alarms".into(), Value::Array(alarms));
}
Value::Object(obj)
}
fn build_journal(journal: &IcalJournal) -> Value {
json!({ "properties": properties_json(&journal.properties) })
}
fn properties_json(props: &[Property]) -> Value {
let arr: Vec<Value> = props
.iter()
.map(|p| {
let mut obj = Map::new();
obj.insert("name".into(), Value::String(p.name.clone()));
if let Some(v) = &p.value {
obj.insert("value".into(), Value::String(v.clone()));
}
if let Some(params) = &p.params
&& !params.is_empty()
{
let mut pm = Map::new();
for (k, vs) in params {
pm.insert(
k.clone(),
Value::Array(vs.iter().map(|v| json!(v)).collect()),
);
}
obj.insert("params".into(), Value::Object(pm));
}
Value::Object(obj)
})
.collect();
Value::Array(arr)
}