C0: blumeops-tasks — show due offset + recurrence, sort by overdue-ness
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
005e2a03ed
commit
f9d9e00057
2 changed files with 49 additions and 2 deletions
|
|
@ -0,0 +1 @@
|
||||||
|
`blumeops-tasks` now annotates each task with a signed `due:±N` offset (or `due:today`) and a `↻ <recurrence>` marker for recurring tasks, and sorts by overdue-ness (most overdue first, no-due-date last) with priority as tiebreaker.
|
||||||
|
|
@ -101,9 +101,45 @@ def is_due(task: dict) -> bool:
|
||||||
return due_date <= date.today()
|
return due_date <= date.today()
|
||||||
|
|
||||||
|
|
||||||
|
def days_until_due(task: dict) -> int | None:
|
||||||
|
"""Return signed days offset from today, or None if no due date.
|
||||||
|
|
||||||
|
Negative = days remaining before due (e.g. -2 = due in 2 days).
|
||||||
|
Positive = days past due (overdue). Zero = due today.
|
||||||
|
"""
|
||||||
|
due = task.get("due")
|
||||||
|
if due is None:
|
||||||
|
return None
|
||||||
|
due_date = date.fromisoformat(due["date"][:10])
|
||||||
|
return (date.today() - due_date).days
|
||||||
|
|
||||||
|
|
||||||
|
def recurrence_string(task: dict) -> str | None:
|
||||||
|
"""Return the Todoist natural-language recurrence string, or None.
|
||||||
|
|
||||||
|
Todoist's REST API doesn't expose RFC 5545 RRULE; the natural-language
|
||||||
|
`due.string` (e.g. "every monday", "every 2 weeks") is the terse form.
|
||||||
|
"""
|
||||||
|
due = task.get("due")
|
||||||
|
if due is None or not due.get("is_recurring"):
|
||||||
|
return None
|
||||||
|
return due.get("string")
|
||||||
|
|
||||||
|
|
||||||
def sort_tasks(tasks: list[dict]) -> list[dict]:
|
def sort_tasks(tasks: list[dict]) -> list[dict]:
|
||||||
"""Sort tasks by custom priority order: p1, p2, p4, p3."""
|
"""Sort by overdue-ness, then priority.
|
||||||
return sorted(tasks, key=lambda t: PRIORITY_SORT_ORDER.get(t["priority"], 5))
|
|
||||||
|
Most overdue first (largest +N); tasks with no due date come last.
|
||||||
|
Within a given day, tiebreaker is the custom priority order p1, p2, p4, p3.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def key(task: dict) -> tuple[int, int, int]:
|
||||||
|
days = days_until_due(task)
|
||||||
|
no_due = 1 if days is None else 0
|
||||||
|
days_key = -(days if days is not None else 0) # descending
|
||||||
|
return (no_due, days_key, PRIORITY_SORT_ORDER.get(task["priority"], 5))
|
||||||
|
|
||||||
|
return sorted(tasks, key=key)
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
|
@ -149,6 +185,16 @@ def main() -> int:
|
||||||
header = Text()
|
header = Text()
|
||||||
header.append(f"[{label}]", style="bold")
|
header.append(f"[{label}]", style="bold")
|
||||||
header.append(f" {content}")
|
header.append(f" {content}")
|
||||||
|
|
||||||
|
meta = []
|
||||||
|
days = days_until_due(task)
|
||||||
|
if days is not None:
|
||||||
|
meta.append(f"due:{days:+d}" if days != 0 else "due:today")
|
||||||
|
recurrence = recurrence_string(task)
|
||||||
|
if recurrence:
|
||||||
|
meta.append(f"↻ {recurrence}")
|
||||||
|
if meta:
|
||||||
|
header.append(f" ({', '.join(meta)})", style="dim")
|
||||||
console.print(header)
|
console.print(header)
|
||||||
|
|
||||||
# Description indented (escape rich markup to preserve brackets)
|
# Description indented (escape rich markup to preserve brackets)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue