mirror of
https://github.com/logos-co/logos-release-set.git
synced 2026-08-30 22:41:13 +00:00
release-set.json is hand-edited and laid out for reading: one entry per line, columns aligned, groups separated. json.dump cannot reproduce that — it reflows every entry onto four lines, so a two-line pin bump lands as a ninety-line diff and the actual change disappears. That happened while cutting 0.2.0.2 and I fixed it by hand, which is not a fix. scripts/format-release-set.py restores the layout and sorts entries by name, so an added component lands in a predictable place rather than wherever the editing script appended it. Output is a pure function of the data: format twice, get the same bytes. It refuses to write if the round-trip would change the data — comparing group-by-group with entries sorted, so the intended reorder passes but a dropped entry or edited value does not. That guard earned itself immediately by catching my first version. Verified it reproduces the existing hand-written layout byte-for-byte apart from the sort, so this commit's reformat is purely the ordering. The resolve job now runs --check before anything is downloaded: a reflowed file is correct data and an unreadable diff, and that is worth failing on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
153 lines
5.7 KiB
Python
Executable File
153 lines
5.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Rewrite release-set.json in its canonical layout.
|
|
|
|
`release-set.json` is hand-edited, and it is laid out for reading: one entry
|
|
per line, columns aligned, groups separated by blank lines. `json.dump` cannot
|
|
reproduce that — it reflows every entry onto four lines each, which turns a
|
|
two-line pin bump into a ninety-line diff and buries the actual change.
|
|
|
|
So any tool that edits this file should hand it back to this formatter, and CI
|
|
checks that what is committed is already canonical. The output is a pure
|
|
function of the data: format twice, get the same bytes.
|
|
|
|
Entries are sorted by name within each group, so an added component lands in a
|
|
predictable place instead of wherever the editing script happened to append it.
|
|
|
|
python3 scripts/format-release-set.py # rewrite in place
|
|
python3 scripts/format-release-set.py --check # exit 1 if not canonical
|
|
python3 scripts/format-release-set.py --stdout # print, don't write
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
# Groups pinned by GitHub release tag share one set of column widths; groups
|
|
# pinned by catalog version share another. That is what makes `apps` and
|
|
# `devUtils` line up with each other, and `modules` with `uiApps`.
|
|
TAG_GROUPS = ("apps", "devUtils")
|
|
VERSION_GROUPS = ("modules", "uiApps")
|
|
ORDER = ("apps", "devUtils", "modules", "uiApps")
|
|
|
|
|
|
def q(value):
|
|
"""A JSON string, quoted exactly as json.dumps would."""
|
|
return json.dumps(value, ensure_ascii=False)
|
|
|
|
|
|
def entry_line(item, widths, kind):
|
|
"""One `{ ... }` entry, padded to the group's column widths."""
|
|
name = f'"name": {q(item["name"])},'
|
|
if kind == "tag":
|
|
repo = f'"repo": {q(item["repo"])},'
|
|
return (f' {{ {name:<{widths["name"]}} '
|
|
f'{repo:<{widths["repo"]}} '
|
|
f'"releaseTag": {q(item["releaseTag"])} }}')
|
|
return f' {{ {name:<{widths["name"]}} "version": {q(item["version"])} }}'
|
|
|
|
|
|
def widths_for(spec, groups, kind):
|
|
"""Column widths shared across a family of groups."""
|
|
names, repos = [], []
|
|
for group in groups:
|
|
for item in spec.get(group, []):
|
|
names.append(f'"name": {q(item["name"])},')
|
|
if kind == "tag":
|
|
repos.append(f'"repo": {q(item["repo"])},')
|
|
return {
|
|
"name": max((len(x) for x in names), default=0),
|
|
"repo": max((len(x) for x in repos), default=0),
|
|
}
|
|
|
|
|
|
def render(spec):
|
|
tag_widths = widths_for(spec, TAG_GROUPS, "tag")
|
|
version_widths = widths_for(spec, VERSION_GROUPS, "version")
|
|
|
|
out = ["{"]
|
|
for key in ("schemaVersion", "version", "name", "displayName", "description"):
|
|
if key in spec:
|
|
out.append(f" {q(key)}: {json.dumps(spec[key], ensure_ascii=False)},")
|
|
|
|
catalog = spec.get("catalog")
|
|
if catalog is not None:
|
|
out.append("")
|
|
out.append(' "catalog": {')
|
|
items = list(catalog.items())
|
|
for i, (k, v) in enumerate(items):
|
|
comma = "," if i < len(items) - 1 else ""
|
|
out.append(f" {q(k)}: {json.dumps(v, ensure_ascii=False)}{comma}")
|
|
out.append(" },")
|
|
|
|
for gi, group in enumerate(ORDER):
|
|
if group not in spec:
|
|
continue
|
|
kind = "tag" if group in TAG_GROUPS else "version"
|
|
widths = tag_widths if kind == "tag" else version_widths
|
|
entries = sorted(spec[group], key=lambda item: item["name"])
|
|
out.append("")
|
|
out.append(f" {q(group)}: [")
|
|
for i, item in enumerate(entries):
|
|
comma = "," if i < len(entries) - 1 else ""
|
|
out.append(entry_line(item, widths, kind) + comma)
|
|
last_group = gi == len(ORDER) - 1
|
|
out.append(" ]" + ("" if last_group else ","))
|
|
|
|
out.append("}")
|
|
return "\n".join(out) + "\n"
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("spec", nargs="?", default="release-set.json")
|
|
parser.add_argument("--check", action="store_true",
|
|
help="exit 1 if the file is not already canonical")
|
|
parser.add_argument("--stdout", action="store_true",
|
|
help="write to stdout instead of the file")
|
|
args = parser.parse_args()
|
|
|
|
with open(args.spec, encoding="utf-8") as handle:
|
|
original = handle.read()
|
|
spec = json.loads(original)
|
|
formatted = render(spec)
|
|
|
|
# The formatter must never change the data — only its layout and, within a
|
|
# group, the order of entries. Compare with every group sorted so the
|
|
# intended reorder passes while a dropped entry or an edited value does not.
|
|
def comparable(doc):
|
|
doc = dict(doc)
|
|
for group in ORDER:
|
|
if group in doc:
|
|
doc[group] = sorted(doc[group], key=lambda item: item["name"])
|
|
return doc
|
|
|
|
if comparable(json.loads(formatted)) != comparable(spec):
|
|
print("error: formatting changed the data — refusing to write", file=sys.stderr)
|
|
return 2
|
|
|
|
if args.check:
|
|
if formatted != original:
|
|
print(f"error: {args.spec} is not canonically formatted.\n"
|
|
f" Run: python3 scripts/format-release-set.py {args.spec}",
|
|
file=sys.stderr)
|
|
return 1
|
|
print(f"{args.spec}: canonical", file=sys.stderr)
|
|
return 0
|
|
|
|
if args.stdout:
|
|
sys.stdout.write(formatted)
|
|
return 0
|
|
|
|
if formatted == original:
|
|
print(f"{args.spec}: already canonical", file=sys.stderr)
|
|
return 0
|
|
with open(args.spec, "w", encoding="utf-8") as handle:
|
|
handle.write(formatted)
|
|
print(f"{args.spec}: reformatted", file=sys.stderr)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|