You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
96 lines
3.6 KiB
96 lines
3.6 KiB
# -*- coding: utf-8 -*-
|
|
"""为 Bruno collection docs 块内的裸 JSON 补 ```json fence。
|
|
规则(SKILL.md「示例生成」):补 fence 属格式修复,示例值逐字保留;只插入 fence 行,不改任何既有行。
|
|
用法:python fix_bru_json.py [--write] 不带 --write 为 dry-run。
|
|
"""
|
|
import io, os, sys
|
|
|
|
ROOT = r"D:/code/crm-api-docs"
|
|
WRITE = "--write" in sys.argv
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
|
|
def process(path):
|
|
"""返回 (n_blocks, n_skipped_fenced, ok)"""
|
|
with io.open(path, "r", encoding="utf-8-sig", newline="") as f:
|
|
raw = f.read()
|
|
eol = "\r\n" if "\r\n" in raw else "\n"
|
|
lines = raw.splitlines(keepends=True)
|
|
# 定位 docs 块
|
|
try:
|
|
docs_start = next(i for i, l in enumerate(lines) if l.strip() == "docs {")
|
|
except StopIteration:
|
|
return (0, 0, None) # 无 docs 块
|
|
# 检查 generated 标记(meta 块内)
|
|
generated = any("generated" in l for l in lines[:docs_start])
|
|
if not generated:
|
|
return (0, 0, "private")
|
|
# 找裸 JSON 块(在 docs 块内)
|
|
out, i, n_bare, n_fenced, anomaly = [], 0, 0, 0, False
|
|
n = len(lines)
|
|
while i < n:
|
|
line = lines[i]
|
|
if line.strip() == "{" and i >= docs_start:
|
|
# 前一行已 fence 则跳过
|
|
if out and out[-1].strip() == "```json":
|
|
n_fenced += 1
|
|
out.append(line)
|
|
i += 1
|
|
continue
|
|
# 追踪 depth 找块尾
|
|
depth, j = 0, i
|
|
while j < n:
|
|
t = lines[j]
|
|
depth += t.count("{") - t.count("}")
|
|
if depth <= 0:
|
|
break
|
|
j += 1
|
|
if j >= n or depth != 0:
|
|
anomaly = True
|
|
break
|
|
indent = line[: len(line) - len(line.lstrip())]
|
|
out.append(indent + "```json" + eol)
|
|
out.extend(lines[i : j + 1])
|
|
out.append(indent + "```" + eol)
|
|
n_bare += 1
|
|
i = j + 1
|
|
continue
|
|
out.append(line)
|
|
i += 1
|
|
if anomaly:
|
|
return (0, 0, "anomaly")
|
|
if n_bare and WRITE:
|
|
with io.open(path, "w", encoding="utf-8", newline="") as f:
|
|
f.write("".join(out))
|
|
return (n_bare, n_fenced, "ok")
|
|
|
|
stats = {"files": 0, "blocks": 0, "fenced": 0, "private": [], "anomaly": [], "nodocs": 0}
|
|
for dirpath, dirnames, filenames in os.walk(ROOT):
|
|
dirnames[:] = [d for d in dirnames if d not in ("environments", ".git")]
|
|
for fn in sorted(filenames):
|
|
if not fn.endswith(".bru") or fn == "collection.bru":
|
|
continue
|
|
path = os.path.join(dirpath, fn)
|
|
rel = path.replace(ROOT + os.sep, "").replace(ROOT + "/", "")
|
|
nb, nf, status = process(path)
|
|
if status == "private":
|
|
stats["private"].append(rel)
|
|
continue
|
|
if status == "anomaly":
|
|
stats["anomaly"].append(rel)
|
|
print("ANOMALY-SKIPPED\t%s" % rel)
|
|
continue
|
|
if status is None:
|
|
stats["nodocs"] += 1
|
|
continue
|
|
if nb:
|
|
stats["files"] += 1
|
|
stats["blocks"] += nb
|
|
print("%s\t%s\t+%d fence" % ("WRITE" if WRITE else "PLAN", rel, nb))
|
|
stats["fenced"] += nf
|
|
|
|
print("---- %s: %d files, %d bare blocks fenced, %d already fenced" % (
|
|
"WRITTEN" if WRITE else "DRY-RUN", stats["files"], stats["blocks"], stats["fenced"]))
|
|
if stats["private"]:
|
|
print("private skipped (%d): %s" % (len(stats["private"]), ", ".join(stats["private"])))
|
|
if stats["anomaly"]:
|
|
print("ANOMALY (%d): %s" % (len(stats["anomaly"]), ", ".join(stats["anomaly"])))
|
|
|