diff --git a/src/eventlist/event/date.rs b/src/eventlist/event/date.rs
new file mode 100644
index 0000000..6ccd462
--- /dev/null
+++ b/src/eventlist/event/date.rs
@@ -0,0 +1,53 @@
+/*
+ TU - Time's Up!
+ Copyright (C) 2020 Julio Biason
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+use chrono::prelude::*;
+use chrono::DateTime;
+use serde_derive::Deserialize;
+use serde_derive::Serialize;
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Date {
+ year: i32,
+ month: u32,
+ day: u32,
+}
+
+impl From<&DateTime> for Date {
+ fn from(origin: &DateTime) -> Date {
+ Date {
+ year: origin.year(),
+ month: origin.month(),
+ day: origin.day(),
+ }
+ }
+}
+
+impl Date {
+ pub fn year(&self) -> i32 {
+ self.year
+ }
+
+ pub fn month(&self) -> u32 {
+ self.month
+ }
+
+ pub fn day(&self) -> u32 {
+ self.day
+ }
+}
diff --git a/src/eventlist/event/eventtype.rs b/src/eventlist/event/eventtype.rs
new file mode 100644
index 0000000..b9c1faf
--- /dev/null
+++ b/src/eventlist/event/eventtype.rs
@@ -0,0 +1,61 @@
+/*
+ TU - Time's Up!
+ Copyright (C) 2020 Julio Biason
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+use chrono::prelude::*;
+use chrono::DateTime;
+use serde_derive::Deserialize;
+use serde_derive::Serialize;
+
+use crate::eventlist::event::date;
+use crate::eventlist::event::time;
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(tag = "due", content = "datetime")]
+pub enum EventType {
+ AllDay(date::Date),
+ AtTime(date::Date, time::Time),
+}
+
+impl From<&EventType> for DateTime {
+ fn from(origin: &EventType) -> Self {
+ match origin {
+ EventType::AllDay(d) => Local.ymd(d.year(), d.month(), d.day()).and_hms(23, 59, 59),
+ EventType::AtTime(d, t) => {
+ Local
+ .ymd(d.year(), d.month(), d.day())
+ .and_hms(t.hour(), t.minute(), 59)
+ }
+ }
+ }
+}
+
+impl From<&EventType> for String {
+ fn from(origin: &EventType) -> String {
+ match origin {
+ EventType::AllDay(d) => format!("{}{}{}0000", d.year(), d.month(), d.day()),
+ EventType::AtTime(d, t) => format!(
+ "{}{}{}{}{}",
+ d.year(),
+ d.month(),
+ d.day(),
+ t.hour(),
+ t.minute()
+ ),
+ }
+ }
+}
diff --git a/src/eventlist/event.rs b/src/eventlist/event/mod.rs
similarity index 52%
rename from src/eventlist/event.rs
rename to src/eventlist/event/mod.rs
index 143468a..aeb779c 100644
--- a/src/eventlist/event.rs
+++ b/src/eventlist/event/mod.rs
@@ -25,74 +25,21 @@ use serde_derive::Deserialize;
use serde_derive::Serialize;
use uuid::Uuid;
-static DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct Date {
- year: i32,
- month: u32,
- day: u32,
-}
-
-impl From<&DateTime> for Date {
- fn from(origin: &DateTime) -> Date {
- Date {
- year: origin.year(),
- month: origin.month(),
- day: origin.day(),
- }
- }
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct Time {
- hour: u32,
- min: u32,
-}
+mod date;
+mod eventtype;
+mod time;
-impl From<&DateTime> for Time {
- fn from(origin: &DateTime) -> Time {
- Time {
- hour: origin.hour(),
- min: origin.minute(),
- }
- }
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-#[serde(tag = "due", content = "datetime")]
-pub enum EventDateType {
- AllDay(Date),
- AtTime(Date, Time),
-}
-
-impl From<&EventDateType> for DateTime {
- fn from(origin: &EventDateType) -> Self {
- match origin {
- EventDateType::AllDay(d) => Local.ymd(d.year, d.month, d.day).and_hms(0, 0, 0),
- EventDateType::AtTime(d, t) => {
- Local.ymd(d.year, d.month, d.day).and_hms(t.hour, t.min, 0)
- }
- }
- }
-}
+use date::Date as EventDate;
+use eventtype::EventType;
+use time::Time as EventTime;
-impl From<&EventDateType> for String {
- fn from(origin: &EventDateType) -> String {
- match origin {
- EventDateType::AllDay(d) => format!("{}{}{}0000", d.year, d.month, d.day),
- EventDateType::AtTime(d, t) => {
- format!("{}{}{}{}{}", d.year, d.month, d.day, t.hour, t.min)
- }
- }
- }
-}
+static DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
#[derive(Serialize, Deserialize, Debug)]
pub struct Event {
pub id: String,
pub description: String,
- due: EventDateType,
+ due: EventType,
}
fn uuid() -> String {
@@ -100,32 +47,46 @@ fn uuid() -> String {
format!("{:x}", id)
}
+#[derive(Debug)]
+pub enum EventError {
+ InvalidDate(String),
+ TooOld,
+}
+
+impl From for EventError {
+ fn from(error: chrono::format::ParseError) -> EventError {
+ EventError::InvalidDate(error.to_string())
+ }
+}
+
impl Event {
- // TODO result this
- pub fn new_on_date(description: &str, date: &str) -> Self {
+ pub fn new_on_date(description: &str, date: &str) -> Result {
let fake_datetime = format!("{} 00:00:00", date);
- if let Ok(dt) = Local.datetime_from_str(&fake_datetime, DATE_FORMAT) {
- // TODO turn format into static
- Self {
+ let dt = Local.datetime_from_str(&fake_datetime, DATE_FORMAT)?;
+
+ if dt < Local::now() {
+ Err(EventError::TooOld)
+ } else {
+ Ok(Self {
id: uuid(),
description: description.into(),
- due: EventDateType::AllDay(Date::from(&dt)),
- }
- } else {
- panic!("Failed to parse the date");
+ due: EventType::AllDay(EventDate::from(&dt)),
+ })
}
}
- pub fn new_on_date_time(description: &str, date: &str, time: &str) -> Self {
+ pub fn new_on_date_time(description: &str, date: &str, time: &str) -> Result {
let fake_datetime = format!("{} {}:00", date, time);
- if let Ok(dt) = Local.datetime_from_str(&fake_datetime, DATE_FORMAT) {
- Self {
+ let dt = Local.datetime_from_str(&fake_datetime, DATE_FORMAT)?;
+
+ if dt < Local::now() {
+ Err(EventError::TooOld)
+ } else {
+ Ok(Self {
id: uuid(),
description: description.into(),
- due: EventDateType::AtTime(Date::from(&dt), Time::from(&dt)),
- }
- } else {
- panic!("Failed to parse the date");
+ due: EventType::AtTime(EventDate::from(&dt), EventTime::from(&dt)),
+ })
}
}
@@ -136,15 +97,11 @@ impl Event {
log::debug!("ETA for {}: {}", self.id, eta.num_minutes());
match self.due {
- EventDateType::AllDay(_) if eta.num_minutes() > 0 => {
- Some(format!("{}d", eta.num_days()))
- }
- EventDateType::AtTime(_, _) if eta.num_days() > 0 => {
+ EventType::AllDay(_) if eta.num_minutes() > 0 => Some(format!("{}d", eta.num_days())),
+ EventType::AtTime(_, _) if eta.num_days() > 0 => {
Some(format!("{}d {}h", eta.num_days(), eta.num_hours()))
}
- EventDateType::AtTime(_, _) if eta.num_hours() > 0 => {
- Some(format!("{}h", eta.num_hours()))
- }
+ EventType::AtTime(_, _) if eta.num_hours() > 0 => Some(format!("{}h", eta.num_hours())),
_ => None,
}
}
diff --git a/src/eventlist/event/time.rs b/src/eventlist/event/time.rs
new file mode 100644
index 0000000..ce9a0a5
--- /dev/null
+++ b/src/eventlist/event/time.rs
@@ -0,0 +1,47 @@
+/*
+ TU - Time's Up!
+ Copyright (C) 2020 Julio Biason
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+use chrono::prelude::*;
+use chrono::DateTime;
+use serde_derive::Deserialize;
+use serde_derive::Serialize;
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Time {
+ hour: u32,
+ min: u32,
+}
+
+impl From<&DateTime> for Time {
+ fn from(origin: &DateTime) -> Time {
+ Time {
+ hour: origin.hour(),
+ min: origin.minute(),
+ }
+ }
+}
+
+impl Time {
+ pub fn hour(&self) -> u32 {
+ self.hour
+ }
+
+ pub fn minute(&self) -> u32 {
+ self.min
+ }
+}
diff --git a/src/eventlist/eventlist.rs b/src/eventlist/eventlist.rs
index 41462eb..1028dbf 100644
--- a/src/eventlist/eventlist.rs
+++ b/src/eventlist/eventlist.rs
@@ -24,6 +24,7 @@ use serde_derive::Serialize;
use toml;
use crate::eventlist::event::Event;
+use crate::eventlist::event::EventError;
static FILENAME: &str = "events.toml";
@@ -40,6 +41,21 @@ pub struct EventListIterator<'a> {
list: &'a Vec,
}
+#[derive(Debug)]
+pub enum EventListError {
+ InvalidDate,
+ TooOld,
+}
+
+impl From for EventListError {
+ fn from(error: EventError) -> EventListError {
+ match error {
+ EventError::InvalidDate(_) => EventListError::InvalidDate,
+ EventError::TooOld => EventListError::TooOld,
+ }
+ }
+}
+
// TODO separate business rule from repository
impl EventList {
fn empty() -> Self {
@@ -73,6 +89,32 @@ impl EventList {
fp.write_all(content.as_bytes()).unwrap();
}
}
+
+ /// Load the event list, add an all day event, and save it back.
+ /// Returns the ID of the new event.
+ pub fn add_event_with_date(description: &str, date: &str) -> Result {
+ let mut list = EventList::load();
+ let event = Event::new_on_date(description, date)?;
+ let id = String::from(&event.id);
+ list.push(event);
+ list.save();
+ Ok(id)
+ }
+
+ /// Load the event list, add an event with date and time, and save it back.
+ /// Returns the ID of the new event.
+ pub fn add_event_with_date_and_time(
+ description: &str,
+ date: &str,
+ time: &str,
+ ) -> Result {
+ let mut list = EventList::load();
+ let event = Event::new_on_date_time(description, date, time).unwrap();
+ let id = String::from(&event.id);
+ list.push(event);
+ list.save();
+ Ok(id)
+ }
}
impl<'a> IntoIterator for &'a EventList {
diff --git a/src/main.rs b/src/main.rs
index 39e7929..3afdb5e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,7 +21,6 @@ use log;
mod args;
mod eventlist;
-use crate::eventlist::event::Event;
use crate::eventlist::eventlist::EventList;
fn main() {
@@ -31,9 +30,14 @@ fn main() {
log::debug!("Command: {:?}", command);
match command {
args::Action::List => list(),
- args::Action::Add(description, date) => add_with_date(&description, &date),
+ args::Action::Add(description, date) => {
+ let event_id = EventList::add_event_with_date(&description, &date).unwrap();
+ println!("Created new event {}", event_id);
+ }
args::Action::AddWithTime(description, date, time) => {
- add_with_date_time(&description, &date, &time)
+ let event_id =
+ EventList::add_event_with_date_and_time(&description, &date, &time).unwrap();
+ println!("Created new event {}", event_id);
}
}
}
@@ -42,8 +46,10 @@ fn main() {
fn list() {
let event_list = EventList::load(); // TODO hide load from outside
println!("{:^8} | {:^7} | {}", "ID", "ETA", "Description");
+ // TODO: EventList::iter()
for record in event_list.into_iter() {
let eta = if let Some(eta) = record.eta() {
+ // TODO: "1d" == Tomorrow; "0d" == Today
eta
} else {
"Over".into()
@@ -52,23 +58,3 @@ fn list() {
println!("{:>8} | {:>7} | {}", record.id, eta, record.description);
}
}
-
-// TODO business rule (should be in EventList)
-fn add_with_date(description: &str, date: &str) {
- let event = Event::new_on_date(description, date);
- add_event(event);
-}
-
-fn add_with_date_time(description: &str, date: &str, time: &str) {
- let event = Event::new_on_date_time(description, date, time);
- add_event(event);
-}
-
-fn add_event(event: Event) {
- println!("Adding event {}", event.id);
-
- let mut event_list = EventList::load();
- log::debug!("EventList: {:?}", event_list);
- event_list.push(event);
- event_list.save();
-}