#!/usr/bin/env python3 from __future__ import annotations import argparse import json import sys from pathlib import Path from typing import Any def read_json(path: str | None) -> Any: """Read JSON from a file path, or stdin when no path is provided.""" text = sys.stdin.read() if path is None or path == "-" else Path(path).read_text(encoding="utf-8") return json.loads(text) def pretty_json(value: Any, indent: int = 2, sort_keys: bool = False) -> str: """Return stable, human-readable JSON.""" return json.dumps(value, indent=indent, sort_keys=sort_keys, ensure_ascii=False) + "\n" def run_tests() -> None: # Basic object formatting. assert pretty_json({"b": 1, "a": 2}, sort_keys=True) == '{\n "a": 2,\n "b": 1\n}\n' # Nested values and unicode should round-trip cleanly. data = {"name": "Ada", "items": [True, None, "π"]} assert json.loads(pretty_json(data)) == data def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Tiny JSON pretty-printer.") parser.add_argument("file", nargs="?", help="JSON file to format; reads stdin when omitted or '-'") parser.add_argument("-i", "--indent", type=int, default=2, help="spaces per indent level") parser.add_argument("-s", "--sort-keys", action="store_true", help="sort object keys") parser.add_argument("--test", action="store_true", help="run built-in tests") return parser def main(argv: list[str] | None = None) -> int: args = build_parser().parse_args(argv) if args.test: run_tests() return 0 try: value = read_json(args.file) sys.stdout.write(pretty_json(value, indent=args.indent, sort_keys=args.sort_keys)) except (OSError, json.JSONDecodeError) as exc: print(f"error: {exc}", file=sys.stderr) return 1 return 0 if __name__ == "__main__": raise SystemExit(main())