Skip to content

Commit

Permalink
Change to "kind of" a repository pattern
Browse files Browse the repository at this point in the history
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
  • Loading branch information
toddhainsworth committed Nov 30, 2021
1 parent 1587e24 commit 80d147b
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 135 deletions.
12 changes: 11 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "todo"
version = "0.2.2"
version = "0.3.0"
authors = ["Todd Hainsworth <[email protected]>"]

[dependencies]
Expand All @@ -10,3 +10,4 @@ serde_derive = "1.0"
dirs = "4.0"
colored = "2.0"
clap = "2.32.0"
home = "0.5.3"
84 changes: 84 additions & 0 deletions src/item.rs
Original file line number Diff line number Diff line change
@@ -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<Item>,
}

impl ItemRepository {
pub fn new() -> Result<Self> {
let item_json = ItemRepository::load_items()?;
let items: Vec<Item> = 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<Item> {
self.items.as_mut()
}

fn load_items() -> Result<String> {
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)
}
}
113 changes: 26 additions & 87 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -57,112 +47,61 @@ 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<TodoItem> = 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::<usize>() {
Ok(id) => id,
match item_id.parse::<usize>() {
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::<usize>() {
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);
}
};
}

// Edit existing item
if let Some(item_id) = matches.value_of("edit") {
match item_id.parse::<usize>() {
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::<usize>() {
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::<usize>() {
item.priority = priority
}
}
if let Some(text) = matches.value_of("TEXT") {
if let Some(item_id) = matches.value_of("edit") {
match item_id.parse::<usize>() {
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);
}
if let Err(e) = repository.publish() {
eprintln!("Failed to publish todo file: {}", e);
}
}
46 changes: 0 additions & 46 deletions src/todo_item.rs

This file was deleted.

0 comments on commit 80d147b

Please sign in to comment.