use crate::elements::{Element, ElementKind};
use crate::ref_resolver::RefResolver;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElementJson {
#[serde(rename = "ref")]
pub epoch_ref: String,
pub human_ref: Option<String>,
pub date: String,
#[serde(rename = "type")]
pub element_type: String,
pub tags: Vec<String>,
pub body: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct StatsJson {
pub dates: Vec<String>,
pub tasks: TaskStats,
pub notes: usize,
pub logs: LogStats,
pub reminders: usize,
pub characters: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct TaskStats {
pub total: usize,
pub open: usize,
pub done: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct LogStats {
pub total: usize,
pub duration_minutes: i64,
}
#[derive(Debug, Clone, Serialize)]
pub struct ErrorResponse {
pub error: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AppendRequest {
#[serde(rename = "type")]
pub element_type: String,
pub body: String,
#[serde(default)]
pub tags: Vec<String>,
pub status: Option<String>,
pub at: Option<String>,
pub start: Option<String>,
pub end: Option<String>,
pub name: Option<String>,
pub date: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UpdateRequest {
pub status: Option<String>,
pub at: Option<String>,
pub start: Option<String>,
pub end: Option<String>,
pub name: Option<String>,
pub date: Option<String>,
}
pub fn element_to_json(
el: &Element,
epoch_ref: &str,
date_str: &str,
resolver: &RefResolver,
) -> ElementJson {
let human_ref = resolver.to_human(epoch_ref).map(|s| s.to_string());
let date = format_date_str(date_str);
match el {
Element::Task { data, body_str, .. } => ElementJson {
epoch_ref: epoch_ref.to_string(),
human_ref,
date,
element_type: "task".into(),
tags: data.tags.clone(),
body: body_str.trim().to_string(),
status: Some(data.status_str().to_string()),
at: None,
start: None,
end: None,
name: None,
},
Element::Note { data, body_str, .. } => ElementJson {
epoch_ref: epoch_ref.to_string(),
human_ref,
date,
element_type: "note".into(),
tags: data.tags.clone(),
body: body_str.trim().to_string(),
status: None,
at: None,
start: None,
end: None,
name: None,
},
Element::Log { data, body_str, .. } => ElementJson {
epoch_ref: epoch_ref.to_string(),
human_ref,
date,
element_type: "log".into(),
tags: data.tags.clone(),
body: body_str.trim().to_string(),
status: None,
at: None,
start: data.start.clone(),
end: data.end.clone(),
name: None,
},
Element::Reminder { data, body_str, .. } => ElementJson {
epoch_ref: epoch_ref.to_string(),
human_ref,
date,
element_type: "reminder".into(),
tags: data.tags.clone(),
body: body_str.trim().to_string(),
status: None,
at: data.at.clone(),
start: None,
end: None,
name: None,
},
Element::Character { data, body_str, .. } => ElementJson {
epoch_ref: epoch_ref.to_string(),
human_ref,
date,
element_type: "character".into(),
tags: data.tags.clone(),
body: body_str.trim().to_string(),
status: None,
at: None,
start: None,
end: None,
name: data.name.clone(),
},
Element::MpsGroup { body_str, .. } => ElementJson {
epoch_ref: epoch_ref.to_string(),
human_ref,
date,
element_type: "mps".into(),
tags: vec![],
body: body_str.trim().to_string(),
status: None,
at: None,
start: None,
end: None,
name: None,
},
Element::Unknown { sign, body_str, .. } => ElementJson {
epoch_ref: epoch_ref.to_string(),
human_ref,
date,
element_type: sign.clone(),
tags: vec![],
body: body_str.trim().to_string(),
status: None,
at: None,
start: None,
end: None,
name: None,
},
}
}
pub fn format_date_str(date_str: &str) -> String {
if date_str.len() >= 8 {
format!(
"{}-{}-{}",
&date_str[..4],
&date_str[4..6],
&date_str[6..8]
)
} else {
date_str.to_string()
}
}
pub fn compute_stats(
date_elements: Vec<(String, indexmap::IndexMap<String, Element>)>,
) -> StatsJson {
let mut dates = Vec::new();
let mut task_total = 0usize;
let mut task_open = 0usize;
let mut task_done = 0usize;
let mut notes = 0usize;
let mut log_total = 0usize;
let mut log_duration = 0i64;
let mut reminders = 0usize;
let mut characters = 0usize;
for (date_str, els) in date_elements {
dates.push(format_date_str(&date_str));
for (_, el) in &els {
match el.kind() {
ElementKind::Task => {
task_total += 1;
if let Element::Task { data, .. } = el {
if data.is_done() {
task_done += 1;
} else {
task_open += 1;
}
}
}
ElementKind::Note => notes += 1,
ElementKind::Log => {
log_total += 1;
if let Element::Log { data, .. } = el {
if let Some(mins) = data.duration_minutes() {
log_duration += mins as i64;
}
}
}
ElementKind::Reminder => reminders += 1,
ElementKind::Character => characters += 1,
ElementKind::MpsGroup | ElementKind::Unknown => {}
}
}
}
dates.sort();
dates.dedup();
StatsJson {
dates,
tasks: TaskStats {
total: task_total,
open: task_open,
done: task_done,
},
notes,
logs: LogStats {
total: log_total,
duration_minutes: log_duration,
},
reminders,
characters,
}
}