feat: improved cli, added edit last

This commit is contained in:
əlemi 2022-12-16 02:25:41 +01:00
parent c5c5082972
commit b18ccfc290
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
2 changed files with 98 additions and 51 deletions

View file

@ -42,6 +42,17 @@ struct Cli {
path: Option<String>,
}
#[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<String>,
#[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<DateTime<Utc>> = 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<Memo> = 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<Memo> = 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);
}
}

View file

@ -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<Duration, Error> {
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::<i64>().unwrap(); // safely unwrap because regex gave only digits
@ -89,20 +90,14 @@ pub fn parse_human_duration(input: &str) -> Result<Duration, Error> {
}
}
return Ok(Duration::seconds(secs));
return Duration::seconds(secs);
}
pub fn find_by_regex(re: Regex, memos: Vec<Memo>) -> Option<Memo> {
let mut found = false;
let mut out: Option<Memo> = None;
pub fn find_by_regex(re: Regex, memos: Vec<Memo>) -> Vec<Memo> {
let mut out: Vec<Memo> = 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;