123 lines
3.8 KiB
Python
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)
|