1use caretta_id::CarettaId;
2use chrono::NaiveDateTime;
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Frontmatter {
9 pub id: CarettaId,
10
11 #[serde(default)]
12 pub title: String,
13
14 #[serde(skip_serializing_if = "Option::is_none")]
16 pub slug: Option<String>,
17
18 #[serde(default, with = "naive_datetime_serde")]
20 pub created_at: NaiveDateTime,
21
22 #[serde(default, with = "naive_datetime_serde")]
24 pub updated_at: NaiveDateTime,
25
26 #[serde(default, skip_serializing_if = "Vec::is_empty")]
27 pub tags: Vec<String>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub task: Option<TaskMeta>,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub event: Option<EventMeta>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct TaskMeta {
44 #[serde(default, skip_serializing_if = "Option::is_none", with = "naive_datetime_serde::eod::opt")]
46 pub due: Option<NaiveDateTime>,
47
48 #[serde(default = "default_task_status")]
50 pub status: String,
51
52 #[serde(default, skip_serializing_if = "Option::is_none", with = "naive_datetime_serde::opt")]
55 pub closed_at: Option<NaiveDateTime>,
56}
57
58fn default_task_status() -> String {
59 "open".to_owned()
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct EventMeta {
65 #[serde(with = "naive_datetime_serde")]
66 pub start: NaiveDateTime,
67 #[serde(with = "naive_datetime_serde::eod")]
68 pub end: NaiveDateTime,
69}
70
71#[derive(Debug, Clone)]
74pub struct Entry {
75 pub path: PathBuf,
77
78 pub frontmatter: Frontmatter,
80
81 pub body: String,
83}
84
85impl Entry {
86 pub fn id(&self) -> CarettaId {
88 self.frontmatter.id
89 }
90
91 pub fn title(&self) -> &str {
93 return &self.frontmatter.title;
94 }
95}
96
97mod naive_datetime_serde {
105 use chrono::{NaiveDate, NaiveDateTime};
106 use serde::{Deserialize, Deserializer, Serializer};
107
108 const FORMAT: &str = "%Y-%m-%dT%H:%M";
109
110 pub(super) fn parse_with_fallback(s: &str, h: u32, m: u32) -> Result<NaiveDateTime, String> {
112 for fmt in [FORMAT, "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%.f"] {
113 if let Ok(dt) = NaiveDateTime::parse_from_str(s, fmt) {
114 return Ok(dt);
115 }
116 }
117 if let Ok(d) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
118 return Ok(d.and_hms_opt(h, m, 0).unwrap());
119 }
120 Err(format!(
121 "cannot parse `{s}` as a datetime; expected format YYYY-MM-DDTHH:MM"
122 ))
123 }
124
125 pub fn serialize<S: Serializer>(dt: &NaiveDateTime, s: S) -> Result<S::Ok, S::Error> {
126 s.serialize_str(&dt.format(FORMAT).to_string())
127 }
128
129 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<NaiveDateTime, D::Error> {
131 let raw = String::deserialize(d)?;
132 parse_with_fallback(&raw, 0, 0).map_err(serde::de::Error::custom)
133 }
134
135 pub mod opt {
137 use chrono::NaiveDateTime;
138 use serde::{Deserialize, Deserializer, Serializer};
139
140 pub fn serialize<S: Serializer>(
141 opt: &Option<NaiveDateTime>,
142 s: S,
143 ) -> Result<S::Ok, S::Error> {
144 match opt {
145 Some(dt) => super::serialize(dt, s),
146 None => s.serialize_none(),
147 }
148 }
149
150 pub fn deserialize<'de, D: Deserializer<'de>>(
151 d: D,
152 ) -> Result<Option<NaiveDateTime>, D::Error> {
153 let raw = String::deserialize(d)?;
154 super::parse_with_fallback(&raw, 0, 0).map(Some).map_err(serde::de::Error::custom)
155 }
156 }
157
158 pub mod eod {
160 use chrono::NaiveDateTime;
161 use serde::{Deserialize, Deserializer, Serializer};
162
163 pub fn serialize<S: Serializer>(dt: &NaiveDateTime, s: S) -> Result<S::Ok, S::Error> {
164 super::serialize(dt, s)
165 }
166
167 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<NaiveDateTime, D::Error> {
168 let raw = String::deserialize(d)?;
169 super::parse_with_fallback(&raw, 23, 59).map_err(serde::de::Error::custom)
170 }
171
172 pub mod opt {
173 use chrono::NaiveDateTime;
174 use serde::{Deserialize, Deserializer, Serializer};
175
176 pub fn serialize<S: Serializer>(
177 opt: &Option<NaiveDateTime>,
178 s: S,
179 ) -> Result<S::Ok, S::Error> {
180 match opt {
181 Some(dt) => super::serialize(dt, s),
182 None => s.serialize_none(),
183 }
184 }
185
186 pub fn deserialize<'de, D: Deserializer<'de>>(
187 d: D,
188 ) -> Result<Option<NaiveDateTime>, D::Error> {
189 let raw = String::deserialize(d)?;
190 super::super::parse_with_fallback(&raw, 23, 59)
191 .map(Some)
192 .map_err(serde::de::Error::custom)
193 }
194 }
195 }
196}