mod storage; mod utils; use chrono::{DateTime, Local, Utc}; use clap::{Parser, Subcommand}; use colored::Colorize; use const_format::concatcp; use git_version::git_version; use regex::Regex; use storage::{open_sqlite_storage, Memo, MemoStorage}; use utils::{find_by_regex, parse_human_duration, HumanDisplay}; const GIT_VERSION: &str = git_version!(); const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); const VERSION: &str = concatcp!(PKG_VERSION, "-", GIT_VERSION); #[derive(Parser)] #[clap(author, about, long_about = None)] #[clap(version = VERSION)] #[clap(disable_colored_help = true)] #[clap(subcommand_required = false)] #[clap(disable_help_subcommand = true)] struct Cli { #[clap(subcommand)] command: Option, #[clap(short, long, help = "show memos in a notification")] notify: bool, #[clap(long, help = "show completed tasks")] old: bool, #[clap(short, long, help = "location for database file")] path: Option, } #[derive(Subcommand)] enum Commands { /// create a new memo #[clap(trailing_var_arg = true)] New { #[clap(multiple_values = true)] #[clap(min_values = 1)] #[clap(required = true)] body: Vec, #[clap(short, long, help = "due time relative to now")] due: Option, // TODO allow to pass date }, /// mark existing memo as done Done { search: String, #[clap(long, help = "delete more than one task if matched")] many: bool, }, /// change existing memo Edit { search: String, #[clap(short, long, help = "set memo message")] body: Option, #[clap(short, long, help = "set due time relative to now")] due: Option, // TODO allow to pass date }, } fn main() { let args = Cli::parse(); let home_path = std::env!("HOME").to_string(); let mut db_path: String = home_path + "/.local/share/memo-cli.db"; if let Some(db) = args.path { db_path = db; } let storage = open_sqlite_storage(&db_path).unwrap(); match args.command { Some(Commands::New { body, due }) => { let mut due_date: Option> = None; if let Some(d) = due { if d.len() > 0 { due_date = Some(Utc::now() + parse_human_duration(d.as_str()).unwrap()); } } let txt = body.join(" "); storage.add(txt.as_str(), due_date).unwrap(); println!("{} new memo: {}", "[+]".bold(), txt); } Some(Commands::Done { search, many }) => { let rex = Regex::new(search.as_str()); let mut found = false; let mut to_remove: Option = None; if let Some(re) = rex.ok() { for memo in storage.all(false).unwrap() { if re.is_match(memo.body.as_str()) { if many { storage.del(memo.id).unwrap(); println!("[-] done task : {}", memo.body); } else if found { println!("[!] would remove multiple tasks"); to_remove = None; break; } else { to_remove = Some(memo); found = true; } } } if let Some(rm) = to_remove { storage.del(rm.id).unwrap(); println!("{} done memo: {}", "[-]".bold(), rm.body); } } else { println!("{} invalid regex", format!("[{}]", "!".red()).bold()); } } Some(Commands::Edit { search, body, due }) => { let mut m = find_by_regex( regex::Regex::new(search.as_str()).unwrap(), storage.all(false).unwrap(), ) .unwrap(); if let Some(b) = body { m.body = b; } if let Some(d) = due { if d == "null" || d == "none" { m.due = None } else { m.due = Some(Utc::now() + parse_human_duration(d.as_str()).unwrap()); } } storage.set(&m).unwrap(); println!( "{} updated memo\n{}", format!("[{}]", ">".green()).bold().to_string(), m.colored(), ); } None => { let all = storage.all(args.old).unwrap(); let mut builder = String::new(); if args.old { builder.push_str("Archived memos:\n"); } if all.len() < 1 { builder.push_str("[ ] nothing to remember\n"); } for m in all { let tmp = if args.notify { m.human() } else { m.colored() }; builder.push_str(tmp.as_str()); builder.push('\n'); } if args.notify { libnotify::init("memo-cli").unwrap(); let n = libnotify::Notification::new( format!("memo-cli | {}", Local::now().format("%a %d/%m, %H:%M")).as_str(), Some(builder.as_str()), None, ); n.show().unwrap(); libnotify::uninit(); } else { println!("{} | {}", "memo-cli".bold(), Local::now().format("%a %d/%m, %H:%M")); print!("{}", builder); } } } }