#!/usr/bin/env python # -*- coding: utf-8 -*- """骨架单一可信源闸门:`prd/2b-界面布局.md` §二 ↔ 原型里的 FROZEN 表。 为什么单拎出来:页面里的自检只能证明「渲染出来的板块 == FROZEN 表」, 证明不了「FROZEN 表 == ②段原文」。两张表各改一边就没人发现,所以这条闸门 直接把②段那份 Markdown 当权威读,逐页逐位比。 判据:板块**数量**必须相等,**顺序**必须相等,名字**互为前缀**(②段的名字带 「(可点入)」这类注解,不是给界面抄的标题,⛔ 不逐字比)。 用法:python _tools/check_skeleton.py """ import argparse import os, re, sys HERE = os.path.dirname(os.path.abspath(__file__)) UI = os.path.abspath(os.path.join(HERE, "..")) GOAL = os.path.abspath(os.path.join(UI, "..")) DOC = os.path.join(GOAL, "prd", "2b-界面布局.md") PROTO = os.path.join(UI, "mcn-workbench.html") Z2 = "\u3000\u3000" # 全角空格:注释切到右边界用 def from_doc(path=None): s = open(path or DOC, encoding="utf-8").read() if "## 二、八页骨架" not in s or "## 三、页面流转" not in s: raise SystemExit("⛔ 读不到②段的 §二 八页骨架 —— 文件被改结构了?") seg = s[s.index("## 二、八页骨架"):s.index("## 三、页面流转")] out, cur = {}, None for line in seg.split("\n"): m = re.match(r"^### (P\d) ", line) if m: cur = m.group(1).lower() out[cur] = [] continue m2 = re.match(r"^\s*(\d+)\.\s*(.+)$", line) if cur and m2: out[cur].append(m2.group(2).split("——")[0].strip()) return out def from_proto(path=None): s = open(path or PROTO, encoding="utf-8").read() m = re.search(r"^const FROZEN=\{(.*?)^\};", s, re.S | re.M) if not m: raise SystemExit("⛔ 原型里找不到 const FROZEN 表") out = {} # ⚠️ 逗号必须可选:表里最后一项后面没有逗号 —— 早前写成必选,p8 被静默漏掉 # (表现为「页面集合不同」,不是静默通过,但那也是漏;已修) for k, body in re.findall(r"^\s*(p\d):\[(.*?)\],?\s*$", m.group(1), re.M): items = re.findall(r"'([^']*)'", body) out[k] = items[1:] # 第 0 项是页面标题 if len(out) != 8: raise SystemExit("⛔ FROZEN 表只解析出 %d 页(应为 8):%s" % (len(out), sorted(out))) return out def norm(x): return x.split("(")[0].strip() def main(): ap = argparse.ArgumentParser(description="骨架单一可信源闸门(②段原文 ↔ 原型 FROZEN 表)") ap.add_argument("--doc", default=None, help="②段界面布局文档(默认 prd/2b-界面布局.md)") ap.add_argument("--proto", default=None, help="原型 HTML(默认 ui/mcn-workbench.html)") a = ap.parse_args() doc, proto = from_doc(a.doc), from_proto(a.proto) bad = [] if set(doc) != set(proto): bad.append("页面集合不同:②段 %s / 原型 %s" % (sorted(doc), sorted(proto))) for p in sorted(set(doc) & set(proto)): a, b = doc[p], proto[p] if len(a) != len(b): bad.append("%s 板块数 ②段 %d ≠ 原型 %d(②段 %s / 原型 %s)" % (p, len(a), len(b), a, b)) continue for i, (x, y) in enumerate(zip(a, b)): nx, ny = norm(x), norm(y) if not (nx.startswith(ny) or ny.startswith(nx)): bad.append("%s 第 %d 块 ②段「%s」≠ 原型「%s」" % (p, i + 1, x, y)) n = sum(len(v) for v in doc.values()) if bad: print("FAIL 骨架与②段不一致(②段 %d 个板块):" % n) for x in bad: print(" · " + x) return 1 print("PASS 骨架冻结:八页 %d 个板块,②段 2b-界面布局.md §二 与原型 FROZEN 表逐页逐位一致" % n) return 0 if __name__ == "__main__": sys.exit(main())