Skip to content

Commit a4d251d

Browse files
committed
Add OptionsType and PresetType
1 parent bc6a322 commit a4d251d

File tree

6 files changed

+113
-46
lines changed

6 files changed

+113
-46
lines changed

markdown_it/main.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
from .renderer import RendererHTML, RendererProtocol
1313
from .rules_core.state_core import StateCore
1414
from .token import Token
15-
from .utils import EnvType, OptionsDict
15+
from .utils import EnvType, OptionsDict, OptionsType, PresetType
1616

1717
try:
1818
import linkify_it
1919
except ModuleNotFoundError:
2020
linkify_it = None
2121

2222

23-
_PRESETS = {
23+
_PRESETS: dict[str, PresetType] = {
2424
"default": presets.default.make(),
2525
"js-default": presets.js_default.make(),
2626
"zero": presets.zero.make(),
@@ -32,8 +32,8 @@
3232
class MarkdownIt:
3333
def __init__(
3434
self,
35-
config: str | Mapping = "commonmark",
36-
options_update: Mapping | None = None,
35+
config: str | PresetType = "commonmark",
36+
options_update: Mapping[str, Any] | None = None,
3737
*,
3838
renderer_cls: Callable[[MarkdownIt], RendererProtocol] = RendererHTML,
3939
):
@@ -75,7 +75,7 @@ def __getitem__(self, name: str) -> Any:
7575
"renderer": self.renderer,
7676
}[name]
7777

78-
def set(self, options: MutableMapping) -> None:
78+
def set(self, options: OptionsType) -> None:
7979
"""Set parser options (in the same format as in constructor).
8080
Probably, you will never need it, but you can change options after constructor call.
8181
@@ -86,7 +86,7 @@ def set(self, options: MutableMapping) -> None:
8686
self.options = OptionsDict(options)
8787

8888
def configure(
89-
self, presets: str | Mapping, options_update: Mapping | None = None
89+
self, presets: str | PresetType, options_update: Mapping[str, Any] | None = None
9090
) -> MarkdownIt:
9191
"""Batch load of all options and component settings.
9292
This is an internal method, and you probably will not need it.
@@ -108,9 +108,9 @@ def configure(
108108

109109
options = config.get("options", {}) or {}
110110
if options_update:
111-
options = {**options, **options_update}
111+
options = {**options, **options_update} # type: ignore
112112

113-
self.set(options)
113+
self.set(options) # type: ignore
114114

115115
if "components" in config:
116116
for name, component in config["components"].items():
@@ -206,15 +206,19 @@ def reset_rules(self) -> Generator[None, None, None]:
206206
self[chain].ruler.enableOnly(rules)
207207
self.inline.ruler2.enableOnly(chain_rules["inline2"])
208208

209-
def add_render_rule(self, name: str, function: Callable, fmt: str = "html") -> None:
209+
def add_render_rule(
210+
self, name: str, function: Callable[..., Any], fmt: str = "html"
211+
) -> None:
210212
"""Add a rule for rendering a particular Token type.
211213
212214
Only applied when ``renderer.__output__ == fmt``
213215
"""
214216
if self.renderer.__output__ == fmt:
215217
self.renderer.rules[name] = function.__get__(self.renderer) # type: ignore
216218

217-
def use(self, plugin: Callable, *params, **options) -> MarkdownIt:
219+
def use(
220+
self, plugin: Callable[..., None], *params: Any, **options: Any
221+
) -> MarkdownIt:
218222
"""Load specified plugin with given params into current parser instance. (chainable)
219223
220224
It's just a sugar to call `plugin(md, params)` with curring.

markdown_it/presets/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
__all__ = ("commonmark", "default", "zero", "js_default", "gfm_like")
22

33
from . import commonmark, default, zero
4+
from ..utils import PresetType
45

56
js_default = default
67

@@ -16,7 +17,7 @@ class gfm_like:
1617
"""
1718

1819
@staticmethod
19-
def make():
20+
def make() -> PresetType:
2021
config = commonmark.make()
2122
config["components"]["core"]["rules"].append("linkify")
2223
config["components"]["block"]["rules"].append("table")

markdown_it/presets/commonmark.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
- block: table
77
- inline: strikethrough
88
"""
9+
from ..utils import PresetType
910

1011

11-
def make():
12+
def make() -> PresetType:
1213
return {
1314
"options": {
1415
"maxNesting": 20, # Internal protection, recursion limit

markdown_it/presets/default.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""markdown-it default options."""
2+
from ..utils import PresetType
23

34

4-
def make():
5+
def make() -> PresetType:
56
return {
67
"options": {
78
"maxNesting": 100, # Internal protection, recursion limit

markdown_it/presets/zero.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
"Zero" preset, with nothing enabled. Useful for manual configuring of simple
33
modes. For example, to parse bold/italic only.
44
"""
5+
from ..utils import PresetType
56

67

7-
def make():
8+
def make() -> PresetType:
89
return {
910
"options": {
1011
"maxNesting": 20, # Internal protection, recursion limit

markdown_it/utils.py

Lines changed: 91 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,160 @@
11
from __future__ import annotations
22

3-
from collections.abc import Callable
3+
from collections.abc import MutableMapping as MutableMappingABC
44
from pathlib import Path
5-
from typing import Any, MutableMapping
5+
from typing import Any, Callable, Iterable, MutableMapping, TypedDict
66

7-
EnvType = MutableMapping[str, Any]
7+
EnvType = MutableMapping[str, Any] # note: could use TypeAlias in python 3.10
88
"""Type for the environment sandbox used in parsing and rendering,
99
which stores mutable variables for use by plugins and rules.
1010
"""
1111

1212

13-
class OptionsDict(dict):
13+
class 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+
36+
class 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+
45+
class OptionsDict(MutableMappingABC): # type: ignore
1446
"""A dictionary, with attribute access to core markdownit configuration options."""
1547

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 = 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+
1675
@property
1776
def maxNesting(self) -> int:
1877
"""Internal protection, recursion limit."""
19-
return self["maxNesting"]
78+
return self._options["maxNesting"]
2079

2180
@maxNesting.setter
22-
def maxNesting(self, value: int):
23-
self["maxNesting"] = value
81+
def maxNesting(self, value: int) -> None:
82+
self._options["maxNesting"] = value
2483

2584
@property
2685
def html(self) -> bool:
2786
"""Enable HTML tags in source."""
28-
return self["html"]
87+
return self._options["html"]
2988

3089
@html.setter
31-
def html(self, value: bool):
32-
self["html"] = value
90+
def html(self, value: bool) -> None:
91+
self._options["html"] = value
3392

3493
@property
3594
def linkify(self) -> bool:
3695
"""Enable autoconversion of URL-like texts to links."""
37-
return self["linkify"]
96+
return self._options["linkify"]
3897

3998
@linkify.setter
40-
def linkify(self, value: bool):
41-
self["linkify"] = value
99+
def linkify(self, value: bool) -> None:
100+
self._options["linkify"] = value
42101

43102
@property
44103
def typographer(self) -> bool:
45104
"""Enable smartquotes and replacements."""
46-
return self["typographer"]
105+
return self._options["typographer"]
47106

48107
@typographer.setter
49-
def typographer(self, value: bool):
50-
self["typographer"] = value
108+
def typographer(self, value: bool) -> None:
109+
self._options["typographer"] = value
51110

52111
@property
53112
def quotes(self) -> str:
54113
"""Quote characters."""
55-
return self["quotes"]
114+
return self._options["quotes"]
56115

57116
@quotes.setter
58-
def quotes(self, value: str):
59-
self["quotes"] = value
117+
def quotes(self, value: str) -> None:
118+
self._options["quotes"] = value
60119

61120
@property
62121
def xhtmlOut(self) -> bool:
63122
"""Use '/' to close single tags (<br />)."""
64-
return self["xhtmlOut"]
123+
return self._options["xhtmlOut"]
65124

66125
@xhtmlOut.setter
67-
def xhtmlOut(self, value: bool):
68-
self["xhtmlOut"] = value
126+
def xhtmlOut(self, value: bool) -> None:
127+
self._options["xhtmlOut"] = value
69128

70129
@property
71130
def breaks(self) -> bool:
72131
"""Convert newlines in paragraphs into <br>."""
73-
return self["breaks"]
132+
return self._options["breaks"]
74133

75134
@breaks.setter
76-
def breaks(self, value: bool):
77-
self["breaks"] = value
135+
def breaks(self, value: bool) -> None:
136+
self._options["breaks"] = value
78137

79138
@property
80139
def langPrefix(self) -> str:
81140
"""CSS language prefix for fenced blocks."""
82-
return self["langPrefix"]
141+
return self._options["langPrefix"]
83142

84143
@langPrefix.setter
85-
def langPrefix(self, value: str):
86-
self["langPrefix"] = value
144+
def langPrefix(self, value: str) -> None:
145+
self._options["langPrefix"] = value
87146

88147
@property
89148
def highlight(self) -> Callable[[str, str, str], str] | None:
90149
"""Highlighter function: (content, langName, langAttrs) -> escaped HTML."""
91-
return self["highlight"]
150+
return self._options["highlight"]
92151

93152
@highlight.setter
94-
def highlight(self, value: Callable[[str, str, str], str] | None):
95-
self["highlight"] = value
153+
def highlight(self, value: Callable[[str, str, str], str] | None) -> None:
154+
self._options["highlight"] = value
96155

97156

98-
def read_fixture_file(path: str | Path) -> list[list]:
157+
def read_fixture_file(path: str | Path) -> list[list[Any]]:
99158
text = Path(path).read_text(encoding="utf-8")
100159
tests = []
101160
section = 0

0 commit comments

Comments
 (0)