lib_tasker/todos/
tasks.rs

1use indexmap::IndexSet;
2use serde::{Deserialize, Serialize};
3use str_slug::slug;
4
5#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
6pub struct Task {
7    pub id: usize,
8    pub description: String,
9    pub state: State,
10    pub tags: IndexSet<String>,
11    pub project: String,
12}
13
14#[derive(
15    Debug,
16    Deserialize,
17    Serialize,
18    Default,
19    PartialEq,
20    Eq,
21    Copy,
22    Clone,
23    PartialOrd,
24    Ord,
25)]
26pub enum State {
27    #[default]
28    ToDo,
29    Doing,
30    Waiting,
31    Done,
32}
33
34impl Task {
35    pub fn create(description: impl Into<String>) -> TaskBuilder {
36        TaskBuilder {
37            id: 0,
38            description: description.into(),
39            state: State::default(),
40            tags: None,
41            project: None,
42        }
43    }
44
45    pub fn add_tag(&mut self, tag: impl Into<String>) {
46        self.tags.insert(slug(tag.into()));
47    }
48
49    pub fn add_tags(
50        &mut self,
51        tags: impl IntoIterator<Item = impl Into<String>>,
52    ) {
53        self.tags
54            .extend(tags.into_iter().map(|tag| slug(tag.into())));
55    }
56
57    pub fn replace_tags(
58        &mut self,
59        tags: impl IntoIterator<Item = impl Into<String>>,
60    ) {
61        self.tags = tags.into_iter().map(|tag| slug(tag.into())).collect();
62    }
63
64    pub fn change_description(&mut self, description: impl Into<String>) {
65        self.description = description.into();
66    }
67
68    pub fn change_state(&mut self, state: State) {
69        self.state = state;
70    }
71}
72
73#[derive(Debug)]
74pub struct TaskBuilder {
75    id: usize,
76    description: String,
77    state: State,
78    tags: Option<IndexSet<String>>,
79    project: Option<String>,
80}
81
82impl TaskBuilder {
83    pub fn id(&mut self, id: usize) -> &mut Self {
84        self.id = id;
85        self
86    }
87
88    pub fn state(&mut self, state: State) -> &mut Self {
89        self.state = state;
90        self
91    }
92
93    pub fn project(&mut self, project: impl Into<String>) -> &mut Self {
94        self.project = Some(project.into());
95        self
96    }
97
98    pub fn tag(&mut self, tag: impl Into<String>) -> &mut Self {
99        if self.tags.is_none() {
100            let mut tags = IndexSet::new();
101            tags.insert(slug(tag.into()));
102
103            self.tags = Some(tags);
104        } else {
105            self.tags.as_mut().map(|tags| tags.insert(slug(tag.into())));
106        }
107
108        self
109    }
110
111    pub fn tags(
112        &mut self,
113        tags: impl IntoIterator<Item = impl Into<String>>,
114    ) -> &mut Self {
115        self.tags =
116            Some(tags.into_iter().map(|tag| slug(tag.into())).collect());
117        self
118    }
119
120    #[must_use]
121    pub fn build(&self) -> Task {
122        Task {
123            id: self.id,
124            description: self.description.clone(),
125            state: self.state,
126            tags: self.tags.clone().unwrap_or_default(),
127            project: self
128                .project
129                .clone()
130                .unwrap_or_else(|| "Inbox".to_string()),
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn task_builder_works() {
141        let task = Task::create("This is a test").build();
142
143        assert_eq!(
144            task,
145            Task {
146                id: 0,
147                description: "This is a test".to_string(),
148                state: State::ToDo,
149                tags: IndexSet::new(),
150                project: "Inbox".to_string()
151            }
152        );
153    }
154
155    #[test]
156    fn task_builder_change_state_works() {
157        let task = Task::create("This is a test").state(State::Waiting).build();
158
159        assert_eq!(
160            task,
161            Task {
162                id: 0,
163                description: "This is a test".to_string(),
164                state: State::Waiting,
165                tags: IndexSet::new(),
166                project: "Inbox".to_string()
167            }
168        );
169    }
170
171    #[test]
172    fn task_builder_change_tag_works() {
173        let task = Task::create("This is a test").tag("Test").build();
174        let mut set = IndexSet::new();
175        set.insert("test".to_string());
176
177        assert_eq!(
178            task,
179            Task {
180                id: 0,
181                description: "This is a test".to_string(),
182                state: State::ToDo,
183                tags: set,
184                project: "Inbox".to_string()
185            }
186        );
187    }
188
189    #[test]
190    fn task_builder_change_tags_works() {
191        let task = Task::create("This is a test")
192            .tags(["Test 1", "Test 2"])
193            .build();
194
195        let mut set = IndexSet::new();
196        set.insert("test-1".to_string());
197        set.insert("test-2".to_string());
198
199        assert_eq!(
200            task,
201            Task {
202                id: 0,
203                description: "This is a test".to_string(),
204                state: State::ToDo,
205                tags: set,
206                project: "Inbox".to_string()
207            }
208        );
209    }
210
211    #[test]
212    fn task_builder_change_project_works() {
213        let task = Task::create("This is a test").project("Testing").build();
214
215        assert_eq!(
216            task,
217            Task {
218                id: 0,
219                description: "This is a test".to_string(),
220                state: State::ToDo,
221                tags: IndexSet::new(),
222                project: "Testing".to_string()
223            }
224        );
225    }
226
227    #[test]
228    fn add_tag_works() {
229        let mut task =
230            Task::create("This is a test").project("Testing").build();
231        task.add_tag("Testing tags");
232
233        assert_eq!(
234            task,
235            Task {
236                id: 0,
237                description: "This is a test".to_string(),
238                state: State::ToDo,
239                tags: IndexSet::from(["testing-tags".to_string()]),
240                project: "Testing".to_string()
241            }
242        );
243    }
244
245    #[test]
246    fn add_tags_works() {
247        let mut task =
248            Task::create("This is a test").project("Testing").build();
249        task.add_tags(["Testing tags", "another tag", "Yet Another Tag"]);
250
251        assert_eq!(
252            task,
253            Task {
254                id: 0,
255                description: "This is a test".to_string(),
256                state: State::ToDo,
257                tags: IndexSet::from([
258                    "testing-tags".to_string(),
259                    "another-tag".to_string(),
260                    "yet-another-tag".to_string()
261                ]),
262                project: "Testing".to_string()
263            }
264        );
265    }
266}