from __future__ import annotations import argparse import json import sys import unittest from pathlib import Path from typing import Any def pretty_json(raw_json: str, indent: int = 2, sort_keys: bool = False) -> str: """Parse JSON text and return consistently formatted JSON.""" data: Any = json.loads(raw_json) return json.dumps(data, indent=indent, sort_keys=sort_keys, ensure_ascii=False) + "\n" def read_text(path: str | None) -> str: # A missing path or "-" means standard input. if path is None or path == "-": return sys.stdin.read() return Path(path).read_text(encoding="utf-8") def write_text(path: str | None, text: str) -> None: # A missing path or "-" means standard output. if path is None or path == "-": sys.stdout.write(text) return Path(path).write_text(text, encoding="utf-8") def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Pretty-print JSON from a file or stdin.") parser.add_argument("input", nargs="?", help="JSON file to read; defaults to stdin") parser.add_argument("-o", "--output", help="Output file; defaults to stdout") 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 the built-in tests") return parser class PrettyJsonTests(unittest.TestCase): def test_pretty_prints_object(self) -> None: self.assertEqual( pretty_json('{"name":"Ada","active":true}'), '{\n "name": "Ada",\n "active": true\n}\n', ) def test_sorts_keys(self) -> None: self.assertEqual( pretty_json('{"b":2,"a":1}', sort_keys=True), '{\n "a": 1,\n "b": 2\n}\n', ) def run_tests() -> int: result = unittest.TextTestRunner(verbosity=2).run( unittest.defaultTestLoader.loadTestsFromTestCase(PrettyJsonTests) ) return 0 if result.wasSuccessful() else 1 def main(argv: list[str] | None = None) -> int: args = build_parser().parse_args(argv) if args.test: return run_tests() try: formatted = pretty_json(read_text(args.input), indent=args.indent, sort_keys=args.sort_keys) write_text(args.output, formatted) except (OSError, json.JSONDecodeError) as error: print(f"error: {error}", file=sys.stderr) return 1 return 0 if __name__ == "__main__": raise SystemExit(main())