A file-based task manager
0
fork

Configure Feed

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

Reject foreign links with non-ident prefixes; remove main.rs panic sites

[[ns/tsk-12]] previously parsed as a Foreign link with prefix "ns/tsk",
which isn't a valid namespace. Restrict the foreign-link prefix to
[A-Za-z0-9_]+; anything else falls through to plain text. Adds a
regression test.

Also: remove panic sites from main.rs.
- default_dir now returns Result; cwd-resolution failures propagate as
proper errors instead of panicking.
- Commands::Rot / Commands::Tor used Workspace::from_path(dir).unwrap()
which would panic on an uninitialized workspace; replaced with `?` so
they surface the same error every other command does.
- Restructured main() into a small wrapper around run() -> Result so the
Cli is the only thing parsed at the top level.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+29 -14
+16 -14
src/main.rs
··· 20 20 use clap::{Args, CommandFactory, Parser, Subcommand}; 21 21 use edit::edit as open_editor; 22 22 23 - fn default_dir() -> PathBuf { 24 - current_dir().unwrap() 23 + fn default_dir() -> Result<PathBuf> { 24 + Ok(current_dir()?) 25 25 } 26 26 27 27 fn parse_id(s: &str) -> std::result::Result<Id, &'static str> { ··· 317 317 } 318 318 } 319 319 320 - fn main() { 321 - let cli = Cli::parse(); 322 - let dir = cli.dir.unwrap_or(default_dir()); 323 - let sync_dir = dir.clone(); 324 - let var_name = match cli.command { 320 + fn run(cli: Cli) -> Result<()> { 321 + let dir = match cli.dir { 322 + Some(d) => d, 323 + None => default_dir()?, 324 + }; 325 + match cli.command { 325 326 Commands::Init => command_init(dir), 326 327 Commands::Push { edit, body, title } => command_push(dir, edit, body, title), 327 328 Commands::Append { edit, body, title } => command_append(dir, edit, body, title), ··· 345 346 Commands::Completion { shell } => command_completion(shell), 346 347 Commands::Drop { task_id } => command_drop(dir, task_id), 347 348 Commands::Find { args, short_id } => command_find(dir, short_id, args), 348 - Commands::Rot => Workspace::from_path(dir).unwrap().rot(), 349 - Commands::Tor => Workspace::from_path(dir).unwrap().tor(), 349 + Commands::Rot => Workspace::from_path(dir)?.rot(), 350 + Commands::Tor => Workspace::from_path(dir)?.tor(), 350 351 Commands::Prioritize { task_id } => command_prioritize(dir, task_id), 351 352 Commands::Deprioritize { task_id } => command_deprioritize(dir, task_id), 352 353 Commands::Clean => command_clean(dir), ··· 357 358 Commands::Export { output } => command_export(dir, output), 358 359 Commands::Migrate => command_migrate(dir), 359 360 Commands::Reopen { task_id } => command_reopen(dir, task_id), 360 - }; 361 - let _ = sync_dir; 362 - let result = var_name; 363 - match result { 364 - Ok(_) => exit(0), 361 + } 362 + } 363 + 364 + fn main() { 365 + match run(Cli::parse()) { 366 + Ok(()) => exit(0), 365 367 Err(e) => { 366 368 eprintln!("{e}"); 367 369 exit(2);
+13
src/task.rs
··· 102 102 links.push(ParsedLink::Internal(id)); 103 103 } else if let Some((prefix, id_str)) = contents.split_once('-') 104 104 && let Ok(id) = id_str.parse::<u32>() 105 + && prefix.chars().all(|c| c.is_alphanumeric() || c == '_') 105 106 { 106 107 let linktext = 107 108 format!("{}{}", contents.cyan(), super_num(links.len() + 1).cyan()); ··· 511 512 let input = "see [[not a link]]\n"; 512 513 let output = parse(input).expect("parse to work"); 513 514 assert!(output.links.is_empty()); 515 + assert_eq!(input, output.content); 516 + } 517 + 518 + /// A foreign-style link whose prefix contains `/` (or any other non-ident 519 + /// character) is not a valid namespace; don't register a link, keep the 520 + /// bracketed text as-is. 521 + #[test] 522 + fn test_foreign_link_prefix_with_slash() { 523 + setup(); 524 + let input = "see [[ns/tsk-12]]\n"; 525 + let output = parse(input).expect("parse to work"); 526 + assert!(output.links.is_empty(), "{:?}", output.links); 514 527 assert_eq!(input, output.content); 515 528 } 516 529 }