Skip to content

Commit

Permalink
馃敡 MAINTAIN: Make type checking strict (#267)
Browse files Browse the repository at this point in the history
and introduce `TypeDict` allowed by Python 3.8+
  • Loading branch information
chrisjsewell committed May 31, 2023
1 parent 1ea5457 commit 83d66d4
Show file tree
Hide file tree
Showing 52 changed files with 375 additions and 230 deletions.
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,9 @@ repos:
hooks:
- id: mypy
additional_dependencies: [mdurl]
exclude: >
(?x)^(
benchmarking/.*\.py|
docs/.*\.py|
scripts/.*\.py|
)$
23 changes: 12 additions & 11 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,20 @@
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

nitpicky = True
nitpick_ignore = [
("py:class", "Match"),
("py:class", "Path"),
("py:class", "x in the interval [0, 1)."),
("py:class", "markdown_it.helpers.parse_link_destination._Result"),
("py:class", "markdown_it.helpers.parse_link_title._Result"),
("py:class", "MarkdownIt"),
("py:class", "RuleFunc"),
("py:class", "_NodeType"),
("py:class", "typing_extensions.Protocol"),
nitpick_ignore_regex = [
("py:.*", name)
for name in (
"_ItemTV",
".*_NodeType",
".*Literal.*",
".*_Result",
"EnvType",
"RuleFunc",
"Path",
"Ellipsis",
)
]


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
Expand Down
5 changes: 3 additions & 2 deletions markdown_it/_punycode.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import codecs
import re
from typing import Callable

REGEX_SEPARATORS = re.compile(r"[\x2E\u3002\uFF0E\uFF61]")
REGEX_NON_ASCII = re.compile(r"[^\0-\x7E]")
Expand All @@ -32,10 +33,10 @@ def encode(uni: str) -> str:


def decode(ascii: str) -> str:
return codecs.decode(ascii, encoding="punycode") # type: ignore[call-overload]
return codecs.decode(ascii, encoding="punycode") # type: ignore


def map_domain(string, fn):
def map_domain(string: str, fn: Callable[[str], str]) -> str:
parts = string.split("@")
result = ""
if len(parts) > 1:
Expand Down
2 changes: 1 addition & 1 deletion markdown_it/common/normalize_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def normalizeLinkText(url: str) -> str:
GOOD_DATA_RE = re.compile(r"^data:image\/(gif|png|jpeg|webp);")


def validateLink(url: str, validator: Callable | None = None) -> bool:
def validateLink(url: str, validator: Callable[[str], bool] | None = None) -> bool:
"""Validate URL link is allowed in output.
This validator can prohibit more than really needed to prevent XSS.
Expand Down
31 changes: 8 additions & 23 deletions markdown_it/common/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Utilities for parsing source text
"""
from __future__ import annotations

import html
import re
from typing import Any
from typing import Any, Match, TypeVar

from .entities import entities

Expand All @@ -22,29 +24,12 @@ def charCodeAt(src: str, pos: int) -> Any:
return None


# Merge objects
#
def assign(obj):
"""Merge objects /*from1, from2, from3, ...*/)"""
raise NotImplementedError
# sources = Array.prototype.slice.call(arguments, 1)

# sources.forEach(function (source) {
# if (!source) { return; }

# if (typeof source !== 'object') {
# throw new TypeError(source + 'must be object')
# }

# Object.keys(source).forEach(function (key) {
# obj[key] = source[key]
# })
# })

# return obj
_ItemTV = TypeVar("_ItemTV")


def arrayReplaceAt(src: list, pos: int, newElements: list) -> list:
def arrayReplaceAt(
src: list[_ItemTV], pos: int, newElements: list[_ItemTV]
) -> list[_ItemTV]:
"""
Remove element from array and put another array at those position.
Useful for some operations with tokens
Expand Down Expand Up @@ -133,7 +118,7 @@ def unescapeMd(string: str) -> str:


def unescapeAll(string: str) -> str:
def replacer_func(match):
def replacer_func(match: Match[str]) -> str:
escaped = match.group(1)
if escaped:
return escaped
Expand Down
2 changes: 1 addition & 1 deletion markdown_it/helpers/parse_link_destination.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class _Result:
__slots__ = ("ok", "pos", "lines", "str")

def __init__(self):
def __init__(self) -> None:
self.ok = False
self.pos = 0
self.lines = 0
Expand Down
4 changes: 2 additions & 2 deletions markdown_it/helpers/parse_link_title.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
class _Result:
__slots__ = ("ok", "pos", "lines", "str")

def __init__(self):
def __init__(self) -> None:
self.ok = False
self.pos = 0
self.lines = 0
self.str = ""

def __str__(self):
def __str__(self) -> str:
return self.str


Expand Down
54 changes: 39 additions & 15 deletions markdown_it/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping
from contextlib import contextmanager
from typing import Any
from typing import Any, Literal, overload

from . import helpers, presets # noqa F401
from .common import normalize_url, utils # noqa F401
Expand All @@ -12,15 +12,15 @@
from .renderer import RendererHTML, RendererProtocol
from .rules_core.state_core import StateCore
from .token import Token
from .utils import OptionsDict
from .utils import EnvType, OptionsDict, OptionsType, PresetType

try:
import linkify_it
except ModuleNotFoundError:
linkify_it = None


_PRESETS = {
_PRESETS: dict[str, PresetType] = {
"default": presets.default.make(),
"js-default": presets.js_default.make(),
"zero": presets.zero.make(),
Expand All @@ -32,8 +32,8 @@
class MarkdownIt:
def __init__(
self,
config: str | Mapping = "commonmark",
options_update: Mapping | None = None,
config: str | PresetType = "commonmark",
options_update: Mapping[str, Any] | None = None,
*,
renderer_cls: Callable[[MarkdownIt], RendererProtocol] = RendererHTML,
):
Expand Down Expand Up @@ -67,6 +67,26 @@ def __init__(
def __repr__(self) -> str:
return f"{self.__class__.__module__}.{self.__class__.__name__}()"

@overload
def __getitem__(self, name: Literal["inline"]) -> ParserInline:
...

@overload
def __getitem__(self, name: Literal["block"]) -> ParserBlock:
...

@overload
def __getitem__(self, name: Literal["core"]) -> ParserCore:
...

@overload
def __getitem__(self, name: Literal["renderer"]) -> RendererProtocol:
...

@overload
def __getitem__(self, name: str) -> Any:
...

def __getitem__(self, name: str) -> Any:
return {
"inline": self.inline,
Expand All @@ -75,7 +95,7 @@ def __getitem__(self, name: str) -> Any:
"renderer": self.renderer,
}[name]

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

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

options = config.get("options", {}) or {}
if options_update:
options = {**options, **options_update}
options = {**options, **options_update} # type: ignore

self.set(options)
self.set(options) # type: ignore

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

def add_render_rule(self, name: str, function: Callable, fmt: str = "html") -> None:
def add_render_rule(
self, name: str, function: Callable[..., Any], fmt: str = "html"
) -> None:
"""Add a rule for rendering a particular Token type.
Only applied when ``renderer.__output__ == fmt``
"""
if self.renderer.__output__ == fmt:
self.renderer.rules[name] = function.__get__(self.renderer) # type: ignore

def use(self, plugin: Callable, *params, **options) -> MarkdownIt:
def use(
self, plugin: Callable[..., None], *params: Any, **options: Any
) -> MarkdownIt:
"""Load specified plugin with given params into current parser instance. (chainable)
It's just a sugar to call `plugin(md, params)` with curring.
Expand All @@ -229,7 +253,7 @@ def func(tokens, idx):
plugin(self, *params, **options)
return self

def parse(self, src: str, env: MutableMapping | None = None) -> list[Token]:
def parse(self, src: str, env: EnvType | None = None) -> list[Token]:
"""Parse the source string to a token stream
:param src: source string
Expand All @@ -252,7 +276,7 @@ def parse(self, src: str, env: MutableMapping | None = None) -> list[Token]:
self.core.process(state)
return state.tokens

def render(self, src: str, env: MutableMapping | None = None) -> Any:
def render(self, src: str, env: EnvType | None = None) -> Any:
"""Render markdown string into html. It does all magic for you :).
:param src: source string
Expand All @@ -266,7 +290,7 @@ def render(self, src: str, env: MutableMapping | None = None) -> Any:
env = {} if env is None else env
return self.renderer.render(self.parse(src, env), self.options, env)

def parseInline(self, src: str, env: MutableMapping | None = None) -> list[Token]:
def parseInline(self, src: str, env: EnvType | None = None) -> list[Token]:
"""The same as [[MarkdownIt.parse]] but skip all block rules.
:param src: source string
Expand All @@ -286,7 +310,7 @@ def parseInline(self, src: str, env: MutableMapping | None = None) -> list[Token
self.core.process(state)
return state.tokens

def renderInline(self, src: str, env: MutableMapping | None = None) -> Any:
def renderInline(self, src: str, env: EnvType | None = None) -> Any:
"""Similar to [[MarkdownIt.render]] but for single paragraph content.
:param src: source string
Expand Down
27 changes: 15 additions & 12 deletions markdown_it/parser_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

from . import rules_block
from .ruler import Ruler
from .rules_block.state_block import StateBlock
from .token import Token
from .utils import EnvType

if TYPE_CHECKING:
from markdown_it import MarkdownIt

LOGGER = logging.getLogger(__name__)


_rules: list[tuple] = [
_rules: list[tuple[str, Any, list[str]]] = [
# First 2 params - rule name & source. Secondary array - list of rules,
# which can be terminated by this one.
("table", rules_block.table, ["paragraph", "reference"]),
("code", rules_block.code),
("code", rules_block.code, []),
("fence", rules_block.fence, ["paragraph", "reference", "blockquote", "list"]),
(
"blockquote",
Expand All @@ -24,11 +29,11 @@
),
("hr", rules_block.hr, ["paragraph", "reference", "blockquote", "list"]),
("list", rules_block.list_block, ["paragraph", "reference", "blockquote"]),
("reference", rules_block.reference),
("reference", rules_block.reference, []),
("html_block", rules_block.html_block, ["paragraph", "reference", "blockquote"]),
("heading", rules_block.heading, ["paragraph", "reference", "blockquote"]),
("lheading", rules_block.lheading),
("paragraph", rules_block.paragraph),
("lheading", rules_block.lheading, []),
("paragraph", rules_block.paragraph, []),
]


Expand All @@ -39,12 +44,10 @@ class ParserBlock:
[[Ruler]] instance. Keep configuration of block rules.
"""

def __init__(self):
def __init__(self) -> None:
self.ruler = Ruler()
for data in _rules:
name = data[0]
rule = data[1]
self.ruler.push(name, rule, {"alt": data[2] if len(data) > 2 else []})
for name, rule, alt in _rules:
self.ruler.push(name, rule, {"alt": alt})

def tokenize(
self, state: StateBlock, startLine: int, endLine: int, silent: bool = False
Expand Down Expand Up @@ -96,8 +99,8 @@ def tokenize(
def parse(
self,
src: str,
md,
env,
md: MarkdownIt,
env: EnvType,
outTokens: list[Token],
ords: tuple[int, ...] | None = None,
) -> list[Token] | None:
Expand Down
2 changes: 1 addition & 1 deletion markdown_it/parser_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@


class ParserCore:
def __init__(self):
def __init__(self) -> None:
self.ruler = Ruler()
for name, rule in _rules:
self.ruler.push(name, rule)
Expand Down
Loading

0 comments on commit 83d66d4

Please sign in to comment.