diff --git a/CHANGELOG.md b/CHANGELOG.md index 040ff138..1cc16b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* šŸ‘Œ Speed up parsing (~6% on the CommonMark spec) by making `StateBase.src` a plain attribute instead of a property, in [#450](https://github.com/executablebooks/markdown-it-py/pull/450) * ✨ Add `--enable-tables` to the CLI for file, standard input and interactive parsing in [#422](https://github.com/executablebooks/markdown-it-py/pull/422) * šŸ› Fix CLI interactive mode joining input lines with an extra newline, which split every line into its own paragraph and broke hard line breaks, in [#172](https://github.com/executablebooks/markdown-it-py/issues/172) * šŸ› Fix trimming and splitting with the Python whitespace set instead of the CommonMark one, which dropped U+001C–U+001F and U+0085 from paragraphs, headings, table cells and fence info strings and let distinct reference labels resolve each other, in [#418](https://github.com/executablebooks/markdown-it-py/pull/418), thanks to [@Nexory](https://github.com/Nexory) diff --git a/markdown_it/ruler.py b/markdown_it/ruler.py index 91ab5804..34a4af92 100644 --- a/markdown_it/ruler.py +++ b/markdown_it/ruler.py @@ -30,20 +30,19 @@ class Ruler class StateBase: + # `src` is a plain attribute, not a property: rules read it on almost + # every step, and a property call there costs ~5% of total parse time. + src: str + + # Cache for the deprecated `srcCharCode`, as `(src, char_codes)`. + # This is a class-level default, since subclasses do not call `__init__`. + _src_char_code: tuple[str, tuple[int, ...]] | None = None + def __init__(self, src: str, md: MarkdownIt, env: EnvType): self.src = src self.env = env self.md = md - @property - def src(self) -> str: - return self._src - - @src.setter - def src(self, value: str) -> None: - self._src = value - self._srcCharCode: tuple[int, ...] | None = None - @property def srcCharCode(self) -> tuple[int, ...]: warnings.warn( @@ -51,9 +50,10 @@ def srcCharCode(self) -> tuple[int, ...]: DeprecationWarning, stacklevel=2, ) - if self._srcCharCode is None: - self._srcCharCode = tuple(ord(c) for c in self._src) - return self._srcCharCode + # rebuild the cache if `src` has been reassigned since it was built + if self._src_char_code is None or self._src_char_code[0] is not self.src: + self._src_char_code = (self.src, tuple(ord(c) for c in self.src)) + return self._src_char_code[1] class RuleOptionsType(TypedDict, total=False): diff --git a/tests/test_api/test_main.py b/tests/test_api/test_main.py index c84e33de..0f106603 100644 --- a/tests/test_api/test_main.py +++ b/tests/test_api/test_main.py @@ -544,3 +544,15 @@ def peek_rule(state, silent): # the rule saw the text accumulate one character at a time assert seen[:4] == [0, 1, 2, 3] assert max(seen) == 9_999 + + +def test_src_char_code_follows_src(): + """The deprecated ``srcCharCode`` is rebuilt when ``src`` is reassigned.""" + from markdown_it.rules_block.state_block import StateBlock + + state = StateBlock("ab", MarkdownIt(), {}, []) + with pytest.warns(DeprecationWarning): + assert state.srcCharCode == (97, 98) + state.src = "xyz" + with pytest.warns(DeprecationWarning): + assert list(state.srcCharCode) == [120, 121, 122]