nb: passthrough todo subcommand

This commit is contained in:
Matthew Ryan Dillon 2025-12-15 17:52:26 -05:00
parent dc9ebef089
commit 4e08da2614
2 changed files with 9 additions and 0 deletions

View file

@ -1,169 +0,0 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "stage-left",
# ]
# ///
"""
Archive completed (DONE) todos from a [x]it! file.
Extracts all checked items, outputs them (preserving groups), and removes them
from the original file.
"""
import argparse
import sys
from pathlib import Path
from stage_left import parse_file
from stage_left.types import State, Group, Item
def format_item(item: Item) -> str:
"""Format an item back to [x]it! syntax."""
return f"[{item.state.value}] {item.description}"
def format_groups(groups: list[Group]) -> str:
"""Format a list of groups back to [x]it! syntax."""
lines = []
for i, group in enumerate(groups):
if group.title:
if i > 0:
lines.append("") # Blank line before group title
lines.append(group.title)
for item in group.items:
lines.append(format_item(item))
return "\n".join(lines)
def extract_done_items(groups: list[Group]) -> tuple[list[Group], list[Group]]:
"""
Split groups into done and remaining items.
Returns:
(done_groups, remaining_groups) - Both preserve group structure.
Empty groups are preserved in remaining_groups.
"""
done_groups = []
remaining_groups = []
for group in groups:
done_items = [item for item in group.items if item.state == State.CHECKED]
remaining_items = [item for item in group.items if item.state != State.CHECKED]
if done_items:
done_groups.append(Group(title=group.title, items=done_items))
# Always preserve the group in remaining, even if empty
remaining_groups.append(Group(title=group.title, items=remaining_items))
return done_groups, remaining_groups
def merge_groups(existing: list[Group], new: list[Group]) -> list[Group]:
"""
Merge new groups into existing groups.
Groups with matching titles have their items combined.
New groups without a matching title are appended.
"""
merged: dict[str | None, Group] = {}
order: list[str | None] = []
for group in existing:
if group.title in merged:
# Extend existing group's items
merged[group.title] = Group(
title=group.title,
items=merged[group.title].items + group.items,
)
else:
merged[group.title] = Group(title=group.title, items=list(group.items))
order.append(group.title)
for group in new:
if group.title in merged:
# Merge into existing group
merged[group.title] = Group(
title=group.title,
items=merged[group.title].items + group.items,
)
else:
# New group
merged[group.title] = Group(title=group.title, items=list(group.items))
order.append(group.title)
return [merged[title] for title in order]
def main() -> int:
parser = argparse.ArgumentParser(
description="Archive completed todos from a [x]it! file.",
epilog="Done items are extracted and removed from the input file.",
)
parser.add_argument(
"input",
type=Path,
help="Path to the [x]it! file to process",
)
parser.add_argument(
"output",
type=Path,
nargs="?",
default=None,
help="Output file for archived todos (default: stdout)",
)
args = parser.parse_args()
if not args.input.exists():
print(f"Error: Input file not found: {args.input}", file=sys.stderr)
return 1
with open(args.input, "r") as fp:
groups = parse_file(fp)
done_groups, remaining_groups = extract_done_items(groups)
if not done_groups:
print("No completed todos found.", file=sys.stderr)
return 0
if args.output:
if args.output.exists():
with open(args.output, "r") as fp:
existing_groups = parse_file(fp)
merged_groups = merge_groups(existing_groups, done_groups)
else:
merged_groups = done_groups
merged_output = format_groups(merged_groups)
with open(args.output, "w") as fp:
fp.write(merged_output)
fp.write("\n")
else:
print(format_groups(done_groups))
remaining_output = format_groups(remaining_groups)
with open(args.input, "w") as fp:
if remaining_output:
fp.write(remaining_output)
fp.write("\n")
done_count = sum(len(g.items) for g in done_groups)
remaining_count = sum(len(g.items) for g in remaining_groups)
print(
f"Archived {done_count} completed todo(s). {remaining_count} remaining.",
file=sys.stderr,
)
return 0
if __name__ == "__main__":
sys.exit(main())