Coverage for src/lektor_ng/markdown/__init__.py: 97%

72 statements  

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

1import sys 

2import warnings 

3from collections.abc import Hashable 

4from importlib import metadata 

5from typing import TYPE_CHECKING, Any 

6from weakref import ref as weakref 

7 

8from markupsafe import Markup 

9 

10from lektor_ng.markdown.controller import ( 

11 ControllerCache, 

12 FieldOptions, 

13 MarkdownController, 

14 Meta, 

15 RenderResult, 

16) 

17from lektor_ng.sourceobj import SourceObject 

18from lektor_ng.utils import DeprecatedWarning, deprecated 

19 

20if TYPE_CHECKING: # pragma: no cover 

21 from lektor.environment import Environment 

22 

23controller_class: type[MarkdownController] 

24 

25 

26MISTUNE_VERSION = metadata.version("mistune") 

27if MISTUNE_VERSION.startswith("0."): 

28 from lektor_ng.markdown.mistune0 import MarkdownController0 as controller_class 

29elif MISTUNE_VERSION.startswith("2."): 

30 from lektor_ng.markdown.mistune2 import MarkdownController2 as controller_class 

31else: # pragma: no cover 

32 raise ImportError("Unsupported version of mistune") 

33 

34 

35get_controller = ControllerCache(controller_class) 

36 

37 

38class Markdown: 

39 def __init__(self, source: str, record: SourceObject | None, field_options: FieldOptions) -> None: 

40 self.source = source 

41 self.__record = weakref(record) if record is not None else None 

42 self.__field_options = field_options 

43 self.__cache: dict[Hashable, RenderResult] = {} 

44 

45 def __bool__(self) -> bool: 

46 return bool(self.source) 

47 

48 __nonzero__ = __bool__ 

49 

50 @property 

51 def record(self) -> SourceObject: 

52 ref = self.__record 

53 if ref is None: 

54 return None 

55 record = ref() 

56 if record is None: 

57 raise RuntimeError("Record has gone away") 

58 return record 

59 

60 def __render(self) -> RenderResult: 

61 # When the markdown instance is attached to a cached object we 

62 # can end up in the situation where, e.g., the base_url has 

63 # changed from the time we were put into the cache to the time 

64 # where we got referenced by something elsewhere. Since this 

65 # affects the processing of relative links, in that case we 

66 # need to re-process our markdown. 

67 controller = get_controller() 

68 key = controller.get_cache_key() 

69 result = self.__cache.get(key) if key is not None else None 

70 if result is None: 

71 result = controller.render(self.source, self.record, self.__field_options) 

72 if key is not None: 

73 self.__cache[key] = result 

74 return result 

75 

76 @property 

77 def meta(self) -> Meta: 

78 return self.__render().meta 

79 

80 @property 

81 def html(self) -> Markup: 

82 return Markup(self.__render().html) 

83 

84 def __getitem__(self, name: str) -> Any: 

85 return self.meta[name] 

86 

87 def __str__(self) -> str: 

88 return self.__render().html 

89 

90 def __html__(self) -> Markup: 

91 return self.html 

92 

93 

94# Deprecated methods and attributes follow 

95# 

96# It is hoped that these are sufficient that most plugins that extend 

97# the markdown processing will continue to work (so long as mistune is 

98# pinned to 0.x). 

99_deprecated_moved_to_submodule = {"escape", "ImprovedRenderer", "MarkdownConfig"} 

100 

101 

102def __getattr__(name): 

103 """Access to ``escape``, ``ImprovedRenderer``, and ``MarkdownConfig``. 

104 

105 These are imported from from either our .mistune0 or .mistune2 modules, 

106 as appropriate for the installed version of mistune. 

107 """ 

108 if name not in _deprecated_moved_to_submodule: 

109 raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 

110 message = DeprecatedWarning(f"{__name__}.{name}", version="3.4.0") 

111 warnings.warn(message, stacklevel=2) 

112 mistune_module = sys.modules[controller_class.__module__] 

113 return getattr(mistune_module, name) 

114 

115 

116def __dir__(): 

117 return sorted(set(globals().keys()) | _deprecated_moved_to_submodule) 

118 

119 

120@deprecated(version="3.4.0") 

121def make_markdown(env: "Environment") -> Any: # (Environment) -> mistune.Markdown 

122 return get_controller(env).make_parser() 

123 

124 

125@deprecated(version="3.4.0") 

126def markdown_to_html(text: str, record: SourceObject, field_options: FieldOptions) -> RenderResult: 

127 return get_controller().render(text, record, field_options)