From 80d147bbe33233dff38c76f9cb0376fab6b991ee Mon Sep 17 00:00:00 2001 From: Todd Hainsworth Date: Tue, 30 Nov 2021 21:50:13 +1030 Subject: [PATCH] Change to "kind of" a repository pattern NOTE: THIS IS A BREAKING CHANGE The main aim of this was to take all the item handling out of main.rs which I think we've accomplished. There's still ALOT of duplication in there, so that's the next big change --- Cargo.lock | 12 ++++- Cargo.toml | 3 +- src/item.rs | 84 +++++++++++++++++++++++++++++++++++ src/main.rs | 113 +++++++++++------------------------------------ src/todo_item.rs | 46 ------------------- 5 files changed, 123 insertions(+), 135 deletions(-) create mode 100644 src/item.rs delete mode 100644 src/todo_item.rs diff --git a/Cargo.lock b/Cargo.lock index 753c600..6b9ac8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,15 @@ dependencies = [ "libc", ] +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi", +] + [[package]] name = "itoa" version = "0.4.8" @@ -217,11 +226,12 @@ dependencies = [ [[package]] name = "todo" -version = "0.2.2" +version = "0.3.0" dependencies = [ "clap", "colored", "dirs", + "home", "serde", "serde_derive", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 289280f..cc27985 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "todo" -version = "0.2.2" +version = "0.3.0" authors = ["Todd Hainsworth "] [dependencies] @@ -10,3 +10,4 @@ serde_derive = "1.0" dirs = "4.0" colored = "2.0" clap = "2.32.0" +home = "0.5.3" diff --git a/src/item.rs b/src/item.rs new file mode 100644 index 0000000..58d6dcc --- /dev/null +++ b/src/item.rs @@ -0,0 +1,84 @@ +use serde_json; +use std::fs; +use std::io::Result; +use home::home_dir; + +const TODO_FILENAME: &'static str = ".todos"; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Item { + pub id: usize, + pub text: String, + pub completed: bool, +} + +impl Item { + pub fn new(id: usize, text: &str, completed: bool) -> Self { + Self { + id, + text: String::from(text), + completed, + } + } +} + +impl Default for Item { + fn default() -> Self { + Item::new(0, "", false) + } +} + +pub struct ItemRepository { + items: Vec, +} + +impl ItemRepository { + pub fn new() -> Result { + let item_json = ItemRepository::load_items()?; + let items: Vec = serde_json::from_str(&item_json)?; + Ok(Self { items }) + } + + pub fn delete(&mut self, id: usize) { + self.items.retain(|item| item.id != id) + } + + pub fn publish(self) -> Result<()> { + let path = Self::get_todo_file_path(); + let buf = serde_json::to_string(&self.items).unwrap(); + fs::write(path, buf) + } + + pub fn toggle(&mut self, id: usize) { + self.items + .iter_mut() + .find(|item| item.id == id) + .map(|item| item.completed = !item.completed); + } + + pub fn update_text(&mut self, id: usize, text: &str) { + self.items + .iter_mut() + .find(|item| item.id == id) + .map(|item| item.text = text.to_string()); + } + + pub fn add(&mut self, text: &str) { + let id = self.items.iter().map(|item| item.id).max().unwrap_or(0) + 1; + self.items.push(Item::new(id, text, false)) + } + + pub fn items(&mut self) -> &mut Vec { + self.items.as_mut() + } + + fn load_items() -> Result { + fs::read_to_string(Self::get_todo_file_path()) + } + + fn get_todo_file_path() -> String { + // unwrapping because if this were to fail then there's something _really_ wrong with the users setup... + let home = home_dir().unwrap(); + format!("{}/{}", home.display(), TODO_FILENAME) + } +} diff --git a/src/main.rs b/src/main.rs index 40cbca2..d775d52 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate colored; extern crate dirs; extern crate serde; extern crate serde_json; +extern crate home; #[macro_use] extern crate serde_derive; @@ -12,9 +13,9 @@ use std::process; use clap::{App, Arg}; -mod todo_item; +mod item; -use todo_item::TodoItem; +use item::ItemRepository; fn main() { let matches = App::new("Todo") @@ -35,18 +36,7 @@ fn main() { .help("edit an entry") .value_delimiter("-"), ) - .arg( - Arg::with_name("TEXT") - .help("The todo item text") - .index(1) - ) - .arg( - Arg::with_name("priority") - .short("p") - .long("priority") - .help("change the priority of an entry") - .value_delimiter(" "), - ) + .arg(Arg::with_name("TEXT").help("The todo item text").index(1)) .arg( Arg::with_name("complete") .short("c") @@ -57,48 +47,29 @@ fn main() { ) .get_matches(); - let f = match todo_item::get_todo_file() { - Ok(text) => text, + let mut repository = match ItemRepository::new() { + Ok(r) => r, Err(e) => { - eprintln!("Could not read todo file: {}", e); + eprintln!("Failed to load items: {}", e); process::exit(1); } }; - let mut items: Vec = match serde_json::from_str(&f) { - Ok(items) => items, - Err(_) => Vec::new(), - }; - - // Sort items by priority 1 = highest, Infinity = lowest - items.sort_by(|a, b| a.priority.cmp(&b.priority)); // Delete items if let Some(item_id) = matches.value_of("delete") { - let item_id = match item_id.parse::() { - Ok(id) => id, + match item_id.parse::() { + Ok(id) => repository.delete(id), Err(e) => { eprintln!("Could not mark item as complete: {}", e); process::exit(1); } }; - - if item_id >= items.len() { - eprintln!("Could not find item with id: {}", item_id); - process::exit(1); - } - - items.remove(item_id); } // Toggle completion of items if let Some(item_id) = matches.value_of("complete") { match item_id.parse::() { - Ok(id) => { - match items.get_mut(id) { - Some(item) => item.toggle_complete(), - None => eprintln!("Could not mark item {} as complete, it doesn't exist", id) - }; - } + Ok(id) => repository.toggle(id), Err(e) => { eprintln!("Could not mark item as complete: {}", e); process::exit(1); @@ -106,63 +77,31 @@ fn main() { }; } - // Edit existing item - if let Some(item_id) = matches.value_of("edit") { - match item_id.parse::() { - Ok(id) => { - match items.get_mut(id) { - Some(item) => { - item.text = matches.value_of("TEXT").unwrap_or("EMPTY").to_string() - } - None => (), - }; - } - Err(e) => { - eprintln!("Could not edit item: {}", e); - process::exit(1); - } - }; - } - - // Change priority of item - if let Some(item_id) = matches.value_of("priority") { - match item_id.parse::() { - Ok(id) => { - // FIXME: Yuck - if let Some(item) = items.get_mut(id) { - if let Some(priority) = matches.value_of("TEXT") { - if let Ok(priority) = priority.parse::() { - item.priority = priority - } - } + if let Some(text) = matches.value_of("TEXT") { + if let Some(item_id) = matches.value_of("edit") { + match item_id.parse::() { + Ok(id) => repository.update_text(id, text), + Err(e) => { + eprintln!("Could not edit item: {}", e); + process::exit(1); } } - Err(e) => { - eprintln!("Could not edit item: {}", e); - process::exit(1); - } - }; - } - - if let Some(text) = matches.value_of("TEXT") { - items.push(TodoItem::new(text, false, 1)); - } - - if let Err(e) = todo_item::update_todo_file(&items) { - eprintln!("Failed to update todo file: {}", e); - process::exit(1); + } else { + repository.add(text); + } } - for (i, item) in items.into_iter().enumerate() { + for item in repository.items() { let text = if item.completed { item.text.green() } else { item.text.yellow() }; - println!("{} - {}", i, text); + println!("{} - {}", item.id, text); } - // this probably ins't necesarry...but it feels wrong to _assume_ - process::exit(0); -} \ No newline at end of file + if let Err(e) = repository.publish() { + eprintln!("Failed to publish todo file: {}", e); + } +} diff --git a/src/todo_item.rs b/src/todo_item.rs deleted file mode 100644 index 3dd821b..0000000 --- a/src/todo_item.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::io::Result; -use std::path::Path; -use std::fs; -use dirs; -use serde_json; - -const TODO_FILENAME: &'static str = ".todos"; - -#[derive(Serialize, Deserialize, Debug)] -pub struct TodoItem { - pub text: String, - pub completed: bool, - pub priority: usize -} - -impl TodoItem { - pub fn new(text: &str, completed: bool, priority: usize) -> Self { - TodoItem { text: String::from(text), completed, priority } - } - - pub fn toggle_complete(&mut self) { - self.completed = !self.completed; - } -} - -impl Default for TodoItem { - fn default() -> Self { - TodoItem::new("", false, 1) - } -} - -pub fn get_todo_file() -> Result { - fs::read_to_string(get_todo_file_path()) -} - -pub fn get_todo_file_path() -> String { - let home = dirs::home_dir().unwrap(); - format!("{}/{}", home.display(), Path::new(TODO_FILENAME).display()) -} - -pub fn update_todo_file(items: &Vec) -> Result<()> { - let path = get_todo_file_path(); - let buf = serde_json::to_string(&items).unwrap(); - fs::write(path, buf) -} -