calendar: add todo edit command

Allow editing todo fields (due date, priority) in place via todoman,
instead of requiring delete + recreate. Updates all docs for consistency.
This commit is contained in:
Yanxin Lu
2026-03-24 21:56:23 -07:00
parent 20ee61498c
commit 734790a599
5 changed files with 136 additions and 5 deletions

View File

@@ -10,6 +10,7 @@ Subcommands:
python calendar.py reply [options] # accept/decline/tentative
python calendar.py todo add [options] # create a VTODO task
python calendar.py todo list [options] # list pending tasks
python calendar.py todo edit [options] # edit a task's fields
python calendar.py todo complete [options] # mark task as done
python calendar.py todo delete [options] # remove a task
python calendar.py todo check # daily digest for cron
@@ -600,6 +601,58 @@ def cmd_todo_complete(args):
_sync_calendar()
def _find_todo(uid="", match=""):
"""Find a single todo by UID or summary match. Returns (todoman_id, todo_dict)."""
todos = _todoman_list_json("--sort", "due")
if uid:
matches = [t for t in todos if uid in (t.get("uid") or "")]
if not matches:
print(f"Error: No todo with UID '{uid}'", file=sys.stderr)
sys.exit(1)
elif match:
matches = [t for t in todos if match in (t.get("summary") or "")]
if not matches:
print(f"Error: No todo matching '{match}'", file=sys.stderr)
sys.exit(1)
if len(matches) > 1:
print(f"Error: Multiple todos match '{match}':", file=sys.stderr)
for t in matches:
print(f" - {t.get('summary')} (uid: {t.get('uid')})", file=sys.stderr)
sys.exit(1)
else:
print("Error: --uid or --match is required", file=sys.stderr)
sys.exit(1)
return matches[0]["id"], matches[0]
def cmd_todo_edit(args):
"""Edit a todo's fields via todoman."""
todo_id, matched = _find_todo(uid=args.uid, match=args.match)
# Build todoman edit command args
todo_args = []
changes = []
if args.due:
todo_args += ["--due", args.due]
changes.append(f"due -> {args.due}")
if args.priority:
todo_args += ["--priority", args.priority]
prio_label = PRIORITY_LABELS.get(PRIORITY_MAP.get(args.priority, 5), "")
changes.append(f"priority -> {prio_label}")
if not changes:
print("Nothing to change. Specify at least one of --due, --priority.")
return
# Run todoman edit (non-interactive)
_run_todoman("edit", str(todo_id), "--no-interactive", *todo_args)
print(f"Updated todo: {matched.get('summary')}")
for c in changes:
print(f" {c}")
_sync_calendar()
def cmd_todo_delete(args):
"""Delete a todo via todoman."""
if args.uid:
@@ -688,6 +741,13 @@ def main():
comp_p.add_argument("--uid", default="", help="Todo UID")
comp_p.add_argument("--match", default="", help="Fuzzy match on summary")
# todo edit
edit_p = todo_sub.add_parser("edit", help="Edit a todo's fields")
edit_p.add_argument("--uid", default="", help="Todo UID")
edit_p.add_argument("--match", default="", help="Fuzzy match on summary")
edit_p.add_argument("--due", default="", help="New due date (YYYY-MM-DD)")
edit_p.add_argument("--priority", default="", choices=["", "high", "medium", "low"], help="New priority")
# todo delete
del_p = todo_sub.add_parser("delete", help="Delete a todo")
del_p.add_argument("--uid", default="", help="Todo UID")
@@ -709,6 +769,8 @@ def main():
cmd_todo_list(args)
elif args.todo_command == "complete":
cmd_todo_complete(args)
elif args.todo_command == "edit":
cmd_todo_edit(args)
elif args.todo_command == "delete":
cmd_todo_delete(args)
elif args.todo_command == "check":