Coverage for src/lektor_ng/markdown/mistune0.py: 100%
45 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-30 13:49 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-30 13:49 +0000
1"""MarkdownController implementation for mistune 0.x"""
3import threading
4from typing import ClassVar
6import mistune # type: ignore[import]
8from lektor_ng.markdown.controller import (
9 MarkdownController,
10 Meta, # FIXME: move this?
11 RendererHelper,
12)
13from lektor_ng.sourceobj import SourceObject
14from lektor_ng.utils import deprecated
17def escape(text: str) -> str:
18 return mistune.escape(text, quote=True)
21class ImprovedRenderer(
22 # pylint: disable=no-member
23 mistune.Renderer # type: ignore[misc]
24):
25 lektor: ClassVar = RendererHelper()
27 @property # type: ignore[misc] # https://github.com/python/mypy/issues/1362
28 @deprecated("Use ImprovedRenderer.lektor.record instead.", version="3.4.0")
29 def record(self) -> SourceObject:
30 return self.lektor.record
32 @property # type: ignore[misc]
33 @deprecated("Use ImprovedRenderer.lektor.meta instead.", version="3.4.0")
34 def meta(self) -> Meta:
35 return self.lektor.meta
37 def link(self, link: str, title: str | None, text: str) -> str:
38 url = self.lektor.resolve_url(link)
39 if not title:
40 return f'<a href="{escape(url)}">{text}</a>'
41 return f'<a href="{escape(url)}" title="{escape(title)}">{text}</a>'
43 def image(self, src: str, title: str | None, text: str) -> str:
44 url = escape(self.lektor.resolve_url(src))
45 if not title:
46 return f'<img src="{url}" alt="{escape(text)}">'
47 return f'<img src="{url}" alt="{escape(text)}" title="{escape(title)}">'
50class MarkdownConfig:
51 def __init__(self) -> None:
52 self.options = {
53 "escape": False,
54 }
55 self.renderer_base = ImprovedRenderer
56 self.renderer_mixins: list[type] = []
58 def make_renderer(self) -> ImprovedRenderer:
59 bases = tuple(self.renderer_mixins) + (self.renderer_base,)
60 renderer_cls = type("renderer_cls", bases, {})
61 return renderer_cls(**self.options)
64class MarkdownController0(MarkdownController, threading.local):
65 # NB: making this a threading.local means the results in the
66 # cached_property MarkdownController.parser having a separate
67 # value in each thread.
68 #
69 # We need that since the mistune 0.x parser is not thread-safe.
71 def make_parser(self) -> mistune.Markdown:
72 env = self.env
73 cfg = MarkdownConfig()
74 env.plugin_controller.emit("markdown-config", config=cfg)
75 renderer = cfg.make_renderer()
76 env.plugin_controller.emit("markdown-lexer-config", config=cfg, renderer=renderer)
77 # pylint: disable=unexpected-keyword-arg
78 return mistune.Markdown(renderer, **cfg.options)