A file-based task manager

WIP: add reprioritize

+55 -10
+30 -7
src/main.rs
··· 83 83 /// bodies in the search criteria. 84 84 #[arg(short = 'a', default_value_t = false)] 85 85 search_archived: bool, 86 + 87 + #[arg(short = 't', default_value_t = true)] 88 + full_id: bool, 86 89 }, 87 90 88 91 /// Drops the task on the top of the stack and archives it. ··· 90 93 91 94 Rot, 92 95 Tor, 96 + 97 + Reprioritize { 98 + /// The [TSK-]ID to prioritize. If it exists, it is moved to the top of the stack. 99 + #[command(flatten)] 100 + task_id: TaskId, 101 + }, 93 102 } 94 103 95 104 #[derive(Args)] ··· 105 114 } 106 115 107 116 #[derive(Args)] 108 - #[group(required = false, multiple = false)] 117 + #[group(required = true, multiple = false)] 109 118 struct TaskId { 110 119 #[arg(short = 't', value_name = "ID")] 111 120 id: Option<u32>, 112 121 113 122 #[arg(short = 'T', value_name = "TSK-ID", value_parser = value_parser!(String))] 114 123 tsk_id: Option<Id>, 124 + 125 + /// If no option is specified 126 + #[arg(short = 'r', value_name = "RELATIVE")] 127 + relative_id: Option<u32> 115 128 } 116 129 117 130 fn main() { ··· 125 138 Commands::Edit { task_id } => command_edit(dir, task_id), 126 139 Commands::Completion { shell } => command_completion(shell), 127 140 Commands::Drop => command_drop(dir), 128 - Commands::Find { .. } => command_search(dir), 141 + Commands::Find { full_id, .. } => command_search(dir, full_id), 129 142 Commands::Rot => Workspace::from_path(dir).unwrap().rot().unwrap(), 130 143 Commands::Tor => Workspace::from_path(dir).unwrap().tor().unwrap(), 144 + Commands::Reprioritize { task_id } => command_reprioritize(dir, task_id), 131 145 } 132 146 } 133 147 ··· 229 243 } 230 244 } 231 245 232 - fn command_search(dir: PathBuf) { 246 + fn command_search(dir: PathBuf, full_id: bool) { 233 247 let id = Workspace::from_path(dir).unwrap().search().unwrap(); 234 248 if let Some(id) = id { 235 - eprint!("Dropping "); 236 - println!("{id}"); 249 + if full_id { 250 + println!("{id}"); 251 + } else { 252 + // print as integer 253 + println!("{}", id.0); 254 + } 237 255 } else { 238 256 eprintln!("No task to drop.") 239 257 } 240 258 } 241 259 242 - fn command_rot(dir: PathBuf) { 243 - Workspace::from_path(dir).unwrap().rot().unwrap(); 260 + fn command_reprioritize(dir: PathBuf, task_id: TaskId) { 261 + // unwrap is safe here because clap will ensure we have at least one of these 262 + let tsk_id: Id = task_id.id.map(Id::from).or(task_id.tsk_id).unwrap(); 263 + Workspace::from_path(dir) 264 + .unwrap() 265 + .reprioritize(tsk_id) 266 + .unwrap() 244 267 }
+10
src/stack.rs
··· 4 4 5 5 use crate::errors::{Error, Result}; 6 6 use crate::util; 7 + use std::collections::vec_deque::Iter; 7 8 use std::collections::VecDeque; 8 9 use std::fmt::Display; 9 10 use std::io::{self, BufRead, BufReader, Seek, Write}; ··· 166 167 pub fn empty(&self) -> bool { 167 168 self.all.is_empty() 168 169 } 170 + 171 + pub fn remove(&mut self, index: usize) -> Option<StackItem> { 172 + self.all.remove(index) 173 + } 174 + 175 + pub fn iter(&self) -> Iter<StackItem> { 176 + self.all.iter() 177 + } 169 178 } 179 + 170 180 171 181 impl IntoIterator for TaskStack { 172 182 type Item = StackItem;
+15 -3
src/workspace.rs
··· 15 15 const TITLECACHEFILE: &str = "cache"; 16 16 /// A unique identifier for a task. When referenced in text, it is prefixed with `tsk-`. 17 17 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 18 - pub struct Id(u32); 18 + pub struct Id(pub u32); 19 19 20 20 impl FromStr for Id { 21 21 type Err = Error; ··· 154 154 let third = stack.pop(); 155 155 156 156 if top.is_none() || second.is_none() || third.is_none() { 157 - return Ok(()) 157 + return Ok(()); 158 158 } 159 159 160 160 stack.push(second.unwrap()); ··· 173 173 let third = stack.pop(); 174 174 175 175 if top.is_none() || second.is_none() || third.is_none() { 176 - return Ok(()) 176 + return Ok(()); 177 177 } 178 178 179 179 stack.push(top.unwrap()); ··· 201 201 pub fn search(&self) -> Result<Option<Id>> { 202 202 let stack = self.read_stack()?; 203 203 Ok(fzf::select(stack)?.map(|si| si.id)) 204 + } 205 + 206 + pub fn reprioritize(&self, id: Id) -> Result<()> { 207 + let mut stack = self.read_stack()?; 208 + let index = &stack.iter().map(|i| i.id).position(|i| i == id); 209 + if let Some(index) = index { 210 + let prioritized_task = stack.remove(*index); 211 + // unwrap here is safe because we just searched for the index and know it exists 212 + stack.push(prioritized_task.unwrap()); 213 + stack.save()?; 214 + } 215 + Ok(()) 204 216 } 205 217 } 206 218