From 1ab58c1c4438396fb14b74f036b3e1ddba86e3f7 Mon Sep 17 00:00:00 2001 From: Jeff-Lowrey Date: Thu, 8 Jan 2026 12:20:55 -0600 Subject: [PATCH 1/2] fix/type-annotations Add cast() and mypy to pyproject.toml --- marko/element.py | 4 +- marko/helpers.py | 4 +- pdm.lock | 98 ++---------------------------------------------- pyproject.toml | 18 ++++++--- 4 files changed, 20 insertions(+), 104 deletions(-) diff --git a/marko/element.py b/marko/element.py index c8a8bde..56b3d50 100644 --- a/marko/element.py +++ b/marko/element.py @@ -1,3 +1,5 @@ +from typing import cast + from .helpers import camel_to_snake_case @@ -41,4 +43,4 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__}{children}>" else: - return objstr(self, honor_existing=False, include=["children"]) + return cast(str, objstr(self, honor_existing=False, include=["children"])) diff --git a/marko/helpers.py b/marko/helpers.py index ff3041a..73c8bf4 100644 --- a/marko/helpers.py +++ b/marko/helpers.py @@ -8,7 +8,7 @@ import re from functools import partial from importlib import import_module -from typing import TYPE_CHECKING, overload +from typing import TYPE_CHECKING, cast, overload from marko.renderer import Renderer @@ -129,7 +129,7 @@ def load_extension(name: str, **kwargs: Any) -> MarkoExtension: raise ImportError(f"Extension {name} cannot be imported") from e try: - return module.make_extension(**kwargs) + return cast(MarkoExtension, module.make_extension(**kwargs)) except AttributeError: raise AttributeError( f"Module {name} does not have 'make_extension' attributte." diff --git a/pdm.lock b/pdm.lock index e528128..758c9b9 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,10 +5,10 @@ groups = ["default", "benchmark", "codehilite", "dev", "doc", "repr", "toc"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:883754358bef3df17a052b7a3ba528dd9e8a462b9a46c110cae5e59f60d6f2c1" +content_hash = "sha256:7308b41853c77ad2ddd1a8b624e4217ca4ea7ac0586860893e174999a1c0a72f" [[metadata.targets]] -requires_python = ">=3.9" +requires_python = "==3.13.*" [[package]] name = "alabaster" @@ -397,18 +397,6 @@ files = [ {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, ] -[[package]] -name = "exceptiongroup" -version = "1.0.4" -requires_python = ">=3.7" -summary = "Backport of PEP 654 (exception groups)" -groups = ["dev"] -marker = "python_version < \"3.11\"" -files = [ - {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, - {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, -] - [[package]] name = "idna" version = "3.4" @@ -431,22 +419,6 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] -[[package]] -name = "importlib-metadata" -version = "8.7.0" -requires_python = ">=3.9" -summary = "Read metadata from Python packages" -groups = ["benchmark", "doc"] -marker = "python_version < \"3.10\"" -dependencies = [ - "typing-extensions>=3.6.4; python_version < \"3.8\"", - "zipp>=3.20", -] -files = [ - {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, - {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, -] - [[package]] name = "iniconfig" version = "1.1.1" @@ -1015,64 +987,12 @@ files = [ {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, ] -[[package]] -name = "tomli" -version = "2.3.0" -requires_python = ">=3.8" -summary = "A lil' TOML parser" -groups = ["dev", "doc"] -marker = "python_version < \"3.11\"" -files = [ - {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, - {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, - {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, - {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, - {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, - {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, - {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, - {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, - {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, - {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, - {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, - {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, - {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, - {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, -] - [[package]] name = "typing-extensions" version = "4.12.2" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" -groups = ["benchmark", "dev"] +groups = ["dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1088,15 +1008,3 @@ files = [ {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, ] - -[[package]] -name = "zipp" -version = "3.23.0" -requires_python = ">=3.9" -summary = "Backport of pathlib-compatible object wrapper for zip files" -groups = ["benchmark", "doc"] -marker = "python_version < \"3.10\"" -files = [ - {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, - {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, -] diff --git a/pyproject.toml b/pyproject.toml index 07d038b..c2d051b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,18 +1,15 @@ -[build-system] -requires = ["pdm-backend"] -build-backend = "pdm.backend" - [project] # PEP 621 project metadata # See https://www.python.org/dev/peps/pep-0621/ authors = [ {name = "Frost Ming", email = "mianghong@gmail.com"}, + {name = "Jeff-Lowrey", email = "jeff@jaral.org"}, ] dynamic = ["version"] -requires-python = ">=3.9" +requires-python = "==3.13.*" license = {text = "MIT"} dependencies = [] -description = "A markdown parser with high extensibility." +description = "Default template for PDM package" name = "marko" readme = "README.md" classifiers = [ @@ -27,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] +version = "0.1.0" [project.urls] homepage = "https://github.com/frostming/marko" @@ -44,6 +42,7 @@ marko = "marko.cli:main" plugins = [ "pdm-autoexport", ] +distribution = false [tool.pdm.version] source = "file" @@ -75,3 +74,10 @@ without-hashes = true [tool.isort] profile = "black" + +[tool.mypy] +python_version = "3.13" +mypy_path = "." +warn_return_any = true +warn_unused_configs = true +ignore_missing_imports = true From 8710d9500faec1181f0a47cfb2cc854fbdc7534d Mon Sep 17 00:00:00 2001 From: Jeff-Lowrey Date: Thu, 8 Jan 2026 12:26:47 -0600 Subject: [PATCH 2/2] Added type stubs --- marko/__init__.pyi | 29 ++++++ marko/ast_renderer.pyi | 32 +++++++ marko/block.pyi | 190 ++++++++++++++++++++++++++++++++++++++++ marko/element.pyi | 7 ++ marko/helpers.pyi | 50 +++++++++++ marko/html_renderer.pyi | 38 ++++++++ marko/inline.pyi | 89 +++++++++++++++++++ marko/inline_parser.pyi | 100 +++++++++++++++++++++ marko/md_renderer.pyi | 40 +++++++++ marko/parser.pyi | 15 ++++ marko/renderer.pyi | 18 ++++ marko/source.pyi | 37 ++++++++ 12 files changed, 645 insertions(+) create mode 100644 marko/__init__.pyi create mode 100644 marko/ast_renderer.pyi create mode 100644 marko/block.pyi create mode 100644 marko/element.pyi create mode 100644 marko/helpers.pyi create mode 100644 marko/html_renderer.pyi create mode 100644 marko/inline.pyi create mode 100644 marko/inline_parser.pyi create mode 100644 marko/md_renderer.pyi create mode 100644 marko/parser.pyi create mode 100644 marko/renderer.pyi create mode 100644 marko/source.pyi diff --git a/marko/__init__.pyi b/marko/__init__.pyi new file mode 100644 index 0000000..f855fd4 --- /dev/null +++ b/marko/__init__.pyi @@ -0,0 +1,29 @@ +from typing import Iterable + +from .block import Document +from .helpers import MarkoExtension as MarkoExtension +from .helpers import load_extension as load_extension +from .html_renderer import HTMLRenderer as HTMLRenderer +from .parser import Parser as Parser +from .renderer import Renderer as Renderer + +__all__: list[str] + +class SetupDone(Exception): ... + +class Markdown: + def __init__( + self, + parser: type[Parser] = ..., + renderer: type[Renderer] = ..., + extensions: Iterable[str | MarkoExtension] | None = None, + ) -> None: ... + def use(self, *extensions: str | MarkoExtension) -> None: ... + def convert(self, text: str) -> str: ... + def __call__(self, text: str) -> str: ... + def parse(self, text: str) -> Document: ... + def render(self, parsed: Document) -> str: ... + +def convert(text: str) -> str: ... +def parse(text: str) -> Document: ... +def render(parsed: Document) -> str: ... diff --git a/marko/ast_renderer.pyi b/marko/ast_renderer.pyi new file mode 100644 index 0000000..50c3198 --- /dev/null +++ b/marko/ast_renderer.pyi @@ -0,0 +1,32 @@ +from typing import Any, overload + +from marko import inline +from marko.element import Element + +from .renderer import Renderer, force_delegate + +class ASTRenderer(Renderer): + delegate: bool + + # Narrowed return type for dict-returning renderer + def render(self, element: Element) -> dict[str, Any]: ... + + @force_delegate + def render_raw_text(self, element: inline.RawText) -> dict[str, Any]: ... + @overload + def render_children(self, element: list[Element]) -> list[dict[str, Any]]: ... + @overload + def render_children(self, element: Element) -> dict[str, Any]: ... + @overload + def render_children(self, element: str) -> str: ... + +class XMLRenderer(Renderer): + delegate: bool + indent: int + + # Narrowed return types for str-returning renderer + def render(self, element: Element) -> str: ... + def render_children(self, element: Element) -> str: ... + + def __enter__(self) -> XMLRenderer: ... + def __exit__(self, *args: Any) -> None: ... diff --git a/marko/block.pyi b/marko/block.pyi new file mode 100644 index 0000000..37e6610 --- /dev/null +++ b/marko/block.pyi @@ -0,0 +1,190 @@ +from typing import Any, Match, NamedTuple, Sequence + +from . import inline_parser +from .element import Element +from .source import Source + +__all__ = [ + "Document", + "CodeBlock", + "Heading", + "List", + "ListItem", + "BlankLine", + "Quote", + "FencedCode", + "ThematicBreak", + "HTMLBlock", + "LinkRefDef", + "SetextHeading", + "Paragraph", +] + +class BlockElement(Element): + children: Sequence[Element] + priority: int + virtual: bool + inline_body: str + override: bool + @classmethod + def match(cls, source: Source) -> Any: ... + @classmethod + def parse(cls, source: Source) -> Any: ... + def __lt__(self, o: BlockElement) -> bool: ... + +class Document(BlockElement): + virtual: bool + children: list[Element] + link_ref_defs: dict[str, tuple[str, str]] + def __init__(self) -> None: ... + +class BlankLine(BlockElement): + priority: int + def __init__(self, start: int) -> None: ... + @classmethod + def match(cls, source: Source) -> bool: ... + @classmethod + def parse(cls, source: Source) -> int: ... + +class Heading(BlockElement): + priority: int + pattern: Any + level: int + inline_body: str + def __init__(self, match: Match[str]) -> None: ... + @classmethod + def match(cls, source: Source) -> Match[str] | None: ... + @classmethod + def parse(cls, source: Source) -> Match[str] | None: ... + +class SetextHeading(BlockElement): + virtual: bool + level: int + inline_body: str + def __init__(self, lines: list[str]) -> None: ... + +class CodeBlock(BlockElement): + priority: int + children: list[Element] + lang: str + extra: str + def __init__(self, lines: str) -> None: ... + @classmethod + def match(cls, source: Source) -> str: ... + @classmethod + def parse(cls, source: Source) -> str: ... + @staticmethod + def strip_prefix(line: str, prefix: str) -> str: ... + +class FencedCode(BlockElement): + priority: int + pattern: Any + + class ParseInfo(NamedTuple): + prefix: str + leading: str + lang: str + extra: str + + lang: str + extra: str + children: list[Element] + def __init__(self, match: tuple[str, str, str]) -> None: ... + @classmethod + def match(cls, source: Source) -> Match[str] | None: ... + @classmethod + def parse(cls, source: Source) -> tuple[str, str, str]: ... + +class ThematicBreak(BlockElement): + priority: int + pattern: Any + @classmethod + def match(cls, source: Source) -> bool: ... + @classmethod + def parse(cls, source: Source) -> ThematicBreak: ... + +class HTMLBlock(BlockElement): + priority: int + body: str + def __init__(self, lines: str) -> None: ... + @classmethod + def match(cls, source: Source) -> int | bool: ... + @classmethod + def parse(cls, source: Source) -> str: ... + +class Paragraph(BlockElement): + priority: int + pattern: Any + inline_body: str + def __init__(self, lines: list[str]) -> None: ... + @classmethod + def match(cls, source: Source) -> bool: ... + @staticmethod + def is_setext_heading(line: str) -> bool: ... + @classmethod + def break_paragraph(cls, source: Source, lazy: bool = False) -> bool: ... + @classmethod + def parse(cls, source: Source) -> list[str] | SetextHeading: ... + +class Quote(BlockElement): + priority: int + @classmethod + def match(cls, source: Source) -> Match[str] | None: ... + @classmethod + def parse(cls, source: Source) -> Quote: ... + +class List(BlockElement): + priority: int + pattern: Any + + class ParseInfo(NamedTuple): + bullet: str + ordered: bool + start: int + + tight: bool + ordered: bool + start: int + bullet: str + def __init__(self, info: ParseInfo) -> None: ... + @classmethod + def match(cls, source: Source) -> bool: ... + @classmethod + def parse(cls, source: Source) -> List: ... + +class ListItem(BlockElement): + virtual: bool + pattern: Any + + class ParseInfo(NamedTuple): + indent: int + bullet: str + mid: int + + def __init__(self, info: ParseInfo) -> None: ... + @classmethod + def parse_leading( + cls, line: str, prefix_pos: int + ) -> tuple[int, str, int, str]: ... + @classmethod + def match(cls, source: Source) -> bool: ... + @classmethod + def parse(cls, source: Source) -> ListItem: ... + +class LinkRefDef(BlockElement): + pattern: Any + + class ParseInfo(NamedTuple): + link_label: inline_parser.Group + link_dest: inline_parser.Group + link_title: inline_parser.Group + end: int + + label: str + dest: str + title: str | None + def __init__(self, label: str, text: str, title: str | None = None) -> None: ... + @classmethod + def match(cls, source: Source) -> bool: ... + @classmethod + def parse(cls, source: Source) -> LinkRefDef: ... diff --git a/marko/element.pyi b/marko/element.pyi new file mode 100644 index 0000000..b26c1fa --- /dev/null +++ b/marko/element.pyi @@ -0,0 +1,7 @@ +from .helpers import camel_to_snake_case as camel_to_snake_case + +class Element: + override: bool + @classmethod + def get_type(cls, snake_case: bool = False) -> str: ... + def __repr__(self) -> str: ... diff --git a/marko/helpers.pyi b/marko/helpers.pyi new file mode 100644 index 0000000..1d9a4b1 --- /dev/null +++ b/marko/helpers.pyi @@ -0,0 +1,50 @@ +import dataclasses +from typing import Any, Callable, Container, Iterable, TypeVar, overload + +from .element import Element +from .renderer import Renderer + +RendererFunc = Callable[[Any, Element], Any] +TRenderer = TypeVar("TRenderer", bound=RendererFunc) +D = TypeVar("D", bound="_RendererDispatcher") + +def camel_to_snake_case(name: str) -> str: ... +def is_paired(text: Iterable[str], open: str = "(", close: str = ")") -> bool: ... +def normalize_label(label: str) -> str: ... +def find_next( + text: str, + target: Container[str], + start: int = 0, + end: int | None = None, + disallowed: Container[str] = (), +) -> int: ... +def partition_by_spaces(text: str, spaces: str = " \t") -> tuple[str, str, str]: ... + +@dataclasses.dataclass(frozen=True) +class MarkoExtension: + parser_mixins: list[type] = dataclasses.field(default_factory=list) + renderer_mixins: list[type] = dataclasses.field(default_factory=list) + elements: list[type[Element]] = dataclasses.field(default_factory=list) + +def load_extension(name: str, **kwargs: Any) -> MarkoExtension: ... + +class _RendererDispatcher: + name: str + def __init__( + self, types: type[Renderer] | tuple[type[Renderer], ...], func: RendererFunc + ) -> None: ... + def dispatch( + self: D, types: type[Renderer] | tuple[type[Renderer], ...] + ) -> Callable[[RendererFunc], D]: ... + def __set_name__(self, owner: type, name: str) -> None: ... + @staticmethod + def render_ast(self: Any, element: Element) -> Any: ... + def super_render(self, r: Any, element: Element) -> Any: ... + @overload + def __get__(self: D, obj: None, owner: type) -> D: ... + @overload + def __get__(self: D, obj: Renderer, owner: type) -> RendererFunc: ... + +def render_dispatch( + types: type[Renderer] | tuple[type[Renderer], ...], +) -> Callable[[RendererFunc], _RendererDispatcher]: ... diff --git a/marko/html_renderer.pyi b/marko/html_renderer.pyi new file mode 100644 index 0000000..3657ac9 --- /dev/null +++ b/marko/html_renderer.pyi @@ -0,0 +1,38 @@ +from typing import Any + +from . import block, inline +from .element import Element +from .renderer import Renderer + +class HTMLRenderer(Renderer): + # Narrowed return types for str-returning renderer + def render(self, element: Element) -> str: ... + def render_children(self, element: Any) -> str: ... + + def render_paragraph(self, element: block.Paragraph) -> str: ... + def render_list(self, element: block.List) -> str: ... + def render_list_item(self, element: block.ListItem) -> str: ... + def render_quote(self, element: block.Quote) -> str: ... + def render_fenced_code(self, element: block.FencedCode) -> str: ... + def render_code_block(self, element: block.CodeBlock) -> str: ... + def render_html_block(self, element: block.HTMLBlock) -> str: ... + def render_thematic_break(self, element: block.ThematicBreak) -> str: ... + def render_heading(self, element: block.Heading) -> str: ... + def render_setext_heading(self, element: block.SetextHeading) -> str: ... + def render_blank_line(self, element: block.BlankLine) -> str: ... + def render_link_ref_def(self, element: block.LinkRefDef) -> str: ... + def render_emphasis(self, element: inline.Emphasis) -> str: ... + def render_strong_emphasis(self, element: inline.StrongEmphasis) -> str: ... + def render_inline_html(self, element: inline.InlineHTML) -> str: ... + def render_plain_text(self, element: Any) -> str: ... + def render_link(self, element: inline.Link) -> str: ... + def render_auto_link(self, element: inline.AutoLink) -> str: ... + def render_image(self, element: inline.Image) -> str: ... + def render_literal(self, element: inline.Literal) -> str: ... + def render_raw_text(self, element: inline.RawText) -> str: ... + def render_line_break(self, element: inline.LineBreak) -> str: ... + def render_code_span(self, element: inline.CodeSpan) -> str: ... + @staticmethod + def escape_html(raw: str) -> str: ... + @staticmethod + def escape_url(raw: str) -> str: ... diff --git a/marko/inline.pyi b/marko/inline.pyi new file mode 100644 index 0000000..8ad8312 --- /dev/null +++ b/marko/inline.pyi @@ -0,0 +1,89 @@ +from typing import Iterator, Pattern, Sequence + +from .element import Element +from .inline_parser import _Match +from .source import Source + +__all__ = [ + "LineBreak", + "Literal", + "InlineHTML", + "CodeSpan", + "Emphasis", + "StrongEmphasis", + "Link", + "Image", + "AutoLink", + "RawText", +] + +class InlineElement(Element): + priority: int + pattern: Pattern[str] | str + parse_children: bool + parse_group: int + virtual: bool + override: bool + children: str | Sequence[Element] + def __init__(self, match: _Match) -> None: ... + @classmethod + def find(cls, text: str, *, source: Source) -> Iterator[_Match]: ... + +class Literal(InlineElement): + priority: int + pattern: Pattern[str] + @classmethod + def strip_backslash(cls, text: str) -> str: ... + +class LineBreak(InlineElement): + priority: int + pattern: str + soft: bool + children: str + def __init__(self, match: _Match) -> None: ... + +class InlineHTML(InlineElement): + priority: int + pattern: Pattern[str] + +class StrongEmphasis(InlineElement): + virtual: bool + parse_children: bool + +class Emphasis(InlineElement): + virtual: bool + parse_children: bool + +class Link(InlineElement): + virtual: bool + parse_children: bool + dest: str + title: str | None + def __init__(self, match: _Match) -> None: ... + +class Image(InlineElement): + virtual: bool + parse_children: bool + dest: str + title: str | None + def __init__(self, match: _Match) -> None: ... + +class CodeSpan(InlineElement): + priority: int + pattern: Pattern[str] + children: str + def __init__(self, match: _Match) -> None: ... + +class AutoLink(InlineElement): + priority: int + pattern: Pattern[str] + dest: str + children: str + title: str + def __init__(self, match: _Match) -> None: ... + +class RawText(InlineElement): + virtual: bool + children: str + escape: bool + def __init__(self, match: str, escape: bool = True) -> None: ... diff --git a/marko/inline_parser.pyi b/marko/inline_parser.pyi new file mode 100644 index 0000000..afc411c --- /dev/null +++ b/marko/inline_parser.pyi @@ -0,0 +1,100 @@ +from typing import Any, NamedTuple, Pattern + +from . import patterns as patterns +from .helpers import find_next as find_next +from .helpers import is_paired as is_paired +from .helpers import normalize_label as normalize_label +from .inline import InlineElement +from .source import Source + +ElementType = type[InlineElement] + +# Type alias for match objects used in inline parsing +_Match = Any # Can be re.Match[str] or MatchObj + +class Group(NamedTuple): + start: int + end: int + text: str | None + +WHITESPACE: str +ASCII_CONTROL: Pattern[str] + +class ParseError(ValueError): ... + +def parse( + text: str, elements: list[ElementType], fallback: ElementType, source: Source +) -> list[InlineElement]: ... +def make_elements( + tokens: list[Token], + text: str, + start: int = 0, + end: int | None = None, + fallback: ElementType | None = None, +) -> list[InlineElement]: ... + +class Token: + PRECEDE: int + INTERSECT: int + CONTAIN: int + SHADE: int + etype: ElementType + match: _Match + start: int + end: int + inner_start: int + inner_end: int + text: str + fallback: ElementType + children: list[Token] + def __init__( + self, etype: ElementType, match: _Match, text: str, fallback: ElementType + ) -> None: ... + def relation(self, other: Token) -> int: ... + def append_child(self, child: Token) -> None: ... + def as_element(self) -> InlineElement: ... + def __lt__(self, o: Token) -> bool: ... + +def find_links_or_emphs( + text: str, link_ref_defs: dict[str, tuple[str, str]] +) -> list[MatchObj]: ... +def look_for_image_or_link( + text: str, + delimiters: list[Delimiter], + close: int, + link_ref_defs: dict[str, tuple[str, str]], + matches: list[MatchObj], +) -> MatchObj | None: ... +def process_emphasis( + text: str, + delimiters: list[Delimiter], + stack_bottom: int | None, + matches: list[MatchObj], +) -> None: ... + +class Delimiter: + whitespace_re: Pattern[str] + start: int + end: int + content: str + text: str + active: bool + can_open: bool + can_close: bool + def __init__(self, match: _Match, text: str) -> None: ... + def is_left_flanking(self) -> bool: ... + def is_right_flanking(self) -> bool: ... + def followed_by_punc(self) -> bool: ... + def preceded_by_punc(self) -> bool: ... + def closed_by(self, other: Delimiter) -> bool: ... + def remove(self, n: int, left: bool = False) -> bool: ... + +class MatchObj: + etype: str + def __init__( + self, etype: str, text: str, start: int, end: int, *groups: Group + ) -> None: ... + def group(self, n: int = 0) -> str: ... + def start(self, n: int = 0) -> int: ... + def end(self, n: int = 0) -> int: ... + def span(self, n: int = 0) -> tuple[int, int]: ... diff --git a/marko/md_renderer.pyi b/marko/md_renderer.pyi new file mode 100644 index 0000000..d00a39f --- /dev/null +++ b/marko/md_renderer.pyi @@ -0,0 +1,40 @@ +from contextlib import contextmanager +from typing import Any, Generator + +from . import block, inline +from .element import Element +from .renderer import Renderer + +class MarkdownRenderer(Renderer): + # Narrowed return types for str-returning renderer + def render(self, element: Element) -> str: ... + def render_children(self, element: Any) -> str: ... + + def __init__(self) -> None: ... + def __enter__(self) -> MarkdownRenderer: ... + @contextmanager + def container( + self, prefix: str, second_prefix: str = "" + ) -> Generator[None, None, None]: ... + def render_paragraph(self, element: block.Paragraph) -> str: ... + def render_list(self, element: block.List) -> str: ... + def render_list_item(self, element: block.ListItem) -> str: ... + def render_quote(self, element: block.Quote) -> str: ... + def render_fenced_code(self, element: block.FencedCode) -> str: ... + def render_code_block(self, element: block.CodeBlock) -> str: ... + def render_html_block(self, element: block.HTMLBlock) -> str: ... + def render_thematic_break(self, element: block.ThematicBreak) -> str: ... + def render_heading(self, element: block.Heading) -> str: ... + def render_setext_heading(self, element: block.SetextHeading) -> str: ... + def render_blank_line(self, element: block.BlankLine) -> str: ... + def render_link_ref_def(self, element: block.LinkRefDef) -> str: ... + def render_emphasis(self, element: inline.Emphasis) -> str: ... + def render_strong_emphasis(self, element: inline.StrongEmphasis) -> str: ... + def render_inline_html(self, element: inline.InlineHTML) -> str: ... + def render_link(self, element: inline.Link) -> str: ... + def render_auto_link(self, element: inline.AutoLink) -> str: ... + def render_image(self, element: inline.Image) -> str: ... + def render_literal(self, element: inline.Literal) -> str: ... + def render_raw_text(self, element: inline.RawText) -> str: ... + def render_line_break(self, element: inline.LineBreak) -> str: ... + def render_code_span(self, element: inline.CodeSpan) -> str: ... diff --git a/marko/parser.pyi b/marko/parser.pyi new file mode 100644 index 0000000..46e5cb2 --- /dev/null +++ b/marko/parser.pyi @@ -0,0 +1,15 @@ +from . import block, element, inline, inline_parser +from .source import Source + +BlockElementType = type[block.BlockElement] +InlineElementType = type[inline.InlineElement] +ElementType = BlockElementType | InlineElementType + +class Parser: + block_elements: dict[str, BlockElementType] + inline_elements: dict[str, InlineElementType] + def __init__(self) -> None: ... + def add_element(self, element: ElementType) -> None: ... + def parse(self, text: str) -> block.Document: ... + def parse_source(self, source: Source) -> list[block.BlockElement]: ... + def parse_inline(self, element: block.BlockElement, source: Source) -> None: ... diff --git a/marko/renderer.pyi b/marko/renderer.pyi new file mode 100644 index 0000000..dfe098c --- /dev/null +++ b/marko/renderer.pyi @@ -0,0 +1,18 @@ +from typing import Any, Callable, TypeVar + +from .block import Document +from .element import Element + +_T = TypeVar("_T", bound="Renderer") +_F = TypeVar("_F", bound=Callable[..., Any]) + +class Renderer: + delegate: bool + root_node: Document | None + def __init__(self) -> None: ... + def __enter__(self: _T) -> _T: ... + def __exit__(self, *args: Any) -> None: ... + def render(self, element: Element) -> Any: ... + def render_children(self, element: Any) -> Any: ... + +def force_delegate(func: _F) -> _F: ... diff --git a/marko/source.pyi b/marko/source.pyi new file mode 100644 index 0000000..75277a3 --- /dev/null +++ b/marko/source.pyi @@ -0,0 +1,37 @@ +import functools +import types +from contextlib import contextmanager +from typing import Generator, Literal, Match, Pattern, overload + +from .block import BlockElement, Document +from .parser import Parser + +class Source: + parser: Parser + pos: int + match: Match[str] | None + context: types.SimpleNamespace + def __init__(self, text: str) -> None: ... + @property + def state(self) -> BlockElement: ... + @property + def root(self) -> Document: ... + def push_state(self, element: BlockElement) -> None: ... + def pop_state(self) -> BlockElement: ... + @contextmanager + def under_state(self, element: BlockElement) -> Generator[Source, None, None]: ... + @property + def exhausted(self) -> bool: ... + @property + def prefix(self) -> str: ... + @staticmethod + @functools.lru_cache + def match_prefix(prefix: str, line: str) -> int: ... + def expect_re(self, regexp: Pattern[str] | str) -> Match[str] | None: ... + @overload + def next_line(self, require_prefix: Literal[False] = ...) -> str: ... + @overload + def next_line(self, require_prefix: Literal[True] = ...) -> str | None: ... + def consume(self) -> None: ... + def anchor(self) -> None: ... + def reset(self) -> None: ...