Parse, filter, and sort todo.txt files as editable JSON. Zero runtime deps, strict format compliance, completed-task support. Designed for CLI, pipelines, and scripting.
Find a file
kartikay-bagla 5e0a24b2ee Initial commit
2026-06-13 16:08:25 +05:30
src/todoquery Initial commit 2026-06-13 16:08:25 +05:30
tests Initial commit 2026-06-13 16:08:25 +05:30
.gitignore Initial commit 2026-06-13 16:08:25 +05:30
.python-version Initial commit 2026-06-13 16:08:25 +05:30
AGENTS.md Initial commit 2026-06-13 16:08:25 +05:30
CONTEXT.md Initial commit 2026-06-13 16:08:25 +05:30
pyproject.toml Initial commit 2026-06-13 16:08:25 +05:30
README.md Initial commit 2026-06-13 16:08:25 +05:30
sample.txt Initial commit 2026-06-13 16:08:25 +05:30
TDD_LOG.md Initial commit 2026-06-13 16:08:25 +05:30
uv.lock Initial commit 2026-06-13 16:08:25 +05:30

todoquery

A small zero-runtime-dependency Python 3.11+ library and CLI for parsing todo.txt-like files into editable JSON, then filtering and sorting the parsed items.

Repository navigation

For future agents or maintainers entering this repo cold:

  • AGENTS.md contains operating instructions, commands, contracts, and change guidelines.
  • CONTEXT.md contains static project context, architecture, parser/query behavior, and known limitations.
  • TDD_LOG.md records the original red/green implementation trail.

Install in another uv project

From another project, install this local package:

cd /path/to/your/other/project
uv add /home/raspi/todoquery

For editable/development mode, so changes in /home/raspi/todoquery are reflected immediately:

cd /path/to/your/other/project
uv add --editable /home/raspi/todoquery

Then use it from Python:

from todoquery import TodoQuery

json_text = (
    TodoQuery.from_file("todos.txt")
    .filter(priority="A")
    .sort("due_date")
    .to_json(indent=2)
)

print(json_text)

The CLI is also available in the other project after install:

uv run todoquery todos.txt --filter priority=A --sort due_date

Run locally with uv

uv run todoquery path/to/todos.txt
uv run todoquery path/to/todos.txt --filter priority=A --sort due_date
uv run todoquery path/to/todos.txt --filter completed=true --sort completion_date
uv run todoquery path/to/todos.txt --output todos.json

For descending sort fields that begin with -, pass the value with equals syntax so argparse does not treat it as an option:

uv run todoquery path/to/todos.txt --sort=-priority

Python API

from todoquery import TodoQuery, parse_todo_line

todo = parse_todo_line("(A) 2026-06-13 Call Bob @phone +Personal due:2026-06-14 tag:errand")
print(todo.to_dict())

query = TodoQuery.from_text(open("todos.txt").read())
print(query.filter(priority="A").sort("due_date").to_json())

Each todo dictionary has this shape:

{
  "raw": "(A) 2026-06-13 Call Bob @phone +Personal due:2026-06-14 tag:errand",
  "text": "Call Bob",
  "priority": "A",
  "created_date": "2026-06-13",
  "contexts": ["phone"],
  "projects": ["Personal"],
  "due_date": "2026-06-14",
  "tags": {"tag": "errand"},
  "completed": false,
  "completion_date": null
}

Completed todo.txt tasks are supported. Lines beginning with x COMPLETION_DATE are marked completed, and an optional following creation date plus original priority tag are extracted:

x 2026-06-13 2026-06-12 pri:A Return library books +Errands @town due:2026-06-13

The example above parses with completed=true, completion_date="2026-06-13", created_date="2026-06-12", and priority="A". The completed-task pri:A token is treated as the original todo.txt priority; on incomplete tasks, pri:A remains a generic tag (tags["pri"] = "A").

Filters

TodoQuery.filter(**criteria) supports:

  • Exact fields: priority="A", due_date="2026-06-14", completed=True
  • Contains: contexts__contains="phone", projects__contains="Personal", tags__contains="tag", text__contains="Bob"
  • Exact tag value: tags__tag="errand"
  • ISO string comparisons: due_date__lt, due_date__lte, due_date__gt, due_date__gte, and the same for created_date and completion_date. Missing values do not match comparisons.

Sorting

TodoQuery.sort(*fields) accepts field names, with -field for descending. Multi-field sorts are stable. Missing values sort last in both ascending and descending order.

completion_date can be sorted like any other field, for example .sort("completion_date") or CLI --sort completion_date.