1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use std::collections::BTreeSet;
use std::fmt::{Display, Formatter};
use pest_consume::Parser;
use crate::todo::body_token::TodoBodyToken;
use crate::todo::parser::TodoParserError;
use crate::todo::parser::{Rule, TodoParser};
use crate::TODO_DATE_FORMAT;
mod body_token;
mod builder;
mod parser;
#[derive(Eq, PartialEq, Debug)]
pub struct Todo {
pub is_completed: bool,
pub priority: Option<char>,
pub creation_date: Option<time::Date>,
pub completion_date: Option<time::Date>,
body_tokens: Vec<TodoBodyToken>,
pub threshold_date: Option<time::Date>,
pub due_date: Option<time::Date>,
pub contexts: BTreeSet<String>,
pub projects: BTreeSet<String>,
pub is_hidden: bool,
}
#[allow(dead_code)]
impl Todo {
pub fn has_context(&self, context: &str) -> bool {
if !context.starts_with('@') {
let c = "@".to_string() + context;
return self.has_context(&c);
}
self.contexts.contains(context)
}
pub fn has_project(&self, project: &str) -> bool {
if !project.starts_with('+') {
let c = "+".to_string() + project;
return self.has_project(&c);
}
self.projects.contains(project)
}
pub fn canonical_context(&self) -> Option<String> {
self.contexts.first().map(|s| s.to_owned())
}
pub fn canonical_project(&self) -> Option<String> {
self.projects.first().map(|s| s.to_owned())
}
pub fn parse(input: &str) -> std::result::Result<Todo, TodoParserError> {
let input = input.trim();
if input.is_empty() {
return Err(TodoParserError::EmptyInput);
}
let nodes = TodoParser::parse(Rule::todo, input)?;
let node = nodes.single()?;
let todo = TodoParser::todo(node)?;
Ok(todo)
}
}
impl Display for Todo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut tokens = Vec::new();
if self.is_completed {
tokens.push("x".to_string())
};
if let Some(d) = self.completion_date {
tokens.push(d.format(TODO_DATE_FORMAT))
};
if let Some(p) = self.priority {
tokens.push(format!("({})", p.to_uppercase()))
};
if let Some(d) = self.creation_date {
tokens.push(d.format(TODO_DATE_FORMAT))
};
for t in &self.body_tokens {
tokens.push(format!("{}", t))
}
write!(f, "{}", tokens.join(" "))?;
Ok(())
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn round_tripping_a_todo_with_display() {
let input = "x 2021-01-03 (A) 2021-01-02 impl Display for Todos @pc +tudor due:2021-01-01 t:2021-01-01";
let t = Todo::parse(input).unwrap();
let got = format!("{}", t);
assert_eq!(input, got);
}
}