1pub fn format_all_day(entry: &crate::record::Record) -> String {
2 format!(
3 "All Day Event: {}{}",
4 entry.detail(),
5 if entry.completed() { "- Completed" } else { "" }
6 )
7}
8
9pub fn format_at(entry: &crate::record::Record, at: chrono::NaiveTime) -> String {
10 format!(
11 "{} at {}: {}{}",
12 entry.date(),
13 at,
14 entry.detail(),
15 if entry.completed() { "- Completed" } else { "" }
16 )
17}
18
19pub fn format_scheduled(
20 entry: &crate::record::Record,
21 schedule: crate::record::Schedule,
22) -> String {
23 format!(
24 "{} at {} to {}: {}{}",
25 entry.date(),
26 schedule.0,
27 schedule.1,
28 entry.detail(),
29 if entry.completed() { "- Completed" } else { "" }
30 )
31}
32
33#[macro_export]
34macro_rules! launch_editor {
35 ($db: ident, $id: ident, $typ:ty, $fetch:ident, $recur: ident) => {{
36 let record = $db.$fetch($id).await?;
37 let presented: $typ = record.clone().into();
38 let f = tempfile::NamedTempFile::new()?;
39 serde_yaml::to_writer(&f, &presented)?;
40 let (f, path) = f.keep()?;
41 drop(f);
42 let mut cmd = tokio::process::Command::new(
43 std::env::var("EDITOR").unwrap_or("/usr/bin/vim".to_string()),
44 );
45 cmd.args([path.clone()]);
46 let mut child = cmd.spawn()?;
47 if child.wait().await?.success() {
48 let mut io = std::fs::OpenOptions::new();
49 io.read(true);
50 let f = io.open(path)?;
51 let presented: $typ = serde_yaml::from_reader(&f)?;
52 $crate::update_record!($db, presented, record, $recur);
53 }
54 }};
55}
56
57#[macro_export]
58macro_rules! map_record {
59 ($db: ident, $id:ident, true) => {{
60 $db.get_recurring($id).await
61 }};
62 ($db: ident, $id:ident, false) => {{
63 $db.get($id).await
64 }};
65 ($db: ident, $id: ident) => {{
66 map_record!($db, $id, false)
67 }};
68}
69
70#[macro_export]
71macro_rules! update_record {
72 ($db: ident, $presented: ident, $record:ident, true) => {{
73 $db.update_recurring($presented.to_record(
74 $record.clone().record().primary_key(),
75 $record.recurrence_key(),
76 $record.clone().record().internal_key(),
77 $record.internal_key(),
78 ))
79 .await?;
80 }};
81 ($db: ident, $presented: ident, $record:ident, false) => {{
82 $db.update($presented.to_record(
83 $record.primary_key(),
84 $record.recurrence_key(),
85 $record.internal_key(),
86 $record.internal_recurrence_key(),
87 ))
88 .await?;
89 }};
90}
91
92#[macro_export]
94macro_rules! process_cli {
95 ($cli:ident, $config:ident, $db:ident) => {
96 use $crate::db::google::GoogleClient;
97
98 process_cli!($cli, $config, $db, None::<GoogleClient>);
99 };
100 ($cli:ident, $config:ident, $db:ident, $client:expr) => {{
101 use chrono::{Datelike, Timelike};
102 use $crate::cli_processor::{format_all_day, format_at, format_scheduled};
103 $db.load().await?;
104
105 match $cli.command {
106 Command::Config { command } => match command {
107 ConfigCommand::SetQueryWindow { set } => {
108 let mut config = Config::load(None)?;
109 config.set_query_window(FancyDuration::parse(&set)?.duration());
110 config.save(None)?;
111 }
112 ConfigCommand::Set24hTime { set } => {
113 let mut config = Config::load(None)?;
114 config.set_use_24h_time(set);
115 config.save(None)?;
116 }
117 ConfigCommand::SetClient {
118 client_id,
119 client_secret,
120 } => {
121 let mut config = Config::load(None)?;
122 config.set_client_info(client_id, client_secret);
123 config.save(None)?;
124 }
125 ConfigCommand::GetToken {} => $crate::oauth::get_access_token().await?,
126 ConfigCommand::DBType { db_type } => {
127 let mut config = Config::load(None)?;
128 let typ = match db_type.as_str() {
129 "google" => DBType::Google,
130 "unixfile" => DBType::UnixFile,
131 _ => {
132 return Err(anyhow!(
133 "Invalid db type: valid types are `google` and `unixfile`"
134 ))
135 }
136 };
137
138 config.set_db_type(typ);
139 config.save(None)?;
140 }
141 ConfigCommand::ListCalendars => {
142 if $client.is_none() {
143 eprintln!("Not supported in unixfile mode");
144 } else {
145 list_calendars($client.unwrap()).await?;
146 }
147 }
148 ConfigCommand::SetCalendarID { id } => {
149 if $client.is_none() {
150 eprintln!("Not supported in unixfile mode");
151 } else {
152 set_calendar_id(id, $config)?;
153 }
154 }
155 ConfigCommand::SetDefaultDuration { duration } => {
156 let duration: fancy_duration::FancyDuration<chrono::Duration> =
157 fancy_duration::FancyDuration::parse(&duration)?;
158 let mut config = $crate::config::Config::load(None)?;
159 config.set_default_duration(Some(duration));
160 config.save(None)?;
161 }
162 },
163 Command::Complete { id } => $db.complete_task(id).await?,
164 Command::Delete { ids, recur } => {
165 for id in ids {
166 if recur {
167 $db.delete_recurrence(id).await?;
168 } else {
169 $db.delete(id).await?;
170 }
171 }
172 }
173 Command::Notify {
174 well,
175 timeout,
176 include_completed,
177 icon,
178 } => {
179 let timeout = timeout.map_or(std::time::Duration::new(60, 0), |t| {
180 fancy_duration::FancyDuration::<std::time::Duration>::parse(&t)
181 .expect("Invalid Duration")
182 .duration()
183 });
184
185 let mut notification = notify_rust::Notification::new();
186 notification.summary("Calendar Event");
187 notification.timeout(timeout);
188 let well = well.map_or_else(
189 || chrono::TimeDelta::try_minutes(1).unwrap_or_default(),
190 |x| {
191 FancyDuration::<chrono::Duration>::parse(&x)
192 .expect("Invalid duration")
193 .duration()
194 },
195 );
196
197 let time = $crate::time::now()
198 .with_second(0)
199 .unwrap()
200 .with_nanosecond(0)
201 .unwrap();
202
203 for entry in &$db.list_all(include_completed).await? {
204 if let Some(notifications) = entry.notifications() {
205 for duration in notifications.iter().map(FancyDuration::duration) {
206 let notify = match entry.record_type() {
207 $crate::record::RecordType::AllDay => {
208 let top = (entry.datetime() + chrono::TimeDelta::try_days(1).unwrap_or_default())
209 .with_hour(0)
210 .unwrap()
211 .with_minute(0)
212 .unwrap()
213 .with_second(0)
214 .unwrap()
215 .with_nanosecond(0)
216 .unwrap()
217 - duration;
218
219 if time - well < top && time + well > top {
220 Some(notification.body(&format_all_day(entry)))
221 } else {
222 None
223 }
224 }
225 $crate::record::RecordType::At => {
226 let at = entry.at().unwrap() - duration;
227 let top = (chrono::NaiveDateTime::new(entry.date(), at)
228 - duration)
229 .and_local_timezone(chrono::Local)
230 .unwrap();
231 if time - well < top && time + well > top {
232 Some(notification.body(&format_at(entry, at)))
233 } else {
234 None
235 }
236 }
237 $crate::record::RecordType::Schedule => {
238 let schedule = entry.scheduled().unwrap();
239 let top =
240 (chrono::NaiveDateTime::new(entry.date(), schedule.0)
241 - duration)
242 .and_local_timezone(chrono::Local)
243 .unwrap();
244
245 if time - well < top && time + well > top {
246 Some(notification.body(&format_scheduled(entry, schedule)))
247 } else {
248 None
249 }
250 }
251 };
252
253 if let Some(mut notify) = notify {
254 if let Some(icon) = icon.clone() {
255 notify = notify.icon(&icon);
256 }
257
258 notify.show()?;
259 }
260 }
261 }
262 }
263 }
264 Command::Now {
265 well,
266 include_completed,
267 } => {
268 print_entries($db.events_now(get_well(well)?, include_completed).await?);
269 }
270 Command::List { all, recur } => {
271 if recur {
272 print_recurring($db.list_recurrence().await?);
273 } else {
274 let mut list = if all {
275 $db.list_all(false).await?
276 } else {
277 $db.list_today(false).await?
278 };
279 list.sort_by($crate::record::sort_records);
280 print_entries(list);
281 }
282 }
283 Command::Today {} => {
284 print_entries($db.list_today(false).await?);
285 }
286 Command::Entry { args } => {
287 $db.list_all(false).await?;
288 $db.record_entry($crate::parsers::entry::EntryParser::new(
289 args,
290 $config.use_24h_time(),
291 ))
292 .await?;
293 }
294 Command::Edit { recur, id } => {
295 if recur {
296 $crate::launch_editor!(
297 $db,
298 id,
299 $crate::record::PresentedRecurringRecord,
300 get_recurring,
301 true
302 );
303 } else {
304 $crate::launch_editor!($db, id, $crate::record::PresentedRecord, get, false);
305 }
306 }
307 Command::Show { recur, id } => {
308 if recur {
309 let presented: $crate::record::PresentedRecurringRecord =
310 $db.get_recurring(id).await?.into();
311 println!("{}", serde_yaml::to_string(&presented)?);
312 } else {
313 let presented: $crate::record::PresentedRecord = $db.get(id).await?.into();
314 println!("{}", serde_yaml::to_string(&presented)?);
315 }
316 }
317 Command::Search { terms } => {
318 let parser =
319 $crate::parsers::search::SearchParser::new(terms, $db.list_all(false).await?);
320 print_entries(parser.perform()?);
321 }
322 }
323
324 $db.dump().await?;
325 };};
326}
327
328#[macro_export]
329macro_rules! list_ui {
330 ($db:ident, $list_type:ident) => {{
331 $db.load().await?;
332
333 let all = match $list_type {
334 $crate::ui::types::ListType::All => $db.list_all(true).await?,
335 $crate::ui::types::ListType::Today => $db.list_today(true).await?,
336 $crate::ui::types::ListType::Recurring | $crate::ui::types::ListType::Search => {
337 Vec::new()
338 }
339 };
340
341 $db.dump().await?;
342
343 Ok(all)
344 }};
345}
346
347#[macro_export]
348macro_rules! process_ui_command {
349 ($obj:ident, $db:ident, $config:ident) => {{
350 let mut lock = $obj.lock().await;
351 let commands = lock.commands.clone();
352 lock.commands = Vec::new();
353 lock.block_ui = true;
354 drop(lock);
355 $db.load().await?;
356 for command in commands {
357 match command {
358 $crate::ui::types::CommandType::Search(terms) => {
359 let parser = $crate::parsers::search::SearchParser::new(
360 terms,
361 $db.list_all(false).await?,
362 );
363 let mut inner = $obj.lock().await;
364 inner.list_type = $crate::ui::types::ListType::Search;
365 inner.records = parser.perform()?;
366 inner.records.sort_by($crate::record::sort_records);
367 inner.redraw = true;
368 }
369 $crate::ui::types::CommandType::Delete(items) => {
370 for item in items {
371 $db.delete(item).await?
372 }
373 }
374 $crate::ui::types::CommandType::DeleteRecurring(items) => {
375 for item in items {
376 $db.delete_recurrence(item).await?;
377 }
378 }
379 $crate::ui::types::CommandType::Entry(entry) => {
380 $db.list_all(false).await?;
381 let parts = entry
382 .split(' ')
383 .filter(|x| !x.is_empty())
384 .map(|s| s.to_string())
385 .collect::<Vec<String>>();
386 $db.record_entry($crate::parsers::entry::EntryParser::new(
387 parts,
388 $config.use_24h_time(),
389 ))
390 .await?;
391 }
392 $crate::ui::types::CommandType::Edit(recur, id) => {
393 if recur {
394 $crate::launch_editor!(
395 $db,
396 id,
397 $crate::record::PresentedRecurringRecord,
398 get_recurring,
399 true
400 );
401 } else {
402 $crate::launch_editor!(
403 $db,
404 id,
405 $crate::record::PresentedRecord,
406 get,
407 false
408 );
409 }
410 }
411 $crate::ui::types::CommandType::Show(recur, id) => {
412 if recur {
413 let mut lock = $obj.lock().await;
414 lock.show_recurring = Some($db.get_recurring(id).await?);
415 drop(lock);
416 } else {
417 let mut lock = $obj.lock().await;
418 lock.show = Some($db.get(id).await?);
419 drop(lock);
420 }
421 }
422 };
423 }
424 $db.dump().await?;
425 let mut lock = $obj.lock().await;
426 lock.block_ui = false;
427 }};
428}