Skip to content

Commit 80d147b

Browse files
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
1 parent 1587e24 commit 80d147b

File tree

5 files changed

+123
-135
lines changed

5 files changed

+123
-135
lines changed

Cargo.lock

+11-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "todo"
3-
version = "0.2.2"
3+
version = "0.3.0"
44
authors = ["Todd Hainsworth <hainsworth.todd@gmail.com>"]
55

66
[dependencies]
@@ -10,3 +10,4 @@ serde_derive = "1.0"
1010
dirs = "4.0"
1111
colored = "2.0"
1212
clap = "2.32.0"
13+
home = "0.5.3"

src/item.rs

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use serde_json;
2+
use std::fs;
3+
use std::io::Result;
4+
use home::home_dir;
5+
6+
const TODO_FILENAME: &'static str = ".todos";
7+
8+
#[derive(Serialize, Deserialize, Debug)]
9+
pub struct Item {
10+
pub id: usize,
11+
pub text: String,
12+
pub completed: bool,
13+
}
14+
15+
impl Item {
16+
pub fn new(id: usize, text: &str, completed: bool) -> Self {
17+
Self {
18+
id,
19+
text: String::from(text),
20+
completed,
21+
}
22+
}
23+
}
24+
25+
impl Default for Item {
26+
fn default() -> Self {
27+
Item::new(0, "", false)
28+
}
29+
}
30+
31+
pub struct ItemRepository {
32+
items: Vec<Item>,
33+
}
34+
35+
impl ItemRepository {
36+
pub fn new() -> Result<Self> {
37+
let item_json = ItemRepository::load_items()?;
38+
let items: Vec<Item> = serde_json::from_str(&item_json)?;
39+
Ok(Self { items })
40+
}
41+
42+
pub fn delete(&mut self, id: usize) {
43+
self.items.retain(|item| item.id != id)
44+
}
45+
46+
pub fn publish(self) -> Result<()> {
47+
let path = Self::get_todo_file_path();
48+
let buf = serde_json::to_string(&self.items).unwrap();
49+
fs::write(path, buf)
50+
}
51+
52+
pub fn toggle(&mut self, id: usize) {
53+
self.items
54+
.iter_mut()
55+
.find(|item| item.id == id)
56+
.map(|item| item.completed = !item.completed);
57+
}
58+
59+
pub fn update_text(&mut self, id: usize, text: &str) {
60+
self.items
61+
.iter_mut()
62+
.find(|item| item.id == id)
63+
.map(|item| item.text = text.to_string());
64+
}
65+
66+
pub fn add(&mut self, text: &str) {
67+
let id = self.items.iter().map(|item| item.id).max().unwrap_or(0) + 1;
68+
self.items.push(Item::new(id, text, false))
69+
}
70+
71+
pub fn items(&mut self) -> &mut Vec<Item> {
72+
self.items.as_mut()
73+
}
74+
75+
fn load_items() -> Result<String> {
76+
fs::read_to_string(Self::get_todo_file_path())
77+
}
78+
79+
fn get_todo_file_path() -> String {
80+
// unwrapping because if this were to fail then there's something _really_ wrong with the users setup...
81+
let home = home_dir().unwrap();
82+
format!("{}/{}", home.display(), TODO_FILENAME)
83+
}
84+
}

src/main.rs

+26-87
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ extern crate colored;
33
extern crate dirs;
44
extern crate serde;
55
extern crate serde_json;
6+
extern crate home;
67

78
#[macro_use]
89
extern crate serde_derive;
@@ -12,9 +13,9 @@ use std::process;
1213

1314
use clap::{App, Arg};
1415

15-
mod todo_item;
16+
mod item;
1617

17-
use todo_item::TodoItem;
18+
use item::ItemRepository;
1819

1920
fn main() {
2021
let matches = App::new("Todo")
@@ -35,18 +36,7 @@ fn main() {
3536
.help("edit an entry")
3637
.value_delimiter("-"),
3738
)
38-
.arg(
39-
Arg::with_name("TEXT")
40-
.help("The todo item text")
41-
.index(1)
42-
)
43-
.arg(
44-
Arg::with_name("priority")
45-
.short("p")
46-
.long("priority")
47-
.help("change the priority of an entry")
48-
.value_delimiter(" "),
49-
)
39+
.arg(Arg::with_name("TEXT").help("The todo item text").index(1))
5040
.arg(
5141
Arg::with_name("complete")
5242
.short("c")
@@ -57,112 +47,61 @@ fn main() {
5747
)
5848
.get_matches();
5949

60-
let f = match todo_item::get_todo_file() {
61-
Ok(text) => text,
50+
let mut repository = match ItemRepository::new() {
51+
Ok(r) => r,
6252
Err(e) => {
63-
eprintln!("Could not read todo file: {}", e);
53+
eprintln!("Failed to load items: {}", e);
6454
process::exit(1);
6555
}
6656
};
67-
let mut items: Vec<TodoItem> = match serde_json::from_str(&f) {
68-
Ok(items) => items,
69-
Err(_) => Vec::new(),
70-
};
71-
72-
// Sort items by priority 1 = highest, Infinity = lowest
73-
items.sort_by(|a, b| a.priority.cmp(&b.priority));
7457

7558
// Delete items
7659
if let Some(item_id) = matches.value_of("delete") {
77-
let item_id = match item_id.parse::<usize>() {
78-
Ok(id) => id,
60+
match item_id.parse::<usize>() {
61+
Ok(id) => repository.delete(id),
7962
Err(e) => {
8063
eprintln!("Could not mark item as complete: {}", e);
8164
process::exit(1);
8265
}
8366
};
84-
85-
if item_id >= items.len() {
86-
eprintln!("Could not find item with id: {}", item_id);
87-
process::exit(1);
88-
}
89-
90-
items.remove(item_id);
9167
}
9268

9369
// Toggle completion of items
9470
if let Some(item_id) = matches.value_of("complete") {
9571
match item_id.parse::<usize>() {
96-
Ok(id) => {
97-
match items.get_mut(id) {
98-
Some(item) => item.toggle_complete(),
99-
None => eprintln!("Could not mark item {} as complete, it doesn't exist", id)
100-
};
101-
}
72+
Ok(id) => repository.toggle(id),
10273
Err(e) => {
10374
eprintln!("Could not mark item as complete: {}", e);
10475
process::exit(1);
10576
}
10677
};
10778
}
10879

109-
// Edit existing item
110-
if let Some(item_id) = matches.value_of("edit") {
111-
match item_id.parse::<usize>() {
112-
Ok(id) => {
113-
match items.get_mut(id) {
114-
Some(item) => {
115-
item.text = matches.value_of("TEXT").unwrap_or("EMPTY").to_string()
116-
}
117-
None => (),
118-
};
119-
}
120-
Err(e) => {
121-
eprintln!("Could not edit item: {}", e);
122-
process::exit(1);
123-
}
124-
};
125-
}
126-
127-
// Change priority of item
128-
if let Some(item_id) = matches.value_of("priority") {
129-
match item_id.parse::<usize>() {
130-
Ok(id) => {
131-
// FIXME: Yuck
132-
if let Some(item) = items.get_mut(id) {
133-
if let Some(priority) = matches.value_of("TEXT") {
134-
if let Ok(priority) = priority.parse::<usize>() {
135-
item.priority = priority
136-
}
137-
}
80+
if let Some(text) = matches.value_of("TEXT") {
81+
if let Some(item_id) = matches.value_of("edit") {
82+
match item_id.parse::<usize>() {
83+
Ok(id) => repository.update_text(id, text),
84+
Err(e) => {
85+
eprintln!("Could not edit item: {}", e);
86+
process::exit(1);
13887
}
13988
}
140-
Err(e) => {
141-
eprintln!("Could not edit item: {}", e);
142-
process::exit(1);
143-
}
144-
};
145-
}
146-
147-
if let Some(text) = matches.value_of("TEXT") {
148-
items.push(TodoItem::new(text, false, 1));
149-
}
150-
151-
if let Err(e) = todo_item::update_todo_file(&items) {
152-
eprintln!("Failed to update todo file: {}", e);
153-
process::exit(1);
89+
} else {
90+
repository.add(text);
91+
}
15492
}
15593

156-
for (i, item) in items.into_iter().enumerate() {
94+
for item in repository.items() {
15795
let text = if item.completed {
15896
item.text.green()
15997
} else {
16098
item.text.yellow()
16199
};
162100

163-
println!("{} - {}", i, text);
101+
println!("{} - {}", item.id, text);
164102
}
165103

166-
// this probably ins't necesarry...but it feels wrong to _assume_
167-
process::exit(0);
168-
}
104+
if let Err(e) = repository.publish() {
105+
eprintln!("Failed to publish todo file: {}", e);
106+
}
107+
}

src/todo_item.rs

-46
This file was deleted.

0 commit comments

Comments
 (0)