mirror of
https://git.alemi.dev/memo-cli.git
synced 2024-11-24 09:54:52 +01:00
feat: improved cli, added edit last
This commit is contained in:
parent
c5c5082972
commit
b18ccfc290
2 changed files with 98 additions and 51 deletions
128
src/main.rs
128
src/main.rs
|
@ -42,6 +42,17 @@ struct Cli {
|
||||||
path: Option<String>,
|
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)]
|
#[derive(Subcommand, Clone)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
/// create a new memo
|
/// create a new memo
|
||||||
|
@ -75,7 +86,8 @@ enum Commands {
|
||||||
},
|
},
|
||||||
/// change existing memo
|
/// change existing memo
|
||||||
Edit {
|
Edit {
|
||||||
search: String,
|
#[clap(subcommand, help = "search method to use")]
|
||||||
|
mode: EditMode,
|
||||||
#[clap(short, long, help = "set memo tag")]
|
#[clap(short, long, help = "set memo tag")]
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
#[clap(short, long, help = "set memo message")]
|
#[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
|
//TODO: permissions check, this will panic if it finds an existing db it can't write to
|
||||||
|
|
||||||
if let Some(ext) = db_path.extension() {
|
if let Some(ext) = db_path.extension() {
|
||||||
match ext.to_str().unwrap() {
|
let res = match ext.to_str().unwrap() {
|
||||||
"json" => run_commands(JsonStorage::new(db_path).unwrap(), args2).unwrap(),
|
"json" => run_commands(JsonStorage::new(db_path).unwrap(), args2),
|
||||||
"db" => run_commands(SQLiteStorage::new(db_path, true).unwrap(), args2).unwrap(),
|
"db" => run_commands(SQLiteStorage::new(db_path, true).unwrap(), args2),
|
||||||
_ => println!("[!] unsupported database"),
|
_ => { println!("[!] unsupported database"); return; },
|
||||||
|
};
|
||||||
|
match res {
|
||||||
|
Ok(()) => {},
|
||||||
|
Err(e) => {
|
||||||
|
println!("{} error executing command : {:?}", format!("[{}]", "!".red()).bold(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("[!] no extension on db file");
|
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 {
|
match args.command {
|
||||||
Some(Commands::New { body, due, tag }) => {
|
Some(Commands::New { body, due, tag }) => {
|
||||||
let mut due_date: Option<DateTime<Utc>> = None;
|
let mut due_date: Option<DateTime<Utc>> = None;
|
||||||
if let Some(d) = due {
|
if let Some(d) = due {
|
||||||
if d.len() > 0 {
|
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(" ");
|
let txt = body.join(" ");
|
||||||
println!("{} new memo: {}", "[+]".bold(), &txt);
|
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 }) => {
|
Some(Commands::Done { search, many }) => {
|
||||||
let rex = Regex::new(search.as_str());
|
let rex = Regex::new(search.as_str());
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
let mut to_remove: Option<Memo> = None;
|
let mut to_remove: Option<Memo> = None;
|
||||||
if let Some(re) = rex.ok() {
|
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 re.is_match(memo.body.as_str()) {
|
||||||
if many {
|
if many {
|
||||||
storage.del(&memo.id).unwrap();
|
storage.del(&memo.id)?;
|
||||||
println!("[-] done task : {}", memo.body);
|
println!("[-] done task : {}", memo.body);
|
||||||
} else if found {
|
} else if found {
|
||||||
println!("[!] would remove multiple tasks");
|
println!("[!] would remove multiple tasks");
|
||||||
|
@ -183,47 +200,82 @@ where T: StorageDriver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(rm) = to_remove {
|
if let Some(rm) = to_remove {
|
||||||
storage.del(&rm.id).unwrap();
|
storage.del(&rm.id)?;
|
||||||
println!("{} done memo: {}", "[-]".bold(), rm.body);
|
println!("{} done memo: {}", "[-]".bold(), rm.body);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("{} invalid regex", format!("[{}]", "!".red()).bold());
|
println!("{} invalid regex", format!("[{}]", "!".red()).bold());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Commands::Edit { search, tag, body, due }) => {
|
Some(Commands::Edit { mode, tag, body, due }) => {
|
||||||
let mut m = find_by_regex(
|
let search : Option<Memo> = match mode {
|
||||||
regex::Regex::new(search.as_str()).unwrap(),
|
EditMode::Find { regex } => {
|
||||||
storage.all(false).unwrap(),
|
let mut matches = find_by_regex(
|
||||||
)
|
regex::Regex::new(regex.as_str()).unwrap(),
|
||||||
.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;
|
},
|
||||||
}
|
EditMode::Last => {
|
||||||
if let Some(b) = body {
|
match ctx.last_memo {
|
||||||
m.body = b.to_owned();
|
Some(id) => Some(storage.get(&id)?),
|
||||||
}
|
None => None,
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 }) => {
|
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);
|
display_memos(all, tag, &ctx, notify, depth);
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
let all = storage.all(false).unwrap();
|
let all = storage.all(false)?;
|
||||||
display_memos(all, None, &ctx, false, 5);
|
display_memos(all, None, &ctx, false, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
21
src/utils.rs
21
src/utils.rs
|
@ -1,6 +1,6 @@
|
||||||
use crate::model::memo::Memo;
|
use crate::model::memo::Memo;
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use regex::{Error, Regex};
|
use regex::Regex;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use std::path::PathBuf;
|
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;
|
let mut secs: i64 = 0;
|
||||||
|
|
||||||
for (token, mult) in [("d", 60 * 60 * 24), ("h", 60 * 60), ("m", 60)] {
|
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(captures) = re.captures(input) {
|
||||||
if let Some(cap) = captures.get(1) {
|
if let Some(cap) = captures.get(1) {
|
||||||
secs += mult * cap.as_str().parse::<i64>().unwrap(); // safely unwrap because regex gave only digits
|
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> {
|
pub fn find_by_regex(re: Regex, memos: Vec<Memo>) -> Vec<Memo> {
|
||||||
let mut found = false;
|
let mut out: Vec<Memo> = Vec::new();
|
||||||
let mut out: Option<Memo> = None;
|
|
||||||
for memo in memos {
|
for memo in memos {
|
||||||
if re.is_match(memo.body.as_str()) {
|
if re.is_match(memo.body.as_str()) {
|
||||||
if found {
|
out.push(memo);
|
||||||
return None; // there's at least one duplicate
|
|
||||||
} else {
|
|
||||||
out = Some(memo);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|
Loading…
Reference in a new issue