Pi66

merodi
A markdown-based static site generator.
git clone https://git.pi66.xyz/merodi

← back to log

fix: prevent Jinja from misparsing literal template syntax inside code blocks

author: pi66
date: 2026-07-01 03:02
hash: 2dc0bdd1b33734176b56399f9ec780a82c96df36

Diffstat:

M

src/build.py
35 ++++++++++++------------------
 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
diff --git a/src/build.py b/src/build.py
index 9b1655e..7438751 100644
--- a/src/build.py
+++ b/src/build.py
@@ -73,24 +73,17 @@ def jinja_handler(file, html_content, config=None):
     except Exception as e:
         return html_fatal(e, f"Template error in {file}")

-def escape_code_blocks(md_content: str) -> str:
-    def wrap_fenced(m):
-        return "\n{% raw %}\n" + m.group(0) + "\n{% endraw %}\n"
-
-    def wrap_inline(m):
-        return "\n{% raw %}" + m.group(0) + "{% endraw %}\n"
-
-    # split on fenced blocks, process alternating parts
-    parts = re.split(r'(`{3}[\s\S]*?`{3})', md_content)
-    result = []
-    for i, part in enumerate(parts):
-        if i % 2 == 1:
-            # fenced block — wrap it, skip inline pass
-            result.append(wrap_fenced(re.match(r'`{3}[\s\S]*?`{3}', part)))
-        else:
-            # outside fenced — only apply inline escaping here
-            result.append(re.sub(r'`[^`\n]+`', wrap_inline, part))
-    return "".join(result)
+def escape_code_blocks(html_content: str) -> str:
+    """ Neutralize '{' inside rendered <pre>/<code> blocks so Jinja never
+    reads {% %} or {{ }} out of code samples, even if a sample's text
+    literally contains Jinja syntax (e.g. docs showing `{% raw %}`).
+    &#123; renders back to '{' in the browser, so output is unaffected. """
+    def neutralize(m):
+        return m.group(0).replace("{", "&#123;")
+
+    html_content = re.sub(r'<pre\b[\s\S]*?</pre>', neutralize, html_content)
+    html_content = re.sub(r'<code\b[\s\S]*?</code>', neutralize, html_content)
+    return html_content

 def save_html(html_content:str, html_dest:str):
     makedirs( dirname( abspath(html_dest)), exist_ok=True )
@@ -100,8 +93,7 @@ def compile_md_to_html(md_file:str, html_dest:str, tree_config=None, extras_conf
     """Convert a Markdown file to HTML, applying filters and Jinja2 processing, and save to dest."""
     info(f"Building {GRAY(md_file)}...")
     md_content = read_file(md_file)
-    escaped = escape_code_blocks(md_content)
-    math_rendered = render_math(escaped)
+    math_rendered = render_math(md_content)
     raw_html_content = markdown(
         math_rendered,
         extensions = [
@@ -129,7 +121,8 @@ def compile_md_to_html(md_file:str, html_dest:str, tree_config=None, extras_conf
             }
         }
     )
-    filtered_html = html_filter(raw_html_content)
+    escaped_html = escape_code_blocks(raw_html_content)
+    filtered_html = html_filter(escaped_html)
     html_content = jinja_handler(md_file, filtered_html, tree_config)
     save_html(html_content, html_dest)
     info(f"Done: {GRAY(html_dest)}...")