Files

123 lines
3.8 KiB
Python

"""Audit / convert single-line $$...$$ math blocks for SilverBullet compatibility.
SilverBullet's Math library defines block math with the line-anchored marker
regex ^\\$\\$$ for BOTH start and end, so `$$...$$` written on one line never
closes and is rendered as raw text.
Usage:
python math_fix.py audit <base_dir>
python math_fix.py convert <base_dir> [--apply]
"""
import os
import re
import sys
CMD = sys.argv[1]
BASE = sys.argv[2] if len(sys.argv) > 2 else '.'
APPLY = '--apply' in sys.argv
LINE_RE = re.compile(r'^\$\$(.+)\$\$$')
def notes():
for root, dirs, files in os.walk(BASE):
if any(x in root for x in ['_scripts', '_text', '_digest', '.git']):
continue
for fn in sorted(files):
if fn.endswith('.md') and fn != 'README.md':
yield os.path.join(root, fn)
def walk_lines(path):
"""Yield (index, line, in_fence)."""
fence = False
lines = open(path, encoding='utf-8').read().split('\n')
for i, line in enumerate(lines):
if line.lstrip().startswith('```'):
yield i, line, fence
fence = not fence
continue
yield i, line, fence
def audit():
total = indented = multi = in_fence = trailing = 0
samples = []
for p in notes():
for i, line, fence in walk_lines(p):
s = line.strip()
if not s.startswith('$$') or s.count('$$') < 2:
continue
total += 1
if fence:
in_fence += 1
samples.append(('FENCE', p, i, s[:90]))
if line != line.lstrip():
indented += 1
samples.append(('INDENT', p, i, s[:90]))
if s.count('$$') > 2:
multi += 1
samples.append(('MULTI', p, i, s[:90]))
if not s.endswith('$$'):
trailing += 1
samples.append(('TAILTEXT', p, i, s[-80:]))
print('单行 $$ 块总数 :', total)
print(' 其中在代码块内 :', in_fence)
print(' 其中带前导缩进 :', indented)
print(' 其中一行含 >2 个 $$ :', multi)
print(' 其中不以 $$ 结尾 :', trailing)
for kind, p, i, s in samples[:20]:
print(' [%s] %s:%d %s' % (kind, os.path.relpath(p, BASE), i + 1, s))
def convert():
files_changed = 0
blocks_changed = 0
skipped = []
for p in notes():
lines = open(p, encoding='utf-8').read().split('\n')
out = []
fence = False
n = 0
for line in lines:
if line.lstrip().startswith('```'):
fence = not fence
out.append(line)
continue
m = LINE_RE.match(line)
if m and not fence:
body = m.group(1).strip()
if '$$' in body:
skipped.append((os.path.relpath(p, BASE), line[:90]))
out.append(line)
continue
out.append('$$')
out.append(body)
out.append('$$')
n += 1
continue
out.append(line)
if n:
files_changed += 1
blocks_changed += n
print('%3d 处 %s' % (n, os.path.relpath(p, BASE)))
if APPLY:
with open(p, 'w', encoding='utf-8', newline='\n') as f:
f.write('\n'.join(out))
print()
print('文件数: %d 转换块数: %d' % (files_changed, blocks_changed))
if skipped:
print('跳过(正文含 $$,需人工确认):')
for s in skipped:
print(' ', s)
print('模式:', '已写入' if APPLY else '预演(未写入,加 --apply 才落盘)')
if __name__ == '__main__':
if CMD == 'audit':
audit()
elif CMD == 'convert':
convert()
else:
raise SystemExit('unknown command: ' + CMD)