93 lines
3.9 KiB
Python
93 lines
3.9 KiB
Python
#!/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())
|