1from __future__ import annotations
2
3from collections.abc import MutableMapping as MutableMappingABC
4from pathlib import Path
5from typing import Any, Callable, Iterable, MutableMapping, TypedDict, cast
6
7EnvType = MutableMapping[str, Any] # note: could use TypeAlias in python 3.10
8"""Type for the environment sandbox used in parsing and rendering,
9which stores mutable variables for use by plugins and rules.
10"""
11
12
13class OptionsType(TypedDict):
14 """Options for parsing."""
15
16 maxNesting: int
17 """Internal protection, recursion limit."""
18 html: bool
19 """Enable HTML tags in source."""
20 linkify: bool
21 """Enable autoconversion of URL-like texts to links."""
22 typographer: bool
23 """Enable smartquotes and replacements."""
24 quotes: str
25 """Quote characters."""
26 xhtmlOut: bool
27 """Use '/' to close single tags (<br />)."""
28 breaks: bool
29 """Convert newlines in paragraphs into <br>."""
30 langPrefix: str
31 """CSS language prefix for fenced blocks."""
32 highlight: Callable[[str, str, str], str] | None
33 """Highlighter function: (content, lang, attrs) -> str."""
34
35
36class PresetType(TypedDict):
37 """Preset configuration for markdown-it."""
38
39 options: OptionsType
40 """Options for parsing."""
41 components: MutableMapping[str, MutableMapping[str, list[str]]]
42 """Components for parsing and rendering."""
43
44
45class OptionsDict(MutableMappingABC): # type: ignore
46 """A dictionary, with attribute access to core markdownit configuration options."""
47
48 # Note: ideally we would probably just remove attribute access entirely,
49 # but we keep it for backwards compatibility.
50
51 def __init__(self, options: OptionsType) -> None:
52 self._options = cast(OptionsType, dict(options))
53
54 def __getitem__(self, key: str) -> Any:
55 return self._options[key] # type: ignore[literal-required]
56
57 def __setitem__(self, key: str, value: Any) -> None:
58 self._options[key] = value # type: ignore[literal-required]
59
60 def __delitem__(self, key: str) -> None:
61 del self._options[key] # type: ignore
62
63 def __iter__(self) -> Iterable[str]: # type: ignore
64 return iter(self._options)
65
66 def __len__(self) -> int:
67 return len(self._options)
68
69 def __repr__(self) -> str:
70 return repr(self._options)
71
72 def __str__(self) -> str:
73 return str(self._options)
74
75 @property
76 def maxNesting(self) -> int:
77 """Internal protection, recursion limit."""
78 return self._options["maxNesting"]
79
80 @maxNesting.setter
81 def maxNesting(self, value: int) -> None:
82 self._options["maxNesting"] = value
83
84 @property
85 def html(self) -> bool:
86 """Enable HTML tags in source."""
87 return self._options["html"]
88
89 @html.setter
90 def html(self, value: bool) -> None:
91 self._options["html"] = value
92
93 @property
94 def linkify(self) -> bool:
95 """Enable autoconversion of URL-like texts to links."""
96 return self._options["linkify"]
97
98 @linkify.setter
99 def linkify(self, value: bool) -> None:
100 self._options["linkify"] = value
101
102 @property
103 def typographer(self) -> bool:
104 """Enable smartquotes and replacements."""
105 return self._options["typographer"]
106
107 @typographer.setter
108 def typographer(self, value: bool) -> None:
109 self._options["typographer"] = value
110
111 @property
112 def quotes(self) -> str:
113 """Quote characters."""
114 return self._options["quotes"]
115
116 @quotes.setter
117 def quotes(self, value: str) -> None:
118 self._options["quotes"] = value
119
120 @property
121 def xhtmlOut(self) -> bool:
122 """Use '/' to close single tags (<br />)."""
123 return self._options["xhtmlOut"]
124
125 @xhtmlOut.setter
126 def xhtmlOut(self, value: bool) -> None:
127 self._options["xhtmlOut"] = value
128
129 @property
130 def breaks(self) -> bool:
131 """Convert newlines in paragraphs into <br>."""
132 return self._options["breaks"]
133
134 @breaks.setter
135 def breaks(self, value: bool) -> None:
136 self._options["breaks"] = value
137
138 @property
139 def langPrefix(self) -> str:
140 """CSS language prefix for fenced blocks."""
141 return self._options["langPrefix"]
142
143 @langPrefix.setter
144 def langPrefix(self, value: str) -> None:
145 self._options["langPrefix"] = value
146
147 @property
148 def highlight(self) -> Callable[[str, str, str], str] | None:
149 """Highlighter function: (content, langName, langAttrs) -> escaped HTML."""
150 return self._options["highlight"]
151
152 @highlight.setter
153 def highlight(self, value: Callable[[str, str, str], str] | None) -> None:
154 self._options["highlight"] = value
155
156
157def read_fixture_file(path: str | Path) -> list[list[Any]]:
158 text = Path(path).read_text(encoding="utf-8")
159 tests = []
160 section = 0
161 last_pos = 0
162 lines = text.splitlines(keepends=True)
163 for i in range(len(lines)):
164 if lines[i].rstrip() == ".":
165 if section == 0:
166 tests.append([i, lines[i - 1].strip()])
167 section = 1
168 elif section == 1:
169 tests[-1].append("".join(lines[last_pos + 1 : i]))
170 section = 2
171 elif section == 2:
172 tests[-1].append("".join(lines[last_pos + 1 : i]))
173 section = 0
174
175 last_pos = i
176 return tests