diff --git a/src/main.rs b/src/main.rs index 24e30c0..9c8bad2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,17 @@ struct Cli { path: Option, } +#[derive(Subcommand, Clone)] +enum EditMode { + /// search memo by content + Find { + /// regex to use for filtering + regex: String, + }, + /// edit last edited memo + Last, +} + #[derive(Subcommand, Clone)] enum Commands { /// create a new memo @@ -75,7 +86,8 @@ enum Commands { }, /// change existing memo Edit { - search: String, + #[clap(subcommand, help = "search method to use")] + mode: EditMode, #[clap(short, long, help = "set memo tag")] tag: Option, #[clap(short, long, help = "set memo message")] @@ -111,14 +123,19 @@ fn main() { //TODO: permissions check, this will panic if it finds an existing db it can't write to if let Some(ext) = db_path.extension() { - match ext.to_str().unwrap() { - "json" => run_commands(JsonStorage::new(db_path).unwrap(), args2).unwrap(), - "db" => run_commands(SQLiteStorage::new(db_path, true).unwrap(), args2).unwrap(), - _ => println!("[!] unsupported database"), + let res = match ext.to_str().unwrap() { + "json" => run_commands(JsonStorage::new(db_path).unwrap(), args2), + "db" => run_commands(SQLiteStorage::new(db_path, true).unwrap(), args2), + _ => { println!("[!] unsupported database"); return; }, + }; + match res { + Ok(()) => {}, + Err(e) => { + println!("{} error executing command : {:?}", format!("[{}]", "!".red()).bold(), e); + } } } else { println!("[!] no extension on db file"); - println!("something"); } } @@ -144,29 +161,29 @@ where T: StorageDriver, // } // } - let ctx = storage.ctx().unwrap(); + let ctx = storage.ctx()?; match args.command { Some(Commands::New { body, due, tag }) => { 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()); + due_date = Some(Utc::now() + parse_human_duration(d.as_str())); } } let txt = body.join(" "); println!("{} new memo: {}", "[+]".bold(), &txt); - storage.add(tag.unwrap_or("".to_string()), txt, due_date).unwrap(); + storage.add(tag.unwrap_or("".to_string()), txt, due_date)?; } 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() { + for memo in storage.all(false)? { if re.is_match(memo.body.as_str()) { if many { - storage.del(&memo.id).unwrap(); + storage.del(&memo.id)?; println!("[-] done task : {}", memo.body); } else if found { println!("[!] would remove multiple tasks"); @@ -183,47 +200,82 @@ where T: StorageDriver, } } if let Some(rm) = to_remove { - storage.del(&rm.id).unwrap(); + storage.del(&rm.id)?; println!("{} done memo: {}", "[-]".bold(), rm.body); } } else { println!("{} invalid regex", format!("[{}]", "!".red()).bold()); } } - Some(Commands::Edit { search, tag, body, due }) => { - let mut m = find_by_regex( - regex::Regex::new(search.as_str()).unwrap(), - storage.all(false).unwrap(), - ) - .unwrap(); + Some(Commands::Edit { mode, tag, body, due }) => { + let search : Option = match mode { + EditMode::Find { regex } => { + let mut matches = find_by_regex( + regex::Regex::new(regex.as_str()).unwrap(), + storage.all(false)?, + ); + match matches.len() { + 0 => None, + 1 => Some(matches.pop().unwrap()), + _ => { + println!("{} more than one memo matches criteria", format!("[{}]", "#".bright_red()).bold()); + for memo in matches { + println!("{}", memo.human()); + } + None + } - if let Some(t) = tag { - m.tag = t; - } - if let Some(b) = body { - m.body = b.to_owned(); - } - 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()); + } + }, + EditMode::Last => { + match ctx.last_memo { + Some(id) => Some(storage.get(&id)?), + None => None, + } } + }; + + if let Some(mut m) = search { + let mut changed = false; + + if let Some(t) = tag { + changed = true; + m.tag = t; + } + if let Some(b) = body { + changed = true; + m.body = b.to_owned(); + } + if let Some(d) = due { + changed = true; + if d == "null" || d == "none" { + m.due = None + } else { + m.due = Some(Utc::now() + parse_human_duration(d.as_str())); + } + } + + if changed { + let m_str = m.colored(); + storage.put(m)?; + println!( + "{} updated memo\n{}", + format!("[{}]", ">".green()).bold().to_string(), + m_str, + ); + } else { + println!("{} no change requested", format!("[{}]", "?".magenta()).bold()); + } + } else { + println!("{} no memo matches criteria", format!("[{}]", "!".red()).bold()); } - let m_str = m.colored(); - storage.put(m).unwrap(); - println!( - "{} updated memo\n{}", - format!("[{}]", ">".green()).bold().to_string(), - m_str, - ); } Some(Commands::List { tag, notify, depth, old }) => { - let all = storage.all(old).unwrap(); + let all = storage.all(old)?; display_memos(all, tag, &ctx, notify, depth); }, None => { - let all = storage.all(false).unwrap(); + let all = storage.all(false)?; display_memos(all, None, &ctx, false, 5); } } diff --git a/src/utils.rs b/src/utils.rs index 9f57d1d..e026156 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,6 @@ use crate::model::memo::Memo; use chrono::{Duration, Utc}; -use regex::{Error, Regex}; +use regex::Regex; use colored::Colorize; use std::path::PathBuf; @@ -77,11 +77,12 @@ impl HumanDisplay for Memo { } } -pub fn parse_human_duration(input: &str) -> Result { +pub fn parse_human_duration(input: &str) -> Duration { let mut secs: i64 = 0; for (token, mult) in [("d", 60 * 60 * 24), ("h", 60 * 60), ("m", 60)] { - let re = Regex::new(format!("((?:-|)[0-9]+){token}", token = token).as_str())?; + // TODO don't rebuild the regex every time + let re = Regex::new(format!("((?:-|)[0-9]+){token}", token = token).as_str()).unwrap(); if let Some(captures) = re.captures(input) { if let Some(cap) = captures.get(1) { secs += mult * cap.as_str().parse::().unwrap(); // safely unwrap because regex gave only digits @@ -89,20 +90,14 @@ pub fn parse_human_duration(input: &str) -> Result { } } - return Ok(Duration::seconds(secs)); + return Duration::seconds(secs); } -pub fn find_by_regex(re: Regex, memos: Vec) -> Option { - let mut found = false; - let mut out: Option = None; +pub fn find_by_regex(re: Regex, memos: Vec) -> Vec { + let mut out: Vec = Vec::new(); for memo in memos { if re.is_match(memo.body.as_str()) { - if found { - return None; // there's at least one duplicate - } else { - out = Some(memo); - found = true; - } + out.push(memo); } } return out;