extern crate argparse; use argparse::{ArgumentParser, Store, StoreTrue}; use chrono::{DateTime, Duration, Utc}; use rusqlite::{params, Connection, Error}; struct Memo { id: u32, body: String, due: DateTime, } fn init_db(path: &str) -> Result { let connection = Connection::open(path)?; connection.execute( "CREATE TABLE IF NOT EXISTS memo ( id INTEGER PRIMARY KEY, body TEXT NOT NULL, due DATETIME );", [], )?; return Ok(connection); } fn all(conn: Connection) -> Result, rusqlite::Error> { let mut statement = conn.prepare("SELECT * FROM memo ORDER BY due, id")?; let mut rows = statement.query([])?; let mut results = Vec::new(); while let Some(row) = rows.next()? { results.push(Memo { id: row.get(0)?, body: row.get(1)?, due: row.get(2)?, }); } return Ok(results); } pub trait PrettyPrint { fn pretty(&self) -> String; } impl PrettyPrint for Duration { fn pretty(&self) -> String { if self.num_days().abs() > 400 { return format!("{}y {}m", self.num_days().abs() / 365, (self.num_days() % (30*12)) / 30); } else if self.num_days().abs() >= 365 { return format!("{}y", self.num_days().abs() / 365); } else if self.num_days().abs() >= 90 { return format!("{}m", self.num_days().abs() / 30); // sort of } else if self.num_days().abs() >= 1 { return format!("{}d", self.num_days().abs()); } else if self.num_hours().abs() > 0 { let h = self.num_minutes().abs() / 60; let m = self.num_minutes().abs() % 60; return format!("{}h {}min", h, m); } else if self.num_minutes().abs() > 0 { return format!("{}min", self.num_minutes().abs()); } else if self.num_seconds().abs() > 0 { return format!("{}s", self.num_seconds().abs()); } return self.to_string(); } } fn main() { let home_path = std::env!("HOME").to_string(); if home_path.len() < 1 { panic!("Cannot work without a home folder"); } let mut version = false; let mut memo: String = "".to_string(); let mut db_path: String = home_path + "/.local/share/memo-cli.db"; { // this block limits scope of borrows by ap.refer() method let mut ap = ArgumentParser::new(); ap.set_description("A simple tool to remember things"); ap.refer(&mut version).add_option( &["-V", "--version"], StoreTrue, "Show current version and die", ); ap.refer(&mut memo) .add_option(&["-n", "--new"], Store, "Add a new memo"); ap.refer(&mut db_path) .add_option(&["--path"], Store, "Specify db path"); ap.parse_args_or_exit(); } if version { const VERSION: &str = env!("CARGO_PKG_VERSION"); println!("memo-cli v{}", VERSION); return; } let connection = init_db(&db_path).unwrap_or_else(|err| panic!("Could not open db : {}", err.to_string())); if memo.len() > 0 { connection .execute( "INSERT INTO memo (body, due) VALUES (?, ?)", params![memo, Utc::now()], ) .unwrap_or_else(|err| panic!("Could not insert into db : {}", err.to_string())); println!("New memo : '{}'", memo); } else { let zero_duration = Duration::seconds(0); let memos = all(connection) .unwrap_or_else(|err| panic!("Could not read all memos : {}", err.to_string())); for m in memos { let delta = m.due - Utc::now(); if delta.le(&zero_duration) { println!("[*] {} : +{} \t[{}]", m.body, delta.pretty(), m.id); } else { println!(" * {} : -{} \t[{}]", m.body, delta.pretty(), m.id); } } } }