Skip to content

Commit 31f426b

Browse files
authored
✨ NEW: Add anchors_plugin for headers (#46)
1 parent 893f2cf commit 31f426b

File tree

15 files changed

+362
-73
lines changed

15 files changed

+362
-73
lines changed

docs/conf.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
# add these directories to sys.path here. If the directory is relative to the
1111
# documentation root, use os.path.abspath to make it absolute, like shown here.
1212
#
13-
# import os
13+
import os
14+
1415
# import sys
1516
# sys.path.insert(0, os.path.abspath('.'))
1617

@@ -107,4 +108,5 @@ def run_apidoc(app):
107108

108109
def setup(app):
109110
"""Add functions to the Sphinx setup."""
110-
app.connect("builder-inited", run_apidoc)
111+
if os.environ.get("SKIP_APIDOC", None) is None:
112+
app.connect("builder-inited", run_apidoc)

docs/plugins.md

Lines changed: 26 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,74 +7,42 @@ The following plugins are embedded within the core package (enabled when using t
77
- [tables](https://help.github.com/articles/organizing-information-with-tables/) (GFM)
88
- [strikethrough](https://help.github.com/articles/basic-writing-and-formatting-syntax/#styling-text) (GFM)
99

10-
Other plugins are then available *via* the `markdown_it.extensions` package:
10+
Other plugins are then available *via* the `markdown_it.extensions` package.
11+
They can be chained and loaded *via*:
1112

12-
- [footnote](https://github.com/markdown-it/markdown-it-footnote) is based on the [pandoc definition](http://johnmacfarlane.net/pandoc/README.html#footnotes):
13+
```python
14+
from markdown_it import MarkdownIt
15+
md = MarkdownIt().use(plugin1, keyword=value).use(plugin2, keyword=value)
16+
html_string = md.render("some *Markdown*")
17+
```
1318

14-
```md
15-
Normal footnote:
19+
```{eval-rst}
20+
.. autofunction:: markdown_it.extensions.anchors.anchors_plugin
21+
:noindex:
1622
17-
Here is a footnote reference,[^1] and another.[^longnote]
23+
.. autofunction:: markdown_it.extensions.footnote.footnote_plugin
24+
:noindex:
1825
19-
[^1]: Here is the footnote.
26+
.. autofunction:: markdown_it.extensions.front_matter.front_matter_plugin
27+
:noindex:
2028
21-
[^longnote]: Here's one with multiple blocks.
29+
.. autofunction:: markdown_it.extensions.container.container_plugin
30+
:noindex:
2231
23-
Subsequent paragraphs are indented to show that they
24-
belong to the previous footnote.
25-
```
32+
.. autofunction:: markdown_it.extensions.deflist.deflist_plugin
33+
:noindex:
2634
27-
- [front-matter](https://github.com/ParkSB/markdown-it-front-matter) parses initial metadata, stored between opening/closing dashes:
35+
.. autofunction:: markdown_it.extensions.texmath.texmath_plugin
36+
:noindex:
2837
29-
```md
30-
---
31-
valid-front-matter: true
32-
---
33-
```
38+
.. autofunction:: markdown_it.extensions.dollarmath.dollarmath_plugin
39+
:noindex:
3440
35-
- [containers](https://github.com/markdown-it/markdown-it-container) is a plugin for creating block-level custom containers:
41+
.. autofunction:: markdown_it.extensions.amsmath.amsmath_plugin
42+
:noindex:
43+
```
3644

37-
```md
38-
::::: name
39-
:::: name
40-
*markdown*
41-
::::
42-
:::::
43-
```
44-
45-
- [deflist](https://github.com/markdown-it/markdown-it-deflist) syntax is based on [pandoc definition lists](http://johnmacfarlane.net/pandoc/README.html#definition-lists).
46-
47-
```md
48-
Term 1
49-
50-
: Definition 1 long form
51-
52-
second paragraph
53-
54-
Term 2 with *inline markup*
55-
~ Definition 2a compact style
56-
~ Definition 2b
57-
```
58-
59-
- [texmath](https://github.com/goessner/markdown-it-texmath) parses TeX math equations set inside opening and closing delimiters:
60-
61-
```md
62-
$\alpha = \frac{1}{2}$
63-
```
64-
65-
- `dollarmath` is an improved version of `texmath`, for `$`/`$$` enclosed math only.
66-
It is more performant, handles `\` escaping properly and allows for more configuration.
67-
68-
- `amsmath` also parses TeX math equations, but without the surrounding delimiters and only for top-level [amsmath](https://ctan.org/pkg/amsmath) environments:
69-
70-
```latex
71-
\begin{gather*}
72-
a_1=b_1+c_1\\
73-
a_2=b_2+c_2-d_2+e_2
74-
\end{gather*}
75-
```
76-
77-
- `myst_blocks` and `myst_role` plugins are utilised by the [MyST renderer](https://myst-parser.readthedocs.io/en/latest/using/syntax.html)
45+
`myst_blocks` and `myst_role` plugins are also available, for utilisation by the [MyST renderer](https://myst-parser.readthedocs.io/en/latest/using/syntax.html)
7846

7947
There are also many other plugins which could easily be ported (and hopefully will be):
8048

markdown_it/extensions/amsmath/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@
4747

4848

4949
def amsmath_plugin(md: MarkdownIt):
50+
"""Parses TeX math equations, without any surrounding delimiters,
51+
only for top-level `amsmath <https://ctan.org/pkg/amsmath>`__ environments:
5052
53+
.. code-block:: latex
54+
55+
\\begin{gather*}
56+
a_1=b_1+c_1\\\\
57+
a_2=b_2+c_2-d_2+e_2
58+
\\end{gather*}
59+
60+
"""
5161
md.block.ruler.before(
5262
"blockquote",
5363
"amsmath",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .index import anchors_plugin # noqa F401
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import re
2+
from typing import Callable, List, Optional
3+
4+
from markdown_it import MarkdownIt
5+
from markdown_it.rules_core import StateCore
6+
from markdown_it.token import Token
7+
8+
9+
def anchors_plugin(
10+
md: MarkdownIt,
11+
min_level: int = 1,
12+
max_level: int = 2,
13+
slug_func: Optional[Callable[[str], str]] = None,
14+
permalink: bool = False,
15+
permalinkSymbol: str = "¶",
16+
permalinkBefore: bool = False,
17+
permalinkSpace: bool = True,
18+
):
19+
"""Plugin for adding header anchors, based on
20+
`markdown-it-anchor <https://github.com/valeriangalliat/markdown-it-anchor>`__
21+
22+
.. code-block:: md
23+
24+
# Title String
25+
26+
renders as:
27+
28+
.. code-block:: html
29+
30+
<h1 id="title-string">Title String <a class="header-anchor" href="#title-string">¶</a></h1>
31+
32+
:param min_level: minimum header level to apply anchors
33+
:param max_level: maximum header level to apply anchors
34+
:param slug_func: function to convert title text to id slug.
35+
:param permalink: Add a permalink next to the title
36+
:param permalinkSymbol: the symbol to show
37+
:param permalinkBefore: Add the permalink before the title, otherwise after
38+
:param permalinkSpace: Add a space between the permalink and the title
39+
40+
Note, the default slug function aims to mimic the GitHub Markdown format, see:
41+
42+
- https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb
43+
- https://gist.github.com/asabaylus/3071099
44+
45+
"""
46+
selected_levels = list(range(min_level, max_level + 1))
47+
md.core.ruler.push(
48+
"anchor",
49+
_make_anchors_func(
50+
selected_levels,
51+
slug_func or slugify,
52+
permalink,
53+
permalinkSymbol,
54+
permalinkBefore,
55+
permalinkSpace,
56+
),
57+
)
58+
59+
60+
def _make_anchors_func(
61+
selected_levels: List[int],
62+
slug_func: Callable[[str], str],
63+
permalink: bool,
64+
permalinkSymbol: str,
65+
permalinkBefore: bool,
66+
permalinkSpace: bool,
67+
):
68+
slugs = set()
69+
70+
def _anchor_func(state: StateCore):
71+
for (idx, token) in enumerate(state.tokens):
72+
token: Token
73+
if token.type != "heading_open":
74+
continue
75+
level = int(token.tag[1])
76+
if level not in selected_levels:
77+
continue
78+
title = "".join(
79+
[
80+
child.content
81+
for child in state.tokens[idx + 1].children
82+
if child.type in ["text", "code_inline"]
83+
]
84+
)
85+
slug = unique_slug(slug_func(title), slugs)
86+
token.attrSet("id", slug)
87+
88+
if permalink:
89+
link_tokens = [
90+
Token(
91+
"link_open",
92+
"a",
93+
1,
94+
attrs=[["class", "header-anchor"], ["href", f"#{slug}"]],
95+
),
96+
Token("html_block", "", 0, content=permalinkSymbol),
97+
Token("link_close", "a", -1),
98+
]
99+
if permalinkBefore:
100+
state.tokens[idx + 1].children = (
101+
link_tokens
102+
+ (
103+
[Token("text", "", 0, content=" ")]
104+
if permalinkSpace
105+
else []
106+
)
107+
+ state.tokens[idx + 1].children
108+
)
109+
else:
110+
state.tokens[idx + 1].children.extend(
111+
([Token("text", "", 0, content=" ")] if permalinkSpace else [])
112+
+ link_tokens
113+
)
114+
115+
return _anchor_func
116+
117+
118+
def slugify(title: str):
119+
return re.subn(
120+
r"[^\w\u4e00-\u9fff\- ]", "", title.strip().lower().replace(" ", "-")
121+
)[0]
122+
123+
124+
def unique_slug(slug: str, slugs: set):
125+
uniq = slug
126+
i = 1
127+
while uniq in slugs:
128+
uniq = f"{slug}-{i}"
129+
i += 1
130+
slugs.add(uniq)
131+
return uniq

markdown_it/extensions/container/index.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
11
"""Process block-level custom containers."""
22
from math import floor
3+
from typing import Callable
34

45
from markdown_it import MarkdownIt
56
from markdown_it.common.utils import charCodeAt
67
from markdown_it.rules_block import StateBlock
78

89

9-
def container_plugin(md: MarkdownIt, name, **options):
10-
"""Second param may be useful,
11-
if you decide to increase minimal allowed marker length
10+
def container_plugin(
11+
md: MarkdownIt,
12+
name: str,
13+
marker: str = ":",
14+
validate: Callable[[str, str], bool] = None,
15+
render=None,
16+
):
17+
"""Plugin ported from
18+
`markdown-it-container <https://github.com/markdown-it/markdown-it-container>`__.
19+
20+
It is a plugin for creating block-level custom containers:
21+
22+
.. code-block:: md
23+
24+
:::: name
25+
::: name
26+
*markdown*
27+
:::
28+
::::
29+
30+
:param name: the name of the container to parse
31+
:param marker: the marker character to use
32+
:param validate: func(marker, param) -> bool, default matches against the name
33+
:param render: render func
34+
1235
"""
1336

1437
def validateDefault(params: str, *args):
@@ -22,11 +45,11 @@ def renderDefault(self, tokens, idx, _options, env):
2245
return self.renderToken(tokens, idx, _options, env)
2346

2447
min_markers = 3
25-
marker_str = options.get("marker", ":")
48+
marker_str = marker
2649
marker_char = charCodeAt(marker_str, 0)
2750
marker_len = len(marker_str)
28-
validate = options.get("validate", validateDefault)
29-
render = options.get("render", renderDefault)
51+
validate = validate or validateDefault
52+
render = render or renderDefault
3053

3154
def container_func(state: StateBlock, startLine: int, endLine: int, silent: bool):
3255

markdown_it/extensions/deflist/index.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,24 @@
44

55

66
def deflist_plugin(md: MarkdownIt):
7+
"""Plugin ported from
8+
`markdown-it-deflist <https://github.com/markdown-it/markdown-it-deflist>`__.
9+
10+
The syntax is based on
11+
`pandoc definition lists <http://johnmacfarlane.net/pandoc/README.html#definition-lists>`__:
12+
13+
.. code-block:: md
14+
15+
Term 1
16+
: Definition 1 long form
17+
18+
second paragraph
19+
20+
Term 2 with *inline markup*
21+
~ Definition 2a compact style
22+
~ Definition 2b
23+
24+
"""
725
isSpace = md.utils.isSpace
826

927
def skipMarker(state: StateBlock, line: int):

markdown_it/extensions/dollarmath/index.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ def dollarmath_plugin(
1111
):
1212
"""Plugin for parsing dollar enclosed math, e.g. ``$a=1$``.
1313
14+
This is an improved version of ``texmath``; it is more performant,
15+
and handles ``\\`` escaping properly and allows for more configuration.
16+
1417
:param allow_labels: Capture math blocks with label suffix, e.g. ``$$a=1$$ (eq1)``
1518
:param allow_space: Parse inline math when there is space
1619
after/before the opening/closing ``$``, e.g. ``$ a $``

markdown_it/extensions/footnote/index.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,26 @@
1010

1111

1212
def footnote_plugin(md: MarkdownIt):
13+
"""Plugin ported from
14+
`markdown-it-footnote <https://github.com/markdown-it/markdown-it-footnote>`__.
1315
16+
It is based on the
17+
`pandoc definition <http://johnmacfarlane.net/pandoc/README.html#footnotes>`__:
18+
19+
.. code-block:: md
20+
21+
Normal footnote:
22+
23+
Here is a footnote reference,[^1] and another.[^longnote]
24+
25+
[^1]: Here is the footnote.
26+
27+
[^longnote]: Here's one with multiple blocks.
28+
29+
Subsequent paragraphs are indented to show that they
30+
belong to the previous footnote.
31+
32+
"""
1433
md.block.ruler.before(
1534
"reference", "footnote_def", footnote_def, {"alt": ["paragraph", "reference"]}
1635
)

0 commit comments

Comments
 (0)