diff --git a/wizard-work/quill/README.md b/wizard-work/quill/README.md new file mode 100644 index 0000000..a805a56 --- /dev/null +++ b/wizard-work/quill/README.md @@ -0,0 +1,20 @@ +# todo.py + +Tiny CLI todo tracker. State is stored in `~/.todo.json`. + +## Usage + +``` +python todo.py add "buy milk" +python todo.py list +python todo.py all +python todo.py done 1 +python todo.py done +``` + +## Commands + +- `add ` — append a task +- `list` — show open tasks +- `all` — show every task +- `done [id]` — without id: show completed; with id: mark task done diff --git a/wizard-work/quill/todo.py b/wizard-work/quill/todo.py new file mode 100644 index 0000000..cb8965d --- /dev/null +++ b/wizard-work/quill/todo.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Tiny CLI todo tracker. Data lives in ~/.todo.json.""" + +import argparse +import json +import os +from datetime import datetime, timezone + +DEFAULT_PATH = os.path.expanduser("~/.todo.json") + + +def load(path=DEFAULT_PATH): + if not os.path.exists(path): + return [] + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + + +def save(items, path=DEFAULT_PATH): + with open(path, "w", encoding="utf-8") as f: + json.dump(items, f, indent=2) + + +def cmd_add(args): + items = load() + entry = { + "id": max((i.get("id", 0) for i in items), default=0) + 1, + "task": args.task, + "done": False, + "created": datetime.now(timezone.utc).isoformat(), + } + items.append(entry) + save(items) + print(f"added {entry['id']}: {args.task}") + + +def cmd_list(args): + items = load() + if args.done_only: + items = [i for i in items if i.get("done")] + elif not args.all: + items = [i for i in items if not i.get("done")] + for i in items: + status = "x" if i.get("done") else " " + print(f"[{status}] {i['id']}: {i['task']}") + if not items: + print("(none)") + + +def cmd_done(args): + items = load() + for i in items: + if i.get("id") == args.id: + i["done"] = True + i["finished"] = datetime.now(timezone.utc).isoformat() + save(items) + print(f"done {i['id']}") + return + print(f"id {args.id} not found", flush=True) + raise SystemExit(2) + + +def main(): + p = argparse.ArgumentParser(description="Tiny todo CLI") + sub = p.add_subparsers(dest="cmd") + a = sub.add_parser("add") + a.add_argument("task", help="task text") + sub.add_parser("list", help="show todos") + sub.add_parser("all", help="show all including done") + sub.add_parser("done", help="show done only") + d = sub.add_parser("done") + d.add_argument("id", type=int, help="todo id to mark done") + args = p.parse_args() + if args.cmd == "add": + cmd_add(args) + elif args.cmd == "list": + cmd_list(args) + elif args.cmd == "all": + cmd_list(args) + elif args.cmd == "done": + if hasattr(args, "id"): + cmd_done(args) + else: + cmd_list(args) + else: + p.print_help() + raise SystemExit(1) + + +if __name__ == "__main__": + main()