A file-based task manager
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add -q flag to list command and clean command for orphaned tasks

- tsk-30: Add -q/--ids-only flag to list command to print only task IDs
- tsk-28: Add clean command to remove orphaned task files not in stack index

+57 -6
+22 -4
src/main.rs
··· 5 5 mod task; 6 6 mod util; 7 7 mod workspace; 8 - use clap_complete::{Shell, generate}; 8 + use clap_complete::{generate, Shell}; 9 9 use errors::Result; 10 10 use std::io::{self, Write}; 11 11 use std::path::PathBuf; ··· 86 86 all: bool, 87 87 #[arg(short = 'c', default_value_t = 10)] 88 88 count: usize, 89 + /// Only print task IDs, one per line. 90 + #[arg(short = 'q', default_value_t = false)] 91 + ids_only: bool, 89 92 }, 90 93 91 94 /// Swaps the top two tasks on the stack. If there are less than 2 tasks on the stack, there is ··· 171 174 #[command(flatten)] 172 175 task_id: TaskId, 173 176 }, 177 + 178 + /// Cleans up orphaned task files in .tsk/tasks/ that are no longer in the stack index. 179 + Clean, 174 180 } 175 181 176 182 #[derive(Args)] ··· 253 259 Commands::Init => command_init(dir), 254 260 Commands::Push { edit, body, title } => command_push(dir, edit, body, title), 255 261 Commands::Append { edit, body, title } => command_append(dir, edit, body, title), 256 - Commands::List { all, count } => command_list(dir, all, count), 262 + Commands::List { 263 + all, 264 + count, 265 + ids_only, 266 + } => command_list(dir, all, count, ids_only), 257 267 Commands::Swap => command_swap(dir), 258 268 Commands::Show { 259 269 task_id, ··· 273 283 Commands::Tor => Workspace::from_path(dir).unwrap().tor(), 274 284 Commands::Prioritize { task_id } => command_prioritize(dir, task_id), 275 285 Commands::Deprioritize { task_id } => command_deprioritize(dir, task_id), 286 + Commands::Clean => command_clean(dir), 276 287 }; 277 288 let result = var_name; 278 289 match result { ··· 360 371 workspace.append_task(task) 361 372 } 362 373 363 - fn command_list(dir: PathBuf, all: bool, count: usize) -> Result<()> { 374 + fn command_list(dir: PathBuf, all: bool, count: usize, ids_only: bool) -> Result<()> { 364 375 let workspace = Workspace::from_path(dir)?; 365 376 let stack = workspace.read_stack()?; 366 377 ··· 374 385 .enumerate() 375 386 .take_while(|(idx, _)| all || idx < &count) 376 387 { 377 - if let Some(parsed) = task::parse(&stack_item.title) { 388 + if ids_only { 389 + println!("{}", stack_item.id); 390 + } else if let Some(parsed) = task::parse(&stack_item.title) { 378 391 println!("{}\t{}", stack_item.id, parsed.content.trim()); 379 392 } else { 380 393 println!("{stack_item}"); ··· 493 506 exit(1); 494 507 } 495 508 } 509 + 510 + fn command_clean(dir: PathBuf) -> Result<()> { 511 + Workspace::from_path(dir)?.clean()?; 512 + Ok(()) 513 + }
+35 -2
src/workspace.rs
··· 7 7 use crate::stack::{StackItem, TaskStack}; 8 8 use crate::task::parse as parse_task; 9 9 use crate::{fzf, util}; 10 - use std::collections::{BTreeMap, HashSet, vec_deque}; 10 + use std::collections::{vec_deque, BTreeMap, HashSet}; 11 11 use std::ffi::OsString; 12 12 use std::fmt::Display; 13 - use std::fs::{File, remove_file}; 13 + use std::fs::{remove_file, File}; 14 14 use std::io::{BufRead as _, BufReader, Read, Seek, SeekFrom}; 15 15 use std::ops::Deref; 16 16 use std::os::unix::fs::symlink; ··· 394 394 // unwrap here is safe because we just searched for the index and know it exists 395 395 stack.push_back(deprioritized_task.unwrap()); 396 396 stack.save()?; 397 + } 398 + Ok(()) 399 + } 400 + 401 + pub fn clean(&self) -> Result<()> { 402 + let stack = self.read_stack()?; 403 + let indexed_ids: HashSet<Id> = stack.iter().map(|item| item.id).collect(); 404 + 405 + let tasks_dir = self.path.join("tasks"); 406 + if !tasks_dir.exists() { 407 + return Ok(()); 408 + } 409 + 410 + for entry in std::fs::read_dir(&tasks_dir)? { 411 + let entry = entry?; 412 + let path = entry.path(); 413 + if !path.is_file() { 414 + continue; 415 + } 416 + let filename = entry.file_name(); 417 + let filename_str = filename.to_string_lossy(); 418 + if let Some(id_str) = filename_str 419 + .strip_prefix("tsk-") 420 + .and_then(|s| s.strip_suffix(".tsk")) 421 + { 422 + if let Ok(id_num) = id_str.parse::<u32>() { 423 + let id = Id(id_num); 424 + if !indexed_ids.contains(&id) { 425 + remove_file(&path)?; 426 + eprintln!("Removed orphaned task: {id}"); 427 + } 428 + } 429 + } 397 430 } 398 431 Ok(()) 399 432 }