pipeline-app/scripts/fix_i18n_w1_dspy.py

75 lines
3.1 KiB
Python
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
"""
fix_i18n_w1_dspy.py — dspy 安全修复 W1保守策略
只处理纯字面量 "text": "中文"match 后第一个非空白字符是 , 或 }
跳过所有拼接(+)、多行隐式连接(后跟换行+字符串)、变量插值——那些记入 manual 清单。
替换: "text": "X""otext": "X", "text": "X", "i18n": True
引号风格跟随原文('")。
用法: python3 scripts/fix_i18n_w1_dspy.py /tmp/i18n.json [--dry-run]
"""
import json, re, sys
from collections import defaultdict
TEXT_RE = re.compile(r'''(['"])text\1\s*:\s*(['"])((?:[^'"\\]|\\.)*)\2''')
def safe_literal(raw, m):
"""match 后第一个非空白字符必须是 , 或 }(排除拼接与多行隐式连接)"""
j = m.end()
while j < len(raw) and raw[j] in ' \t':
j += 1
return j < len(raw) and raw[j] in ',}'
def main():
report = json.load(open(sys.argv[1], encoding='utf-8'))
dry = '--dry-run' in sys.argv
viols = [v for v in report['violations']
if v['code'] == 'W1' and not v['git_ignored'] and v['file'].endswith('.dspy')]
by_file = defaultdict(list)
for v in viols:
by_file[v['file']].append(v)
fixed = manual = 0
manual_list = []
for fp, vs in sorted(by_file.items()):
raw = open(fp, encoding='utf-8').read()
# 从后往前
changed = False
for v in sorted(vs, key=lambda x: -x['pos']):
pos = v['pos']
m = TEXT_RE.match(raw, pos)
if not m:
# otext 无 i18n 的情形
mo = re.compile(r'''(['"])otext\1\s*:\s*(['"])((?:[^'"\\]|\\.)*)\2''').match(raw, pos)
if mo and not re.search(r"['\"]i18n['\"]\s*:\s*True", raw[mo.end():mo.end()+60]):
if safe_literal(raw, mo):
ins = f', {mo.group(1)}i18n{mo.group(1)}: True'
if not dry:
raw = raw[:mo.end()] + ins + raw[mo.end():]
fixed += 1; changed = True
else:
manual += 1; manual_list.append((fp, pos, 'otext动态', v['text'][:50]))
else:
manual += 1; manual_list.append((fp, pos, '无法定位', v['text'][:50]))
continue
val = m.group(3)
if not safe_literal(raw, m):
manual += 1
manual_list.append((fp, pos, '拼接/多行串', val[:50]))
continue
q1, q2 = m.group(1), m.group(2)
seg = raw[m.start():m.end()]
new_seg = f'{q1}otext{q1}: {q2}{val}{q2}, {q1}text{q1}: {q2}{val}{q2}, {q1}i18n{q1}: True'
if not dry:
raw = raw[:m.start()] + new_seg + raw[m.end():]
fixed += 1; changed = True
if changed and not dry:
open(fp, 'w', encoding='utf-8').write(raw)
print(f'{"DRY-RUN " if dry else ""}fixed={fixed} manual={manual} files={len(by_file)}')
for mm in manual_list[:40]:
print(' MANUAL', mm[0].split('/repos/')[-1], f'@{mm[1]}', mm[2], '|', mm[3])
if __name__ == '__main__':
main()