11#!/usr/bin/env python3
2- """Convert markdown files in spec directories to HTML."""
2+ """Convert markdown files in spec directories to HTML.
3+
4+ Uses cmark-gfm for full GitHub Flavored Markdown rendering, then
5+ post-processes the output to convert GFM alert syntax (> [!NOTE], etc.)
6+ into styled admonition blocks.
7+ """
38
49import glob
510import os
611import re
12+ import subprocess
713
8- import markdown
14+ ADMONITION_TYPES = { "NOTE" , "TIP" , "IMPORTANT" , "WARNING" , "CAUTION" }
915
1016TEMPLATE = """\
1117 <!doctype html>
1723pre {{ background: #f4f4f4; padding: 1em; overflow: auto; border-radius: 3px; }}
1824pre code {{ background: none; padding: 0; }}
1925img {{ max-width: 100%; }}
26+ .admonition {{ border-left: 4px solid #888; padding: 0.5em 1em; margin: 1em 0; background: #f8f9fa; border-radius: 3px; }}
27+ .admonition-title {{ font-weight: bold; margin: 0 0 0.25em; }}
28+ .admonition.note, .admonition.tip {{ border-color: #0969da; }}
29+ .admonition.important {{ border-color: #8250df; }}
30+ .admonition.warning, .admonition.caution {{ border-color: #d1242f; }}
2031</style>
2132{body}
2233"""
2334
35+ # cmark-gfm renders > [!NOTE]\n> text as:
36+ # <blockquote>\n<p>[!NOTE]\ntext</p>\n</blockquote>
37+ # We convert these to styled admonition divs.
38+ ADMONITION_RE = re .compile (
39+ r"<blockquote>\n<p>\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\n"
40+ r"(.*?)</p>\n</blockquote>" ,
41+ re .DOTALL ,
42+ )
43+
44+
45+ def convert_admonitions (html ):
46+ def replace (m ):
47+ kind = m .group (1 ).lower ()
48+ body = m .group (2 )
49+ title = m .group (1 ).capitalize ()
50+ return (
51+ f'<div class="admonition { kind } ">\n '
52+ f'<p class="admonition-title">{ title } </p>\n '
53+ f"<p>{ body } </p>\n "
54+ f"</div>"
55+ )
56+ return ADMONITION_RE .sub (replace , html )
57+
2458
2559def extract_title (text ):
2660 m = re .search (r"^#\s+(.+)" , text , re .MULTILINE )
2761 return m .group (1 ).strip () if m else "Untitled"
2862
2963
30- def main ():
31- md = markdown .Markdown (extensions = ["fenced_code" , "tables" ])
64+ def render_markdown (text ):
65+ proc = subprocess .run (
66+ ["cmark-gfm" , "--extension" , "table" , "--extension" , "autolink" ,
67+ "--extension" , "strikethrough" , "--extension" , "tasklist" ],
68+ input = text , capture_output = True , encoding = "utf-8" ,
69+ )
70+ if proc .returncode != 0 :
71+ raise RuntimeError (f"cmark-gfm failed: { proc .stderr } " )
72+ return convert_admonitions (proc .stdout )
3273
74+
75+ def main ():
3376 for md_file in sorted (glob .glob ("*/*.md" )):
3477 if md_file .startswith ("." ):
3578 continue
@@ -42,8 +85,7 @@ def main():
4285 text = f .read ()
4386
4487 title = extract_title (text )
45- body = md .convert (text )
46- md .reset ()
88+ body = render_markdown (text )
4789
4890 with open (html_file , "w" , encoding = "utf-8" ) as f :
4991 f .write (TEMPLATE .format (title = title , body = body ))
0 commit comments