Files
appRobotRender/setup/precheck/jsonReadable.py
2026-06-01 16:36:33 +02:00

113 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""
Pretty-print JSON with a simple rule:
- If an object/array fits on one line within 80 characters, keep it on one line.
- Otherwise format it across multiple lines with 2-space indentation.
- Preserve key order as read from the file.
Usage:
python jsonReadable input.json > output.json
python jsonReadable.py input.json --inplace
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
MAX_WIDTH = 120
INDENT_STEP = 2
def compact_json(value: Any) -> str:
"""Return a compact single-line JSON representation."""
if value is None or isinstance(value, (bool, int, float, str)):
return json.dumps(value, ensure_ascii=False, separators=(",", ": "))
if isinstance(value, list):
return "[" + ", ".join(compact_json(v) for v in value) + "]"
if isinstance(value, dict):
items = []
for k, v in value.items():
key = json.dumps(k, ensure_ascii=False)
items.append(f"{key}: {compact_json(v)}")
return "{" + ", ".join(items) + "}"
raise TypeError(f"Unsupported JSON type: {type(value)!r}")
def pretty_json(value: Any, indent: int = 0, width: int = MAX_WIDTH) -> str:
"""Pretty-print JSON with line-length awareness."""
if value is None or isinstance(value, (bool, int, float, str)):
return json.dumps(value, ensure_ascii=False)
if isinstance(value, list):
if not value:
return "[]"
single_line = compact_json(value)
if indent + len(single_line) <= width:
return single_line
inner_indent = " " * (indent + INDENT_STEP)
lines = ["["]
for i, item in enumerate(value):
rendered = pretty_json(item, indent + INDENT_STEP, width)
comma = "," if i < len(value) - 1 else ""
lines.append(f"{inner_indent}{rendered}{comma}")
lines.append(" " * indent + "]")
return "\n".join(lines)
if isinstance(value, dict):
if not value:
return "{}"
single_line = compact_json(value)
if indent + len(single_line) <= width:
return single_line
inner_indent = " " * (indent + INDENT_STEP)
lines = ["{"]
items = list(value.items())
for i, (k, v) in enumerate(items):
key = json.dumps(k, ensure_ascii=False)
rendered = pretty_json(v, indent + INDENT_STEP, width)
comma = "," if i < len(items) - 1 else ""
lines.append(f"{inner_indent}{key}: {rendered}{comma}")
lines.append(" " * indent + "}")
return "\n".join(lines)
raise TypeError(f"Unsupported JSON type: {type(value)!r}")
def format_json_text(text: str) -> str:
data = json.loads(text)
return pretty_json(data, indent=0, width=MAX_WIDTH) + "\n"
def main() -> None:
parser = argparse.ArgumentParser(
description="Format JSON so short objects/arrays stay on one line."
)
parser.add_argument("file", type=Path, help="Path to the .json file")
parser.add_argument(
"--inplace",
action="store_true",
help="Overwrite the input file instead of printing to stdout",
)
args = parser.parse_args()
input_path: Path = args.file
text = input_path.read_text(encoding="utf-8")
formatted = format_json_text(text)
if args.inplace:
input_path.write_text(formatted, encoding="utf-8")
else:
print(formatted, end="")
if __name__ == "__main__":
main()