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

# -*- 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"])))