Skip to content

Commit 9ecda04

Browse files
authored
‼️ BREAKING: Replace use of AttrDict for env/options (#151)
For `env` any Python mutable mapping is now allowed, and so attribute access to keys is not allowed. For `MarkdownIt.options` it is now set as an `OptionsDict`, which is a dictionary sub-class, with attribute access only for core markdownit configuration keys.
1 parent 7a1df9c commit 9ecda04

File tree

15 files changed

+189
-72
lines changed

15 files changed

+189
-72
lines changed

markdown_it/main.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Iterable,
88
List,
99
Mapping,
10+
MutableMapping,
1011
Optional,
1112
Union,
1213
)
@@ -19,7 +20,7 @@
1920
from .parser_inline import ParserInline # noqa F401
2021
from .rules_core.state_core import StateCore
2122
from .renderer import RendererHTML
22-
from .utils import AttrDict
23+
from .utils import OptionsDict
2324

2425
try:
2526
import linkify_it
@@ -69,7 +70,6 @@ def __init__(
6970
f"options_update should be a mapping: {options_update}"
7071
"\n(Perhaps you intended this to be the renderer_cls?)"
7172
)
72-
self.options = AttrDict()
7373
self.configure(config, options_update=options_update)
7474

7575
def __repr__(self) -> str:
@@ -83,15 +83,15 @@ def __getitem__(self, name: str) -> Any:
8383
"renderer": self.renderer,
8484
}[name]
8585

86-
def set(self, options: AttrDict) -> None:
86+
def set(self, options: MutableMapping) -> None:
8787
"""Set parser options (in the same format as in constructor).
8888
Probably, you will never need it, but you can change options after constructor call.
8989
9090
__Note:__ To achieve the best possible performance, don't modify a
9191
`markdown-it` instance options on the fly. If you need multiple configurations
9292
it's best to create multiple instances and initialize each with separate config.
9393
"""
94-
self.options = options
94+
self.options = OptionsDict(options)
9595

9696
def configure(
9797
self, presets: Union[str, Mapping], options_update: Optional[Mapping] = None
@@ -118,8 +118,7 @@ def configure(
118118
if options_update:
119119
options = {**options, **options_update}
120120

121-
if options:
122-
self.set(AttrDict(options))
121+
self.set(options)
123122

124123
if "components" in config:
125124
for name, component in config["components"].items():
@@ -238,7 +237,7 @@ def func(tokens, idx):
238237
plugin(self, *params, **options)
239238
return self
240239

241-
def parse(self, src: str, env: Optional[AttrDict] = None) -> List[Token]:
240+
def parse(self, src: str, env: Optional[MutableMapping] = None) -> List[Token]:
242241
"""Parse the source string to a token stream
243242
244243
:param src: source string
@@ -252,16 +251,16 @@ def parse(self, src: str, env: Optional[AttrDict] = None) -> List[Token]:
252251
inject data in specific cases. Usually, you will be ok to pass `{}`,
253252
and then pass updated object to renderer.
254253
"""
255-
env = AttrDict() if env is None else env
256-
if not isinstance(env, AttrDict): # type: ignore
257-
raise TypeError(f"Input data should be an AttrDict, not {type(env)}")
254+
env = {} if env is None else env
255+
if not isinstance(env, MutableMapping):
256+
raise TypeError(f"Input data should be a MutableMapping, not {type(env)}")
258257
if not isinstance(src, str):
259258
raise TypeError(f"Input data should be a string, not {type(src)}")
260259
state = StateCore(src, self, env)
261260
self.core.process(state)
262261
return state.tokens
263262

264-
def render(self, src: str, env: Optional[AttrDict] = None) -> Any:
263+
def render(self, src: str, env: Optional[MutableMapping] = None) -> Any:
265264
"""Render markdown string into html. It does all magic for you :).
266265
267266
:param src: source string
@@ -272,11 +271,12 @@ def render(self, src: str, env: Optional[AttrDict] = None) -> Any:
272271
But you will not need it with high probability. See also comment
273272
in [[MarkdownIt.parse]].
274273
"""
275-
if env is None:
276-
env = AttrDict()
274+
env = {} if env is None else env
277275
return self.renderer.render(self.parse(src, env), self.options, env)
278276

279-
def parseInline(self, src: str, env: Optional[AttrDict] = None) -> List[Token]:
277+
def parseInline(
278+
self, src: str, env: Optional[MutableMapping] = None
279+
) -> List[Token]:
280280
"""The same as [[MarkdownIt.parse]] but skip all block rules.
281281
282282
:param src: source string
@@ -286,17 +286,17 @@ def parseInline(self, src: str, env: Optional[AttrDict] = None) -> List[Token]:
286286
block tokens list with the single `inline` element, containing parsed inline
287287
tokens in `children` property. Also updates `env` object.
288288
"""
289-
env = AttrDict() if env is None else env
290-
if not isinstance(env, AttrDict): # type: ignore
291-
raise TypeError(f"Input data should be an AttrDict, not {type(env)}")
289+
env = {} if env is None else env
290+
if not isinstance(env, MutableMapping):
291+
raise TypeError(f"Input data should be an MutableMapping, not {type(env)}")
292292
if not isinstance(src, str):
293293
raise TypeError(f"Input data should be a string, not {type(src)}")
294294
state = StateCore(src, self, env)
295295
state.inlineMode = True
296296
self.core.process(state)
297297
return state.tokens
298298

299-
def renderInline(self, src: str, env: Optional[AttrDict] = None) -> Any:
299+
def renderInline(self, src: str, env: Optional[MutableMapping] = None) -> Any:
300300
"""Similar to [[MarkdownIt.render]] but for single paragraph content.
301301
302302
:param src: source string
@@ -305,7 +305,7 @@ def renderInline(self, src: str, env: Optional[AttrDict] = None) -> Any:
305305
Similar to [[MarkdownIt.render]] but for single paragraph content. Result
306306
will NOT be wrapped into `<p>` tags.
307307
"""
308-
env = AttrDict() if env is None else env
308+
env = {} if env is None else env
309309
return self.renderer.render(self.parseInline(src, env), self.options, env)
310310

311311
# link methods

markdown_it/port.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
Convert JS `for` loops to `while` loops
1212
this is generally the main difference between the codes,
1313
because in python you can't do e.g. `for {i=1;i<x;i++} {}`
14+
- |
15+
`env` is a common Python dictionary, and so does not have attribute access to keys,
16+
as with JavaScript dictionaries.
17+
`options` have attribute access only to core markdownit configuration options
1418
- |
1519
`Token.attrs` is a dictionary, instead of a list of lists.
1620
Upstream the list format is only used to guarantee order: https://github.com/markdown-it/markdown-it/issues/142,

markdown_it/presets/commonmark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def make():
3434
# or '' if the source string is not changed and should be escaped externally.
3535
# If result starts with <pre... internal wrapper is skipped.
3636
#
37-
# function (/*str, lang*/) { return ''; }
37+
# function (/*str, lang, attrs*/) { return ''; }
3838
#
3939
"highlight": None,
4040
},

markdown_it/presets/default.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def make():
2626
# or '' if the source string is not changed and should be escaped externaly.
2727
# If result starts with <pre... internal wrapper is skipped.
2828
#
29-
# function (/*str, lang*/) { return ''; }
29+
# function (/*str, lang, attrs*/) { return ''; }
3030
#
3131
"highlight": None,
3232
},

markdown_it/presets/zero.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def make():
2828
# Highlighter function. Should return escaped HTML,
2929
# or '' if the source string is not changed and should be escaped externaly.
3030
# If result starts with <pre... internal wrapper is skipped.
31-
# function (/*str, lang*/) { return ''; }
31+
# function (/*str, lang, attrs*/) { return ''; }
3232
"highlight": None,
3333
},
3434
"components": {

markdown_it/renderer.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ class Renderer
66
rules if you create plugin and adds new token types.
77
"""
88
import inspect
9-
from typing import Optional, Sequence
9+
from typing import MutableMapping, Optional, Sequence
1010

1111
from .common.utils import unescapeAll, escapeHtml
1212
from .token import Token
13+
from .utils import OptionsDict
1314

1415

1516
class RendererHTML:
@@ -51,7 +52,9 @@ def __init__(self, parser=None):
5152
if not (k.startswith("render") or k.startswith("_"))
5253
}
5354

54-
def render(self, tokens: Sequence[Token], options, env) -> str:
55+
def render(
56+
self, tokens: Sequence[Token], options: OptionsDict, env: MutableMapping
57+
) -> str:
5558
"""Takes token stream and generates HTML.
5659
5760
:param tokens: list on block tokens to render
@@ -73,7 +76,9 @@ def render(self, tokens: Sequence[Token], options, env) -> str:
7376

7477
return result
7578

76-
def renderInline(self, tokens: Sequence[Token], options, env) -> str:
79+
def renderInline(
80+
self, tokens: Sequence[Token], options: OptionsDict, env: MutableMapping
81+
) -> str:
7782
"""The same as ``render``, but for single token of `inline` type.
7883
7984
:param tokens: list on block tokens to render
@@ -91,7 +96,11 @@ def renderInline(self, tokens: Sequence[Token], options, env) -> str:
9196
return result
9297

9398
def renderToken(
94-
self, tokens: Sequence[Token], idx: int, options: dict, env: dict
99+
self,
100+
tokens: Sequence[Token],
101+
idx: int,
102+
options: OptionsDict,
103+
env: MutableMapping,
95104
) -> str:
96105
"""Default token renderer.
97106
@@ -161,7 +170,10 @@ def renderAttrs(token: Token) -> str:
161170
return result
162171

163172
def renderInlineAsText(
164-
self, tokens: Optional[Sequence[Token]], options, env
173+
self,
174+
tokens: Optional[Sequence[Token]],
175+
options: OptionsDict,
176+
env: MutableMapping,
165177
) -> str:
166178
"""Special kludge for image `alt` attributes to conform CommonMark spec.
167179
@@ -195,7 +207,13 @@ def code_inline(self, tokens: Sequence[Token], idx: int, options, env) -> str:
195207
+ "</code>"
196208
)
197209

198-
def code_block(self, tokens: Sequence[Token], idx: int, options, env) -> str:
210+
def code_block(
211+
self,
212+
tokens: Sequence[Token],
213+
idx: int,
214+
options: OptionsDict,
215+
env: MutableMapping,
216+
) -> str:
199217
token = tokens[idx]
200218

201219
return (
@@ -206,7 +224,13 @@ def code_block(self, tokens: Sequence[Token], idx: int, options, env) -> str:
206224
+ "</code></pre>\n"
207225
)
208226

209-
def fence(self, tokens: Sequence[Token], idx: int, options, env) -> str:
227+
def fence(
228+
self,
229+
tokens: Sequence[Token],
230+
idx: int,
231+
options: OptionsDict,
232+
env: MutableMapping,
233+
) -> str:
210234
token = tokens[idx]
211235
info = unescapeAll(token.info).strip() if token.info else ""
212236
langName = ""
@@ -252,7 +276,13 @@ def fence(self, tokens: Sequence[Token], idx: int, options, env) -> str:
252276
+ "</code></pre>\n"
253277
)
254278

255-
def image(self, tokens: Sequence[Token], idx: int, options, env) -> str:
279+
def image(
280+
self,
281+
tokens: Sequence[Token],
282+
idx: int,
283+
options: OptionsDict,
284+
env: MutableMapping,
285+
) -> str:
256286
token = tokens[idx]
257287

258288
# "alt" attr MUST be set, even if empty. Because it's mandatory and
@@ -268,10 +298,14 @@ def image(self, tokens: Sequence[Token], idx: int, options, env) -> str:
268298

269299
return self.renderToken(tokens, idx, options, env)
270300

271-
def hardbreak(self, tokens: Sequence[Token], idx: int, options, *args) -> str:
301+
def hardbreak(
302+
self, tokens: Sequence[Token], idx: int, options: OptionsDict, *args
303+
) -> str:
272304
return "<br />\n" if options.xhtmlOut else "<br>\n"
273305

274-
def softbreak(self, tokens: Sequence[Token], idx: int, options, *args) -> str:
306+
def softbreak(
307+
self, tokens: Sequence[Token], idx: int, options: OptionsDict, *args
308+
) -> str:
275309
return (
276310
("<br />\n" if options.xhtmlOut else "<br>\n") if options.breaks else "\n"
277311
)

markdown_it/ruler.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,22 @@ class Ruler
2020
Dict,
2121
Iterable,
2222
List,
23+
MutableMapping,
2324
Optional,
2425
Tuple,
2526
TYPE_CHECKING,
2627
Union,
2728
)
2829
import attr
2930

30-
from markdown_it.utils import AttrDict
31-
3231
if TYPE_CHECKING:
3332
from markdown_it import MarkdownIt
3433

3534

3635
class StateBase:
3736
srcCharCode: Tuple[int, ...]
3837

39-
def __init__(self, src: str, md: "MarkdownIt", env: AttrDict):
38+
def __init__(self, src: str, md: "MarkdownIt", env: MutableMapping):
4039
self.src = src
4140
self.env = env
4241
self.md = md

markdown_it/rules_block/reference.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22

33
from ..common.utils import isSpace, normalizeReference, charCodeAt
4-
from ..utils import AttrDict
54
from .state_block import StateBlock
65

76

@@ -189,19 +188,19 @@ def reference(state: StateBlock, startLine, _endLine, silent):
189188
state.line = startLine + lines + 1
190189

191190
if label not in state.env["references"]:
192-
state.env["references"][label] = AttrDict(
193-
{"title": title, "href": href, "map": [startLine, state.line]}
194-
)
191+
state.env["references"][label] = {
192+
"title": title,
193+
"href": href,
194+
"map": [startLine, state.line],
195+
}
195196
else:
196197
state.env.setdefault("duplicate_refs", []).append(
197-
AttrDict(
198-
{
199-
"title": title,
200-
"href": href,
201-
"label": label,
202-
"map": [startLine, state.line],
203-
}
204-
)
198+
{
199+
"title": title,
200+
"href": href,
201+
"label": label,
202+
"map": [startLine, state.line],
203+
}
205204
)
206205

207206
state.parentType = oldParentType

markdown_it/rules_core/state_core.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from typing import List, Optional, TYPE_CHECKING
1+
from typing import List, MutableMapping, Optional, TYPE_CHECKING
22

3-
from ..utils import AttrDict
43
from ..token import Token
54
from ..ruler import StateBase
65

@@ -13,7 +12,7 @@ def __init__(
1312
self,
1413
src: str,
1514
md: "MarkdownIt",
16-
env: AttrDict,
15+
env: MutableMapping,
1716
tokens: Optional[List[Token]] = None,
1817
):
1918
self.src = src

markdown_it/rules_inline/image.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,13 @@ def image(state: StateInline, silent: bool):
117117

118118
label = normalizeReference(label)
119119

120-
ref = state.env.references.get(label, None)
120+
ref = state.env["references"].get(label, None)
121121
if not ref:
122122
state.pos = oldPos
123123
return False
124124

125-
href = ref.href
126-
title = ref.title
125+
href = ref["href"]
126+
title = ref["title"]
127127

128128
#
129129
# We found the end of the link, and know for a fact it's a valid link

0 commit comments

Comments
 (0)