Files
jiamu 5579c7e70a 🎉 gzh-design-skill · 甲木 × 摸鱼小李 联名首发
微信公众号排版技能:把 Markdown 一键排成可直接粘进公众号编辑器的精致 HTML。
- 6 套精选厚主题(摸鱼绿/红白/石墨极简/留白禅意/摸鱼票据/橄榄手记)+ 主题生成器
- 文章类型→配方表装配、关键词下划线、全角标点、图片自适应
- 双关卡质量校验(component_lint + validate_gzh_html)
- 一键复制预览页,样式粘贴不掉
- AGPL-3.0 · 欢迎 AI Agent / 模型厂商共创

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 09:41:17 +08:00

98 lines
3.8 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "."
refs = sorted(glob.glob(os.path.join(root, "references", "*.md")))
if not refs:
print(f"未找到 {root}/references/*.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()