149 lines
5.7 KiB
Python
149 lines
5.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""页面文案守卫·口语化词 (2026-09-14 页面专业化包, 台账 015)。静态扫描, 不连库、不起浏览器。
|
||
|
||
## 为什么要有它
|
||
|
||
用户的第一条意见是「页面文案口语化,不像交易系统」。板块标题写成「等我拍板」「我的持仓」,
|
||
处置词写成「出手」「休假」,第一二人称满页都是。改完之后要有一道机器检查, 免得日后又飘回口语。
|
||
|
||
## 它检查什么
|
||
|
||
一份禁用词清单, 分三类: 第一二人称、拟人与口语、生造动词。**只查会显示给人看的文本** ——
|
||
先剥掉三种注释 (HTML 注释、JS 块注释、JS 行注释), 注释里为解释代码用到这些词不算。命中就打
|
||
行号、词、片段, 退出码 1。
|
||
|
||
剥行注释时跳过字符串内容与网址里的双斜线 (`https://`), 免得把字符串里的 `//` 当注释起点。
|
||
|
||
## 豁免
|
||
|
||
确实要在显示文本里用某个词 (极少), 用成对标记圈起来并写原因:
|
||
模板里: <!-- copy-guard:off 原因... --> ... <!-- copy-guard:on -->
|
||
脚本里: // copy-guard:off 原因... ... // copy-guard:on
|
||
缺原因或不成对, 判红 (不许无声豁免)。
|
||
|
||
跑法: python3 scripts/test_page_copy_guard.py
|
||
"""
|
||
import os
|
||
import re
|
||
import sys
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
PAGE = os.path.join(os.path.dirname(HERE), "app", "web", "static", "index.html")
|
||
|
||
# 禁用词。命中即报 (在显示文本里)。分三类只为可读, 检查时一视同仁。
|
||
BANNED = [
|
||
# 第一二人称
|
||
"我的", "等我", "等你", "要你", "给你", "替你", "让你", "问过你", "你自己",
|
||
"你设", "你改", "你点", "你来", "你现在", "你还没", "你做过", "你来定", "交你定",
|
||
# 拟人与口语
|
||
"拍板", "点头", "盯着", "在盯", "盯的", "拿不准", "记一笔", "挤掉", "平安无事",
|
||
# 生造动词与口语动词
|
||
"出手", "休假", "一跳", "发生了什么",
|
||
]
|
||
|
||
OFF, ON = "copy-guard:off", "copy-guard:on"
|
||
|
||
|
||
def _strip_line_comment(line: str) -> str:
|
||
"""去掉一行里的 JS 行注释 (// 到行尾), 但跳过字符串内的 // 与网址里的 ://。
|
||
走一遍字符, 记录当前是否在 ' " ` 里; 不在字符串里遇到 // 且前一个字符不是 : 就截断。"""
|
||
q = None # 当前字符串引号, None 表示不在字符串里
|
||
i, n = 0, len(line)
|
||
while i < n:
|
||
ch = line[i]
|
||
if q:
|
||
if ch == "\\":
|
||
i += 2
|
||
continue
|
||
if ch == q:
|
||
q = None
|
||
else:
|
||
if ch in "'\"`":
|
||
q = ch
|
||
elif ch == "/" and i + 1 < n and line[i + 1] == "/":
|
||
if i > 0 and line[i - 1] == ":": # 网址 :// 不是注释
|
||
i += 2
|
||
continue
|
||
return line[:i]
|
||
i += 1
|
||
return line
|
||
|
||
|
||
def _exempt_ranges(raw: str):
|
||
"""在原始文本上找成对豁免区。返回 (豁免行号集合, 报错列表)。缺原因或不成对判红。"""
|
||
bad = []
|
||
lines = raw.split("\n")
|
||
ranges = set()
|
||
stack = []
|
||
for i, line in enumerate(lines, 1):
|
||
if OFF in line:
|
||
after = line.split(OFF, 1)[1]
|
||
reason = re.sub(r"[-#>*/\s]+$", "", after).strip(" -#>*/")
|
||
if not reason:
|
||
bad.append(f"第 {i} 行:copy-guard:off 缺原因")
|
||
stack.append(i)
|
||
if ON in line:
|
||
if not stack:
|
||
bad.append(f"第 {i} 行:copy-guard:on 没有对应的 off")
|
||
else:
|
||
start = stack.pop()
|
||
for k in range(start, i + 1):
|
||
ranges.add(k)
|
||
for s in stack:
|
||
bad.append(f"第 {s} 行:copy-guard:off 没有对应的 on")
|
||
return ranges, bad
|
||
|
||
|
||
def main():
|
||
raw = open(PAGE, encoding="utf-8").read()
|
||
exempt, bad = _exempt_ranges(raw)
|
||
|
||
# 剥注释 (保行号)
|
||
def _blank(m):
|
||
return "\n" * m.group(0).count("\n")
|
||
src = re.sub(r"<!--.*?-->", _blank, raw, flags=re.S)
|
||
src = re.sub(r"/\*.*?\*/", _blank, src, flags=re.S)
|
||
lines = src.split("\n")
|
||
|
||
for i, line in enumerate(lines, 1):
|
||
if i in exempt:
|
||
continue
|
||
text = _strip_line_comment(line)
|
||
for w in BANNED:
|
||
idx = text.find(w)
|
||
if idx >= 0:
|
||
seg = text.strip()[:100]
|
||
bad.append(f"第 {i} 行:口语化词「{w}」出现在显示文本里。\n {seg}")
|
||
|
||
# 内置自检: 只在注释里出现不算, 在字符串里出现要算
|
||
_self_ok(bad)
|
||
|
||
if bad:
|
||
print(f"页面文案守卫·口语化:发现 {len(bad)} 处")
|
||
for m in bad:
|
||
print(" FAIL", m)
|
||
sys.exit(1)
|
||
print(f"ALL OK — 页面文案守卫·口语化:{len(BANNED)} 个禁用词、注释剥离、成对豁免 全部通过")
|
||
|
||
|
||
def _self_ok(bad):
|
||
"""自检样例: 注释里的禁用词不该报, 字符串里的该报。任一不符往 bad 里加一条。"""
|
||
only_comment = " // 这里解释一下等我拍板的逻辑\n const x = 1;"
|
||
in_string = " const t = '等我拍板';"
|
||
# 注释行剥掉后不含禁用词
|
||
c = _strip_line_comment(only_comment.split("\n")[0])
|
||
if any(w in c for w in BANNED):
|
||
bad.append("自检失败:行注释里的禁用词没有被剥掉")
|
||
# 字符串行剥不掉 (没有 //), 仍含禁用词
|
||
s = _strip_line_comment(in_string)
|
||
if not any(w in s for w in BANNED):
|
||
bad.append("自检失败:字符串里的禁用词被误剥")
|
||
# 网址不被当注释截断
|
||
u = _strip_line_comment(" const url = 'https://a.b/c'; const y = 2;")
|
||
if "const y = 2" not in u:
|
||
bad.append("自检失败:网址里的 // 被当成注释起点")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|