diff --git a/src/main.rs b/src/main.rs index 0e83a34..7639035 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ mod stack; mod task; mod util; mod workspace; -use clap_complete::{Shell, generate}; +use clap_complete::{generate, Shell}; use errors::Result; use std::io::{self, Write}; use std::path::PathBuf; @@ -86,6 +86,9 @@ enum Commands { all: bool, #[arg(short = 'c', default_value_t = 10)] count: usize, + /// Only print task IDs, one per line. + #[arg(short = 'q', default_value_t = false)] + ids_only: bool, }, /// Swaps the top two tasks on the stack. If there are less than 2 tasks on the stack, there is @@ -171,6 +174,9 @@ enum Commands { #[command(flatten)] task_id: TaskId, }, + + /// Cleans up orphaned task files in .tsk/tasks/ that are no longer in the stack index. + Clean, } #[derive(Args)] @@ -253,7 +259,11 @@ fn main() { Commands::Init => command_init(dir), Commands::Push { edit, body, title } => command_push(dir, edit, body, title), Commands::Append { edit, body, title } => command_append(dir, edit, body, title), - Commands::List { all, count } => command_list(dir, all, count), + Commands::List { + all, + count, + ids_only, + } => command_list(dir, all, count, ids_only), Commands::Swap => command_swap(dir), Commands::Show { task_id, @@ -273,6 +283,7 @@ fn main() { Commands::Tor => Workspace::from_path(dir).unwrap().tor(), Commands::Prioritize { task_id } => command_prioritize(dir, task_id), Commands::Deprioritize { task_id } => command_deprioritize(dir, task_id), + Commands::Clean => command_clean(dir), }; let result = var_name; match result { @@ -360,7 +371,7 @@ fn command_append(dir: PathBuf, edit: bool, body: Option, title: Title) workspace.append_task(task) } -fn command_list(dir: PathBuf, all: bool, count: usize) -> Result<()> { +fn command_list(dir: PathBuf, all: bool, count: usize, ids_only: bool) -> Result<()> { let workspace = Workspace::from_path(dir)?; let stack = workspace.read_stack()?; @@ -374,7 +385,9 @@ fn command_list(dir: PathBuf, all: bool, count: usize) -> Result<()> { .enumerate() .take_while(|(idx, _)| all || idx < &count) { - if let Some(parsed) = task::parse(&stack_item.title) { + if ids_only { + println!("{}", stack_item.id); + } else if let Some(parsed) = task::parse(&stack_item.title) { println!("{}\t{}", stack_item.id, parsed.content.trim()); } else { println!("{stack_item}"); @@ -493,3 +506,8 @@ fn command_follow(dir: PathBuf, task_id: TaskId, link_index: usize, edit: bool) exit(1); } } + +fn command_clean(dir: PathBuf) -> Result<()> { + Workspace::from_path(dir)?.clean()?; + Ok(()) +} diff --git a/src/workspace.rs b/src/workspace.rs index e31ba07..3a58cdd 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -7,10 +7,10 @@ use crate::errors::{Error, Result}; use crate::stack::{StackItem, TaskStack}; use crate::task::parse as parse_task; use crate::{fzf, util}; -use std::collections::{BTreeMap, HashSet, vec_deque}; +use std::collections::{vec_deque, BTreeMap, HashSet}; use std::ffi::OsString; use std::fmt::Display; -use std::fs::{File, remove_file}; +use std::fs::{remove_file, File}; use std::io::{BufRead as _, BufReader, Read, Seek, SeekFrom}; use std::ops::Deref; use std::os::unix::fs::symlink; @@ -397,6 +397,39 @@ impl Workspace { } Ok(()) } + + pub fn clean(&self) -> Result<()> { + let stack = self.read_stack()?; + let indexed_ids: HashSet = stack.iter().map(|item| item.id).collect(); + + let tasks_dir = self.path.join("tasks"); + if !tasks_dir.exists() { + return Ok(()); + } + + for entry in std::fs::read_dir(&tasks_dir)? { + let entry = entry?; + let path = entry.path(); + if !path.is_file() { + continue; + } + let filename = entry.file_name(); + let filename_str = filename.to_string_lossy(); + if let Some(id_str) = filename_str + .strip_prefix("tsk-") + .and_then(|s| s.strip_suffix(".tsk")) + { + if let Ok(id_num) = id_str.parse::() { + let id = Id(id_num); + if !indexed_ids.contains(&id) { + remove_file(&path)?; + eprintln!("Removed orphaned task: {id}"); + } + } + } + } + Ok(()) + } } pub struct Task {