From 5450884bb046964ce802f31fe47235b2813e6eea Mon Sep 17 00:00:00 2001 From: Fabien Freling Date: Mon, 23 Jun 2025 13:05:23 +0200 Subject: [PATCH] use custom Task type --- Cargo.lock | 7 +++ Cargo.toml | 1 + flake.nix | 1 + src/lib.rs | 14 +++++ src/main.rs | 146 ++++++++++++++++---------------------------------- src/models.rs | 10 ++++ 6 files changed, 79 insertions(+), 100 deletions(-) create mode 100644 src/lib.rs create mode 100644 src/models.rs diff --git a/Cargo.lock b/Cargo.lock index 4a03f2b..159d1fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,6 +214,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "dsl_auto_type" version = "0.1.3" @@ -737,6 +743,7 @@ dependencies = [ "color-eyre", "crossterm", "diesel", + "dotenvy", "ratatui", ] diff --git a/Cargo.toml b/Cargo.toml index b2f7bec..40a35f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ ratatui = "0.29.0" color-eyre = "0.6.3" diesel = { version = "2.2", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] } +dotenvy = "0.15" diff --git a/flake.nix b/flake.nix index 8df85e2..588d4d1 100644 --- a/flake.nix +++ b/flake.nix @@ -18,6 +18,7 @@ rust-toolchain cargo-generate diesel-cli + sqlite just lazysql diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..eba84f7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +pub mod models; +pub mod schema; + +use diesel::prelude::*; +use dotenvy::dotenv; +use std::env; + +pub fn establish_connection() -> SqliteConnection { + dotenv().ok(); + + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + SqliteConnection::establish(&database_url) + .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) +} diff --git a/src/main.rs b/src/main.rs index 7e94155..baae611 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use color_eyre::Result; +use diesel::prelude::*; use ratatui::{ DefaultTerminal, buffer::Buffer, @@ -15,6 +16,8 @@ use ratatui::{ StatefulWidget, Widget, Wrap, }, }; +use todo::establish_connection; +use todo::models::*; const TODO_HEADER_STYLE: Style = Style::new().fg(SLATE.c100).bg(BLUE.c800); const NORMAL_ROW_BG: Color = SLATE.c950; @@ -26,91 +29,32 @@ const COMPLETED_TEXT_FG_COLOR: Color = GREEN.c500; fn main() -> Result<()> { color_eyre::install()?; let terminal = ratatui::init(); - let app_result = App::default().run(terminal); + let app = App::default().load_db(); + let app_result = app.run(terminal); ratatui::restore(); app_result } struct App { should_exit: bool, - todo_list: TodoList, + tasks: TaskList, + sqlite: SqliteConnection, } -struct TodoList { - items: Vec, +struct TaskList { + items: Vec, state: ListState, } -#[derive(Debug)] -struct TodoItem { - todo: String, - info: String, - status: Status, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -enum Status { - Todo, - Completed, -} - impl Default for App { fn default() -> Self { Self { should_exit: false, - todo_list: TodoList::from_iter([ - ( - Status::Todo, - "Rewrite everything with Rust!", - "I can't hold my inner voice. He tells me to rewrite the complete universe with Rust", - ), - ( - Status::Completed, - "Rewrite all of your tui apps with Ratatui", - "Yes, you heard that right. Go and replace your tui with Ratatui.", - ), - ( - Status::Todo, - "Pet your cat", - "Minnak loves to be pet by you! Don't forget to pet and give some treats!", - ), - ( - Status::Todo, - "Walk with your dog", - "Max is bored, go walk with him!", - ), - ( - Status::Completed, - "Pay the bills", - "Pay the train subscription!!!", - ), - ( - Status::Completed, - "Refactor list example", - "If you see this info that means I completed this task!", - ), - ]), - } - } -} - -impl FromIterator<(Status, &'static str, &'static str)> for TodoList { - fn from_iter>(iter: I) -> Self { - let items = iter - .into_iter() - .map(|(status, todo, info)| TodoItem::new(status, todo, info)) - .collect(); - let state = ListState::default(); - Self { items, state } - } -} - -impl TodoItem { - fn new(status: Status, todo: &str, info: &str) -> Self { - Self { - status, - todo: todo.to_string(), - info: info.to_string(), + tasks: TaskList { + items: Vec::new(), + state: ListState::default(), + }, + sqlite: establish_connection(), } } } @@ -126,6 +70,17 @@ impl App { Ok(()) } + fn load_db(mut self) -> Self { + use todo::schema::tasks::dsl::*; + + self.tasks.items = tasks + .select(Task::as_select()) + .load(&mut self.sqlite) + .expect("Error loading posts"); + + self + } + fn handle_key(&mut self, key: KeyEvent) { if key.kind != KeyEventKind::Press { return; @@ -143,32 +98,32 @@ impl App { } fn select_none(&mut self) { - self.todo_list.state.select(None); + self.tasks.state.select(None); } fn select_next(&mut self) { - self.todo_list.state.select_next(); + self.tasks.state.select_next(); } fn select_previous(&mut self) { - self.todo_list.state.select_previous(); + self.tasks.state.select_previous(); } fn select_first(&mut self) { - self.todo_list.state.select_first(); + self.tasks.state.select_first(); } fn select_last(&mut self) { - self.todo_list.state.select_last(); + self.tasks.state.select_last(); } /// Changes the status of the selected list item fn toggle_status(&mut self) { - if let Some(i) = self.todo_list.state.selected() { - self.todo_list.items[i].status = match self.todo_list.items[i].status { - Status::Completed => Status::Todo, - Status::Todo => Status::Completed, - } - } + // if let Some(i) = self.tasks.state.selected() { + // self.tasks.items[i].status = match self.tasks.items[i].status { + // Status::Completed => Status::Todo, + // Status::Todo => Status::Completed, + // } + // } } } @@ -216,13 +171,14 @@ impl App { // Iterate through all elements in the `items` and stylize them. let items: Vec = self - .todo_list + .tasks .items .iter() .enumerate() - .map(|(i, todo_item)| { + .map(|(i, task)| { let color = alternate_colors(i); - ListItem::from(todo_item).bg(color) + let item = item_from_task(task); + item.bg(color) }) .collect(); @@ -235,16 +191,13 @@ impl App { // We need to disambiguate this trait method as both `Widget` and `StatefulWidget` share the // same method name `render`. - StatefulWidget::render(list, area, buf, &mut self.todo_list.state); + StatefulWidget::render(list, area, buf, &mut self.tasks.state); } fn render_selected_item(&self, area: Rect, buf: &mut Buffer) { // We get the info depending on the item's state. - let info = if let Some(i) = self.todo_list.state.selected() { - match self.todo_list.items[i].status { - Status::Completed => format!("✓ DONE: {}", self.todo_list.items[i].info), - Status::Todo => format!("☐ TODO: {}", self.todo_list.items[i].info), - } + let info = if let Some(i) = self.tasks.state.selected() { + format!("☐ TODO: {}", self.tasks.items[i].title) } else { "Nothing selected...".to_string() }; @@ -275,14 +228,7 @@ const fn alternate_colors(i: usize) -> Color { } } -impl From<&TodoItem> for ListItem<'_> { - fn from(value: &TodoItem) -> Self { - let line = match value.status { - Status::Todo => Line::styled(format!(" ☐ {}", value.todo), TEXT_FG_COLOR), - Status::Completed => { - Line::styled(format!(" ✓ {}", value.todo), COMPLETED_TEXT_FG_COLOR) - } - }; - ListItem::new(line) - } +fn item_from_task(task: &Task) -> ListItem<'_> { + let line = Line::styled(format!(" ☐ {}", task.title), TEXT_FG_COLOR); + ListItem::new(line) } diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..e17ac21 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,10 @@ +use diesel::prelude::*; + +#[derive(Queryable, Selectable)] +#[diesel(table_name = crate::schema::tasks)] +#[diesel(check_for_backend(diesel::sqlite::Sqlite))] +pub struct Task { + pub id: i32, + pub title: String, + pub description: String, +}