Skip to content

✨ NEW: Add anchors_plugin for headers #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
import os

# import sys
# sys.path.insert(0, os.path.abspath('.'))

Expand Down Expand Up @@ -107,4 +108,5 @@ def run_apidoc(app):

def setup(app):
"""Add functions to the Sphinx setup."""
app.connect("builder-inited", run_apidoc)
if os.environ.get("SKIP_APIDOC", None) is None:
app.connect("builder-inited", run_apidoc)
84 changes: 26 additions & 58 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,74 +7,42 @@ The following plugins are embedded within the core package (enabled when using t
- [tables](https://help.github.com/articles/organizing-information-with-tables/) (GFM)
- [strikethrough](https://help.github.com/articles/basic-writing-and-formatting-syntax/#styling-text) (GFM)

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

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

```md
Normal footnote:
```{eval-rst}
.. autofunction:: markdown_it.extensions.anchors.anchors_plugin
:noindex:

Here is a footnote reference,[^1] and another.[^longnote]
.. autofunction:: markdown_it.extensions.footnote.footnote_plugin
:noindex:

[^1]: Here is the footnote.
.. autofunction:: markdown_it.extensions.front_matter.front_matter_plugin
:noindex:

[^longnote]: Here's one with multiple blocks.
.. autofunction:: markdown_it.extensions.container.container_plugin
:noindex:

Subsequent paragraphs are indented to show that they
belong to the previous footnote.
```
.. autofunction:: markdown_it.extensions.deflist.deflist_plugin
:noindex:

- [front-matter](https://github.com/ParkSB/markdown-it-front-matter) parses initial metadata, stored between opening/closing dashes:
.. autofunction:: markdown_it.extensions.texmath.texmath_plugin
:noindex:

```md
---
valid-front-matter: true
---
```
.. autofunction:: markdown_it.extensions.dollarmath.dollarmath_plugin
:noindex:

- [containers](https://github.com/markdown-it/markdown-it-container) is a plugin for creating block-level custom containers:
.. autofunction:: markdown_it.extensions.amsmath.amsmath_plugin
:noindex:
```

```md
::::: name
:::: name
*markdown*
::::
:::::
```

- [deflist](https://github.com/markdown-it/markdown-it-deflist) syntax is based on [pandoc definition lists](http://johnmacfarlane.net/pandoc/README.html#definition-lists).

```md
Term 1

: Definition 1 long form

second paragraph

Term 2 with *inline markup*
~ Definition 2a compact style
~ Definition 2b
```

- [texmath](https://github.com/goessner/markdown-it-texmath) parses TeX math equations set inside opening and closing delimiters:

```md
$\alpha = \frac{1}{2}$
```

- `dollarmath` is an improved version of `texmath`, for `$`/`$$` enclosed math only.
It is more performant, handles `\` escaping properly and allows for more configuration.

- `amsmath` also parses TeX math equations, but without the surrounding delimiters and only for top-level [amsmath](https://ctan.org/pkg/amsmath) environments:

```latex
\begin{gather*}
a_1=b_1+c_1\\
a_2=b_2+c_2-d_2+e_2
\end{gather*}
```

- `myst_blocks` and `myst_role` plugins are utilised by the [MyST renderer](https://myst-parser.readthedocs.io/en/latest/using/syntax.html)
`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)

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

Expand Down
10 changes: 10 additions & 0 deletions markdown_it/extensions/amsmath/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,17 @@


def amsmath_plugin(md: MarkdownIt):
"""Parses TeX math equations, without any surrounding delimiters,
only for top-level `amsmath <https://ctan.org/pkg/amsmath>`__ environments:

.. code-block:: latex

\\begin{gather*}
a_1=b_1+c_1\\\\
a_2=b_2+c_2-d_2+e_2
\\end{gather*}

"""
md.block.ruler.before(
"blockquote",
"amsmath",
Expand Down
1 change: 1 addition & 0 deletions markdown_it/extensions/anchors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .index import anchors_plugin # noqa F401
131 changes: 131 additions & 0 deletions markdown_it/extensions/anchors/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import re
from typing import Callable, List, Optional

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,
):
"""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,
):
slugs = set()

def _anchor_func(state: StateCore):
for (idx, token) in enumerate(state.tokens):
token: Token
if token.type != "heading_open":
continue
level = int(token.tag[1])
if level not in selected_levels:
continue
title = "".join(
[
child.content
for child in state.tokens[idx + 1].children
if child.type in ["text", "code_inline"]
]
)
slug = unique_slug(slug_func(title), slugs)
token.attrSet("id", slug)

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

return _anchor_func


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


def unique_slug(slug: str, slugs: set):
uniq = slug
i = 1
while uniq in slugs:
uniq = f"{slug}-{i}"
i += 1
slugs.add(uniq)
return uniq
35 changes: 29 additions & 6 deletions markdown_it/extensions/container/index.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
"""Process block-level custom containers."""
from math import floor
from typing import Callable

from markdown_it import MarkdownIt
from markdown_it.common.utils import charCodeAt
from markdown_it.rules_block import StateBlock


def container_plugin(md: MarkdownIt, name, **options):
"""Second param may be useful,
if you decide to increase minimal allowed marker length
def container_plugin(
md: MarkdownIt,
name: str,
marker: str = ":",
validate: Callable[[str, str], bool] = None,
render=None,
):
"""Plugin ported from
`markdown-it-container <https://github.com/markdown-it/markdown-it-container>`__.

It is a plugin for creating block-level custom containers:

.. code-block:: md

:::: name
::: name
*markdown*
:::
::::

:param name: the name of the container to parse
:param marker: the marker character to use
:param validate: func(marker, param) -> bool, default matches against the name
:param render: render func

"""

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

min_markers = 3
marker_str = options.get("marker", ":")
marker_str = marker
marker_char = charCodeAt(marker_str, 0)
marker_len = len(marker_str)
validate = options.get("validate", validateDefault)
render = options.get("render", renderDefault)
validate = validate or validateDefault
render = render or renderDefault

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

Expand Down
18 changes: 18 additions & 0 deletions markdown_it/extensions/deflist/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@


def deflist_plugin(md: MarkdownIt):
"""Plugin ported from
`markdown-it-deflist <https://github.com/markdown-it/markdown-it-deflist>`__.

The syntax is based on
`pandoc definition lists <http://johnmacfarlane.net/pandoc/README.html#definition-lists>`__:

.. code-block:: md

Term 1
: Definition 1 long form

second paragraph

Term 2 with *inline markup*
~ Definition 2a compact style
~ Definition 2b

"""
isSpace = md.utils.isSpace

def skipMarker(state: StateBlock, line: int):
Expand Down
3 changes: 3 additions & 0 deletions markdown_it/extensions/dollarmath/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ def dollarmath_plugin(
):
"""Plugin for parsing dollar enclosed math, e.g. ``$a=1$``.

This is an improved version of ``texmath``; it is more performant,
and handles ``\\`` escaping properly and allows for more configuration.

:param allow_labels: Capture math blocks with label suffix, e.g. ``$$a=1$$ (eq1)``
:param allow_space: Parse inline math when there is space
after/before the opening/closing ``$``, e.g. ``$ a $``
Expand Down
19 changes: 19 additions & 0 deletions markdown_it/extensions/footnote/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,26 @@


def footnote_plugin(md: MarkdownIt):
"""Plugin ported from
`markdown-it-footnote <https://github.com/markdown-it/markdown-it-footnote>`__.

It is based on the
`pandoc definition <http://johnmacfarlane.net/pandoc/README.html#footnotes>`__:

.. code-block:: md

Normal footnote:

Here is a footnote reference,[^1] and another.[^longnote]

[^1]: Here is the footnote.

[^longnote]: Here's one with multiple blocks.

Subsequent paragraphs are indented to show that they
belong to the previous footnote.

"""
md.block.ruler.before(
"reference", "footnote_def", footnote_def, {"alt": ["paragraph", "reference"]}
)
Expand Down
Loading