aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2022-01-09 13:12:45 +0100
committerJoris2022-01-09 13:12:45 +0100
commit99af88a840bef534540a4b273d24a8a17e7fc9b9 (patch)
tree20bda6c9eccd66d0c165e1f5d42fe76f80be58d2
parentbd59a5128c05dcd550e91bbdd0cd9d5996a65586 (diff)
downloadcalendar-99af88a840bef534540a4b273d24a8a17e7fc9b9.tar.gz
calendar-99af88a840bef534540a4b273d24a8a17e7fc9b9.tar.bz2
calendar-99af88a840bef534540a4b273d24a8a17e7fc9b9.zip
Split app into modules
-rw-r--r--src/app.rs371
-rw-r--r--src/app/app.rs57
-rw-r--r--src/app/calendar.rs157
-rw-r--r--src/app/form.rs79
-rw-r--r--src/app/mod.rs38
-rw-r--r--src/app/style.css (renamed from src/style.css)0
-rw-r--r--src/app/update.rs52
-rw-r--r--src/app/utils.rs9
-rw-r--r--src/db/mod.rs4
-rw-r--r--src/main.rs2
-rw-r--r--src/model/event.rs42
11 files changed, 435 insertions, 376 deletions
diff --git a/src/app.rs b/src/app.rs
deleted file mode 100644
index 0eb2b1e..0000000
--- a/src/app.rs
+++ /dev/null
@@ -1,371 +0,0 @@
-use gtk4 as gtk;
-
-use async_channel::{Receiver, Sender};
-use chrono::{Datelike, NaiveDate, NaiveTime, Weekday};
-use gtk::gdk::Display;
-use gtk::glib::signal::Inhibit;
-use gtk::glib;
-use gtk::prelude::*;
-use rusqlite::Connection;
-use std::future::Future;
-use std::rc::Rc;
-
-use crate::model::event;
-use crate::model::event::Event;
-use crate::db;
-
-/// Spawns a task on the default executor, without waiting for it to complete
-pub fn spawn<F>(future: F)
-where
- F: Future<Output = ()> + 'static,
-{
- gtk::glib::MainContext::default().spawn_local(future);
-}
-
-fn send_message(tx: Sender<Msg>, msg: Msg) {
- spawn(async move {
- let _ = tx.send(msg).await;
- })
-}
-
-enum Msg {
- ShowAddForm { date: NaiveDate },
- AddEvent { event: Event },
-}
-
-static DAYS: [&str; 7] = ["LUN", "MAR", "MER", "JEU", "VEN", "SAM", "DIM"];
-static MONTHES: [&str; 12] = [
- "Jan", "Fév", "Mar", "Avr", "Mai", "Juin", "Juil", "Aoû", "Sep", "Oct", "Nov", "Déc",
-];
-
-pub fn run(conn: Connection) {
- let conn = Rc::new(conn);
- let app = gtk::Application::new(Some("me.guyonvarch.calendar"), Default::default());
- app.connect_startup(|_| load_style());
- app.connect_activate(move |app| build_ui(conn.clone(), app));
- app.run();
-}
-
-fn build_ui(conn: Rc<Connection>, app: &gtk::Application) {
- let (tx, rx) = async_channel::unbounded();
- let app = App::new(conn.clone(), app, tx.clone());
- spawn(event_handler(conn, rx, tx, app))
-}
-
-async fn event_handler(conn: Rc<Connection>, rx: Receiver<Msg>, tx: Sender<Msg>, mut app: App) {
- while let Ok(msg) = rx.recv().await {
- match msg {
- Msg::ShowAddForm { date } => {
- add_event_dialog(Rc::clone(&conn), tx.clone(), Rc::clone(&app.window), date).await;
- }
- Msg::AddEvent { event } => {
- let date = event.date.clone();
-
- let d = date.signed_duration_since(app.start_date).num_days();
-
- app.events.push(event);
-
- let col = (d % 7) as i32;
- let row = 1 + (d / 7) as i32;
-
- app.grid.attach(
- &day_entry(tx.clone(), &date, &app.today, &app.events),
- col,
- row,
- 1,
- 1,
- );
- }
- }
- }
-}
-
-struct App {
- pub window: Rc<gtk::ApplicationWindow>,
- pub grid: gtk::Grid,
- pub events: Vec<Event>,
- pub today: NaiveDate,
- pub start_date: NaiveDate,
-}
-
-impl App {
- fn new(conn: Rc<Connection>, app: &gtk::Application, tx: Sender<Msg>) -> Self {
- let window = Rc::new(
- gtk::ApplicationWindow::builder()
- .application(app)
- .title("Calendar")
- .default_width(800)
- .default_height(600)
- .visible(true)
- .build(),
- );
-
- let grid = gtk::Grid::builder().build();
- window.set_child(Some(&grid));
-
- for col in 0..7 {
- grid.attach(&day_title(col), col, 0, 1, 1);
- }
-
- let today = chrono::offset::Local::today().naive_utc();
- let start_date =
- NaiveDate::from_isoywd(today.year(), today.iso_week().week(), Weekday::Mon);
-
-
- let events = db::list(&conn).unwrap_or(vec!());
- show_days(tx, &grid, &start_date, &today, &events);
-
- window.connect_close_request(move |window| {
- if let Some(application) = window.application() {
- application.remove_window(window);
- }
- Inhibit(false)
- });
-
- Self {
- window,
- grid,
- events: events.clone(),
- today,
- start_date,
- }
- }
-}
-
-fn load_style() {
- let provider = gtk::CssProvider::new();
- provider.load_from_data(include_bytes!("style.css"));
- gtk::StyleContext::add_provider_for_display(
- &Display::default().expect("Error initializing gtk css provider."),
- &provider,
- gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
- );
-}
-
-fn show_days(
- tx: Sender<Msg>,
- grid: &gtk::Grid,
- start_day: &NaiveDate,
- today: &NaiveDate,
- events: &Vec<Event>,
-) {
- let mut d = *start_day;
- for row in 1..5 {
- for col in 0..7 {
- grid.attach(&day_entry(tx.clone(), &d, &today, &events), col, row, 1, 1);
- d = d.succ();
- }
- }
-}
-
-fn day_title(col: i32) -> gtk::Box {
- let vbox = gtk::Box::builder()
- .orientation(gtk::Orientation::Vertical)
- .build();
-
- vbox.add_css_class("g-Calendar__DayTitle");
-
- let label = gtk::Label::builder().label(DAYS[col as usize]).build();
-
- vbox.append(&label);
-
- vbox
-}
-
-fn day_entry(
- tx: Sender<Msg>,
- date: &NaiveDate,
- today: &NaiveDate,
- events: &Vec<Event>,
-) -> gtk::ScrolledWindow {
- let vbox = gtk::Box::builder()
- .orientation(gtk::Orientation::Vertical)
- .build();
-
- vbox.add_css_class("g-Calendar__Day");
-
- let gesture = gtk::GestureClick::new();
- gesture.connect_pressed(glib::clone!(@strong date => move |_, n, _, _| {
- if n == 2 {
- send_message(tx.clone(), Msg::ShowAddForm { date });
- }
- }));
- vbox.add_controller(&gesture);
-
- if date == today {
- vbox.add_css_class("g-Calendar__Day--Today");
- }
-
- vbox.append(&day_label(date));
-
- let mut events = events
- .iter()
- .filter(|e| e.date == *date)
- .collect::<Vec<&Event>>();
- events.sort_by_key(|e| e.start);
-
- if !events.is_empty() {
- vbox.append(&day_events(events));
- }
-
- let scrolled_window = gtk::ScrolledWindow::builder()
- .hscrollbar_policy(gtk::PolicyType::Never)
- .hexpand(true)
- .vexpand(true)
- .child(&vbox)
- .build();
-
- scrolled_window
-}
-
-fn day_label(date: &NaiveDate) -> gtk::Label {
- let label = gtk::Label::builder()
- .label(&format!(
- "{} {}",
- date.day(),
- MONTHES[date.month0() as usize]
- ))
- .halign(gtk::Align::Start)
- .build();
-
- label.add_css_class("g-Calendar__DayNumber");
-
- label
-}
-
-fn day_events(events: Vec<&Event>) -> gtk::Box {
- let vbox = gtk::Box::builder()
- .orientation(gtk::Orientation::Vertical)
- .build();
-
- for event in events {
- let hbox = gtk::Box::builder()
- .orientation(gtk::Orientation::Horizontal)
- .hexpand(true)
- .build();
-
- let gesture = gtk::GestureClick::new();
- let click_event = event.clone();
- gesture.connect_pressed(move |gesture, _, _, _| {
- gesture.set_state(gtk::EventSequenceState::Claimed);
- println!("Click: {:?}", click_event);
- });
- hbox.add_controller(&gesture);
-
- hbox.add_css_class("g-Calendar__DayEvent");
-
- let event_txt = &event.pprint();
- let label = gtk::Label::builder()
- .label(&event_txt)
- .ellipsize(gtk::pango::EllipsizeMode::End)
- .tooltip_text(&event_txt)
- .halign(gtk::Align::Start)
- .build();
-
- hbox.append(&label);
- vbox.append(&hbox);
- }
-
- vbox
-}
-
-static DATE_FORMAT: &str = "%d/%m/%Y";
-
-async fn add_event_dialog(conn: Rc<Connection>, tx: Sender<Msg>, window: Rc<gtk::ApplicationWindow>, date: NaiveDate) {
- let dialog = gtk::Dialog::builder()
- .transient_for(&*window)
- .modal(true)
- .title("Ajouter")
- .css_classes(vec!["g-Form".to_string()])
- .build();
-
- let content_area = dialog.content_area();
-
- let vbox = gtk::Box::builder()
- .orientation(gtk::Orientation::Vertical)
- .build();
- vbox.add_css_class("g-Form__Inputs");
- content_area.append(&vbox);
-
- let name = entry("");
- vbox.append(&label("Événement"));
- vbox.append(&name);
-
- let date = entry(&date.format(DATE_FORMAT).to_string());
- vbox.append(&label("Jour"));
- vbox.append(&date);
-
- let start = entry("");
- vbox.append(&label("Début"));
- vbox.append(&start);
-
- let end = entry("");
- vbox.append(&label("Fin"));
- vbox.append(&end);
-
- let button = gtk::Button::with_label("Créer");
- vbox.append(&button);
- button.connect_clicked(glib::clone!(@weak dialog => move |_| {
- match validate_event(date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text()) {
- Some(event) => {
- match db::insert(&conn, &event) {
- Ok(_) => {
- send_message(tx.clone(), Msg::AddEvent { event: event });
- dialog.close()
- },
- Err(_) => ()
- }
- },
- None => ()
- }
- }));
-
- dialog.run_future().await;
-}
-
-fn entry(text: &str) -> gtk::Entry {
- gtk::Entry::builder().text(text).margin_bottom(10).build()
-}
-
-fn label(text: &str) -> gtk::Label {
- gtk::Label::builder()
- .label(text)
- .halign(gtk::Align::Start)
- .margin_bottom(5)
- .build()
-}
-
-fn validate_event(date: String, name: String, start: String, end: String) -> Option<Event> {
- let start = validate_time(start)?;
- let end = validate_time(end)?;
-
- match (start, end) {
- (Some(s), Some(e)) if s > e => None?,
- _ => (),
- }
-
- Some(Event {
- date: NaiveDate::parse_from_str(&date, DATE_FORMAT).ok()?,
- name: validate_name(name)?,
- start,
- end,
- })
-}
-
-fn validate_time(time: String) -> Option<Option<NaiveTime>> {
- let time = time.trim();
- if time.is_empty() {
- Some(None)
- } else {
- event::parse_time(time).map(|t| Some(t))
- }
-}
-
-fn validate_name(name: String) -> Option<String> {
- let name = name.trim();
- if name.is_empty() {
- None
- } else {
- Some(name.to_string())
- }
-}
diff --git a/src/app/app.rs b/src/app/app.rs
new file mode 100644
index 0000000..45904a9
--- /dev/null
+++ b/src/app/app.rs
@@ -0,0 +1,57 @@
+use gtk4 as gtk;
+
+use async_channel::Sender;
+use chrono::{Datelike, NaiveDate, Weekday};
+use gtk::glib::signal::Inhibit;
+use gtk::prelude::*;
+use rusqlite::Connection;
+use std::rc::Rc;
+
+use crate::app::calendar;
+use crate::app::update::Msg;
+use crate::{db, model::event::Event};
+
+pub struct App {
+ pub window: Rc<gtk::ApplicationWindow>,
+ pub grid: gtk::Grid,
+ pub events: Vec<Event>,
+ pub today: NaiveDate,
+ pub start_date: NaiveDate,
+}
+
+impl App {
+ pub fn new(conn: Rc<Connection>, app: &gtk::Application, tx: Sender<Msg>) -> Self {
+ let window = Rc::new(
+ gtk::ApplicationWindow::builder()
+ .application(app)
+ .title("Calendar")
+ .default_width(800)
+ .default_height(600)
+ .visible(true)
+ .build(),
+ );
+
+ let today = chrono::offset::Local::today().naive_utc();
+ let start_date =
+ NaiveDate::from_isoywd(today.year(), today.iso_week().week(), Weekday::Mon);
+ let events = db::list(&conn).unwrap_or(vec![]);
+ let grid = calendar::grid(tx, &today, &start_date, &events);
+
+ window.set_child(Some(&grid));
+
+ window.connect_close_request(move |window| {
+ if let Some(application) = window.application() {
+ application.remove_window(window);
+ }
+ Inhibit(false)
+ });
+
+ Self {
+ window,
+ grid,
+ events,
+ today,
+ start_date,
+ }
+ }
+}
diff --git a/src/app/calendar.rs b/src/app/calendar.rs
new file mode 100644
index 0000000..847ea71
--- /dev/null
+++ b/src/app/calendar.rs
@@ -0,0 +1,157 @@
+use gtk4 as gtk;
+
+use async_channel::Sender;
+use chrono::{Datelike, NaiveDate};
+use gtk::glib;
+use gtk::prelude::*;
+
+use crate::{app::update, app::update::Msg, model::event::Event};
+
+static DAYS: [&str; 7] = ["LUN", "MAR", "MER", "JEU", "VEN", "SAM", "DIM"];
+static MONTHES: [&str; 12] = [
+ "Jan", "Fév", "Mar", "Avr", "Mai", "Juin", "Juil", "Aoû", "Sep", "Oct", "Nov", "Déc",
+];
+
+pub fn grid(
+ tx: Sender<Msg>,
+ today: &NaiveDate,
+ start_date: &NaiveDate,
+ events: &Vec<Event>,
+) -> gtk::Grid {
+ let grid = gtk::Grid::builder().build();
+
+ for col in 0..7 {
+ grid.attach(&day_title(col), col, 0, 1, 1);
+ }
+
+ show_days(tx, &grid, &start_date, &today, &events);
+
+ grid
+}
+
+fn show_days(
+ tx: Sender<Msg>,
+ grid: &gtk::Grid,
+ start_date: &NaiveDate,
+ today: &NaiveDate,
+ events: &Vec<Event>,
+) {
+ let mut d = *start_date;
+ for row in 1..5 {
+ for col in 0..7 {
+ grid.attach(&day_entry(tx.clone(), &d, &today, &events), col, row, 1, 1);
+ d = d.succ();
+ }
+ }
+}
+
+fn day_title(col: i32) -> gtk::Box {
+ let vbox = gtk::Box::builder()
+ .orientation(gtk::Orientation::Vertical)
+ .build();
+
+ vbox.add_css_class("g-Calendar__DayTitle");
+
+ let label = gtk::Label::builder().label(DAYS[col as usize]).build();
+
+ vbox.append(&label);
+
+ vbox
+}
+
+pub fn day_entry(
+ tx: Sender<Msg>,
+ date: &NaiveDate,
+ today: &NaiveDate,
+ events: &Vec<Event>,
+) -> gtk::ScrolledWindow {
+ let vbox = gtk::Box::builder()
+ .orientation(gtk::Orientation::Vertical)
+ .build();
+
+ vbox.add_css_class("g-Calendar__Day");
+
+ let gesture = gtk::GestureClick::new();
+ gesture.connect_pressed(glib::clone!(@strong date => move |_, n, _, _| {
+ if n == 2 {
+ update::send(tx.clone(), Msg::ShowAddForm { date });
+ }
+ }));
+ vbox.add_controller(&gesture);
+
+ if date == today {
+ vbox.add_css_class("g-Calendar__Day--Today");
+ }
+
+ vbox.append(&day_label(date));
+
+ let mut events = events
+ .iter()
+ .filter(|e| e.date == *date)
+ .collect::<Vec<&Event>>();
+ events.sort_by_key(|e| e.start);
+
+ if !events.is_empty() {
+ vbox.append(&day_events(events));
+ }
+
+ let scrolled_window = gtk::ScrolledWindow::builder()
+ .hscrollbar_policy(gtk::PolicyType::Never)
+ .hexpand(true)
+ .vexpand(true)
+ .child(&vbox)
+ .build();
+
+ scrolled_window
+}
+
+fn day_label(date: &NaiveDate) -> gtk::Label {
+ let label = gtk::Label::builder()
+ .label(&format!(
+ "{} {}",
+ date.day(),
+ MONTHES[date.month0() as usize]
+ ))
+ .halign(gtk::Align::Start)
+ .build();
+
+ label.add_css_class("g-Calendar__DayNumber");
+
+ label
+}
+
+fn day_events(events: Vec<&Event>) -> gtk::Box {
+ let vbox = gtk::Box::builder()
+ .orientation(gtk::Orientation::Vertical)
+ .build();
+
+ for event in events {
+ let hbox = gtk::Box::builder()
+ .orientation(gtk::Orientation::Horizontal)
+ .hexpand(true)
+ .build();
+
+ let gesture = gtk::GestureClick::new();
+ let click_event = event.clone();
+ gesture.connect_pressed(move |gesture, _, _, _| {
+ gesture.set_state(gtk::EventSequenceState::Claimed);
+ println!("Click: {:?}", click_event);
+ });
+ hbox.add_controller(&gesture);
+
+ hbox.add_css_class("g-Calendar__DayEvent");
+
+ let event_txt = &event.pprint();
+ let label = gtk::Label::builder()
+ .label(&event_txt)
+ .ellipsize(gtk::pango::EllipsizeMode::End)
+ .tooltip_text(&event_txt)
+ .halign(gtk::Align::Start)
+ .build();
+
+ hbox.append(&label);
+ vbox.append(&hbox);
+ }
+
+ vbox
+}
diff --git a/src/app/form.rs b/src/app/form.rs
new file mode 100644
index 0000000..fc3dc83
--- /dev/null
+++ b/src/app/form.rs
@@ -0,0 +1,79 @@
+use gtk4 as gtk;
+
+use async_channel::Sender;
+use chrono::NaiveDate;
+use gtk::glib;
+use gtk::prelude::*;
+use rusqlite::Connection;
+use std::rc::Rc;
+
+use crate::{app::update, app::update::Msg, db, model::event};
+
+pub async fn dialog(
+ conn: Rc<Connection>,
+ tx: Sender<Msg>,
+ window: Rc<gtk::ApplicationWindow>,
+ date: NaiveDate,
+) {
+ let dialog = gtk::Dialog::builder()
+ .transient_for(&*window)
+ .modal(true)
+ .title("Ajouter")
+ .css_classes(vec!["g-Form".to_string()])
+ .build();
+
+ let content_area = dialog.content_area();
+
+ let vbox = gtk::Box::builder()
+ .orientation(gtk::Orientation::Vertical)
+ .build();
+ vbox.add_css_class("g-Form__Inputs");
+ content_area.append(&vbox);
+
+ let name = entry("");
+ vbox.append(&label("Événement"));
+ vbox.append(&name);
+
+ let date = entry(&date.format(event::DATE_FORMAT).to_string());
+ vbox.append(&label("Jour"));
+ vbox.append(&date);
+
+ let start = entry("");
+ vbox.append(&label("Début"));
+ vbox.append(&start);
+
+ let end = entry("");
+ vbox.append(&label("Fin"));
+ vbox.append(&end);
+
+ let button = gtk::Button::with_label("Créer");
+ vbox.append(&button);
+ button.connect_clicked(glib::clone!(@weak dialog => move |_| {
+ match event::validate(date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text()) {
+ Some(event) => {
+ match db::insert(&conn, &event) {
+ Ok(_) => {
+ update::send(tx.clone(), Msg::AddEvent { event: event });
+ dialog.close()
+ },
+ Err(_) => ()
+ }
+ },
+ None => ()
+ }
+ }));
+
+ dialog.run_future().await;
+}
+
+fn entry(text: &str) -> gtk::Entry {
+ gtk::Entry::builder().text(text).margin_bottom(10).build()
+}
+
+fn label(text: &str) -> gtk::Label {
+ gtk::Label::builder()
+ .label(text)
+ .halign(gtk::Align::Start)
+ .margin_bottom(5)
+ .build()
+}
diff --git a/src/app/mod.rs b/src/app/mod.rs
new file mode 100644
index 0000000..30b59af
--- /dev/null
+++ b/src/app/mod.rs
@@ -0,0 +1,38 @@
+mod app;
+mod calendar;
+mod form;
+mod update;
+mod utils;
+
+use gtk4 as gtk;
+
+use gtk::gdk::Display;
+use gtk::prelude::*;
+use rusqlite::Connection;
+use std::rc::Rc;
+
+use app::App;
+
+pub fn run(conn: Connection) {
+ let conn = Rc::new(conn);
+ let app = gtk::Application::new(Some("me.guyonvarch.calendar"), Default::default());
+ app.connect_startup(|_| load_style());
+ app.connect_activate(move |app| build_ui(conn.clone(), app));
+ app.run();
+}
+
+fn build_ui(conn: Rc<Connection>, app: &gtk::Application) {
+ let (tx, rx) = async_channel::unbounded();
+ let app = App::new(conn.clone(), app, tx.clone());
+ utils::spawn(update::event_handler(conn, rx, tx, app))
+}
+
+fn load_style() {
+ let provider = gtk::CssProvider::new();
+ provider.load_from_data(include_bytes!("style.css"));
+ gtk::StyleContext::add_provider_for_display(
+ &Display::default().expect("Error initializing gtk css provider."),
+ &provider,
+ gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
+ );
+}
diff --git a/src/style.css b/src/app/style.css
index 5cd1394..5cd1394 100644
--- a/src/style.css
+++ b/src/app/style.css
diff --git a/src/app/update.rs b/src/app/update.rs
new file mode 100644
index 0000000..288ec51
--- /dev/null
+++ b/src/app/update.rs
@@ -0,0 +1,52 @@
+use gtk4 as gtk;
+
+use async_channel::{Receiver, Sender};
+use chrono::NaiveDate;
+use gtk::prelude::*;
+use rusqlite::Connection;
+use std::rc::Rc;
+
+use crate::app::calendar;
+use crate::app::form;
+use crate::app::utils;
+use crate::app::App;
+use crate::model::event::Event;
+
+pub fn send(tx: Sender<Msg>, msg: Msg) {
+ utils::spawn(async move {
+ let _ = tx.send(msg).await;
+ })
+}
+
+pub enum Msg {
+ ShowAddForm { date: NaiveDate },
+ AddEvent { event: Event },
+}
+
+pub async fn event_handler(conn: Rc<Connection>, rx: Receiver<Msg>, tx: Sender<Msg>, mut app: App) {
+ while let Ok(msg) = rx.recv().await {
+ match msg {
+ Msg::ShowAddForm { date } => {
+ form::dialog(Rc::clone(&conn), tx.clone(), Rc::clone(&app.window), date).await;
+ }
+ Msg::AddEvent { event } => {
+ let date = event.date.clone();
+
+ let d = date.signed_duration_since(app.start_date).num_days();
+
+ app.events.push(event);
+
+ let col = (d % 7) as i32;
+ let row = 1 + (d / 7) as i32;
+
+ app.grid.attach(
+ &calendar::day_entry(tx.clone(), &date, &app.today, &app.events),
+ col,
+ row,
+ 1,
+ 1,
+ );
+ }
+ }
+ }
+}
diff --git a/src/app/utils.rs b/src/app/utils.rs
new file mode 100644
index 0000000..673b96e
--- /dev/null
+++ b/src/app/utils.rs
@@ -0,0 +1,9 @@
+use std::future::Future;
+
+/// Spawns a task on the default executor, without waiting for it to complete
+pub fn spawn<F>(future: F)
+where
+ F: Future<Output = ()> + 'static,
+{
+ gtk4::glib::MainContext::default().spawn_local(future);
+}
diff --git a/src/db/mod.rs b/src/db/mod.rs
index 3348673..6cfa2eb 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -23,14 +23,14 @@ pub fn insert(conn: &Connection, event: &Event) -> Result<()> {
pub fn list(conn: &Connection) -> Result<Vec<Event>> {
let mut stmt = conn.prepare("SELECT date, start, end, name FROM events")?;
- let iter = stmt.query_map([], |row|
+ let iter = stmt.query_map([], |row| {
Ok(Event {
date: row.get(0)?,
start: row.get(1)?,
end: row.get(2)?,
name: row.get(3)?,
})
- )?;
+ })?;
Ok(iter.map(|r| r.unwrap()).collect())
}
diff --git a/src/main.rs b/src/main.rs
index f30e38e..f5f4861 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,6 @@
mod app;
-mod model;
mod db;
+mod model;
use anyhow::Result;
diff --git a/src/model/event.rs b/src/model/event.rs
index 2650c47..a0bdc85 100644
--- a/src/model/event.rs
+++ b/src/model/event.rs
@@ -1,7 +1,8 @@
use chrono::Timelike;
use chrono::{NaiveDate, NaiveTime};
-// #[derive(Debug, Clone, sqlx::FromRow)]
+pub static DATE_FORMAT: &str = "%d/%m/%Y";
+
#[derive(Debug, Clone)]
pub struct Event {
pub date: NaiveDate,
@@ -34,7 +35,7 @@ fn pprint_time(t: NaiveTime) -> String {
}
}
-pub fn parse_time(t: &str) -> Option<NaiveTime> {
+fn parse_time(t: &str) -> Option<NaiveTime> {
match t.split('h').collect::<Vec<&str>>()[..] {
[hours, minutes] => {
if minutes.trim().is_empty() {
@@ -46,3 +47,40 @@ pub fn parse_time(t: &str) -> Option<NaiveTime> {
_ => None,
}
}
+
+// Validation
+
+pub fn validate(date: String, name: String, start: String, end: String) -> Option<Event> {
+ let start = validate_time(start)?;
+ let end = validate_time(end)?;
+
+ match (start, end) {
+ (Some(s), Some(e)) if s > e => None?,
+ _ => (),
+ }
+
+ Some(Event {
+ date: NaiveDate::parse_from_str(&date, DATE_FORMAT).ok()?,
+ name: validate_name(name)?,
+ start,
+ end,
+ })
+}
+
+fn validate_time(time: String) -> Option<Option<NaiveTime>> {
+ let time = time.trim();
+ if time.is_empty() {
+ Some(None)
+ } else {
+ parse_time(time).map(|t| Some(t))
+ }
+}
+
+fn validate_name(name: String) -> Option<String> {
+ let name = name.trim();
+ if name.is_empty() {
+ None
+ } else {
+ Some(name.to_string())
+ }
+}