From 111243f29d93914676220ac04133369ec55f57e4 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 5 Jun 2023 20:16:08 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20MAINTAIN:=20Make=20type=20checki?= =?UTF-8?q?ng=20strict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 1 + mdit_py_plugins/admon/index.py | 24 +++- mdit_py_plugins/amsmath/__init__.py | 27 +++- mdit_py_plugins/anchors/index.py | 10 +- mdit_py_plugins/attrs/index.py | 18 +-- mdit_py_plugins/attrs/parse.py | 6 +- mdit_py_plugins/colon_fence.py | 21 ++- mdit_py_plugins/container/index.py | 31 +++-- mdit_py_plugins/deflist/index.py | 8 +- mdit_py_plugins/dollarmath/index.py | 42 +++++- mdit_py_plugins/field_list/__init__.py | 12 +- mdit_py_plugins/footnote/index.py | 90 ++++++++++--- mdit_py_plugins/front_matter/index.py | 171 ++++++++++++------------- mdit_py_plugins/myst_blocks/index.py | 32 ++++- mdit_py_plugins/myst_role/index.py | 18 ++- mdit_py_plugins/substitution.py | 6 +- mdit_py_plugins/tasklists/__init__.py | 55 ++++---- mdit_py_plugins/texmath/index.py | 79 +++++++++--- mdit_py_plugins/wordcount/__init__.py | 2 +- pyproject.toml | 3 +- 20 files changed, 429 insertions(+), 227 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 553f4f6..c13db42 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,3 +39,4 @@ repos: hooks: - id: mypy additional_dependencies: [markdown-it-py~=3.0] + exclude: ^tests/ diff --git a/mdit_py_plugins/admon/index.py b/mdit_py_plugins/admon/index.py index f7a2477..a46e8cf 100644 --- a/mdit_py_plugins/admon/index.py +++ b/mdit_py_plugins/admon/index.py @@ -1,14 +1,20 @@ # Process admonitions and pass to cb. +from __future__ import annotations -from typing import Callable, List, Optional, Tuple +from typing import TYPE_CHECKING, Callable, Sequence from markdown_it import MarkdownIt from markdown_it.rules_block import StateBlock from mdit_py_plugins.utils import is_code_block +if TYPE_CHECKING: + from markdown_it.renderer import RendererProtocol + from markdown_it.token import Token + from markdown_it.utils import EnvType, OptionsDict -def _get_tag(params: str) -> Tuple[str, str]: + +def _get_tag(params: str) -> tuple[str, str]: """Separate the tag name from the admonition title.""" if not params.strip(): return "", "" @@ -36,7 +42,7 @@ def _validate(params: str) -> bool: MAX_MARKER_LEN = max(len(_m) for _m in MARKERS) -def _extra_classes(markup: str) -> List[str]: +def _extra_classes(markup: str) -> list[str]: """Return the list of additional classes based on the markup.""" if markup.startswith("?"): if markup.endswith("+"): @@ -158,7 +164,7 @@ def admonition(state: StateBlock, startLine: int, endLine: int, silent: bool) -> return True -def admon_plugin(md: MarkdownIt, render: Optional[Callable] = None) -> None: +def admon_plugin(md: MarkdownIt, render: None | Callable[..., str] = None) -> None: """Plugin to use `python-markdown style admonitions `_. @@ -181,8 +187,14 @@ def admon_plugin(md: MarkdownIt, render: Optional[Callable] = None) -> None: `_. """ - def renderDefault(self, tokens, idx, _options, env): - return self.renderToken(tokens, idx, _options, env) + def renderDefault( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + _options: OptionsDict, + env: EnvType, + ) -> str: + return self.renderToken(tokens, idx, _options, env) # type: ignore render = render or renderDefault diff --git a/mdit_py_plugins/amsmath/__init__.py b/mdit_py_plugins/amsmath/__init__.py index 6e66b5d..d2f71ca 100644 --- a/mdit_py_plugins/amsmath/__init__.py +++ b/mdit_py_plugins/amsmath/__init__.py @@ -1,6 +1,8 @@ """An extension to capture amsmath latex environments.""" +from __future__ import annotations + import re -from typing import Callable, Optional +from typing import TYPE_CHECKING, Callable, Optional, Sequence from markdown_it import MarkdownIt from markdown_it.common.utils import escapeHtml @@ -8,6 +10,11 @@ from mdit_py_plugins.utils import is_code_block +if TYPE_CHECKING: + from markdown_it.renderer import RendererProtocol + from markdown_it.token import Token + from markdown_it.utils import EnvType, OptionsDict + # Taken from amsmath version 2.1 # http://anorien.csc.warwick.ac.uk/mirrors/CTAN/macros/latex/required/amsmath/amsldoc.pdf ENVIRONMENTS = [ @@ -49,7 +56,9 @@ RE_OPEN = re.compile(r"\\begin\{(" + "|".join(ENVIRONMENTS) + r")([\*]?)\}") -def amsmath_plugin(md: MarkdownIt, *, renderer: Optional[Callable[[str], str]] = None): +def amsmath_plugin( + md: MarkdownIt, *, renderer: Optional[Callable[[str], str]] = None +) -> None: """Parses TeX math equations, without any surrounding delimiters, only for top-level `amsmath `__ environments: @@ -72,14 +81,20 @@ def amsmath_plugin(md: MarkdownIt, *, renderer: Optional[Callable[[str], str]] = _renderer = (lambda content: escapeHtml(content)) if renderer is None else renderer - def render_amsmath_block(self, tokens, idx, options, env): + def render_amsmath_block( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: content = _renderer(str(tokens[idx].content)) return f'
\n{content}\n
\n' md.add_render_rule("amsmath", render_amsmath_block) -def match_environment(string): +def match_environment(string: str) -> None | tuple[str, str, int]: match_open = RE_OPEN.match(string) if not match_open: return None @@ -93,7 +108,9 @@ def match_environment(string): return (environment, numbered, match_close.end()) -def amsmath_block(state: StateBlock, startLine: int, endLine: int, silent: bool): +def amsmath_block( + state: StateBlock, startLine: int, endLine: int, silent: bool +) -> bool: if is_code_block(state, startLine): return False diff --git a/mdit_py_plugins/anchors/index.py b/mdit_py_plugins/anchors/index.py index b0179be..59186c2 100644 --- a/mdit_py_plugins/anchors/index.py +++ b/mdit_py_plugins/anchors/index.py @@ -15,7 +15,7 @@ def anchors_plugin( permalinkSymbol: str = "¶", permalinkBefore: bool = False, permalinkSpace: bool = True, -): +) -> None: """Plugin for adding header anchors, based on `markdown-it-anchor `__ @@ -64,8 +64,8 @@ def _make_anchors_func( permalinkSymbol: str, permalinkBefore: bool, permalinkSpace: bool, -): - def _anchor_func(state: StateCore): +) -> Callable[[StateCore], None]: + def _anchor_func(state: StateCore) -> None: slugs: Set[str] = set() for idx, token in enumerate(state.tokens): if token.type != "heading_open": @@ -115,11 +115,11 @@ def _anchor_func(state: StateCore): return _anchor_func -def slugify(title: str): +def slugify(title: str) -> str: return re.sub(r"[^\w\u4e00-\u9fff\- ]", "", title.strip().lower().replace(" ", "-")) -def unique_slug(slug: str, slugs: set): +def unique_slug(slug: str, slugs: Set[str]) -> str: uniq = slug i = 1 while uniq in slugs: diff --git a/mdit_py_plugins/attrs/index.py b/mdit_py_plugins/attrs/index.py index be29596..9a6875d 100644 --- a/mdit_py_plugins/attrs/index.py +++ b/mdit_py_plugins/attrs/index.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Sequence from markdown_it import MarkdownIt from markdown_it.rules_block import StateBlock @@ -14,10 +14,10 @@ def attrs_plugin( md: MarkdownIt, *, - after=("image", "code_inline", "link_close", "span_close"), - spans=False, - span_after="link", -): + after: Sequence[str] = ("image", "code_inline", "link_close", "span_close"), + spans: bool = False, + span_after: str = "link", +) -> None: """Parse inline attributes that immediately follow certain inline elements:: ![alt](https://image.com){#id .a b=c} @@ -50,7 +50,7 @@ def attrs_plugin( :param span_after: The name of an inline rule after which spans may be specified. """ - def _attr_inline_rule(state: StateInline, silent: bool): + def _attr_inline_rule(state: StateInline, silent: bool) -> bool: if state.pending or not state.tokens: return False token = state.tokens[-1] @@ -77,7 +77,7 @@ def _attr_inline_rule(state: StateInline, silent: bool): md.inline.ruler.push("attr", _attr_inline_rule) -def attrs_block_plugin(md: MarkdownIt): +def attrs_block_plugin(md: MarkdownIt) -> None: """Parse block attributes. Block attributes are attributes on a single line, with no other content. @@ -111,7 +111,7 @@ def _find_opening(tokens: List[Token], index: int) -> Optional[int]: return None -def _span_rule(state: StateInline, silent: bool): +def _span_rule(state: StateInline, silent: bool) -> bool: if state.src[state.pos] != "[": return False @@ -197,7 +197,7 @@ def _attr_block_rule( return True -def _attr_resolve_block_rule(state: StateCore): +def _attr_resolve_block_rule(state: StateCore) -> None: """Find attribute block then move its attributes to the next block.""" i = 0 len_tokens = len(state.tokens) diff --git a/mdit_py_plugins/attrs/parse.py b/mdit_py_plugins/attrs/parse.py index a99d43b..6b03117 100644 --- a/mdit_py_plugins/attrs/parse.py +++ b/mdit_py_plugins/attrs/parse.py @@ -46,14 +46,14 @@ class State(Enum): class TokenState: - def __init__(self): - self._tokens = [] + def __init__(self) -> None: + self._tokens: list[tuple[int, int, str]] = [] self.start: int = 0 def set_start(self, start: int) -> None: self.start = start - def append(self, start: int, end: int, ttype: str): + def append(self, start: int, end: int, ttype: str) -> None: self._tokens.append((start, end, ttype)) def compile(self, string: str) -> dict[str, str]: diff --git a/mdit_py_plugins/colon_fence.py b/mdit_py_plugins/colon_fence.py index 6f6e1f6..c09e89c 100644 --- a/mdit_py_plugins/colon_fence.py +++ b/mdit_py_plugins/colon_fence.py @@ -1,11 +1,20 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Sequence + from markdown_it import MarkdownIt from markdown_it.common.utils import escapeHtml, unescapeAll from markdown_it.rules_block import StateBlock from mdit_py_plugins.utils import is_code_block +if TYPE_CHECKING: + from markdown_it.renderer import RendererProtocol + from markdown_it.token import Token + from markdown_it.utils import EnvType, OptionsDict + -def colon_fence_plugin(md: MarkdownIt): +def colon_fence_plugin(md: MarkdownIt) -> None: """This plugin directly mimics regular fences, but with `:` colons. Example:: @@ -25,7 +34,7 @@ def colon_fence_plugin(md: MarkdownIt): md.add_render_rule("colon_fence", _render) -def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool): +def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: if is_code_block(state, startLine): return False @@ -126,7 +135,13 @@ def _skipCharsStr(state: StateBlock, pos: int, ch: str) -> int: return pos -def _render(self, tokens, idx, options, env): +def _render( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: token = tokens[idx] info = unescapeAll(token.info).strip() if token.info else "" content = escapeHtml(token.content) diff --git a/mdit_py_plugins/container/index.py b/mdit_py_plugins/container/index.py index e397de2..454aa0e 100644 --- a/mdit_py_plugins/container/index.py +++ b/mdit_py_plugins/container/index.py @@ -1,20 +1,27 @@ """Process block-level custom containers.""" +from __future__ import annotations + from math import floor -from typing import Callable, Optional +from typing import TYPE_CHECKING, Any, Callable, Sequence from markdown_it import MarkdownIt from markdown_it.rules_block import StateBlock from mdit_py_plugins.utils import is_code_block +if TYPE_CHECKING: + from markdown_it.renderer import RendererProtocol + from markdown_it.token import Token + from markdown_it.utils import EnvType, OptionsDict + def container_plugin( md: MarkdownIt, name: str, marker: str = ":", - validate: Optional[Callable[[str, str], bool]] = None, - render=None, -): + validate: None | Callable[[str, str], bool] = None, + render: None | Callable[..., str] = None, +) -> None: """Plugin ported from `markdown-it-container `__. @@ -35,15 +42,21 @@ def container_plugin( """ - def validateDefault(params: str, *args): + def validateDefault(params: str, *args: Any) -> bool: return params.strip().split(" ", 2)[0] == name - def renderDefault(self, tokens, idx, _options, env): + def renderDefault( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + _options: OptionsDict, + env: EnvType, + ) -> str: # add a class to the opening tag if tokens[idx].nesting == 1: tokens[idx].attrJoin("class", name) - return self.renderToken(tokens, idx, _options, env) + return self.renderToken(tokens, idx, _options, env) # type: ignore min_markers = 3 marker_str = marker @@ -52,7 +65,9 @@ def renderDefault(self, tokens, idx, _options, env): validate = validate or validateDefault render = render or renderDefault - def container_func(state: StateBlock, startLine: int, endLine: int, silent: bool): + def container_func( + state: StateBlock, startLine: int, endLine: int, silent: bool + ) -> bool: if is_code_block(state, startLine): return False diff --git a/mdit_py_plugins/deflist/index.py b/mdit_py_plugins/deflist/index.py index 4cf52f3..dd8a47b 100644 --- a/mdit_py_plugins/deflist/index.py +++ b/mdit_py_plugins/deflist/index.py @@ -5,7 +5,7 @@ from mdit_py_plugins.utils import is_code_block -def deflist_plugin(md: MarkdownIt): +def deflist_plugin(md: MarkdownIt) -> None: """Plugin ported from `markdown-it-deflist `__. @@ -25,7 +25,7 @@ def deflist_plugin(md: MarkdownIt): """ - def skipMarker(state: StateBlock, line: int): + def skipMarker(state: StateBlock, line: int) -> int: """Search `[:~][\n ]`, returns next pos after marker on success or -1 on fail.""" start = state.bMarks[line] + state.tShift[line] maximum = state.eMarks[line] @@ -51,7 +51,7 @@ def skipMarker(state: StateBlock, line: int): return start - def markTightParagraphs(state: StateBlock, idx: int): + def markTightParagraphs(state: StateBlock, idx: int) -> None: level = state.level + 2 i = idx + 2 @@ -66,7 +66,7 @@ def markTightParagraphs(state: StateBlock, idx: int): i += 2 i += 1 - def deflist(state: StateBlock, startLine: int, endLine: int, silent: bool): + def deflist(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: if is_code_block(state, startLine): return False diff --git a/mdit_py_plugins/dollarmath/index.py b/mdit_py_plugins/dollarmath/index.py index f2531e6..2cd7c3f 100644 --- a/mdit_py_plugins/dollarmath/index.py +++ b/mdit_py_plugins/dollarmath/index.py @@ -1,5 +1,7 @@ +from __future__ import annotations + import re -from typing import Any, Callable, Dict, Optional +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Sequence from markdown_it import MarkdownIt from markdown_it.common.utils import escapeHtml, isWhiteSpace @@ -8,6 +10,11 @@ from mdit_py_plugins.utils import is_code_block +if TYPE_CHECKING: + from markdown_it.renderer import RendererProtocol + from markdown_it.token import Token + from markdown_it.utils import EnvType, OptionsDict + def dollarmath_plugin( md: MarkdownIt, @@ -67,6 +74,7 @@ def dollarmath_plugin( (lambda content, _: escapeHtml(content)) if renderer is None else renderer ) + _label_renderer: Callable[[str], str] if label_renderer is None: _label_renderer = ( lambda label: f'' # noqa: E501 @@ -74,19 +82,43 @@ def dollarmath_plugin( else: _label_renderer = label_renderer - def render_math_inline(self, tokens, idx, options, env) -> str: + def render_math_inline( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: content = _renderer(str(tokens[idx].content).strip(), {"display_mode": False}) return f'{content}' - def render_math_inline_double(self, tokens, idx, options, env) -> str: + def render_math_inline_double( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True}) return f'
{content}
' - def render_math_block(self, tokens, idx, options, env) -> str: + def render_math_block( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True}) return f'
\n{content}\n
\n' - def render_math_block_label(self, tokens, idx, options, env) -> str: + def render_math_block_label( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True}) _id = tokens[idx].info label = _label_renderer(tokens[idx].info) diff --git a/mdit_py_plugins/field_list/__init__.py b/mdit_py_plugins/field_list/__init__.py index 2bc2e8e..50bb907 100644 --- a/mdit_py_plugins/field_list/__init__.py +++ b/mdit_py_plugins/field_list/__init__.py @@ -1,6 +1,6 @@ """Field list plugin""" from contextlib import contextmanager -from typing import Optional, Tuple +from typing import Iterator, Optional, Tuple from markdown_it import MarkdownIt from markdown_it.rules_block import StateBlock @@ -8,7 +8,7 @@ from mdit_py_plugins.utils import is_code_block -def fieldlist_plugin(md: MarkdownIt): +def fieldlist_plugin(md: MarkdownIt) -> None: """Field lists are mappings from field names to field bodies, based on the `reStructureText syntax `_. @@ -87,7 +87,7 @@ def parseNameMarker(state: StateBlock, startLine: int) -> Tuple[int, str]: @contextmanager -def set_parent_type(state: StateBlock, name: str): +def set_parent_type(state: StateBlock, name: str) -> Iterator[None]: """Temporarily set parent type to `name`""" oldParentType = state.parentType state.parentType = name @@ -95,7 +95,9 @@ def set_parent_type(state: StateBlock, name: str): state.parentType = oldParentType -def _fieldlist_rule(state: StateBlock, startLine: int, endLine: int, silent: bool): +def _fieldlist_rule( + state: StateBlock, startLine: int, endLine: int, silent: bool +) -> bool: # adapted from markdown_it/rules_block/list.py::list_block if is_code_block(state, startLine): @@ -239,7 +241,7 @@ def _fieldlist_rule(state: StateBlock, startLine: int, endLine: int, silent: boo @contextmanager -def temp_state_changes(state: StateBlock, startLine: int): +def temp_state_changes(state: StateBlock, startLine: int) -> Iterator[None]: """Allow temporarily changing certain state attributes.""" oldTShift = state.tShift[startLine] oldSCount = state.sCount[startLine] diff --git a/mdit_py_plugins/footnote/index.py b/mdit_py_plugins/footnote/index.py index e7ef4b0..6dc3602 100644 --- a/mdit_py_plugins/footnote/index.py +++ b/mdit_py_plugins/footnote/index.py @@ -1,7 +1,7 @@ -# Process footnotes -# +"""Process footnotes""" +from __future__ import annotations -from typing import List, Optional +from typing import TYPE_CHECKING, List, Optional, Sequence from markdown_it import MarkdownIt from markdown_it.helpers import parseLinkLabel @@ -12,8 +12,12 @@ from mdit_py_plugins.utils import is_code_block +if TYPE_CHECKING: + from markdown_it.renderer import RendererProtocol + from markdown_it.utils import EnvType, OptionsDict -def footnote_plugin(md: MarkdownIt): + +def footnote_plugin(md: MarkdownIt) -> None: """Plugin ported from `markdown-it-footnote `__. @@ -56,7 +60,7 @@ def footnote_plugin(md: MarkdownIt): # ## RULES ## -def footnote_def(state: StateBlock, startLine: int, endLine: int, silent: bool): +def footnote_def(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: """Process footnote block definition""" if is_code_block(state, startLine): @@ -153,7 +157,7 @@ def footnote_def(state: StateBlock, startLine: int, endLine: int, silent: bool): return True -def footnote_inline(state: StateInline, silent: bool): +def footnote_inline(state: StateInline, silent: bool) -> bool: """Process inline footnotes (^[...])""" maximum = state.posMax @@ -195,7 +199,7 @@ def footnote_inline(state: StateInline, silent: bool): return True -def footnote_ref(state: StateInline, silent: bool): +def footnote_ref(state: StateInline, silent: bool) -> bool: """Process footnote references ([^...])""" maximum = state.posMax @@ -355,7 +359,13 @@ def footnote_tail(state: StateCore) -> None: # Renderer partials -def render_footnote_anchor_name(self, tokens, idx, options, env): +def render_footnote_anchor_name( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: n = str(tokens[idx].meta["id"] + 1) prefix = "" @@ -366,7 +376,13 @@ def render_footnote_anchor_name(self, tokens, idx, options, env): return prefix + n -def render_footnote_caption(self, tokens, idx, options, env): +def render_footnote_caption( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: n = str(tokens[idx].meta["id"] + 1) if tokens[idx].meta.get("subId", -1) > 0: @@ -375,9 +391,15 @@ def render_footnote_caption(self, tokens, idx, options, env): return "[" + n + "]" -def render_footnote_ref(self, tokens, idx, options, env): - ident = self.rules["footnote_anchor_name"](tokens, idx, options, env) - caption = self.rules["footnote_caption"](tokens, idx, options, env) +def render_footnote_ref( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: + ident: str = self.rules["footnote_anchor_name"](tokens, idx, options, env) # type: ignore[attr-defined] + caption: str = self.rules["footnote_caption"](tokens, idx, options, env) # type: ignore[attr-defined] refid = ident if tokens[idx].meta.get("subId", -1) > 0: @@ -394,7 +416,13 @@ def render_footnote_ref(self, tokens, idx, options, env): ) -def render_footnote_block_open(self, tokens, idx, options, env): +def render_footnote_block_open( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: return ( ( '
\n' @@ -406,12 +434,24 @@ def render_footnote_block_open(self, tokens, idx, options, env): ) -def render_footnote_block_close(self, tokens, idx, options, env): +def render_footnote_block_close( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: return "\n\n" -def render_footnote_open(self, tokens, idx, options, env): - ident = self.rules["footnote_anchor_name"](tokens, idx, options, env) +def render_footnote_open( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: + ident: str = self.rules["footnote_anchor_name"](tokens, idx, options, env) # type: ignore[attr-defined] if tokens[idx].meta.get("subId", -1) > 0: ident += ":" + tokens[idx].meta["subId"] @@ -419,12 +459,24 @@ def render_footnote_open(self, tokens, idx, options, env): return '
  • ' -def render_footnote_close(self, tokens, idx, options, env): +def render_footnote_close( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: return "
  • \n" -def render_footnote_anchor(self, tokens, idx, options, env): - ident = self.rules["footnote_anchor_name"](tokens, idx, options, env) +def render_footnote_anchor( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: + ident: str = self.rules["footnote_anchor_name"](tokens, idx, options, env) # type: ignore[attr-defined] if tokens[idx].meta["subId"] > 0: ident += ":" + str(tokens[idx].meta["subId"]) diff --git a/mdit_py_plugins/front_matter/index.py b/mdit_py_plugins/front_matter/index.py index d848b76..7ae67a8 100644 --- a/mdit_py_plugins/front_matter/index.py +++ b/mdit_py_plugins/front_matter/index.py @@ -1,13 +1,11 @@ -# Process front matter and pass to cb -from math import floor - +"""Process front matter.""" from markdown_it import MarkdownIt from markdown_it.rules_block import StateBlock from mdit_py_plugins.utils import is_code_block -def front_matter_plugin(md: MarkdownIt): +def front_matter_plugin(md: MarkdownIt) -> None: """Plugin ported from `markdown-it-front-matter `__. @@ -20,119 +18,110 @@ def front_matter_plugin(md: MarkdownIt): --- """ - frontMatter = make_front_matter_rule() md.block.ruler.before( "table", "front_matter", - frontMatter, + _front_matter_rule, {"alt": ["paragraph", "reference", "blockquote", "list"]}, ) -def make_front_matter_rule(): +def _front_matter_rule( + state: StateBlock, startLine: int, endLine: int, silent: bool +) -> bool: + marker_chr = "-" min_markers = 3 - marker_str = "-" - marker_char = marker_str[0] - marker_len = len(marker_str) - - def frontMatter(state: StateBlock, startLine: int, endLine: int, silent: bool): - auto_closed = False - start = state.bMarks[startLine] + state.tShift[startLine] - maximum = state.eMarks[startLine] - src_len = len(state.src) - - # Check out the first character of the first line quickly, - # this should filter out non-front matter - if startLine != 0 or marker_char != state.src[0]: - return False - - # Check out the rest of the marker string - # while pos <= 3 - pos = start + 1 - while pos <= maximum and pos < src_len: - if marker_str[(pos - start) % marker_len] != state.src[pos]: - break - pos += 1 - - marker_count = floor((pos - start) / marker_len) - if marker_count < min_markers: - return False + auto_closed = False + start = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + src_len = len(state.src) + + # Check out the first character of the first line quickly, + # this should filter out non-front matter + if startLine != 0 or state.src[0] != marker_chr: + return False + + # Check out the rest of the marker string + # while pos <= 3 + pos = start + 1 + while pos <= maximum and pos < src_len: + if state.src[pos] != marker_chr: + break + pos += 1 - pos -= (pos - start) % marker_len + marker_count = pos - start - # Since start is found, we can report success here in validation mode - if silent: - return True + if marker_count < min_markers: + return False - # Search for the end of the block - nextLine = startLine + # Since start is found, we can report success here in validation mode + if silent: + return True - while True: - nextLine += 1 - if nextLine >= endLine: - # unclosed block should be autoclosed by end of document. - return False + # Search for the end of the block + nextLine = startLine - if state.src[start:maximum] == "...": - break + while True: + nextLine += 1 + if nextLine >= endLine: + # unclosed block should be autoclosed by end of document. + return False - start = state.bMarks[nextLine] + state.tShift[nextLine] - maximum = state.eMarks[nextLine] + if state.src[start:maximum] == "...": + break - if start < maximum and state.sCount[nextLine] < state.blkIndent: - # non-empty line with negative indent should stop the list: - # - ``` - # test - break + start = state.bMarks[nextLine] + state.tShift[nextLine] + maximum = state.eMarks[nextLine] - if marker_char != state.src[start]: - continue + if start < maximum and state.sCount[nextLine] < state.blkIndent: + # non-empty line with negative indent should stop the list: + # - ``` + # test + break - if is_code_block(state, nextLine): - continue + if state.src[start] != marker_chr: + continue - pos = start + 1 - while pos < maximum: - if marker_str[(pos - start) % marker_len] != state.src[pos]: - break - pos += 1 + if is_code_block(state, nextLine): + continue - # closing code fence must be at least as long as the opening one - if floor((pos - start) / marker_len) < marker_count: - continue + pos = start + 1 + while pos < maximum: + if state.src[pos] != marker_chr: + break + pos += 1 - # make sure tail has spaces only - pos -= (pos - start) % marker_len - pos = state.skipSpaces(pos) + # closing code fence must be at least as long as the opening one + if (pos - start) < marker_count: + continue - if pos < maximum: - continue + # make sure tail has spaces only + pos = state.skipSpaces(pos) - # found! - auto_closed = True - break + if pos < maximum: + continue - old_parent = state.parentType - old_line_max = state.lineMax - state.parentType = "container" + # found! + auto_closed = True + break - # this will prevent lazy continuations from ever going past our end marker - state.lineMax = nextLine + old_parent = state.parentType + old_line_max = state.lineMax + state.parentType = "container" - token = state.push("front_matter", "", 0) - token.hidden = True - token.markup = marker_str * min_markers - token.content = state.src[ - state.bMarks[startLine + 1] : state.eMarks[nextLine - 1] - ] - token.block = True + # this will prevent lazy continuations from ever going past our end marker + state.lineMax = nextLine - state.parentType = old_parent - state.lineMax = old_line_max - state.line = nextLine + (1 if auto_closed else 0) - token.map = [startLine, state.line] + token = state.push("front_matter", "", 0) + token.hidden = True + token.markup = marker_chr * min_markers + token.content = state.src[state.bMarks[startLine + 1] : state.eMarks[nextLine - 1]] + token.block = True - return True + state.parentType = old_parent + state.lineMax = old_line_max + state.line = nextLine + (1 if auto_closed else 0) + token.map = [startLine, state.line] - return frontMatter + return True diff --git a/mdit_py_plugins/myst_blocks/index.py b/mdit_py_plugins/myst_blocks/index.py index dbae1ba..25d14ff 100644 --- a/mdit_py_plugins/myst_blocks/index.py +++ b/mdit_py_plugins/myst_blocks/index.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import itertools +from typing import TYPE_CHECKING, Sequence from markdown_it import MarkdownIt from markdown_it.common.utils import escapeHtml @@ -6,8 +9,13 @@ from mdit_py_plugins.utils import is_code_block +if TYPE_CHECKING: + from markdown_it.renderer import RendererProtocol + from markdown_it.token import Token + from markdown_it.utils import EnvType, OptionsDict + -def myst_block_plugin(md: MarkdownIt): +def myst_block_plugin(md: MarkdownIt) -> None: """Parse MyST targets (``(name)=``), blockquotes (``% comment``) and block breaks (``+++``).""" md.block.ruler.before( "blockquote", @@ -31,7 +39,7 @@ def myst_block_plugin(md: MarkdownIt): md.add_render_rule("myst_line_comment", render_myst_line_comment) -def line_comment(state: StateBlock, startLine: int, endLine: int, silent: bool): +def line_comment(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: if is_code_block(state, startLine): return False @@ -66,7 +74,7 @@ def line_comment(state: StateBlock, startLine: int, endLine: int, silent: bool): return True -def block_break(state: StateBlock, startLine: int, endLine: int, silent: bool): +def block_break(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: if is_code_block(state, startLine): return False @@ -108,7 +116,7 @@ def block_break(state: StateBlock, startLine: int, endLine: int, silent: bool): return True -def target(state: StateBlock, startLine: int, endLine: int, silent: bool): +def target(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: if is_code_block(state, startLine): return False @@ -136,14 +144,26 @@ def target(state: StateBlock, startLine: int, endLine: int, silent: bool): return True -def render_myst_target(self, tokens, idx, options, env): +def render_myst_target( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: label = tokens[idx].content class_name = "myst-target" target = f'({label})=' return f'
    {target}
    ' -def render_myst_line_comment(self, tokens, idx, options, env): +def render_myst_line_comment( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, +) -> str: # Strip leading whitespace from all lines content = "\n".join(line.lstrip() for line in tokens[idx].content.split("\n")) return f"" diff --git a/mdit_py_plugins/myst_role/index.py b/mdit_py_plugins/myst_role/index.py index 794afa8..b82daa1 100644 --- a/mdit_py_plugins/myst_role/index.py +++ b/mdit_py_plugins/myst_role/index.py @@ -1,19 +1,25 @@ import re +from typing import TYPE_CHECKING, Sequence from markdown_it import MarkdownIt from markdown_it.common.utils import escapeHtml from markdown_it.rules_inline import StateInline +if TYPE_CHECKING: + from markdown_it.renderer import RendererProtocol + from markdown_it.token import Token + from markdown_it.utils import EnvType, OptionsDict + VALID_NAME_PATTERN = re.compile(r"^\{([a-zA-Z0-9\_\-\+\:]+)\}") -def myst_role_plugin(md: MarkdownIt): +def myst_role_plugin(md: MarkdownIt) -> None: """Parse ``{role-name}`content```""" md.inline.ruler.before("backticks", "myst_role", myst_role) md.add_render_rule("myst_role", render_myst_role) -def myst_role(state: StateInline, silent: bool): +def myst_role(state: StateInline, silent: bool) -> bool: # check name match = VALID_NAME_PATTERN.match(state.src[state.pos :]) if not match: @@ -56,7 +62,13 @@ def myst_role(state: StateInline, silent: bool): return True -def render_myst_role(self, tokens, idx, options, env): +def render_myst_role( + self: "RendererProtocol", + tokens: Sequence["Token"], + idx: int, + options: "OptionsDict", + env: "EnvType", +) -> str: token = tokens[idx] name = token.meta.get("name", "unknown") return f'{{{name}}}[{escapeHtml(token.content)}]' diff --git a/mdit_py_plugins/substitution.py b/mdit_py_plugins/substitution.py index 5741f8c..3b37d44 100644 --- a/mdit_py_plugins/substitution.py +++ b/mdit_py_plugins/substitution.py @@ -7,7 +7,7 @@ def substitution_plugin( md: MarkdownIt, start_delimiter: str = "{", end_delimiter: str = "}" -): +) -> None: """A plugin to create substitution tokens. These, token should be handled by the renderer. @@ -20,7 +20,7 @@ def substitution_plugin( """ - def _substitution_inline(state: StateInline, silent: bool): + def _substitution_inline(state: StateInline, silent: bool) -> bool: try: if ( state.src[state.pos] != start_delimiter @@ -65,7 +65,7 @@ def _substitution_inline(state: StateInline, silent: bool): def _substitution_block( state: StateBlock, startLine: int, endLine: int, silent: bool - ): + ) -> bool: if is_code_block(state, startLine): return False diff --git a/mdit_py_plugins/tasklists/__init__.py b/mdit_py_plugins/tasklists/__init__.py index 3c4527f..d80f475 100644 --- a/mdit_py_plugins/tasklists/__init__.py +++ b/mdit_py_plugins/tasklists/__init__.py @@ -15,12 +15,13 @@ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +from __future__ import annotations import re -from typing import List from uuid import uuid4 from markdown_it import MarkdownIt +from markdown_it.rules_core import StateCore from markdown_it.token import Token # Regex string to match a whitespace character, as specified in @@ -34,7 +35,7 @@ def tasklists_plugin( enabled: bool = False, label: bool = False, label_after: bool = False, -): +) -> None: """Plugin for building task/todo lists out of markdown lists with items starting with [ ] or [x] .. Nothing else @@ -53,11 +54,11 @@ def tasklists_plugin( use_label_wrapper = label use_label_after = label_after - def fcn(state): - tokens: List[Token] = state.tokens + def fcn(state: StateCore) -> None: + tokens = state.tokens for i in range(2, len(tokens) - 1): if is_todo_item(tokens, i): - todoify(tokens[i], tokens[i].__class__) + todoify(tokens[i]) tokens[i - 2].attrSet( "class", "task-list-item" + (" enabled" if not disable_checkboxes else ""), @@ -68,14 +69,14 @@ def fcn(state): md.core.ruler.after("inline", "github-tasklists", fcn) - def parent_token(tokens, index): + def parent_token(tokens: list[Token], index: int) -> int: target_level = tokens[index].level - 1 for i in range(1, index + 1): if tokens[index - i].level == target_level: return index - i return -1 - def is_todo_item(tokens, index): + def is_todo_item(tokens: list[Token], index: int) -> bool: return ( is_inline(tokens[index]) and is_paragraph(tokens[index - 1]) @@ -83,9 +84,9 @@ def is_todo_item(tokens, index): and starts_with_todo_markdown(tokens[index]) ) - def todoify(token: Token, token_constructor): + def todoify(token: Token) -> None: assert token.children is not None - token.children.insert(0, make_checkbox(token, token_constructor)) + token.children.insert(0, make_checkbox(token)) token.children[1].content = token.children[1].content[3:] token.content = token.content[3:] @@ -98,15 +99,13 @@ def todoify(token: Token, token_constructor): token.children[0].content = ( token.children[0].content[0:-1] + f' id="{checklist_id}">' ) - token.children.append( - after_label(token.content, checklist_id, token_constructor) - ) + token.children.append(after_label(token.content, checklist_id)) else: - token.children.insert(0, begin_label(token_constructor)) - token.children.append(end_label(token_constructor)) + token.children.insert(0, begin_label()) + token.children.append(end_label()) - def make_checkbox(token, token_constructor): - checkbox = token_constructor("html_inline", "", 0) + def make_checkbox(token: Token) -> Token: + checkbox = Token("html_inline", "", 0) disabled_attr = 'disabled="disabled"' if disable_checkboxes else "" if token.content.startswith("[ ] "): checkbox.content = ( @@ -120,33 +119,33 @@ def make_checkbox(token, token_constructor): ) return checkbox - def begin_label(token_constructor): - token = token_constructor("html_inline", "", 0) + def begin_label() -> Token: + token = Token("html_inline", "", 0) token.content = "" return token - def after_label(content, checkbox_id, token_constructor): - token = token_constructor("html_inline", "", 0) + def after_label(content: str, checkbox_id: str) -> Token: + token = Token("html_inline", "", 0) token.content = ( f'' ) - token.attrs = [{"for": checkbox_id}] + token.attrs = {"for": checkbox_id} return token - def is_inline(token): + def is_inline(token: Token) -> bool: return token.type == "inline" - def is_paragraph(token): + def is_paragraph(token: Token) -> bool: return token.type == "paragraph_open" - def is_list_item(token): + def is_list_item(token: Token) -> bool: return token.type == "list_item_open" - def starts_with_todo_markdown(token): + def starts_with_todo_markdown(token: Token) -> bool: # leading whitespace in a list item is already trimmed off by markdown-it - return re.match(rf"\[[ xX]]{_GFM_WHITESPACE_RE}+", token.content) + return re.match(rf"\[[ xX]]{_GFM_WHITESPACE_RE}+", token.content) is not None diff --git a/mdit_py_plugins/texmath/index.py b/mdit_py_plugins/texmath/index.py index fbf9ea2..168d46e 100644 --- a/mdit_py_plugins/texmath/index.py +++ b/mdit_py_plugins/texmath/index.py @@ -1,11 +1,22 @@ +from __future__ import annotations + import re -from typing import Optional +from typing import TYPE_CHECKING, Any, Callable, Match, Sequence, TypedDict from markdown_it import MarkdownIt from markdown_it.common.utils import charCodeAt +if TYPE_CHECKING: + from markdown_it.renderer import RendererProtocol + from markdown_it.rules_block import StateBlock + from markdown_it.rules_inline import StateInline + from markdown_it.token import Token + from markdown_it.utils import EnvType, OptionsDict + -def texmath_plugin(md: MarkdownIt, delimiters="dollars", macros: Optional[dict] = None): +def texmath_plugin( + md: MarkdownIt, delimiters: str = "dollars", macros: Any = None +) -> None: """Plugin ported from `markdown-it-texmath `__. @@ -26,7 +37,13 @@ def texmath_plugin(md: MarkdownIt, delimiters="dollars", macros: Optional[dict] "escape", rule_inline["name"], make_inline_func(rule_inline) ) - def render_math_inline(self, tokens, idx, options, env): + def render_math_inline( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: return rule_inline["tmpl"].format( # noqa: B023 render(tokens[idx].content, False, macros) ) @@ -38,7 +55,13 @@ def render_math_inline(self, tokens, idx, options, env): "fence", rule_block["name"], make_block_func(rule_block) ) - def render_math_block(self, tokens, idx, options, env): + def render_math_block( + self: RendererProtocol, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: return rule_block["tmpl"].format( # noqa: B023 render(tokens[idx].content, True, macros), tokens[idx].info ) @@ -46,17 +69,32 @@ def render_math_block(self, tokens, idx, options, env): md.add_render_rule(rule_block["name"], render_math_block) -def applyRule(rule, string: str, begin, inBlockquote): +class _RuleDictReqType(TypedDict): + name: str + rex: re.Pattern[str] + tmpl: str + tag: str + + +class RuleDictType(_RuleDictReqType, total=False): + # Note in Python 3.10+ could use Req annotation + pre: Any + post: Any + + +def applyRule( + rule: RuleDictType, string: str, begin: int, inBlockquote: bool +) -> None | Match[str]: if not ( string.startswith(rule["tag"], begin) and (rule["pre"](string, begin) if "pre" in rule else True) ): - return False + return None - match = rule["rex"].match(string[begin:]) # type: re.Match + match = rule["rex"].match(string[begin:]) if not match or match.start() != 0: - return False + return None lastIndex = match.end() + begin - 1 if "post" in rule and not ( @@ -64,12 +102,12 @@ def applyRule(rule, string: str, begin, inBlockquote): # remove evil blockquote bug (https:#github.com/goessner/mdmath/issues/50) and (not inBlockquote or "\n" not in match.group(1)) ): - return False + return None return match -def make_inline_func(rule): - def _func(state, silent): +def make_inline_func(rule: RuleDictType) -> Callable[[StateInline, bool], bool]: + def _func(state: StateInline, silent: bool) -> bool: res = applyRule(rule, state.src, state.pos, False) if res: if not silent: @@ -84,8 +122,8 @@ def _func(state, silent): return _func -def make_block_func(rule): - def _func(state, begLine, endLine, silent): +def make_block_func(rule: RuleDictType) -> Callable[[StateBlock, int, int, bool], bool]: + def _func(state: StateBlock, begLine: int, endLine: int, silent: bool) -> bool: begin = state.bMarks[begLine] + state.tShift[begLine] res = applyRule(rule, state.src, begin, state.parentType == "blockquote") if res: @@ -106,23 +144,21 @@ def _func(state, begLine, endLine, silent): break line += 1 - state.pos = begin + res.end() - return bool(res) return _func -def dollar_pre(str, beg): - prv = charCodeAt(str[beg - 1], 0) if beg > 0 else False +def dollar_pre(src: str, beg: int) -> bool: + prv = charCodeAt(src[beg - 1], 0) if beg > 0 else False return ( (not prv) or prv != 0x5C and (prv < 0x30 or prv > 0x39) # no backslash, ) # no decimal digit .. before opening '$' -def dollar_post(string, end): +def dollar_post(src: str, end: int) -> bool: try: - nxt = string[end + 1] and charCodeAt(string[end + 1], 0) + nxt = src[end + 1] and charCodeAt(src[end + 1], 0) except IndexError: return True return ( @@ -130,7 +166,7 @@ def dollar_post(string, end): ) # no decimal digit .. after closing '$' -def render(tex, displayMode, macros): +def render(tex: str, displayMode: bool, macros: Any) -> str: return tex # TODO better HTML renderer port for math # try: @@ -149,7 +185,8 @@ def render(tex, displayMode, macros): # All regexes areg global (g) and sticky (y), see: # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky -rules: dict = { + +rules: dict[str, dict[str, list[RuleDictType]]] = { "brackets": { "inline": [ { diff --git a/mdit_py_plugins/wordcount/__init__.py b/mdit_py_plugins/wordcount/__init__.py index 0e2e03a..63bdf24 100644 --- a/mdit_py_plugins/wordcount/__init__.py +++ b/mdit_py_plugins/wordcount/__init__.py @@ -16,7 +16,7 @@ def wordcount_plugin( per_minute: int = 200, count_func: Callable[[str], int] = basic_count, store_text: bool = False, -): +) -> None: """Plugin for computing and storing the word count. Stores in the ``env`` e.g.:: diff --git a/pyproject.toml b/pyproject.toml index 08db7f9..d5d6458 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,5 +67,4 @@ extend-ignore = ["E731", "N802", "N803", "N806"] show_error_codes = true warn_unused_ignores = true warn_redundant_casts = true -no_implicit_optional = true -strict_equality = true +strict = true