From a3b46d8e1bf985312f196f3a02578bf050c2d8a5 Mon Sep 17 00:00:00 2001 From: JF Ding Date: Tue, 12 Aug 2025 16:10:43 +0800 Subject: [PATCH 1/6] add -f/--from-logseq opt for import cmd to import new tasks from logseq's journals markdown files Signed-off-by: JF Ding --- Cargo.lock | 4 ++-- src/cli.rs | 3 +++ src/main.rs | 5 ++++- src/taskbox.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d380d09..d3f741d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aes" @@ -1086,7 +1086,7 @@ dependencies = [ [[package]] name = "todor" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "chrono", diff --git a/src/cli.rs b/src/cli.rs index ec3a40a..c8e4fc6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -153,6 +153,9 @@ pub enum Commands { Import{ #[arg(value_name = "markdown-file")] file: Option, + + #[arg(short, long)] + from_logseq: bool, }, /// -> launch file manager on basedir diff --git a/src/main.rs b/src/main.rs index 3650eb8..200e753 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,10 @@ fn main() { if cc > 0 { println!("{}", cc) } } - Some(Commands::Import{ file }) => TaskBox::new(inbox_path).import(file), + Some(Commands::Import{ file, from_logseq }) => { + TaskBox::new(inbox_path).import(file, from_logseq); + } + Some(Commands::Purge { sort }) => { if i_confirm("are you sure?") { if sort && ! i_confirm("sort cannot handle subtasks well, continue?") { return } diff --git a/src/taskbox.rs b/src/taskbox.rs index 40a9d36..f6f5601 100644 --- a/src/taskbox.rs +++ b/src/taskbox.rs @@ -29,6 +29,10 @@ const PREFIX_OPEN :&str = "- [ ] "; const PREFIX_DONE :&str = "- [x] "; const PREFIX_SUBT :&str = " 󱞩 "; +const PREFIX_OPEN_LOGSEQ :&str = "- TODO "; +const PREFIX_OPEN2_LOGSEQ :&str = "- LATER "; +// const PREFIX_DONE_LOGSEQ :&str = "- DONE "; + #[derive(Debug)] pub struct TaskBox { pub fpath: PathBuf, @@ -519,9 +523,30 @@ impl TaskBox { } // specified markdown file -> cur - pub fn import(&mut self, file: Option) { + pub fn import(&mut self, file: Option, from_logseq: bool) { #[allow(clippy::redundant_closure)] - let mdfile= file.unwrap_or_else(|| super::util::pick_file()); + + let mdfile = if from_logseq { + // get icloud path of "Logseq" + let icloud = dirs::home_dir().unwrap().join("Library/Mobile Documents/iCloud~com~logseq~logseq/Documents/journals"); + + // when logseq is true, the input "mdfile" is the date string of the journal file + // and can accept today, yesterday, week, etc. + // if date is not provided, use today + let mut date = match file { + Some(d) if d == "today" => get_today(), + Some(d) if d == "yesterday" => get_yesterday(), + // TODO Some("week") => get_week(), + Some(d) => d, + None => get_today(), + }; + + date = date.replace("-", "_"); + + icloud.join(format!("{}.md", date)).to_str().unwrap().to_string() + } else { + file.unwrap_or_else(|| super::util::pick_file()) + }; let fpath = Path::new(&mdfile); if ! fpath.is_file() { @@ -538,10 +563,16 @@ impl TaskBox { if let Some(stripped) = line.strip_prefix(PREFIX_OPEN) { if RE_ROUTINES.is_match(stripped) { - println!(" {} : {}", S_checkbox!(ROUTINES), stripped); newr.push(stripped.to_string()) } else { - println!(" {} : {}", S_checkbox!(CHECKBOX), stripped); + newt.push(stripped.to_string()) + } + } + + if from_logseq { + if let Some(stripped) = line.strip_prefix(PREFIX_OPEN_LOGSEQ) { + newt.push(stripped.to_string()) + } else if let Some(stripped) = line.strip_prefix(PREFIX_OPEN2_LOGSEQ) { newt.push(stripped.to_string()) } } @@ -550,9 +581,17 @@ impl TaskBox { if newt.is_empty() && newr.is_empty() { println!("{} found!", S_empty!("nothing")); return + } else { + println!("New tasks found in input file:"); + for task in &newt { + println!(" {} : {}", S_checkbox!(CHECKBOX), task); + } + for task in &newr { + println!(" {} : {}", S_checkbox!(ROUTINES), task); + } } - self.add_tasks(newt); + self.load(); self.add_tasks(newt); self.sibling(ROUTINE_BOXNAME).add_tasks(newr); } From 99a00dea766074e5a80ac66e09c96307f1f5af47 Mon Sep 17 00:00:00 2001 From: JF Ding Date: Tue, 12 Aug 2025 16:23:39 +0800 Subject: [PATCH 2/6] update test cases of import cmd --- tests/taskbox.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/taskbox.rs b/tests/taskbox.rs index 26b5e25..badb845 100644 --- a/tests/taskbox.rs +++ b/tests/taskbox.rs @@ -376,6 +376,11 @@ fn test_import_somefile_to_inbox() { - [ ] Task2 to import - [ ] {󰃯:d 2024-10-01Tue 󰳟} one daily to import - [ ] {󰃯:m 2024-10-31Mon 󰳟} one montly to import + +## belows are logseq style +- TODO Task to import to "Today" +- LATER Task to import also to "Inbox" +- DONE Task Done not to import "#; let (mut inbox, dir) = setup_test_taskbox(INBOX_BOXNAME); @@ -390,9 +395,13 @@ fn test_import_somefile_to_inbox() { assert_eq!(inbox.tasks.len(), 1); assert_eq!(routine.tasks.len(), 1); - inbox.import(Some(fpath.to_str().unwrap().to_string())); + inbox.import(Some(fpath.to_str().unwrap().to_string()), false); routine = inbox.sibling("routine"); //reset for force reload routine.load(); assert_eq!(inbox.tasks.len(), 4); assert_eq!(routine.tasks.len(), 3); + + // import again with "from_logseq" true + inbox.import(Some(fpath.to_str().unwrap().to_string()), true); + assert_eq!(inbox.tasks.len(), 6); } From 1dbf1849191dcb64fd6ea52cf1198bf70febc4bc Mon Sep 17 00:00:00 2001 From: JF Ding Date: Wed, 13 Aug 2025 09:42:07 +0800 Subject: [PATCH 3/6] clean up all clippy warns Signed-off-by: JF Ding --- src/main.rs | 5 +++-- src/taskbox.rs | 11 +++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 200e753..39e1523 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,12 +19,13 @@ fn main() { if let Some(boxname) = args.inbox { &boxname.clone() } else { - let cmdname = arg0.split(path::MAIN_SEPARATOR).last().unwrap(); + let cmdname = arg0.split(path::MAIN_SEPARATOR).next_back().unwrap(); + if cmdname == "todor" { "inbox" } else { // e.g. "today", "tomorrow", "yesterday", "t.read", "todo.working" - cmdname.split('.').last().unwrap() + cmdname.split('.').next_back().unwrap() } }; diff --git a/src/taskbox.rs b/src/taskbox.rs index f6f5601..e5fc1cf 100644 --- a/src/taskbox.rs +++ b/src/taskbox.rs @@ -209,8 +209,8 @@ impl TaskBox { return false } else if *done { // found done sub-task for this major task - if task_status.is_some() { - *task_status.unwrap() = true; + if let Some(task_status) = task_status { + *task_status = true; } return true } @@ -541,9 +541,12 @@ impl TaskBox { None => get_today(), }; - date = date.replace("-", "_"); + if ! date.ends_with(".md") { + date = format!("{}.md", date); + date = date.replace("-", "_"); + } - icloud.join(format!("{}.md", date)).to_str().unwrap().to_string() + icloud.join(date).to_str().unwrap().to_string() } else { file.unwrap_or_else(|| super::util::pick_file()) }; From 12a195cf856b8624bfc2605515288629f3042935 Mon Sep 17 00:00:00 2001 From: JF Ding Date: Wed, 13 Aug 2025 10:01:14 +0800 Subject: [PATCH 4/6] import LATER tasks in logseq to INBOX Signed-off-by: JF Ding --- src/taskbox.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/taskbox.rs b/src/taskbox.rs index e5fc1cf..c6a0b83 100644 --- a/src/taskbox.rs +++ b/src/taskbox.rs @@ -533,7 +533,7 @@ impl TaskBox { // when logseq is true, the input "mdfile" is the date string of the journal file // and can accept today, yesterday, week, etc. // if date is not provided, use today - let mut date = match file { + let mut mdfile_name = match file { Some(d) if d == "today" => get_today(), Some(d) if d == "yesterday" => get_yesterday(), // TODO Some("week") => get_week(), @@ -541,12 +541,12 @@ impl TaskBox { None => get_today(), }; - if ! date.ends_with(".md") { - date = format!("{}.md", date); - date = date.replace("-", "_"); + if ! mdfile_name.ends_with(".md") { + mdfile_name = format!("{}.md", mdfile_name); + mdfile_name = mdfile_name.replace("-", "_"); } - icloud.join(date).to_str().unwrap().to_string() + icloud.join(mdfile_name).to_str().unwrap().to_string() } else { file.unwrap_or_else(|| super::util::pick_file()) }; @@ -558,8 +558,10 @@ impl TaskBox { } println!("importing {} {}", S_fpath!(mdfile), PROGRESS); - let mut newt = Vec::new(); - let mut newr = Vec::new(); + let mut newt = Vec::new(); // new tasks + let mut newr = Vec::new(); // new routines + let mut newl = Vec::new(); // new later tasks from logseq to Inbox + for rline in fs::read_to_string(fpath).expect("cannot read file").lines() { let line = rline.trim(); if line.is_empty() { continue } @@ -576,12 +578,12 @@ impl TaskBox { if let Some(stripped) = line.strip_prefix(PREFIX_OPEN_LOGSEQ) { newt.push(stripped.to_string()) } else if let Some(stripped) = line.strip_prefix(PREFIX_OPEN2_LOGSEQ) { - newt.push(stripped.to_string()) + newl.push(stripped.to_string()) } } } - if newt.is_empty() && newr.is_empty() { + if newt.is_empty() && newr.is_empty() && newl.is_empty() { println!("{} found!", S_empty!("nothing")); return } else { @@ -589,12 +591,20 @@ impl TaskBox { for task in &newt { println!(" {} : {}", S_checkbox!(CHECKBOX), task); } + for task in &newl { + println!(" {} : {} [{}]", S_checkbox!(CHECKBOX), task, S_checkbox!("LATER")); + } for task in &newr { println!(" {} : {}", S_checkbox!(ROUTINES), task); } } self.load(); self.add_tasks(newt); + if self.tbname == INBOX_BOXNAME { + self.add_tasks(newl); + } else { + self.sibling(INBOX_BOXNAME).add_tasks(newl); + } self.sibling(ROUTINE_BOXNAME).add_tasks(newr); } From 7be6b4b1ab849ac7df70a2a5345f5e65ac0b7f5b Mon Sep 17 00:00:00 2001 From: JF Ding Date: Wed, 13 Aug 2025 10:32:22 +0800 Subject: [PATCH 5/6] more powerful import file pickup support Signed-off-by: JF Ding --- src/taskbox.rs | 4 ++-- src/util.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/taskbox.rs b/src/taskbox.rs index c6a0b83..d4bda56 100644 --- a/src/taskbox.rs +++ b/src/taskbox.rs @@ -538,7 +538,7 @@ impl TaskBox { Some(d) if d == "yesterday" => get_yesterday(), // TODO Some("week") => get_week(), Some(d) => d, - None => get_today(), + None => super::util::pick_file(&icloud.to_str().unwrap()), }; if ! mdfile_name.ends_with(".md") { @@ -548,7 +548,7 @@ impl TaskBox { icloud.join(mdfile_name).to_str().unwrap().to_string() } else { - file.unwrap_or_else(|| super::util::pick_file()) + file.unwrap_or_else(|| super::util::pick_file(".")) }; let fpath = Path::new(&mdfile); diff --git a/src/util.rs b/src/util.rs index ec0ddc6..c9d3985 100644 --- a/src/util.rs +++ b/src/util.rs @@ -40,9 +40,9 @@ pub fn path_normalize(path_str: &str) -> String { Path::new(path_str).normalize().unwrap() } -pub fn pick_file() -> String { +pub fn pick_file(dir: &str) -> String { run_fun!( - ls | fzf; + ls -r $dir | fzf -m --preview "cat '$dir/{}'"; ).unwrap_or_else(|_| std::process::exit(1) ) From f39fa2088a743ed63afb549883587fddd77b4a78 Mon Sep 17 00:00:00 2001 From: JF Ding Date: Wed, 13 Aug 2025 10:57:14 +0800 Subject: [PATCH 6/6] support import multiple files Signed-off-by: JF Ding --- src/taskbox.rs | 70 ++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/taskbox.rs b/src/taskbox.rs index d4bda56..f584034 100644 --- a/src/taskbox.rs +++ b/src/taskbox.rs @@ -526,59 +526,57 @@ impl TaskBox { pub fn import(&mut self, file: Option, from_logseq: bool) { #[allow(clippy::redundant_closure)] - let mdfile = if from_logseq { + let mut newt = Vec::new(); // new tasks + let mut newr = Vec::new(); // new routines + let mut newl = Vec::new(); // new later tasks from logseq to Inbox + + let mdfiles :Vec = if from_logseq { // get icloud path of "Logseq" let icloud = dirs::home_dir().unwrap().join("Library/Mobile Documents/iCloud~com~logseq~logseq/Documents/journals"); // when logseq is true, the input "mdfile" is the date string of the journal file // and can accept today, yesterday, week, etc. // if date is not provided, use today - let mut mdfile_name = match file { + let mdfile_names = match file { Some(d) if d == "today" => get_today(), Some(d) if d == "yesterday" => get_yesterday(), - // TODO Some("week") => get_week(), - Some(d) => d, - None => super::util::pick_file(&icloud.to_str().unwrap()), - }; + Some(d) if d.ends_with(".md") => d, + Some(d) => format!("{}.md", d), - if ! mdfile_name.ends_with(".md") { - mdfile_name = format!("{}.md", mdfile_name); - mdfile_name = mdfile_name.replace("-", "_"); - } + None => super::util::pick_file(icloud.to_str().unwrap()), + }; - icloud.join(mdfile_name).to_str().unwrap().to_string() + mdfile_names.split("\n").map(|s| icloud.join(s).to_str().unwrap().to_string()).collect() } else { - file.unwrap_or_else(|| super::util::pick_file(".")) + file.unwrap_or_else(|| super::util::pick_file(".")).split("\n").map(|s| s.to_string()).collect() }; - let fpath = Path::new(&mdfile); - if ! fpath.is_file() { - eprintln!("not a file or not exists: {}", S_fpath!(mdfile)); - std::process::exit(1) - } - println!("importing {} {}", S_fpath!(mdfile), PROGRESS); - - let mut newt = Vec::new(); // new tasks - let mut newr = Vec::new(); // new routines - let mut newl = Vec::new(); // new later tasks from logseq to Inbox + for mdfile in mdfiles { + let fpath = Path::new(&mdfile); + if ! fpath.is_file() { + eprintln!("not a file or not exists: {}", S_fpath!(mdfile)); + std::process::exit(1) + } + println!("importing {} {}", S_fpath!(mdfile), PROGRESS); - for rline in fs::read_to_string(fpath).expect("cannot read file").lines() { - let line = rline.trim(); - if line.is_empty() { continue } + for rline in fs::read_to_string(fpath).expect("cannot read file").lines() { + let line = rline.trim(); + if line.is_empty() { continue } - if let Some(stripped) = line.strip_prefix(PREFIX_OPEN) { - if RE_ROUTINES.is_match(stripped) { - newr.push(stripped.to_string()) - } else { - newt.push(stripped.to_string()) + if let Some(stripped) = line.strip_prefix(PREFIX_OPEN) { + if RE_ROUTINES.is_match(stripped) { + newr.push(stripped.to_string()) + } else { + newt.push(stripped.to_string()) + } } - } - if from_logseq { - if let Some(stripped) = line.strip_prefix(PREFIX_OPEN_LOGSEQ) { - newt.push(stripped.to_string()) - } else if let Some(stripped) = line.strip_prefix(PREFIX_OPEN2_LOGSEQ) { - newl.push(stripped.to_string()) + if from_logseq { + if let Some(stripped) = line.strip_prefix(PREFIX_OPEN_LOGSEQ) { + newt.push(stripped.to_string()) + } else if let Some(stripped) = line.strip_prefix(PREFIX_OPEN2_LOGSEQ) { + newl.push(stripped.to_string()) + } } } }