migrate to python-markdown

This commit is contained in:
Daniel Fichtinger 2025-07-05 00:01:24 -04:00
parent 9f38b16d0c
commit 9b4e18d607
3 changed files with 100 additions and 83 deletions

View file

@ -1,3 +1,4 @@
from markdown import Markdown
from rich import print
from typing import Any, override
from pathlib import Path
@ -8,6 +9,15 @@ from marko.parser import Parser
from zona.config import ZonaConfig
from zona.layout import Layout
from markdown.treeprocessors import Treeprocessor
from markdown.extensions.codehilite import CodeHiliteExtension
from markdown.extensions.extra import ExtraExtension
from markdown.extensions.smarty import SmartyExtension
from markdown.extensions.sane_lists import SaneListExtension
from pymdownx.inlinehilite import InlineHiliteExtension
import xml.etree.ElementTree as etree
from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer
from pygments.formatters import HtmlFormatter
@ -19,7 +29,7 @@ from zona.log import get_logger
logger = get_logger()
class ZonaRenderer(HTMLRenderer):
class ZonaLinkProcessor(Treeprocessor):
def __init__(
self,
config: ZonaConfig | None,
@ -40,73 +50,36 @@ class ZonaRenderer(HTMLRenderer):
self.config: ZonaConfig | None = config
@override
def render_link(self, element: Link):
href = element.dest
assert isinstance(href, str)
if self.resolve:
cur = Path(href)
_href = href
if href.startswith("/"):
# resolve relative to content root
resolved = (
self.layout.content / cur.relative_to("/")
).resolve()
else:
# treat as relative link and try to resolve
resolved = (self.source.parent / cur).resolve()
# only substitute if link points to an actual file
if resolved.exists():
item = self.item_map.get(resolved)
if item:
href = util.normalize_url(item.url)
logger.debug(
f"Link in file {self.source}: {_href} resolved to {href}"
)
def run(self, root: etree.Element):
for element in root.iter("a"):
href = element.get("href")
if not href:
continue
if self.resolve:
cur = Path(href)
_href = href
if href.startswith("/"):
# resolve relative to content root
resolved = (
self.layout.content / cur.relative_to("/")
).resolve()
else:
logger.debug(
f"Warning: resolved path {resolved} not found in item map"
)
body: Any = self.render_children(element)
return f'<a href="{href}" target="_blank">{body}</a>'
# TODO: image compression/dithering?
@override
def render_image(self, element: Image):
assert self.config
if not self.config.markdown.image_labels:
return super().render_image(element)
# get label text from children
text = self.render_children(element)
title = element.title or ""
caption = f"<small>{text}</small>" if text else ""
return (
f'<div class="image-container">\n'
# TODO: convert to plaintext and add as alt attribute
f'<img src="{element.dest}" title="{title}">\n'
f"{caption}</div>"
)
@override
def render_fenced_code(self, element: FencedCode):
assert self.config
config = self.config.markdown.syntax_highlighting
if not config.enabled:
return super().render_fenced_code(element)
code = "".join(child.children for child in element.children) # type: ignore
lang = element.lang or "text"
try:
lexer = get_lexer_by_name(lang, stripall=False)
except Exception:
lexer = TextLexer(stripall=False) # type: ignore
formatter = get_formatter(self.config)
highlighted = highlight(code, lexer, formatter) # type: ignore
return (
f'<pre class="code-block language-{lang}">'
f"<code>{highlighted}</code></pre>"
)
# treat as relative link and try to resolve
resolved = (self.source.parent / cur).resolve()
# only substitute if link points to an actual file
if resolved.exists():
item = self.item_map.get(resolved)
if item:
href = util.normalize_url(item.url)
element.set("href", href)
logger.debug(
f"Link in file {self.source}: {_href} resolved to {href}"
)
else:
logger.debug(
f"Warning: resolved path {resolved} not found in item map"
)
element.set("target", "_blank")
def get_formatter(config: ZonaConfig):
@ -125,26 +98,42 @@ def md_to_html(
layout: Layout | None = None,
item_map: dict[Path, Item] | None = None,
) -> str:
if resolve_links and (
source is None or layout is None or item_map is None
):
raise TypeError(
"md_to_html() missing source and ctx when resolve_links is true"
if config:
md = Markdown(
extensions=[
CodeHiliteExtension(
linenums=False,
noclasses=False,
pygments_style=config.markdown.syntax_highlighting.theme,
),
ExtraExtension(),
SmartyExtension(),
"pymdownx.tilde",
"pymdownx.caret",
"pymdownx.smartsymbols",
InlineHiliteExtension(css_class="codehilite"),
SaneListExtension(),
]
)
parser = Parser()
ast = parser.parse(content)
renderer = ZonaRenderer(
config,
resolve_links,
source,
layout=layout,
item_map=item_map,
)
return renderer.render(ast)
else:
md = Markdown()
if resolve_links:
if source is None or layout is None or item_map is None:
raise TypeError(
"md_to_html() missing source and ctx when resolve_links is true"
)
md.treeprocessors.register(
item=ZonaLinkProcessor(
config, resolve_links, source, layout, item_map
),
name="zona_links",
priority=15,
)
return md.convert(content)
def get_style_defs(config: ZonaConfig) -> str:
formatter = get_formatter(config)
defs = formatter.get_style_defs("pre.code-block code")
defs = formatter.get_style_defs(".codehilite")
assert isinstance(defs, str)
return defs