tradingSystem/scripts/test_page_copy_guard.py

149 lines
5.7 KiB
Python
Raw 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.

# -*- 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()