todo_bin/
lib.rs

1use colored::*;
2use std::fs::OpenOptions;
3use std::io::prelude::Read;
4use std::io::{BufReader, BufWriter, Write};
5use std::{env, path, process};
6
7pub struct Todo {
8    pub todo: Vec<String>,
9    pub home: String,
10}
11
12impl Todo {
13    pub fn new() -> Result<Self, String> {
14        // Instead of creating the TODO file in the current directory,
15        // maybe create it in `$XDG_DATA_HOME/TODO`?
16        let home: String = match env::consts::OS{
17            "linux" => {
18                env::var_os("XDG_DATA_HOME").unwrap_or(env::var_os("HOME").unwrap()).into_string().unwrap()
19            },
20
21            "windows" => {
22                env::var_os("USERPROFILE").unwrap().into_string().unwrap()
23            },
24
25            _ => { panic!() },
26        };
27
28        let todo = path::Path::new("TODO");
29
30        let todofile = OpenOptions::new()
31            .write(true)
32            .read(true)
33            .create(true)
34            .open(path::Path::new(&home).join(todo))
35            .expect("Couldn't open the todofile");
36
37        // Creates a new buf reader
38        let mut buf_reader = BufReader::new(&todofile);
39
40        // Empty String ready to be filled with TODOs
41        let mut contents = String::new();
42
43        // Loads "contents" string with data
44        buf_reader.read_to_string(&mut contents).unwrap();
45
46        // Splits contents of the TODO file into a todo vector
47        let todo = contents.to_string().lines().map(str::to_string).collect();
48
49        // Returns todo
50        Ok(Self { todo, home })
51    }
52
53    // Prints every todo saved
54    pub fn list(&self) -> () {
55        // This loop will repeat itself for each taks in TODO file
56        for (number, task) in self.todo.iter().enumerate() {
57            if task.len() > 5 {
58                // Converts virgin default number into a chad BOLD string
59                let number = (number + 1).to_string().bold();
60
61                // Saves the symbol of current task
62                let symbol = &task[..4];
63                // Saves a task without a symbol
64                let task = &task[4..];
65
66                // Checks if the current task is completed or not...
67                if symbol == "[*] " {
68                    // DONE
69                    // If the task is completed, then it prints it with a strikethrough
70                    println!("{} {}", number, task.strikethrough());
71                } else if symbol == "[ ] " {
72                    // NOT DONE
73                    // If the task is not completed yet, then it will print it as it is
74                    println!("{} {}", number, task);
75                }
76            }
77        }
78    }
79
80    // This one is for yall, dmenu chads <3
81    pub fn raw(&self, arg: &[String]) {
82        if arg.len() > 1 {
83            eprintln!("todo raw takes only 1 argument, not {}", arg.len())
84        } else if arg.len() < 1 {
85            eprintln!("todo raw takes 1 argument (done/todo)");
86        } else {
87            // This loop will repeat itself for each taks in TODO file
88            for task in self.todo.iter() {
89                if task.len() > 5 {
90                    // Saves the symbol of current task
91                    let symbol = &task[..4];
92                    // Saves a task without a symbol
93                    let task = &task[4..];
94
95                    // Checks if the current task is completed or not...
96                    if symbol == "[*] " && arg[0] == "done" {
97                        // DONE
98                        //If the task is completed, then it prints it with a strikethrough
99                        println!("{}", task);
100                    } else if symbol == "[ ] " && arg[0] == "todo" {
101                        // NOT DONE
102
103                        //If the task is not completed yet, then it will print it as it is
104                        println!("{}", task);
105                    }
106                }
107            }
108        }
109    }
110    // Adds a new todo
111    pub fn add(&self, args: &[String]) {
112        if args.len() < 1 {
113            eprintln!("todo add takes at least 1 argument");
114            process::exit(1);
115        } else {
116            let todo = path::Path::new("TODO");
117
118            // Opens the TODO file with a permission to:
119            let todofile = OpenOptions::new()
120                .create(true) // a) create the file if it does not exist
121                .append(true) // b) append a line to it
122                .open(path::Path::new(&self.home).join(todo))
123                .expect("Couldn't open the todofile");
124
125            let mut buffer = BufWriter::new(todofile);
126            for arg in args {
127                if arg.trim().len() < 1 {
128                    continue;
129                }
130
131                let line = format!("[ ] {}\n", arg);
132                buffer
133                    .write_all(line.as_bytes())
134                    .expect("unable to write data");
135            }
136
137            // Appends a new task/s to the file
138        }
139    }
140
141    // Removes a task
142    pub fn remove(&self, args: &[String]) {
143        if args.len() < 1 {
144            eprintln!("todo rm takes at least 1 argument");
145            process::exit(1);
146        } else {
147            let todo = path::Path::new("TODO");
148
149            // Opens the TODO file with a permission to:
150            let todofile = OpenOptions::new()
151                .write(true) // a) write
152                .truncate(true) // b) truncrate
153                .open(path::Path::new(&self.home).join(todo))
154                .expect("Couldn't open the todo file");
155
156            let mut buffer = BufWriter::new(todofile);
157
158            for (pos, line) in self.todo.iter().enumerate() {
159                if args.contains(&"done".to_string()) && &line[..4] == "[*] " {
160                    continue;
161                }
162                if args.contains(&(pos + 1).to_string()) {
163                    continue;
164                }
165
166                let line = format!("{}\n", line);
167
168                buffer
169                    .write_all(line.as_bytes())
170                    .expect("unable to write data");
171            }
172        }
173    }
174
175    // Sorts done tasks
176    pub fn sort(&self) {
177        // Creates a new empty string
178        let newtodo: String;
179
180        let mut todo = String::new();
181        let mut done = String::new();
182
183        for line in self.todo.iter() {
184            if line.len() > 5 {
185                if &line[..4] == "[ ] " {
186                    let line = format!("{}\n", line);
187                    todo.push_str(&line);
188                } else if &line[..4] == "[*] " {
189                    let line = format!("{}\n", line);
190                    done.push_str(&line);
191                }
192            }
193        }
194
195        newtodo = format!("{}{}", &todo, &done);
196        // Opens the TODO file with a permission to:
197        let mut todofile = OpenOptions::new()
198            .write(true) // a) write
199            .truncate(true) // b) truncrate
200            .open("TODO")
201            .expect("Couldn't open the todo file");
202
203        // Writes contents of a newtodo variable into the TODO file
204        todofile
205            .write_all(newtodo.as_bytes())
206            .expect("Error while trying to save the todofile");
207    }
208
209    pub fn done(&self, args: &[String]) {
210        if args.len() < 1 {
211            eprintln!("todo done takes at least 1 argument");
212            process::exit(1);
213        } else {
214            // Opens the TODO file with a permission to overwrite it
215            let todofile = OpenOptions::new()
216                .write(true)
217                .open("TODO")
218                .expect("Couldn't open the todofile");
219            let mut buffer = BufWriter::new(todofile);
220
221            for (pos, line) in self.todo.iter().enumerate() {
222                if line.len() > 5 {
223                    if args.contains(&(pos + 1).to_string()) {
224                        if &line[..4] == "[ ] " {
225                            let line = format!("[*] {}\n", &line[4..]);
226                            buffer
227                                .write_all(line.as_bytes())
228                                .expect("unable to write data");
229                        } else if &line[..4] == "[*] " {
230                            let line = format!("[ ] {}\n", &line[4..]);
231                            buffer
232                                .write_all(line.as_bytes())
233                                .expect("unable to write data");
234                        }
235                    } else {
236                        if &line[..4] == "[ ] " || &line[..4] == "[*] " {
237                            let line = format!("{}\n", line);
238                            buffer
239                                .write_all(line.as_bytes())
240                                .expect("unable to write data");
241                        }
242                    }
243                }
244            }
245        }
246    }
247}
248
249const TODO_HELP: &str = "Usage: todo [COMMAND] [ARGUMENTS]
250Todo is a super fast and simple tasks organizer written in rust
251Example: todo list
252Available commands:
253    - add [TASK/s] 
254        adds new task/s
255        Example: todo add \"buy carrots\"
256    - list
257        lists all tasks
258        Example: todo list
259    - done [INDEX]
260        marks task as done
261        Example: todo done 2 3 (marks second and third tasks as completed)
262    - rm [INDEX] 
263        removes a task
264        Example: todo rm 4 
265    - sort
266        sorts completed and uncompleted tasks
267        Example: todo sort 
268    - raw [todo/done]
269        prints nothing but done/incompleted tasks in plain text, useful for scripting
270        Example: todo raw done
271";
272
273pub fn help() {
274    // For readability
275    println!("{}", TODO_HELP);
276}