forked from swaroopch/byte-of-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathunescape_gitbook.py
More file actions
78 lines (61 loc) · 2.41 KB
/
Copy pathunescape_gitbook.py
File metadata and controls
78 lines (61 loc) · 2.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/usr/bin/env python3
"""Remove GitBook's backslash escaping from the markdown.
GitBook's exporter escaped parentheses, brackets and similar characters in
prose - `\\(like this\\)`. Standard markdown parsers such as the one MkDocs
uses do not need the escaping and render the backslash literally, so it has to
come out before the notes are built with anything else.
Only prose is touched. Fenced code blocks and inline code spans are left
exactly as they are, because a backslash inside them is real content - the line
continuations and `'\\n'` escapes in the examples depend on it.
Run from the repo root: uv run unescape_gitbook.py
"""
import re
import sys
from pathlib import Path
ROOT = Path(__file__).parent / "docs"
# The characters GitBook escaped that plain markdown is happy to see bare.
ESCAPED = re.compile(r"\\([()\[\]#_>|+.!-])")
FENCE = re.compile(r"^(\s*)(```|~~~)")
INLINE_CODE = re.compile(r"(`+)(.*?)\1", re.S)
def unescape_prose(text):
"""Strip escaping outside inline code spans."""
pieces = []
last = 0
for match in INLINE_CODE.finditer(text):
pieces.append(ESCAPED.sub(r"\1", text[last:match.start()]))
pieces.append(match.group(0)) # leave code spans untouched
last = match.end()
pieces.append(ESCAPED.sub(r"\1", text[last:]))
return "".join(pieces)
def process(text):
"""Unescape prose lines, skipping fenced code blocks entirely."""
out = []
in_fence = False
fence_marker = None
for line in text.splitlines(keepends=True):
fence = FENCE.match(line)
if fence:
marker = fence.group(2)
if not in_fence:
in_fence, fence_marker = True, marker
elif marker == fence_marker:
in_fence, fence_marker = False, None
out.append(line)
continue
out.append(line if in_fence else unescape_prose(line))
return "".join(out)
def main():
changed = 0
for path in sorted(ROOT.glob("*.md")):
original = path.read_text(encoding="utf-8")
updated = process(original)
if updated == original:
continue
removed = len(ESCAPED.findall(original)) - len(ESCAPED.findall(updated))
path.write_text(updated, encoding="utf-8")
print(f"{path.name}: removed {removed} escape(s)")
changed += 1
print(f"\n{changed} file(s) updated.")
return 0
if __name__ == "__main__":
sys.exit(main())