Skip to content

Instantly share code, notes, and snippets.

@nyanpasu64
Created September 26, 2026 00:53
Show Gist options
  • Select an option

  • Save nyanpasu64/cc1dc692bb15eb98966187fb64660e63 to your computer and use it in GitHub Desktop.

Select an option

Save nyanpasu64/cc1dc692bb15eb98966187fb64660e63 to your computer and use it in GitHub Desktop.
AI-generated script to migrate Zola pages to 0.22 to 0.23 (does not fix non-strings)
#!/usr/bin/env python3
"""
Migrate old Tera macro calls in Markdown files:
{{ youtube(id="abc", autoplay=true) }}
becomes:
{{ <youtube id="abc" autoplay=true /> }}
Commas inside quoted strings or backtick-delimited strings are preserved.
Nested parentheses are also handled.
"""
from __future__ import annotations
import argparse
from pathlib import Path
def matching_paren(text: str, opening: int) -> int:
"""Return the index of the parenthesis matching text[opening]."""
depth = 0
quote: str | None = None
escaped = False
for i in range(opening, len(text)):
char = text[i]
if quote:
if escaped:
escaped = False
elif char == "\\" and quote != "`":
escaped = True
elif char == quote:
quote = None
continue
if char in "\"'`":
quote = char
elif char == "(":
depth += 1
elif char == ")":
depth -= 1
if depth == 0:
return i
raise ValueError("Unmatched opening parenthesis")
def split_arguments(arguments: str) -> list[str]:
"""
Split function arguments on commas that are not inside strings or
nested parentheses/brackets/braces.
"""
result: list[str] = []
start = 0
stack: list[str] = []
quote: str | None = None
escaped = False
pairs = {")": "(", "]": "[", "}": "{"}
for i, char in enumerate(arguments):
if quote:
if escaped:
escaped = False
elif char == "\\" and quote != "`":
escaped = True
elif char == quote:
quote = None
continue
if char in "\"'`":
quote = char
elif char in "([{":
stack.append(char)
elif char in ")]}":
if stack and stack[-1] == pairs[char]:
stack.pop()
elif char == "," and not stack:
result.append(arguments[start:i].strip())
start = i + 1
result.append(arguments[start:].strip())
return [item for item in result if item]
def migrate_expression(expression: str) -> str:
"""
Convert:
youtube(id="abc", autoplay=true)
into:
<youtube id="abc" autoplay=true />
"""
expression = expression.strip()
# Find the first opening parenthesis outside a string.
quote: str | None = None
escaped = False
opening = -1
for i, char in enumerate(expression):
if quote:
if escaped:
escaped = False
elif char == "\\" and quote != "`":
escaped = True
elif char == quote:
quote = None
elif char in "\"'`":
quote = char
elif char == "(":
opening = i
break
# Not a function call; leave it unchanged.
if opening == -1:
return expression
closing = matching_paren(expression, opening)
# Only migrate expressions whose outermost call ends at the end.
if expression[closing + 1 :].strip():
return expression
name = expression[:opening].strip()
arguments = expression[opening + 1 : closing]
# Optional: convert macros::youtube(...) to youtube(...).
# Remove this line if namespace preservation is required.
name = name.rsplit("::", 1)[-1]
args = split_arguments(arguments)
if args:
return f"<{name} {' '.join(args)} />"
return f"<{name} />"
def migrate_mustaches(text: str) -> str:
"""Migrate all {{ ... }} expressions in a Markdown document."""
output: list[str] = []
position = 0
while True:
start = text.find("{{", position)
if start == -1:
output.append(text[position:])
break
end = text.find("}}", start + 2)
if end == -1:
output.append(text[position:])
break
output.append(text[position:start])
expression = text[start + 2 : end]
try:
migrated = migrate_expression(expression)
except ValueError:
# Leave malformed expressions unchanged.
migrated = expression.strip()
output.append("{{ " + migrated + " }}")
position = end + 2
return "".join(output)
def migrate_file(path: Path, output_dir: Path | None, in_place: bool) -> None:
original = path.read_text(encoding="utf-8")
migrated = migrate_mustaches(original)
if in_place:
destination = path
elif output_dir:
destination = output_dir / path.name
destination.parent.mkdir(parents=True, exist_ok=True)
else:
print(migrated, end="")
return
destination.write_text(migrated, encoding="utf-8")
print(f"Migrated {path} -> {destination}")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("path", type=Path, help="Markdown file or directory")
parser.add_argument(
"--in-place",
action="store_true",
help="Modify files directly",
)
parser.add_argument(
"--output-dir",
type=Path,
help="Write migrated files to this directory",
)
args = parser.parse_args()
if args.in_place and args.output_dir:
parser.error("--in-place and --output-dir cannot be used together")
if args.path.is_file():
files = [args.path]
else:
files = sorted(args.path.rglob("*.md"))
for path in files:
migrate_file(path, args.output_dir, args.in_place)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment