File: index.py

package info (click to toggle)
mdit-py-plugins 0.4.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 672 kB
  • sloc: python: 3,595; sh: 8; makefile: 7
file content (129 lines) | stat: -rw-r--r-- 4,114 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import re
from typing import Callable, List, Optional, Set

from markdown_it import MarkdownIt
from markdown_it.rules_core import StateCore
from markdown_it.token import Token


def anchors_plugin(
    md: MarkdownIt,
    min_level: int = 1,
    max_level: int = 2,
    slug_func: Optional[Callable[[str], str]] = None,
    permalink: bool = False,
    permalinkSymbol: str = "¶",
    permalinkBefore: bool = False,
    permalinkSpace: bool = True,
) -> None:
    """Plugin for adding header anchors, based on
    `markdown-it-anchor <https://github.com/valeriangalliat/markdown-it-anchor>`__

    .. code-block:: md

        # Title String

    renders as:

    .. code-block:: html

        <h1 id="title-string">Title String <a class="header-anchor" href="#title-string">¶</a></h1>

    :param min_level: minimum header level to apply anchors
    :param max_level: maximum header level to apply anchors
    :param slug_func: function to convert title text to id slug.
    :param permalink: Add a permalink next to the title
    :param permalinkSymbol: the symbol to show
    :param permalinkBefore: Add the permalink before the title, otherwise after
    :param permalinkSpace: Add a space between the permalink and the title

    Note, the default slug function aims to mimic the GitHub Markdown format, see:

    - https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb
    - https://gist.github.com/asabaylus/3071099

    """
    selected_levels = list(range(min_level, max_level + 1))
    md.core.ruler.push(
        "anchor",
        _make_anchors_func(
            selected_levels,
            slug_func or slugify,
            permalink,
            permalinkSymbol,
            permalinkBefore,
            permalinkSpace,
        ),
    )


def _make_anchors_func(
    selected_levels: List[int],
    slug_func: Callable[[str], str],
    permalink: bool,
    permalinkSymbol: str,
    permalinkBefore: bool,
    permalinkSpace: bool,
) -> 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":
                continue
            level = int(token.tag[1])
            if level not in selected_levels:
                continue
            inline_token = state.tokens[idx + 1]
            assert inline_token.children is not None
            title = "".join(
                child.content
                for child in inline_token.children
                if child.type in ["text", "code_inline"]
            )
            slug = unique_slug(slug_func(title), slugs)
            token.attrSet("id", slug)

            if permalink:
                link_open = Token(
                    "link_open",
                    "a",
                    1,
                )
                link_open.attrSet("class", "header-anchor")
                link_open.attrSet("href", f"#{slug}")
                link_tokens = [
                    link_open,
                    Token("html_block", "", 0, content=permalinkSymbol),
                    Token("link_close", "a", -1),
                ]
                if permalinkBefore:
                    inline_token.children = (
                        link_tokens
                        + (
                            [Token("text", "", 0, content=" ")]
                            if permalinkSpace
                            else []
                        )
                        + inline_token.children
                    )
                else:
                    inline_token.children.extend(
                        ([Token("text", "", 0, content=" ")] if permalinkSpace else [])
                        + link_tokens
                    )

    return _anchor_func


def slugify(title: str) -> str:
    return re.sub(r"[^\w\u4e00-\u9fff\- ]", "", title.strip().lower().replace(" ", "-"))


def unique_slug(slug: str, slugs: Set[str]) -> str:
    uniq = slug
    i = 1
    while uniq in slugs:
        uniq = f"{slug}-{i}"
        i += 1
    slugs.add(uniq)
    return uniq