Skip to content

Commit

Permalink
Restructure code and split into multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
lukahartwig committed Apr 8, 2023
1 parent 569ecbe commit 3b64270
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 75 deletions.
95 changes: 20 additions & 75 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,19 @@
use anyhow::Result;
use chrono::prelude::*;
use chrono_humanize::HumanTime;
use clap::{Parser, Subcommand, ValueEnum};
use rusqlite::{params, Connection, ToSql};
use std::fmt;
use clap::{Parser, Subcommand};
use std::fs;

mod embedded {
use refinery::embed_migrations;
embed_migrations!("./migrations");
}

#[derive(Debug, Clone, ValueEnum)]
enum TodoStatus {
Ready,
Doing,
Done,
}

impl fmt::Display for TodoStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TodoStatus::Ready => write!(f, "READY"),
TodoStatus::Doing => write!(f, "DOING"),
TodoStatus::Done => write!(f, "DONE"),
}
}
}
mod store;
mod todo;

impl ToSql for TodoStatus {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(self.to_string().into())
}
}
use crate::store::Store;
use crate::todo::{Todo, TodoStatus};

#[derive(Debug)]
struct Todo {
id: u32,
created_at: DateTime<Local>,
title: String,
#[derive(Debug, Parser)]
#[clap(name = "todo", version)]
struct App {
#[clap(subcommand)]
command: Commands,
}

#[derive(Subcommand, Debug)]
Expand All @@ -54,13 +29,6 @@ enum Commands {
Prune,
}

#[derive(Debug, Parser)]
#[clap(name = "todo", version)]
struct App {
#[clap(subcommand)]
command: Commands,
}

fn main() -> Result<()> {
let app = App::parse();

Expand All @@ -70,55 +38,32 @@ fn main() -> Result<()> {
if !path.exists() {
fs::create_dir_all(path.as_path())?;
}

let mut db = Connection::open(path.join("todo.db")).expect("failed to open todo database");
embedded::migrations::runner()
.run(&mut db)
.expect("failed to run migrations");

let store = Store::open(path.join("todo.db"))?;

match app.command {
Commands::Add { message } => {
db.execute("INSERT INTO todos (title) VALUES (?1)", params![message])
.expect("failed to insert todo");
store.insert_todo(message).expect("failed to insert todo");
}
Commands::List => {
let mut stmt = db
.prepare("SELECT id, created_at, title FROM todos WHERE status != 'DONE'")
.expect("failed to prepare query");
let todos = store.find_open_todos().expect("failed to find open todos");

stmt.query_map([], |row| {
Ok(Todo {
id: row.get(0)?,
created_at: row.get(1)?,
title: row.get(2)?,
})
})?
.filter_map(|todo| todo.ok())
.for_each(|todo| {
for todo in todos {
println!(
"{}) {} ({})",
todo.id,
todo.title,
HumanTime::from(todo.created_at)
);
});
}
}
Commands::Set { id, status } => {
db.execute(
"UPDATE todos SET status = ?1 WHERE id = ?2",
params![status, id],
)
.expect("failed to update todo status");
store
.update_todo_status(id, status)
.expect("failed to update todo status");
}
Commands::Prune => {
db.execute("DELETE FROM todos WHERE status = 'DONE'", ())
.expect("failed to delete todos");

db.execute(
"UPDATE SQLITE_SEQUENCE SET SEQ = (SELECT MAX(id) FROM todos) WHERE NAME = 'todos'",
(),
)
.expect("failed to reset autoincrement");
store.prune_todos().expect("failed pruning todos");
}
}

Expand Down
80 changes: 80 additions & 0 deletions src/store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use crate::{Todo, TodoStatus};
use anyhow::Result;
use rusqlite::{params, Connection};
use std::path::Path;

mod embedded {
use refinery::embed_migrations;
embed_migrations!("./migrations");
}

pub struct Store {
conn: Connection,
}

impl Store {
pub fn open<P>(path: P) -> Result<Self>
where
P: AsRef<Path>,
{
let mut conn = Connection::open(path).expect("failed to open todo database");

embedded::migrations::runner()
.run(&mut conn)
.expect("failed to run migrations");

Ok(Self { conn })
}

pub fn insert_todo(&self, msg: String) -> Result<()> {
self.conn
.execute("INSERT INTO todos (title) VALUES (?1)", params![msg])
.expect("failed to insert todo");
Ok(())
}

pub fn find_open_todos(&self) -> Result<Vec<Todo>> {
let mut stmt = self
.conn
.prepare("SELECT id, created_at, title FROM todos WHERE status != 'DONE'")
.expect("failed to prepare query");

let todos = stmt
.query_map([], |row| {
Ok(Todo {
id: row.get(0)?,
created_at: row.get(1)?,
title: row.get(2)?,
})
})?
.filter_map(|todo| todo.ok())
.collect();

Ok(todos)
}

pub fn update_todo_status(&self, id: u32, status: TodoStatus) -> Result<()> {
self.conn
.execute(
"UPDATE todos SET status = ?1 WHERE id = ?2",
params![status, id],
)
.expect("failed to update todo status");
Ok(())
}

pub fn prune_todos(&self) -> Result<()> {
self.conn
.execute("DELETE FROM todos WHERE status = 'DONE'", ())
.expect("failed to delete todos");

self.conn
.execute(
"UPDATE SQLITE_SEQUENCE SET SEQ = (SELECT MAX(id) FROM todos) WHERE NAME = 'todos'",
(),
)
.expect("failed to reset autoincrement");

Ok(())
}
}
34 changes: 34 additions & 0 deletions src/todo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use std::fmt;
use chrono::{DateTime, Local};
use clap::ValueEnum;
use rusqlite::ToSql;

#[derive(Debug, Clone, ValueEnum)]
pub enum TodoStatus {
Ready,
Doing,
Done,
}

impl fmt::Display for TodoStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TodoStatus::Ready => write!(f, "READY"),
TodoStatus::Doing => write!(f, "DOING"),
TodoStatus::Done => write!(f, "DONE"),
}
}
}

impl ToSql for TodoStatus {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(self.to_string().into())
}
}

#[derive(Debug)]
pub struct Todo {
pub id: u32,
pub created_at: DateTime<Local>,
pub title: String,
}

0 comments on commit 3b64270

Please sign in to comment.