1use ansi_term::Colour::{Blue, Cyan, Green, Purple, Red, Yellow};
2use ansi_term::Style;
3use regex::Regex;
4use std::fs::{File, OpenOptions};
5use std::io::{self, BufRead, BufReader, BufWriter, Write};
6use std::path::Path;
7use std::path::PathBuf;
8use std::process::exit;
9use xdg::BaseDirectories;
10
11pub struct ToDo {
13 pub todo_path: PathBuf,
14 pub config_path: PathBuf,
15}
16
17impl ToDo {
23 pub fn new() -> Result<Self, String> {
26 let xdg_dir = BaseDirectories::with_prefix("ToDo").expect("Failed to get XDG directories.");
27
28 let config_path = xdg_dir
29 .place_config_file("config.ini")
30 .expect("Unable to create Config file.");
31
32 if !Path::new(&config_path.as_path()).exists() {
34 File::create(&config_path).expect("Failed to create Config file.");
35 }
36
37 let todo_path = xdg_dir
38 .place_config_file("todo.lst")
39 .expect("Unable to create ToDo lst file.");
40
41 if !Path::new(&todo_path.as_path()).exists() {
43 File::create(&todo_path).expect("Failed to create ToDo lst file.");
44 }
45
46 Ok(Self {
47 todo_path,
48 config_path,
49 })
50 }
51
52 pub fn add(&self, args: &[String]) {
54 if args.is_empty() {
55 eprintln!("Add option needs atleast 1 argument.");
56 exit(1);
57 }
58
59 let todo_file = OpenOptions::new()
61 .create(true)
62 .read(true)
63 .append(true)
64 .open(&self.todo_path)
65 .expect("Unable to open todo.lst.");
66 let mut buffer_writter = BufWriter::new(&todo_file);
67
68 for arg in args {
69 if arg.trim().is_empty() {
70 continue;
71 }
72
73 let re_multiple_spaces = Regex::new(r"\s+").unwrap();
75 let formated_task: String =
76 re_multiple_spaces.replace_all(&arg.trim(), "_").to_string();
77 let line: String = format!("{} 0\n", &formated_task);
79
80 buffer_writter
81 .write_all(&line.as_bytes())
82 .expect("Unable to write task in todo.lst.");
83
84 println!("{}: {}", Purple.bold().paint("Added"), arg.trim());
85 }
86 }
87
88 pub fn list(&self, via_status: Option<u8>) {
90 let todo_file = File::open(&self.todo_path).expect("Unable to open todo.lst.");
93 let buffer_reader = BufReader::new(&todo_file);
95
96 let mut index = 1;
97 for line in buffer_reader.lines() {
98 if let Ok(line) = line {
99 let task_details: Vec<&str> = line.split_whitespace().collect();
100 let task_status: u8 = task_details[1].parse::<u8>().unwrap_or(0);
102
103 match via_status {
104 Some(via) => {
105 if via == 1 && task_status == 1 {
106 println!(
107 "{}: {}",
108 Yellow.bold().italic().paint(&index.to_string()),
109 Style::new().paint(task_details[0])
110 );
111 } else if via == 0 && task_status == 0 {
112 println!(
113 "{}: {}",
114 Blue.bold().italic().paint(&index.to_string()),
115 Style::new().paint(task_details[0])
116 );
117 }
118 }
119 None => {
120 if task_status == 1 {
121 println!(
122 "{}: {} {}",
123 Yellow.bold().italic().paint(&index.to_string()),
124 Style::new().strikethrough().italic().paint(task_details[0]),
125 Green.bold().paint("")
126 );
127 } else {
128 println!(
129 "{}: {} {}",
130 Blue.bold().italic().paint(&index.to_string()),
131 &task_details[0].to_string().replace("_", " "),
132 Cyan.bold().paint("")
133 );
134 }
135 }
136 }
137 index += 1;
138 }
139 }
140 }
141
142 pub fn done_undone(&self, args: &[String], status_todo: u8) {
146 if args.is_empty() {
147 eprintln!("done option needs atleast 1 argument.");
148 exit(1);
149 }
150 let done_line_no: Vec<u64> = args[..].iter().map(|z| z.parse::<u64>().unwrap()).collect();
151
152 let mut todo_file = File::open(&self.todo_path).expect("Unable to open todo.lst.");
155 let buffer_reader = BufReader::new(&todo_file);
157
158 let mut new_list: Vec<String> = Vec::new();
159
160 let mut index: u64 = 1;
161 for line in buffer_reader.lines() {
162 let line = line.unwrap();
163 if done_line_no.contains(&index) {
164 let mut task_details: Vec<&str> = line.split_whitespace().collect();
165
166 task_details[1] = if status_todo == 1 { "1" } else { "0" };
168
169 let updated_line: String = format!(
170 "{} {}",
171 task_details[0].to_string(),
172 task_details[1].to_string()
173 );
174
175 match status_todo {
176 1 => println!(
177 "{}: {} : {}",
178 Purple.bold().italic().paint(&index.to_string()),
179 &task_details[0],
180 Green.bold().paint("Completed ")
181 ),
182 0 => println!(
183 "{}: {} : {}",
184 Purple.bold().italic().paint(&index.to_string()),
185 &task_details[0],
186 Cyan.bold().paint("UnDone ")
187 ),
188 _ => println!("{}", Red.paint("Configuration is wrong.")),
189 }
190
191 new_list.push(updated_line);
192 } else {
193 new_list.push(line);
194 }
195 index += 1;
196 }
197
198 todo_file = OpenOptions::new()
200 .read(true)
201 .write(true)
202 .create(true)
203 .truncate(true)
204 .open(&self.todo_path)
205 .expect("Unable to open todo.lst.");
206 let mut buffer_writter = BufWriter::new(&todo_file);
208 for line in new_list {
209 let line: String = format!("{}\n", line);
211 buffer_writter
212 .write_all(&line.as_bytes())
213 .expect("Unable to write to todo.lst.");
214 }
215 }
216
217 pub fn rm(&self, args: &[String]) {
219 if args.is_empty() {
220 eprintln!("rm option needs atleast 1 argument.");
221 exit(1);
222 }
223
224 let mut del_line_no: Vec<u64> =
225 args[..].iter().map(|z| z.parse::<u64>().unwrap()).collect();
226 del_line_no.sort();
227
228 let mut todo_file = File::open(&self.todo_path).expect("Unable to open todo.lst.");
231 let buffer_reader = BufReader::new(&todo_file);
233
234 let mut new_list: Vec<String> = vec![];
235 let mut index: u64 = 1;
236 for line in buffer_reader.lines() {
237 let task_details = line.unwrap_or("".to_string());
238 if !del_line_no.contains(&index) {
239 new_list.push(task_details);
240 } else {
241 let task = task_details.split_whitespace().nth(0).to_owned();
242 println!(
243 "{} {}: {}",
244 Red.bold().paint("Removed"),
245 Purple.bold().italic().paint(&index.to_string()),
246 &task.unwrap_or("")
247 );
248 }
249 index += 1;
250 }
251
252 todo_file = OpenOptions::new()
254 .read(true)
255 .write(true)
256 .create(true)
257 .truncate(true)
258 .open(&self.todo_path)
259 .expect("Unable to open todo.lst.");
260 let mut buffer_writter = BufWriter::new(&todo_file);
262 for line in new_list {
263 let line: String = format!("{}\n", line);
265 buffer_writter
266 .write_all(&line.as_bytes())
267 .expect("Unable to write to todo.lst.");
268 }
269 }
270
271 pub fn rm_all(&self) {
273 let mut confirmation = String::new();
274 println!(
275 "{}: Do you want to remove all tasks from todo ? {}",
276 Red.bold().paint("WARNING"),
277 Blue.bold().paint("(y/Y/yes/Yes/YES)")
278 );
279 io::stdin()
280 .read_line(&mut confirmation)
281 .expect("Unable to take confirmation.");
282
283 let confirm: Vec<String> = vec!["y".to_string(), "yes".to_string()];
284
285 let confirmation = confirmation.trim().to_lowercase();
287
288 if confirm.iter().any(|z| z == &confirmation) {
289 let todo_file = OpenOptions::new()
290 .write(true)
291 .truncate(true)
292 .create(true)
293 .open(&self.todo_path)
294 .expect("Unable to open todo.lst.");
295
296 let mut buffer_writer = BufWriter::new(&todo_file);
297 buffer_writer
298 .write_all(&"".as_bytes())
299 .expect("Unable to write to todo.lst.");
300 println!(
301 "{}",
302 Cyan.bold().paint("All task are removed from todo.lst.")
303 );
304 } else {
305 exit(1);
306 }
307 }
308
309 pub fn sort(&self, via: u8, via_status: Option<u8>) {
315 let todo_file = File::open(&self.todo_path).expect("Unable to open todo.lst.");
316
317 let buffer_reader = BufReader::new(&todo_file);
319 let mut todo_lst: Vec<String> = vec![];
320
321 let mut index = 1;
322 for line in buffer_reader.lines() {
323 if line.is_ok() {
324 let formated_line = format!("{} {}", index, line.unwrap());
325 todo_lst.push(formated_line);
326 }
327 index += 1;
328 }
329 match via {
331 0u8 => {
332 sort_by_key(&mut todo_lst, 1);
333 display_sorted(&todo_lst, via_status);
334 }
335 1u8 => {
336 sort_by_key(&mut todo_lst, 1);
337 todo_lst.reverse();
338 display_sorted(&todo_lst, via_status);
339 }
340 _ => println!("Configuration is incorrect."),
341 }
342 }
343
344 pub fn help(&self) {
345 println!("{}", USAGE_HELP);
346 }
347}
348
349fn display_sorted(todo_lst: &Vec<String>, via_status: Option<u8>) -> () {
354 for task in todo_lst {
355 let task_details: Vec<&str> = task.split_whitespace().collect();
357 let task_status: u8 = task_details[2].parse::<u8>().unwrap_or(0);
358 match via_status {
359 Some(via) => {
360 if via == 1 && task_status == 1 {
361 println!(
362 "{}: {}",
363 Yellow.bold().italic().paint(task_details[0]),
364 Style::new().paint(task_details[1])
365 );
366 } else if via == 0 && task_status == 0 {
367 println!(
368 "{}: {}",
369 Blue.bold().italic().paint(task_details[0]),
370 Style::new().paint(task_details[1])
371 );
372 }
373 }
374 None => {
375 if task_status == 1 {
376 println!(
377 "{}: {} {}",
378 Yellow.bold().italic().paint(task_details[0]),
379 Style::new().strikethrough().italic().paint(task_details[1]),
380 Green.bold().paint("")
381 );
382 } else {
383 println!(
384 "{}: {} {}",
385 Blue.bold().italic().paint(task_details[0]),
386 &task_details[1].to_string().replace("_", " "),
387 Cyan.bold().paint("")
388 );
389 }
390 }
391 }
392 }
393}
394
395fn sort_by_key(todo_lst: &mut Vec<String>, key: usize) -> () {
397 todo_lst.sort_by_key(|line| line.split_whitespace().nth(key).unwrap_or("").to_owned());
398}
399
400const USAGE_HELP: &str = "Usage: todo [OPTIONS] [ARGUMENTS]
402Todo is a blazingly fast CLI program written in Rust.
403
404 - add [TASK/s]: Adds new task/s.
405 Example: todo add 'do at least 10 dynamic programming questions.'
406 Example: todo add task1 task2 task3
407 Example: todo add 'mine task1', 'mine task2', 'mine task3'
408
409 - list | list-all: Lists all tasks.
410 Example: todo list
411 Example: todo list-all
412
413 - list-done: Lists all completed tasks.
414 Example: todo list-done
415
416 - list-undone: Lists all pending tasks.
417 Example: todo list-undone
418
419 - done [INDEX KEY]: Marks task as completed.
420 Example: todo done 5 6
421
422 - undone [INDEX KEY]: Marks task as pending.
423 Example: todo undone 5 6
424
425 - rm [INDEX KEY]: Removes a task.
426 Example: todo rm 2 1 3
427
428 - rm-all | reset: Removes all tasks.
429 Example: todo rm-all
430 Example: todo reset
431
432 - sort: Sorts all tasks (default - ascending order).
433 Example: todo sort
434
435 - sort-asc: Sorts all tasks in ascending order.
436 Example: todo sort-asc
437
438 - sort-dsc: Sorts all tasks in descending order.
439 Example: todo sort-dsc
440
441 - sort-done: Sorts all completed tasks (default - ascending order).
442 Example: todo sort-done
443
444 - sort-done-asc: Sorts all completed tasks in ascending order.
445 Example: todo sort-done-asc
446
447 - sort-done-dsc: Sorts all completed tasks in descending order.
448 Example: todo sort-done-dsc
449
450 - sort-undone: Sorts all pending tasks (default - ascending order).
451 Example: todo sort-undone
452
453 - sort-undone-asc: Sorts all pending tasks in ascending order.
454 Example: todo sort-undone-asc
455
456 - sort-undone-dsc: Sorts all pending tasks in descending order.
457 Example: todo sort-undone-dsc
458
459Report any bugs or issues at: https://github.com/falcon71181/ToDo-rust";