Skip to content

Commit 63e961d

Browse files
feat: add GitBook sync workflow with markdown conversion script (#203)
- Convert inline math $...$ to $$...$$ for GitBook compatibility - Convert GitHub callouts [!TIP/WARNING] to GitBook hint blocks - Convert TOC anchor links (#N) to (#id-N) to match GitBook anchor format - GitHub Actions workflow to auto-sync main branch to gitbook branch
1 parent f417f89 commit 63e961d

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env python3
2+
"""Convert GitHub markdown syntax to GitBook-compatible syntax.
3+
4+
Transformations:
5+
1. Inline math: $...$ → $$...$$
6+
2. Callouts: > [!TIP/WARNING/...] → {% hint style="..." %}
7+
3. TOC anchors: (#N) → (#id-N)
8+
"""
9+
10+
import re
11+
import os
12+
13+
14+
def convert_inline_math(content):
15+
"""Convert $...$ inline math to $$...$$ for GitBook.
16+
Skips code blocks and already-converted $$...$$ expressions.
17+
"""
18+
# Split by fenced code blocks and inline code to avoid modifying them
19+
parts = re.split(r'(```[\s\S]*?```|`[^`\n]+`)', content)
20+
result = []
21+
for i, part in enumerate(parts):
22+
if i % 2 == 0: # Non-code section
23+
part = re.sub(
24+
r'(?<!\$)\$(?!\$)([^\n$]+?)(?<!\$)\$(?!\$)',
25+
r'$$\1$$',
26+
part
27+
)
28+
result.append(part)
29+
return ''.join(result)
30+
31+
32+
def convert_callouts(content):
33+
"""Convert GitHub [!TYPE] callouts to GitBook hint blocks.
34+
35+
GitHub:
36+
> [!TIP]
37+
> content
38+
39+
GitBook:
40+
{% hint style="info" %}
41+
content
42+
{% endhint %}
43+
"""
44+
style_map = {
45+
'TIP': 'info',
46+
'NOTE': 'info',
47+
'WARNING': 'warning',
48+
'IMPORTANT': 'warning',
49+
'CAUTION': 'danger',
50+
}
51+
52+
def replace_callout(match):
53+
callout_type = match.group(1).upper()
54+
body = match.group(2)
55+
lines = re.sub(r'^> ?', '', body, flags=re.MULTILINE).strip()
56+
style = style_map.get(callout_type, 'info')
57+
return f'{{% hint style="{style}" %}}\n{lines}\n{{% endhint %}}'
58+
59+
pattern = r'> \[!(\w+)\]\s*\n((?:>[ \t]?[^\n]*\n)*)'
60+
return re.sub(pattern, replace_callout, content)
61+
62+
63+
def convert_toc_anchors(content):
64+
"""Convert (#N) TOC links to (#id-N) for GitBook anchor compatibility.
65+
66+
GitBook generates 'id-N' anchors for headings like '## #N'.
67+
"""
68+
return re.sub(r'\(#(\d+)\)', r'(#id-\1)', content)
69+
70+
71+
def convert_file(filepath):
72+
with open(filepath, 'r', encoding='utf-8') as f:
73+
content = f.read()
74+
75+
content = convert_inline_math(content)
76+
content = convert_callouts(content)
77+
content = convert_toc_anchors(content)
78+
79+
with open(filepath, 'w', encoding='utf-8') as f:
80+
f.write(content)
81+
82+
print(f'Converted: {filepath}')
83+
84+
85+
def main():
86+
for root, dirs, files in os.walk('.'):
87+
dirs[:] = [d for d in dirs if not d.startswith('.')]
88+
for filename in files:
89+
if filename.endswith('.md'):
90+
convert_file(os.path.join(root, filename))
91+
92+
93+
if __name__ == '__main__':
94+
main()

.github/workflows/sync-gitbook.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Sync to GitBook
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
sync:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Set up Python
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: '3.x'
24+
25+
- name: Convert markdown for GitBook
26+
run: python .github/scripts/convert_for_gitbook.py
27+
28+
- name: Push to gitbook branch
29+
run: |
30+
git config user.name "github-actions[bot]"
31+
git config user.email "github-actions[bot]@users.noreply.github.com"
32+
git checkout -B gitbook
33+
git add -A
34+
git diff --cached --quiet || git commit -m "chore: sync from main [skip ci]"
35+
git push origin gitbook --force

0 commit comments

Comments
 (0)