le-wechat-article v1.2:融合 gzh-design-skill 主题排版引擎(6 主题组件库 / 合规校验 / 复制预览页,AGPL 隔离于 themes/),md2wechat 降为轻稿快档

This commit is contained in:
leroy
2026-10-06 21:12:21 +08:00
parent 0ba7c1f39d
commit 500d8224db
20 changed files with 6039 additions and 12 deletions
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""组件库源头检查器 —— 可验证循环的第一道关。
扫描 references/ 下所有组件库 .md 里的 ```html 代码块,检测会导致
排版问题的反模式。只看真实组件 HTML,不被说明文字干扰(grep 做不到)。
与 validate_gzh_html.py 配合构成闭环:
改组件库 → component_lint.py 扫源头 → 生成产物 → validate_gzh_html.py 扫产物 → 修 → 重复
用法:
component_lint.py [skill-dir] # 默认当前目录
退出码:1 = 有 ERROR,0 = 通过。
"""
import glob
import os
import re
import sys
CJK = re.compile(r"[一-鿿㐀-䶿]")
# (正则, 级别, 说明) —— 在每个 ```html 组件块内检查
CHECKS = [
(re.compile(r"white-space\s*:\s*pre", re.I), "ERROR",
"用了 white-space:pre —— 会把 HTML 源码缩进/换行渲染成大左缩进+空行;"
"代码块改成每行一个 <p style=\"margin:0\">,缩进用全角空格"),
(re.compile(r"</?div[\s>]", re.I), "ERROR", "出现 <div>,应用 <section>"),
(re.compile(r"\sclass\s*=", re.I), "ERROR", "出现 class 属性(会被公众号剥离)"),
(re.compile(r"\sid\s*=", re.I), "ERROR", "出现 id 属性"),
(re.compile(r"<style[\s>]", re.I), "ERROR", "出现 <style> 标签"),
(re.compile(r"position\s*:\s*(fixed|absolute|sticky)", re.I), "ERROR",
"position fixed/absolute/sticky 不被支持"),
(re.compile(r"display\s*:\s*grid", re.I), "ERROR", "display:grid 不被支持"),
(re.compile(r"var\s*\(\s*--", re.I), "ERROR", "用了 CSS 变量 var(--x)"),
(re.compile(r"@(media|keyframes|import)", re.I), "ERROR", "@media/@keyframes/@import 不被支持"),
]
# 四周虚线框:border: ... dashed(不含方向,如 border-left dashed 不算)
FOURSIDE_DASHED = re.compile(r"border\s*:\s*[^;{}]*dashed", re.I)
CENTERED = re.compile(r"text-align\s*:\s*center", re.I)
def lint_file(path):
text = open(path, encoding="utf-8", errors="replace").read()
name = os.path.basename(path).replace("公众号排版组件库 —— ", "").replace(".md", "")
found = [] # (level, msg)
seen = set()
def add(level, msg):
if msg not in seen:
seen.add(msg)
found.append((level, msg))
for m in re.finditer(r"```html\s*\n(.*?)```", text, re.S):
html = m.group(1)
for rx, level, msg in CHECKS:
if rx.search(html):
add(level, msg)
# 四周虚线框:正文强调勿用;居中块视为"占位/素材"组件,豁免
if FOURSIDE_DASHED.search(html) and not CENTERED.search(html):
add("WARN", "四周虚线框 border:…dashed(正文强调请用左竖条;"
"仅居中的素材占位块可用 dashed)")
return name, found
def main():
root = sys.argv[1] if len(sys.argv) > 1 else "."
# le-wechat-article 融合版:主题组件库在 references/themes/ 子目录
refs = sorted(glob.glob(os.path.join(root, "references", "themes", "*.md")))
if not refs:
print(f"未找到 {root}/references/themes/*.md")
sys.exit(1)
total_err = total_warn = clean = 0
print(f"📐 组件库源头检查:{len(refs)} 个库\n")
for path in refs:
name, found = lint_file(path)
if not found:
clean += 1
continue
errs = [m for lv, m in found if lv == "ERROR"]
warns = [m for lv, m in found if lv == "WARN"]
total_err += len(errs)
total_warn += len(warns)
print(f"── {name} ──")
for m in errs:
print(f" ❌ {m}")
for m in warns:
print(f" ⚠️ {m}")
print(f"\n汇总:{clean}/{len(refs)} 个库干净,ERROR×{total_err},WARN×{total_warn}")
if total_err == 0 and total_warn == 0:
print("✅ 全部组件库源头无反模式")
sys.exit(1 if total_err else 0)
if __name__ == "__main__":
main()