#!/usr/bin/env python3 """A tiny JSON pretty-printer.""" from __future__ import annotations import argparse import json import sys import unittest from pathlib import Path from typing import Any, TextIO def load_json(stream: TextIO) -> Any: """Read one JSON document from a text stream.""" return json.load(stream) def format_json(data: Any, *, indent: int, sort_keys: bool) -> str: """Render JSON in a stable, readable form.""" return json.dumps(data, indent=indent, sort_keys=sort_keys, ensure_ascii=False) + "\n" def pretty_print(stream: TextIO, *, indent: int = 2, sort_keys: bool = False) -> str: """Load JSON from stream and return pretty-printed text.""" return format_json(load_json(stream), indent=indent, sort_keys=sort_keys) def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Pretty-print JSON from a file or stdin.") parser.add_argument("file", nargs="?", help="JSON file to read; defaults to stdin") parser.add_argument("-i", "--indent", type=int, default=2, help="number of spaces per indent") parser.add_argument("-s", "--sort-keys", action="store_true", help="sort object keys") parser.add_argument("--test", action="store_true", help="run the built-in tests") return parser class PrettyPrintTests(unittest.TestCase): def test_formats_nested_json(self) -> None: result = pretty_print(stream_from('{"name":"Ada","items":[1,true,null]}')) self.assertEqual( result, '{\n' ' "name": "Ada",\n' ' "items": [\n' ' 1,\n' ' true,\n' ' null\n' ' ]\n' '}\n', ) def test_sorts_keys_when_requested(self) -> None: result = pretty_print(stream_from('{"b":2,"a":1}'), sort_keys=True) self.assertEqual(result, '{\n "a": 1,\n "b": 2\n}\n') def stream_from(text: str) -> TextIO: # Imported lazily to keep the normal CLI path minimal. from io import StringIO return StringIO(text) def main(argv: list[str] | None = None) -> int: args = build_parser().parse_args(argv) if args.test: suite = unittest.defaultTestLoader.loadTestsFromTestCase(PrettyPrintTests) return 0 if unittest.TextTestRunner().run(suite).wasSuccessful() else 1 try: if args.file: with Path(args.file).open("r", encoding="utf-8") as stream: output = pretty_print(stream, indent=args.indent, sort_keys=args.sort_keys) else: output = pretty_print(sys.stdin, indent=args.indent, sort_keys=args.sort_keys) except (OSError, json.JSONDecodeError) as exc: print(f"jsonpp: {exc}", file=sys.stderr) return 1 print(output, end="") return 0 if __name__ == "__main__": raise SystemExit(main())