Coverage for src/lektor_ng/markdown/mistune0.py: 100%

45 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-08 23:29 +0000

1"""MarkdownController implementation for mistune 0.x""" 

2 

3import threading 

4from typing import ClassVar 

5 

6import mistune # type: ignore[import] 

7 

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 

15 

16 

17def escape(text: str) -> str: 

18 return mistune.escape(text, quote=True) 

19 

20 

21class ImprovedRenderer( 

22 # pylint: disable=no-member 

23 mistune.Renderer # type: ignore[misc] 

24): 

25 lektor: ClassVar = RendererHelper() 

26 

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 

31 

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 

36 

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>' 

42 

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)}">' 

48 

49 

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] = [] 

57 

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) 

62 

63 

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. 

70 

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)