From afe075cb7910fcb538670ae4f0a7286454d15169 Mon Sep 17 00:00:00 2001
From: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com>
Date: Sun, 6 Feb 2022 10:00:07 +0200
Subject: [PATCH 01/16] =?UTF-8?q?=F0=9F=94=A7=20MAINTAIN:=20Add=20a=20prof?=
=?UTF-8?q?iler=20tox=20env=20(#197)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
MANIFEST.in | 1 +
profiler.py | 19 +++++++++++++++++++
setup.cfg | 2 ++
tox.ini | 13 +++++++++++++
4 files changed, 35 insertions(+)
create mode 100644 profiler.py
diff --git a/MANIFEST.in b/MANIFEST.in
index 99f9f382..1302ef01 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -14,6 +14,7 @@ exclude .circleci
exclude .circleci/config.yml
exclude codecov.yml
exclude .mypy.ini
+exclude profiler.py
include LICENSE
include LICENSE.markdown-it
diff --git a/profiler.py b/profiler.py
new file mode 100644
index 00000000..414a7727
--- /dev/null
+++ b/profiler.py
@@ -0,0 +1,19 @@
+"""A script for profiling.
+
+To generate and read results:
+ - `tox -e profile`
+ - `firefox .tox/prof/output.svg`
+"""
+from pathlib import Path
+
+from markdown_it import MarkdownIt
+
+commonmark_spec = (
+ (Path(__file__).parent / "tests" / "test_cmark_spec" / "spec.md")
+ .read_bytes()
+ .decode()
+)
+
+# Run this a few times to emphasize over imports and other overhead above
+for _ in range(10):
+ MarkdownIt().render(commonmark_spec)
diff --git a/setup.cfg b/setup.cfg
index 2815e3eb..d5859efb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -70,6 +70,8 @@ benchmarking =
psutil
pytest
pytest-benchmark~=3.2
+profiling =
+ gprof2dot
[options.packages.find]
exclude =
diff --git a/tox.ini b/tox.ini
index 158faff1..c169f982 100644
--- a/tox.ini
+++ b/tox.ini
@@ -43,3 +43,16 @@ setenv =
commands =
clean: rm -rf docs/_build
sphinx-build -nW --keep-going -b {posargs:html} docs/ docs/_build/{posargs:html}
+
+[testenv:profile]
+description = run profiler (use e.g. `firefox .tox/prof/output.svg` to open)
+extras = profiling
+allowlist_externals =
+ mkdir
+ dot
+commands =
+ mkdir -p "{toxworkdir}/prof"
+ python -m cProfile -o "{toxworkdir}/prof/output.pstats" profiler.py
+ gprof2dot -f pstats -o "{toxworkdir}/prof/output.dot" "{toxworkdir}/prof/output.pstats"
+ dot -Tsvg -o "{toxworkdir}/prof/output.svg" "{toxworkdir}/prof/output.dot"
+ python -c 'import pathlib; print("profiler svg output under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "prof" / "output.svg"))'
From d357240bc00bc00815790c2d36ad499a9db0b7d1 Mon Sep 17 00:00:00 2001
From: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com>
Date: Sun, 6 Feb 2022 10:02:53 +0200
Subject: [PATCH 02/16] =?UTF-8?q?=F0=9F=94=A7=20MAINTAIN:=20Update=20perfo?=
=?UTF-8?q?rmance=20benchmark=20(#196)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/other.md | 24 +++++++++++-------------
setup.cfg | 8 ++++----
2 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/docs/other.md b/docs/other.md
index 8db5a4a4..4d77360f 100644
--- a/docs/other.md
+++ b/docs/other.md
@@ -38,24 +38,22 @@ So, if you decide to use plugins that add extended class syntax or autogeneratin
# Performance
-markdown-it-py is the fastest _**CommonMark compliant**_ parser written in python!
-
You can view our continuous integration benchmarking analysis at:
==hl\n==\n"
+
+
+def test_ordered_list_info():
+ def type_filter(tokens, type_):
+ return [t for t in tokens if t.type == type_]
+
+ md = MarkdownIt()
+
+ tokens = md.parse("1. Foo\n2. Bar\n20. Fuzz")
+ assert len(type_filter(tokens, "ordered_list_open")) == 1
+ tokens = type_filter(tokens, "list_item_open")
+ assert len(tokens) == 3
+ assert tokens[0].info == "1"
+ assert tokens[0].markup == "."
+ assert tokens[1].info == "2"
+ assert tokens[1].markup == "."
+ assert tokens[2].info == "20"
+ assert tokens[2].markup == "."
+
+ tokens = md.parse(" 1. Foo\n2. Bar\n 20. Fuzz\n 199. Flp")
+ assert len(type_filter(tokens, "ordered_list_open")) == 1
+ tokens = type_filter(tokens, "list_item_open")
+ assert len(tokens) == 4
+ assert tokens[0].info == "1"
+ assert tokens[0].markup == "."
+ assert tokens[1].info == "2"
+ assert tokens[1].markup == "."
+ assert tokens[2].info == "20"
+ assert tokens[2].markup == "."
+ assert tokens[3].info == "199"
+ assert tokens[3].markup == "."
From 32097fb8cf95b7e84ad4348aefe79a4743b225ff Mon Sep 17 00:00:00 2001
From: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com>
Date: Fri, 18 Feb 2022 20:18:49 +0200
Subject: [PATCH 04/16] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20UPGRADE:=20Drop=20su?=
=?UTF-8?q?pport=20for=20EOL=20Python=203.6=20(#194)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/tests.yml | 10 ++++-
markdown_it/cli/parse.py | 8 ++--
markdown_it/common/normalize_url.py | 6 ++-
markdown_it/main.py | 50 +++++++++------------
markdown_it/parser_block.py | 11 ++---
markdown_it/parser_core.py | 4 +-
markdown_it/parser_inline.py | 8 ++--
markdown_it/renderer.py | 13 +++---
markdown_it/ruler.py | 37 +++++++---------
markdown_it/rules_block/blockquote.py | 7 +--
markdown_it/rules_block/heading.py | 5 ++-
markdown_it/rules_block/html_block.py | 5 ++-
markdown_it/rules_block/state_block.py | 10 +++--
markdown_it/rules_core/replacements.py | 9 ++--
markdown_it/rules_core/smartquotes.py | 8 ++--
markdown_it/rules_core/state_core.py | 11 +++--
markdown_it/rules_inline/image.py | 5 +--
markdown_it/rules_inline/state_inline.py | 17 +++++---
markdown_it/rules_inline/strikethrough.py | 5 ++-
markdown_it/token.py | 53 ++++++++++-------------
markdown_it/tree.py | 46 +++++++++-----------
markdown_it/utils.py | 10 +++--
setup.cfg | 3 +-
tox.ini | 8 ++--
24 files changed, 171 insertions(+), 178 deletions(-)
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 77ffff89..e592a769 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ['pypy-3.6', '3.6', '3.7', '3.8', '3.9', '3.10']
+ python-version: ['pypy-3.7', '3.7', '3.8', '3.9', '3.10']
steps:
- uses: actions/checkout@v2
@@ -126,3 +126,11 @@ jobs:
with:
user: __token__
password: ${{ secrets.PYPI_KEY }}
+
+ allgood:
+ runs-on: ubuntu-latest
+ needs:
+ - pre-commit
+ - tests
+ steps:
+ - run: echo "Great success!"
diff --git a/markdown_it/cli/parse.py b/markdown_it/cli/parse.py
index 353c526f..e159aad2 100644
--- a/markdown_it/cli/parse.py
+++ b/markdown_it/cli/parse.py
@@ -4,9 +4,11 @@
Parse one or more markdown files, convert each to HTML, and print to stdout.
"""
+from __future__ import annotations
+
import argparse
+from collections.abc import Iterable, Sequence
import sys
-from typing import Iterable, Optional, Sequence
from markdown_it import __version__
from markdown_it.main import MarkdownIt
@@ -15,7 +17,7 @@
version_str = "markdown-it-py [version {}]".format(__version__)
-def main(args: Optional[Sequence[str]] = None) -> int:
+def main(args: Sequence[str] | None = None) -> int:
namespace = parse_args(args)
if namespace.filenames:
convert(namespace.filenames)
@@ -63,7 +65,7 @@ def interactive() -> None:
break
-def parse_args(args: Optional[Sequence[str]]) -> argparse.Namespace:
+def parse_args(args: Sequence[str] | None) -> argparse.Namespace:
"""Parse input CLI arguments."""
parser = argparse.ArgumentParser(
description="Parse one or more markdown files, "
diff --git a/markdown_it/common/normalize_url.py b/markdown_it/common/normalize_url.py
index d1ab85e3..4ecf2ef4 100644
--- a/markdown_it/common/normalize_url.py
+++ b/markdown_it/common/normalize_url.py
@@ -1,5 +1,7 @@
+from __future__ import annotations
+
+from collections.abc import Callable
import re
-from typing import Callable, Optional
from urllib.parse import urlparse, urlunparse, quote, unquote # noqa: F401
import mdurl
@@ -67,7 +69,7 @@ def normalizeLinkText(url: str) -> str:
GOOD_DATA_RE = re.compile(r"^data:image\/(gif|png|jpeg|webp);")
-def validateLink(url: str, validator: Optional[Callable] = None) -> bool:
+def validateLink(url: str, validator: Callable | None = None) -> bool:
"""Validate URL link is allowed in output.
This validator can prohibit more than really needed to prevent XSS.
diff --git a/markdown_it/main.py b/markdown_it/main.py
index e87a7a44..508b5ce4 100644
--- a/markdown_it/main.py
+++ b/markdown_it/main.py
@@ -1,16 +1,8 @@
+from __future__ import annotations
+
from contextlib import contextmanager
-from typing import (
- Any,
- Callable,
- Dict,
- Generator,
- Iterable,
- List,
- Mapping,
- MutableMapping,
- Optional,
- Union,
-)
+from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping
+from typing import Any
from . import helpers, presets # noqa F401
from .common import normalize_url, utils # noqa F401
@@ -40,10 +32,10 @@
class MarkdownIt:
def __init__(
self,
- config: Union[str, Mapping] = "commonmark",
- options_update: Optional[Mapping] = None,
+ config: str | Mapping = "commonmark",
+ options_update: Mapping | None = None,
*,
- renderer_cls: Callable[["MarkdownIt"], RendererProtocol] = RendererHTML,
+ renderer_cls: Callable[[MarkdownIt], RendererProtocol] = RendererHTML,
):
"""Main parser class
@@ -94,8 +86,8 @@ def set(self, options: MutableMapping) -> None:
self.options = OptionsDict(options)
def configure(
- self, presets: Union[str, Mapping], options_update: Optional[Mapping] = None
- ) -> "MarkdownIt":
+ self, presets: str | Mapping, options_update: Mapping | None = None
+ ) -> MarkdownIt:
"""Batch load of all options and component settings.
This is an internal method, and you probably will not need it.
But if you will - see available presets and data structure
@@ -131,7 +123,7 @@ def configure(
return self
- def get_all_rules(self) -> Dict[str, List[str]]:
+ def get_all_rules(self) -> dict[str, list[str]]:
"""Return the names of all active rules."""
rules = {
chain: self[chain].ruler.get_all_rules()
@@ -140,7 +132,7 @@ def get_all_rules(self) -> Dict[str, List[str]]:
rules["inline2"] = self.inline.ruler2.get_all_rules()
return rules
- def get_active_rules(self) -> Dict[str, List[str]]:
+ def get_active_rules(self) -> dict[str, list[str]]:
"""Return the names of all active rules."""
rules = {
chain: self[chain].ruler.get_active_rules()
@@ -150,8 +142,8 @@ def get_active_rules(self) -> Dict[str, List[str]]:
return rules
def enable(
- self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False
- ) -> "MarkdownIt":
+ self, names: str | Iterable[str], ignoreInvalid: bool = False
+ ) -> MarkdownIt:
"""Enable list or rules. (chainable)
:param names: rule name or list of rule names to enable.
@@ -182,8 +174,8 @@ def enable(
return self
def disable(
- self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False
- ) -> "MarkdownIt":
+ self, names: str | Iterable[str], ignoreInvalid: bool = False
+ ) -> MarkdownIt:
"""The same as [[MarkdownIt.enable]], but turn specified rules off. (chainable)
:param names: rule name or list of rule names to disable.
@@ -222,7 +214,7 @@ def add_render_rule(self, name: str, function: Callable, fmt: str = "html") -> N
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, *params, **options) -> MarkdownIt:
"""Load specified plugin with given params into current parser instance. (chainable)
It's just a sugar to call `plugin(md, params)` with curring.
@@ -237,7 +229,7 @@ def func(tokens, idx):
plugin(self, *params, **options)
return self
- def parse(self, src: str, env: Optional[MutableMapping] = None) -> List[Token]:
+ def parse(self, src: str, env: MutableMapping | None = None) -> list[Token]:
"""Parse the source string to a token stream
:param src: source string
@@ -260,7 +252,7 @@ def parse(self, src: str, env: Optional[MutableMapping] = None) -> List[Token]:
self.core.process(state)
return state.tokens
- def render(self, src: str, env: Optional[MutableMapping] = None) -> Any:
+ def render(self, src: str, env: MutableMapping | None = None) -> Any:
"""Render markdown string into html. It does all magic for you :).
:param src: source string
@@ -274,9 +266,7 @@ def render(self, src: str, env: Optional[MutableMapping] = 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: Optional[MutableMapping] = None
- ) -> List[Token]:
+ def parseInline(self, src: str, env: MutableMapping | None = None) -> list[Token]:
"""The same as [[MarkdownIt.parse]] but skip all block rules.
:param src: source string
@@ -296,7 +286,7 @@ def parseInline(
self.core.process(state)
return state.tokens
- def renderInline(self, src: str, env: Optional[MutableMapping] = None) -> Any:
+ def renderInline(self, src: str, env: MutableMapping | None = None) -> Any:
"""Similar to [[MarkdownIt.render]] but for single paragraph content.
:param src: source string
diff --git a/markdown_it/parser_block.py b/markdown_it/parser_block.py
index 8c0e8ab3..f5768058 100644
--- a/markdown_it/parser_block.py
+++ b/markdown_it/parser_block.py
@@ -1,6 +1,7 @@
"""Block-level tokenizer."""
+from __future__ import annotations
+
import logging
-from typing import List, Optional, Tuple
from .ruler import Ruler
from .token import Token
@@ -10,7 +11,7 @@
LOGGER = logging.getLogger(__name__)
-_rules: List[Tuple] = [
+_rules: list[tuple] = [
# First 2 params - rule name & source. Secondary array - list of rules,
# which can be terminated by this one.
("table", rules_block.table, ["paragraph", "reference"]),
@@ -97,9 +98,9 @@ def parse(
src: str,
md,
env,
- outTokens: List[Token],
- ords: Optional[Tuple[int, ...]] = None,
- ) -> Optional[List[Token]]:
+ outTokens: list[Token],
+ ords: tuple[int, ...] | None = None,
+ ) -> list[Token] | None:
"""Process input string and push block tokens into `outTokens`."""
if not src:
return None
diff --git a/markdown_it/parser_core.py b/markdown_it/parser_core.py
index 03982b5c..f0c3ad22 100644
--- a/markdown_it/parser_core.py
+++ b/markdown_it/parser_core.py
@@ -4,14 +4,14 @@
* Top-level rules executor. Glues block/inline parsers and does intermediate
* transformations.
"""
-from typing import List, Tuple
+from __future__ import annotations
from .ruler import Ruler, RuleFunc
from .rules_core.state_core import StateCore
from .rules_core import normalize, block, inline, replace, smartquotes, linkify
-_rules: List[Tuple[str, RuleFunc]] = [
+_rules: list[tuple[str, RuleFunc]] = [
("normalize", normalize),
("block", block),
("inline", inline),
diff --git a/markdown_it/parser_inline.py b/markdown_it/parser_inline.py
index 30fcff98..826665db 100644
--- a/markdown_it/parser_inline.py
+++ b/markdown_it/parser_inline.py
@@ -1,6 +1,6 @@
"""Tokenizes paragraph content.
"""
-from typing import List, Tuple
+from __future__ import annotations
from .ruler import Ruler, RuleFunc
from .token import Token
@@ -8,7 +8,7 @@
from . import rules_inline
# Parser rules
-_rules: List[Tuple[str, RuleFunc]] = [
+_rules: list[tuple[str, RuleFunc]] = [
("text", rules_inline.text),
("newline", rules_inline.newline),
("escape", rules_inline.escape),
@@ -22,7 +22,7 @@
("entity", rules_inline.entity),
]
-_rules2: List[Tuple[str, RuleFunc]] = [
+_rules2: list[tuple[str, RuleFunc]] = [
("balance_pairs", rules_inline.link_pairs),
("strikethrough", rules_inline.strikethrough.postProcess),
("emphasis", rules_inline.emphasis.postProcess),
@@ -114,7 +114,7 @@ def tokenize(self, state: StateInline) -> None:
if state.pending:
state.pushPending()
- def parse(self, src: str, md, env, tokens: List[Token]) -> List[Token]:
+ def parse(self, src: str, md, env, tokens: list[Token]) -> list[Token]:
"""Process input string and push inline tokens into `tokens`"""
state = StateInline(src, md, env, tokens)
self.tokenize(state)
diff --git a/markdown_it/renderer.py b/markdown_it/renderer.py
index 9e9c7751..88ee36fe 100644
--- a/markdown_it/renderer.py
+++ b/markdown_it/renderer.py
@@ -5,14 +5,11 @@ class Renderer
copy of rules. Those can be rewritten with ease. Also, you can add new
rules if you create plugin and adds new token types.
"""
+from __future__ import annotations
+
+from collections.abc import MutableMapping, Sequence
import inspect
-from typing import (
- Any,
- ClassVar,
- MutableMapping,
- Optional,
- Sequence,
-)
+from typing import Any, ClassVar
from .common.utils import unescapeAll, escapeHtml
from .token import Token
@@ -191,7 +188,7 @@ def renderAttrs(token: Token) -> str:
def renderInlineAsText(
self,
- tokens: Optional[Sequence[Token]],
+ tokens: Sequence[Token] | None,
options: OptionsDict,
env: MutableMapping,
) -> str:
diff --git a/markdown_it/ruler.py b/markdown_it/ruler.py
index 997c95dc..6975d2be 100644
--- a/markdown_it/ruler.py
+++ b/markdown_it/ruler.py
@@ -15,17 +15,10 @@ class Ruler
rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and
[[MarkdownIt.use]].
"""
-from typing import (
- Callable,
- Dict,
- Iterable,
- List,
- MutableMapping,
- Optional,
- Tuple,
- TYPE_CHECKING,
- Union,
-)
+from __future__ import annotations
+
+from collections.abc import Callable, Iterable, MutableMapping
+from typing import TYPE_CHECKING
import attr
if TYPE_CHECKING:
@@ -33,9 +26,9 @@ class Ruler
class StateBase:
- srcCharCode: Tuple[int, ...]
+ srcCharCode: tuple[int, ...]
- def __init__(self, src: str, md: "MarkdownIt", env: MutableMapping):
+ def __init__(self, src: str, md: MarkdownIt, env: MutableMapping):
self.src = src
self.env = env
self.md = md
@@ -62,17 +55,17 @@ class Rule:
name: str = attr.ib()
enabled: bool = attr.ib()
fn: RuleFunc = attr.ib(repr=False)
- alt: List[str] = attr.ib()
+ alt: list[str] = attr.ib()
class Ruler:
def __init__(self):
# List of added rules.
- self.__rules__: List[Rule] = []
+ self.__rules__: list[Rule] = []
# Cached rule chains.
# First level - chain name, '' for default.
# Second level - diginal anchor for fast filtering by charcodes.
- self.__cache__: Optional[Dict[str, List[RuleFunc]]] = None
+ self.__cache__: dict[str, list[RuleFunc]] | None = None
def __find__(self, name: str) -> int:
"""Find rule index by name"""
@@ -161,7 +154,7 @@ def push(self, ruleName: str, fn: RuleFunc, options=None):
self.__rules__.append(Rule(ruleName, True, fn, (options or {}).get("alt", [])))
self.__cache__ = None
- def enable(self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False):
+ def enable(self, names: str | Iterable[str], ignoreInvalid: bool = False):
"""Enable rules with given names.
:param names: name or list of rule names to enable.
@@ -183,7 +176,7 @@ def enable(self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False):
self.__cache__ = None
return result
- def enableOnly(self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False):
+ def enableOnly(self, names: str | Iterable[str], ignoreInvalid: bool = False):
"""Enable rules with given names, and disable everything else.
:param names: name or list of rule names to enable.
@@ -197,7 +190,7 @@ def enableOnly(self, names: Union[str, Iterable[str]], ignoreInvalid: bool = Fal
rule.enabled = False
self.enable(names, ignoreInvalid)
- def disable(self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False):
+ def disable(self, names: str | Iterable[str], ignoreInvalid: bool = False):
"""Disable rules with given names.
:param names: name or list of rule names to enable.
@@ -219,7 +212,7 @@ def disable(self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False)
self.__cache__ = None
return result
- def getRules(self, chainName: str) -> List[RuleFunc]:
+ def getRules(self, chainName: str) -> list[RuleFunc]:
"""Return array of active functions (rules) for given chain name.
It analyzes rules configuration, compiles caches if not exists and returns result.
@@ -233,10 +226,10 @@ def getRules(self, chainName: str) -> List[RuleFunc]:
# Chain can be empty, if rules disabled. But we still have to return Array.
return self.__cache__.get(chainName, []) or []
- def get_all_rules(self) -> List[str]:
+ def get_all_rules(self) -> list[str]:
"""Return all available rule names."""
return [r.name for r in self.__rules__]
- def get_active_rules(self) -> List[str]:
+ def get_active_rules(self) -> list[str]:
"""Return the active rule names."""
return [r.name for r in self.__rules__ if r.enabled]
diff --git a/markdown_it/rules_block/blockquote.py b/markdown_it/rules_block/blockquote.py
index 543c1f9a..52616167 100644
--- a/markdown_it/rules_block/blockquote.py
+++ b/markdown_it/rules_block/blockquote.py
@@ -1,6 +1,7 @@
# Block quotes
+from __future__ import annotations
+
import logging
-from typing import Optional
from .state_block import StateBlock
from ..common.utils import isSpace
@@ -36,7 +37,7 @@ def blockquote(state: StateBlock, startLine: int, endLine: int, silent: bool):
initial = offset = state.sCount[startLine] + 1
try:
- second_char_code: Optional[int] = state.srcCharCode[pos]
+ second_char_code: int | None = state.srcCharCode[pos]
except IndexError:
second_char_code = None
@@ -155,7 +156,7 @@ def blockquote(state: StateBlock, startLine: int, endLine: int, silent: bool):
initial = offset = state.sCount[nextLine] + 1
try:
- next_char: Optional[int] = state.srcCharCode[pos]
+ next_char: int | None = state.srcCharCode[pos]
except IndexError:
next_char = None
diff --git a/markdown_it/rules_block/heading.py b/markdown_it/rules_block/heading.py
index 353520a3..3ccc3059 100644
--- a/markdown_it/rules_block/heading.py
+++ b/markdown_it/rules_block/heading.py
@@ -1,6 +1,7 @@
""" Atex heading (#, ##, ...) """
+from __future__ import annotations
+
import logging
-from typing import Optional
from .state_block import StateBlock
from ..common.utils import isSpace
@@ -19,7 +20,7 @@ def heading(state: StateBlock, startLine: int, endLine: int, silent: bool):
if state.sCount[startLine] - state.blkIndent >= 4:
return False
- ch: Optional[int] = state.srcCharCode[pos]
+ ch: int | None = state.srcCharCode[pos]
# /* # */
if ch != 0x23 or pos >= maximum:
diff --git a/markdown_it/rules_block/html_block.py b/markdown_it/rules_block/html_block.py
index 3bb850e5..335d7cd0 100644
--- a/markdown_it/rules_block/html_block.py
+++ b/markdown_it/rules_block/html_block.py
@@ -1,7 +1,8 @@
# HTML block
+from __future__ import annotations
+
import logging
import re
-from typing import List, Tuple, Pattern
from .state_block import StateBlock
from ..common.html_blocks import block_names
@@ -11,7 +12,7 @@
# An array of opening and corresponding closing sequences for html tags,
# last argument defines whether it can terminate a paragraph or not
-HTML_SEQUENCES: List[Tuple[Pattern, Pattern, bool]] = [
+HTML_SEQUENCES: list[tuple[re.Pattern, re.Pattern, bool]] = [
(
re.compile(r"^<(script|pre|style|textarea)(?=(\s|>|$))", re.IGNORECASE),
re.compile(r"<\/(script|pre|style|textarea)>", re.IGNORECASE),
diff --git a/markdown_it/rules_block/state_block.py b/markdown_it/rules_block/state_block.py
index 69ad0c4d..b2c71892 100644
--- a/markdown_it/rules_block/state_block.py
+++ b/markdown_it/rules_block/state_block.py
@@ -1,4 +1,6 @@
-from typing import List, Optional, Tuple, TYPE_CHECKING
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
from ..token import Token
from ..ruler import StateBase
@@ -12,10 +14,10 @@ class StateBlock(StateBase):
def __init__(
self,
src: str,
- md: "MarkdownIt",
+ md: MarkdownIt,
env,
- tokens: List[Token],
- srcCharCode: Optional[Tuple[int, ...]] = None,
+ tokens: list[Token],
+ srcCharCode: tuple[int, ...] | None = None,
):
if srcCharCode is not None:
diff --git a/markdown_it/rules_core/replacements.py b/markdown_it/rules_core/replacements.py
index ee3b9edb..bced7026 100644
--- a/markdown_it/rules_core/replacements.py
+++ b/markdown_it/rules_core/replacements.py
@@ -14,9 +14,10 @@
* ``--`` → &ndash
* ``---`` → &mdash
"""
+from __future__ import annotations
+
import logging
import re
-from typing import List, Match
from .state_core import StateCore
from ..token import Token
@@ -55,11 +56,11 @@
SCOPED_ABBR = {"c": "©", "r": "®", "p": "§", "tm": "™"}
-def replaceFn(match: Match[str]):
+def replaceFn(match: re.Match[str]):
return SCOPED_ABBR[match.group(1).lower()]
-def replace_scoped(inlineTokens: List[Token]) -> None:
+def replace_scoped(inlineTokens: list[Token]) -> None:
inside_autolink = 0
for token in inlineTokens:
@@ -73,7 +74,7 @@ def replace_scoped(inlineTokens: List[Token]) -> None:
inside_autolink += 1
-def replace_rare(inlineTokens: List[Token]) -> None:
+def replace_rare(inlineTokens: list[Token]) -> None:
inside_autolink = 0
for token in inlineTokens:
diff --git a/markdown_it/rules_core/smartquotes.py b/markdown_it/rules_core/smartquotes.py
index c3211191..7c297269 100644
--- a/markdown_it/rules_core/smartquotes.py
+++ b/markdown_it/rules_core/smartquotes.py
@@ -1,7 +1,9 @@
"""Convert straight quotation marks to typographic ones
"""
+from __future__ import annotations
+
import re
-from typing import Any, Dict, List
+from typing import Any
from .state_core import StateCore
from ..common.utils import charCodeAt
@@ -21,8 +23,8 @@ def replaceAt(string: str, index: int, ch: str) -> str:
return string[:index] + ch + string[index + 1 :]
-def process_inlines(tokens: List[Token], state: StateCore) -> None:
- stack: List[Dict[str, Any]] = []
+def process_inlines(tokens: list[Token], state: StateCore) -> None:
+ stack: list[dict[str, Any]] = []
for i in range(len(tokens)):
token = tokens[i]
diff --git a/markdown_it/rules_core/state_core.py b/markdown_it/rules_core/state_core.py
index a560a283..3521df2f 100644
--- a/markdown_it/rules_core/state_core.py
+++ b/markdown_it/rules_core/state_core.py
@@ -1,4 +1,7 @@
-from typing import List, MutableMapping, Optional, TYPE_CHECKING
+from __future__ import annotations
+
+from collections.abc import MutableMapping
+from typing import TYPE_CHECKING
from ..token import Token
from ..ruler import StateBase
@@ -11,12 +14,12 @@ class StateCore(StateBase):
def __init__(
self,
src: str,
- md: "MarkdownIt",
+ md: MarkdownIt,
env: MutableMapping,
- tokens: Optional[List[Token]] = None,
+ tokens: list[Token] | None = None,
):
self.src = src
self.md = md # link to parser instance
self.env = env
- self.tokens: List[Token] = tokens or []
+ self.tokens: list[Token] = tokens or []
self.inlineMode = False
diff --git a/markdown_it/rules_inline/image.py b/markdown_it/rules_inline/image.py
index d3813f77..eb3824b1 100644
--- a/markdown_it/rules_inline/image.py
+++ b/markdown_it/rules_inline/image.py
@@ -1,6 +1,5 @@
# Process 
+[Contact](mailto: mail@mail.com)
+. From 7748e1308c4d8b5a3ad8c147e424faa10eed6320 Mon Sep 17 00:00:00 2001 From: Kian-Meng AngNote that rules #1 and #2 only apply to two cases: (a) cases
in which the lines to be included in a list item begin with a
-characer other than a space or tab, and (b) cases in which
+character other than a space or tab, and (b) cases in which
they begin with an indented code
block. In a case like the following, where the first block begins with
three spaces of indentation, the rules do not allow us to form a list item by
diff --git a/tests/test_port/fixtures/fatal.md b/tests/test_port/fixtures/fatal.md
index dfeeb2e7..7b2afcfc 100644
--- a/tests/test_port/fixtures/fatal.md
+++ b/tests/test_port/fixtures/fatal.md
@@ -1,4 +1,4 @@
-Should not throw exception on invalid chars in URL (`*` not allowed in path) [mailformed URI]
+Should not throw exception on invalid chars in URL (`*` not allowed in path) [malformed URI]
.
[foo](<%test>)
.
@@ -6,7 +6,7 @@ Should not throw exception on invalid chars in URL (`*` not allowed in path) [ma
.
-Should not throw exception on broken utf-8 sequence in URL [mailformed URI]
+Should not throw exception on broken utf-8 sequence in URL [malformed URI]
.
[foo](%C3)
.
@@ -14,7 +14,7 @@ Should not throw exception on broken utf-8 sequence in URL [mailformed URI]
.
-Should not throw exception on broken utf-16 surrogates sequence in URL [mailformed URI]
+Should not throw exception on broken utf-16 surrogates sequence in URL [malformed URI]
.
[foo]()
.
From 4e5be6639c0fd2a3fa8f4e2619e350a88ffb1e2f Mon Sep 17 00:00:00 2001
From: Chris Sewell [Contact](http:// mail.com) [Contact](mailto: mail@mail.com) QUOTE INDENTED QUOTE
+
+
+
+.
From 145a484a94e3f1860f4feecf3a3fb706273dc50d Mon Sep 17 00:00:00 2001
From: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com>
Date: Sat, 16 Apr 2022 16:48:02 +0300
Subject: [PATCH 15/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20REFACTOR:=20=5F=5Fsl?=
=?UTF-8?q?ots=5F=5F=20for=20dataclasses=20(#214)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
markdown_it/_compat.py | 10 ++++++++++
markdown_it/ruler.py | 4 +++-
markdown_it/rules_inline/state_inline.py | 3 ++-
markdown_it/token.py | 4 +++-
4 files changed, 18 insertions(+), 3 deletions(-)
create mode 100644 markdown_it/_compat.py
diff --git a/markdown_it/_compat.py b/markdown_it/_compat.py
new file mode 100644
index 00000000..12df1aa6
--- /dev/null
+++ b/markdown_it/_compat.py
@@ -0,0 +1,10 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+import sys
+from typing import Any
+
+if sys.version_info >= (3, 10):
+ DATACLASS_KWARGS: Mapping[str, Any] = {"slots": True}
+else:
+ DATACLASS_KWARGS: Mapping[str, Any] = {}
diff --git a/markdown_it/ruler.py b/markdown_it/ruler.py
index b46bac69..11b937a0 100644
--- a/markdown_it/ruler.py
+++ b/markdown_it/ruler.py
@@ -21,6 +21,8 @@ class Ruler
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
+from markdown_it._compat import DATACLASS_KWARGS
+
if TYPE_CHECKING:
from markdown_it import MarkdownIt
@@ -50,7 +52,7 @@ def src(self, value: str) -> None:
RuleFunc = Callable
-@dataclass()
+@dataclass(**DATACLASS_KWARGS)
class Rule:
name: str
enabled: bool
diff --git a/markdown_it/rules_inline/state_inline.py b/markdown_it/rules_inline/state_inline.py
index 442dac81..283532cc 100644
--- a/markdown_it/rules_inline/state_inline.py
+++ b/markdown_it/rules_inline/state_inline.py
@@ -5,6 +5,7 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING
+from .._compat import DATACLASS_KWARGS
from ..common.utils import isMdAsciiPunct, isPunctChar, isWhiteSpace
from ..ruler import StateBase
from ..token import Token
@@ -13,7 +14,7 @@
from markdown_it import MarkdownIt
-@dataclass()
+@dataclass(**DATACLASS_KWARGS)
class Delimiter:
# Char code of the starting marker (number).
marker: int
diff --git a/markdown_it/token.py b/markdown_it/token.py
index b3c24728..b20875b6 100644
--- a/markdown_it/token.py
+++ b/markdown_it/token.py
@@ -5,6 +5,8 @@
from typing import Any
import warnings
+from markdown_it._compat import DATACLASS_KWARGS
+
def convert_attrs(value: Any) -> Any:
"""Convert Token.attrs set as ``None`` or ``[[key, value], ...]`` to a dict.
@@ -18,7 +20,7 @@ def convert_attrs(value: Any) -> Any:
return value
-@dc.dataclass()
+@dc.dataclass(**DATACLASS_KWARGS)
class Token:
type: str
From 7e677c4e7b4573eaf406a13882f3fee4b19b97f4 Mon Sep 17 00:00:00 2001
From: Chris Sewell
+
+