Import Upstream version 0.3.3

This commit is contained in:
su-fang 2023-03-22 09:39:30 +08:00
commit 00910e810c
104 changed files with 11841 additions and 0 deletions

143
.gitignore vendored Normal file
View File

@ -0,0 +1,143 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
benchmark/extra/
node_modules/
coverage/
demo/
apidoc/
*.log
__pycache__/
.ropeproject/
*.egg-info/
.vscode/
.DS_Store
docs/api/

42
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,42 @@
# Install pre-commit hooks via
# pre-commit install
exclude: >
(?x)^(
\.vscode/settings\.json|
test.*\.md|
test.*\.txt|
test.*\.html|
)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: check-json
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/timothycrosley/isort
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 22.8.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies: [flake8-bugbear==21.3.1]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.971
hooks:
- id: mypy
additional_dependencies: [markdown-it-py~=2.0]

12
.readthedocs.yml Normal file
View File

@ -0,0 +1,12 @@
version: 2
python:
version: 3
install:
- method: pip
path: .
extra_requirements: [rtd]
sphinx:
builder: html
fail_on_warning: true

65
CHANGELOG.md Normal file
View File

@ -0,0 +1,65 @@
# Change Log
## 0.3.3 - 2022-12-06
🐛 FIX: span with end of inline before attrs
## 0.3.2 - 2022-12-05
- ✨ NEW: Port `admon` plugin by @KyleKing ([#53](https://github.com/executablebooks/mdit-py-plugins/pull/53))
- ✨ NEW: Add span parsing to inline attributes plugin by @chrisjsewell ([#55](https://github.com/executablebooks/mdit-py-plugins/pull/55))
- 🐛 FIX: Task list item marker can be followed by any GFM whitespace by @hukkin in ([#42](https://github.com/executablebooks/mdit-py-plugins/pull/42))
**Full Changelog**: [v0.3.1...v0.4.0](https://github.com/executablebooks/mdit-py-plugins/compare/v0.3.1...v0.4.0)
## 0.3.1 - 2022-09-27
- ⬆️ UPGRADE: Drop Python 3.6, support Python 3.10
- 🐛 FIX: Parsing when newline is between footnote ID and first paragraph
- 🐛 FIX: Anchor ids in separate renders no longer affect each other.
- ✨ NEW: Add `attrs_plugin`
- 🔧 MAINTAIN: Use flit PEP 621 package build
## 0.3.0 - 2021-12-03
- ⬆️ UPGRADE: Compatible with markdown-it-py `v2`.
- ✨ NEW: Add field list plugin, Based on the [restructuredtext syntax](https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#field-lists)
- ♻️ REFACTOR: dollarmath plugin, `math_block_eqno` -> `math_block_label` token
- ♻️ REFACTOR: Remove AttrDict usage from texmath
- 👌 IMPROVE: Default HTML rendering for dollarmath and amsmath plugins
- 👌 IMPROVE: Add render options for dollarmath and amsmath plugins
- 👌 IMPROVE: MyST parsing of target blocks (allow whitespace) and roles (allow for new lines)
## 0.2.8 - 2021-05-03
🐛 FIX: `wordcount` update of minutes
## 0.2.7 - 2021-05-03
- ⬆️ UPDATE: markdown-it-py~=1.0
- ✨ NEW: Add `wordcount_plugin`
- 👌 IMPROVE: `dollarmath`: Allow inline double-dollar
- 👌 IMPROVE: `myst_blocks`: Parse multiline comments
- 👌 IMPROVE: Replace use of `env` as an `AttrDict`
- 🐛 FIX: `front_matter`: don't duplicate content storage in `Token.meta`
## 0.2.6 - 2021-03-17
👌 IMPROVE: Remove direct use of `Token.attrs`
## 0.2.5 - 2021-02-06
🐛 FIX: front-matter: `IndexError` if first line is single dash
## 0.2.2 - 2020-12-16
✨ NEW: Add substitution_plugin
(improvements in 0.2.3 and 0.2.4)
## 0.2.0 - 2020-12-14
Add mypy type-checking, code taken from: https://github.com/executablebooks/markdown-it-py/commit/2eb1fe6b47cc0ad4ebe954cabd91fb8e52a2f03d
## 0.1.0 - 2020-12-14
First release, code taken from: https://github.com/executablebooks/markdown-it-py/commit/3a5bdcc98e67de9df26ebb8bc7cd0221a0d6b51b

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 ExecutableBookProject
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# mdit-py-plugins
[![Github-CI][github-ci]][github-link]
[![Coverage Status][codecov-badge]][codecov-link]
[![PyPI][pypi-badge]][pypi-link]
[![Conda][conda-badge]][conda-link]
[![Code style: black][black-badge]][black-link]
Collection of core plugins for [markdown-it-py](https://github.com/executablebooks/markdown-it-py).
[github-ci]: https://github.com/executablebooks/mdit-py-plugins/workflows/continuous-integration/badge.svg
[github-link]: https://github.com/executablebooks/mdit-py-plugins
[pypi-badge]: https://img.shields.io/pypi/v/mdit-py-plugins.svg
[pypi-link]: https://pypi.org/project/mdit-py-plugins
[conda-badge]: https://anaconda.org/conda-forge/mdit-py-plugins/badges/version.svg
[conda-link]: https://anaconda.org/conda-forge/mdit-py-plugins
[codecov-badge]: https://codecov.io/gh/executablebooks/mdit-py-plugins/branch/master/graph/badge.svg
[codecov-link]: https://codecov.io/gh/executablebooks/mdit-py-plugins
[black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg
[black-link]: https://github.com/ambv/black
[install-badge]: https://img.shields.io/pypi/dw/mdit-py-plugins?label=pypi%20installs
[install-link]: https://pypistats.org/packages/mdit-py-plugins

10
codecov.yml Normal file
View File

@ -0,0 +1,10 @@
coverage:
status:
project:
default:
target: 92%
threshold: 0.2%
patch:
default:
target: 80%
threshold: 0.2%

29
docs/conf.py Normal file
View File

@ -0,0 +1,29 @@
project = "mdit-py-plugins"
copyright = "2020, Executable Book Project"
author = "Executable Book Project"
master_doc = "index"
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.viewcode",
"sphinx.ext.intersphinx",
"myst_parser",
]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
intersphinx_mapping = {
"python": ("https://docs.python.org/3.8", None),
"markdown_it": ("https://markdown-it-py.readthedocs.io/en/latest", None),
}
html_title = "mdit-py-plugins"
html_theme = "sphinx_book_theme"
html_theme_options = {
"single_page": True,
"use_edit_page_button": True,
"repository_url": "https://github.com/executablebooks/mdit-py-plugins",
"repository_branch": "master",
"path_to_docs": "docs",
}

133
docs/index.md Normal file
View File

@ -0,0 +1,133 @@
# Markdown-It-Py Plugin Extensions
## Built-in
The following plugins are embedded within the core package:
- [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)
These can be enabled individually:
```python
from markdown_it import MarkdownIt
md = MarkdownIt("commonmark").enable('table')
```
or as part of a configuration:
```python
from markdown_it import MarkdownIt
md = MarkdownIt("gfm-like")
```
```{seealso}
See [](markdown_it:using)
```
## mdit-py-plugins package
The [`mdit_py_plugins`](https://github.com/executablebooks/mdit-py-plugins), contains a number of common plugins.
They can be chained and loaded *via*:
```python
from markdown_it import MarkdownIt
from mdit_py_plugins import plugin1, plugin2
md = MarkdownIt().use(plugin1, keyword=value).use(plugin2, keyword=value)
html_string = md.render("some *Markdown*")
```
## Front-Matter
```{eval-rst}
.. autofunction:: mdit_py_plugins.front_matter.front_matter_plugin
```
## Footnotes
```{eval-rst}
.. autofunction:: mdit_py_plugins.footnote.footnote_plugin
```
## Definition Lists
```{eval-rst}
.. autofunction:: mdit_py_plugins.deflist.deflist_plugin
```
## Task lists
```{eval-rst}
.. autofunction:: mdit_py_plugins.tasklists.tasklists_plugin
```
## Field Lists
```{eval-rst}
.. autofunction:: mdit_py_plugins.field_list.fieldlist_plugin
```
## Heading Anchors
```{eval-rst}
.. autofunction:: mdit_py_plugins.anchors.anchors_plugin
```
## Word Count
```{eval-rst}
.. autofunction:: mdit_py_plugins.wordcount.wordcount_plugin
```
## Containers
```{eval-rst}
.. autofunction:: mdit_py_plugins.container.container_plugin
```
```{eval-rst}
.. autofunction:: mdit_py_plugins.admon.admon_plugin
```
## Inline Attributes
```{eval-rst}
.. autofunction:: mdit_py_plugins.attrs.attrs_plugin
```
## Math
```{eval-rst}
.. autofunction:: mdit_py_plugins.texmath.texmath_plugin
```
```{eval-rst}
.. autofunction:: mdit_py_plugins.dollarmath.dollarmath_plugin
```
```{eval-rst}
.. autofunction:: mdit_py_plugins.amsmath.amsmath_plugin
```
## MyST plugins
`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)
```{eval-rst}
.. autofunction:: mdit_py_plugins.myst_role.myst_role_plugin
.. autofunction:: mdit_py_plugins.myst_blocks.myst_block_plugin
```
## Write your own
Use the `mdit_py_plugins` as a guide to write your own, following the [markdown-it design principles](markdown_it:architecture).
There are many other plugins which could easily be ported from the JS versions (and hopefully will):
- [subscript](https://github.com/markdown-it/markdown-it-sub)
- [superscript](https://github.com/markdown-it/markdown-it-sup)
- [abbreviation](https://github.com/markdown-it/markdown-it-abbr)
- [emoji](https://github.com/markdown-it/markdown-it-emoji)
- [insert](https://github.com/markdown-it/markdown-it-ins)
- [mark](https://github.com/markdown-it/markdown-it-mark)
- ... and [others](https://www.npmjs.org/browse/keyword/markdown-it-plugin)

View File

@ -0,0 +1 @@
__version__ = "0.3.3"

View File

@ -0,0 +1,24 @@
Copyright (c) 2015 Vitaly Puzrin, Alex Kocharin.
Copyright (c) 2018 jebbs
Copyright (c) 2021- commenthol
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1 @@
from .index import admon_plugin # noqa: F401

View File

@ -0,0 +1,172 @@
# Process admonitions and pass to cb.
import math
from typing import Callable, Optional, Tuple
from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock
def get_tag(params: str) -> Tuple[str, str]:
if not params.strip():
return "", ""
tag, *_title = params.strip().split(" ")
joined = " ".join(_title)
title = ""
if not joined:
title = tag.title()
elif joined != '""':
title = joined
return tag.lower(), title
def validate(params: str) -> bool:
tag = params.strip().split(" ", 1)[-1] or ""
return bool(tag)
MIN_MARKERS = 3
MARKER_STR = "!"
MARKER_CHAR = ord(MARKER_STR)
MARKER_LEN = len(MARKER_STR)
def admonition(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
start = state.bMarks[startLine] + state.tShift[startLine]
maximum = state.eMarks[startLine]
# Check out the first character quickly, which should filter out most of non-containers
if MARKER_CHAR != ord(state.src[start]):
return False
# Check out the rest of the marker string
pos = start + 1
while pos <= maximum and MARKER_STR[(pos - start) % MARKER_LEN] == state.src[pos]:
pos += 1
marker_count = math.floor((pos - start) / MARKER_LEN)
if marker_count < MIN_MARKERS:
return False
marker_pos = pos - ((pos - start) % MARKER_LEN)
params = state.src[marker_pos:maximum]
markup = state.src[start:marker_pos]
if not validate(params):
return False
# Since start is found, we can report success here in validation mode
if silent:
return True
old_parent = state.parentType
old_line_max = state.lineMax
old_indent = state.blkIndent
blk_start = pos
while blk_start < maximum and state.src[blk_start] == " ":
blk_start += 1
state.parentType = "admonition"
state.blkIndent += blk_start - start
was_empty = False
# Search for the end of the block
next_line = startLine
while True:
next_line += 1
if next_line >= endLine:
# unclosed block should be autoclosed by end of document.
# also block seems to be autoclosed by end of parent
break
pos = state.bMarks[next_line] + state.tShift[next_line]
maximum = state.eMarks[next_line]
is_empty = state.sCount[next_line] < state.blkIndent
# two consecutive empty lines autoclose the block
if is_empty and was_empty:
break
was_empty = is_empty
if pos < maximum and state.sCount[next_line] < state.blkIndent:
# non-empty line with negative indent should stop the block:
# - !!!
# test
break
# this will prevent lazy continuations from ever going past our end marker
state.lineMax = next_line
tag, title = get_tag(params)
token = state.push("admonition_open", "div", 1)
token.markup = markup
token.block = True
token.attrs = {"class": f"admonition {tag}"}
token.meta = {"tag": tag}
token.content = title
token.info = params
token.map = [startLine, next_line]
if title:
title_markup = f"{markup} {tag}"
token = state.push("admonition_title_open", "p", 1)
token.markup = title_markup
token.attrs = {"class": "admonition-title"}
token.map = [startLine, startLine + 1]
token = state.push("inline", "", 0)
token.content = title
token.map = [startLine, startLine + 1]
token.children = []
token = state.push("admonition_title_close", "p", -1)
token.markup = title_markup
state.md.block.tokenize(state, startLine + 1, next_line)
token = state.push("admonition_close", "div", -1)
token.markup = state.src[start:pos]
token.block = True
state.parentType = old_parent
state.lineMax = old_line_max
state.blkIndent = old_indent
state.line = next_line
return True
def admon_plugin(md: MarkdownIt, render: Optional[Callable] = None) -> None:
"""Plugin to use
`python-markdown style admonitions
<https://python-markdown.github.io/extensions/admonition>`_.
.. code-block:: md
!!! note
*content*
Note, this is ported from
`markdown-it-admon
<https://github.com/commenthol/markdown-it-admon>`_.
"""
def renderDefault(self, tokens, idx, _options, env):
return self.renderToken(tokens, idx, _options, env)
render = render or renderDefault
md.add_render_rule("admonition_open", render)
md.add_render_rule("admonition_close", render)
md.add_render_rule("admonition_title_open", render)
md.add_render_rule("admonition_title_close", render)
md.block.ruler.before(
"fence",
"admonition",
admonition,
{"alt": ["paragraph", "reference", "blockquote", "list"]},
)

View File

@ -0,0 +1,4 @@
- package: markdown-it-admon
commit: 9820ba89415c464a3cc18a780f222a0ceb3e18bd
date: Jul 3, 2021
version: 1.0.0

View File

@ -0,0 +1,126 @@
"""An extension to capture amsmath latex environments."""
import re
from typing import Callable, Optional
from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml
from markdown_it.rules_block import StateBlock
# Taken from amsmath version 2.1
# http://anorien.csc.warwick.ac.uk/mirrors/CTAN/macros/latex/required/amsmath/amsldoc.pdf
ENVIRONMENTS = [
# 3.2 single equation with an automatically gen-erated number
"equation",
# 3.3 variation equation, used for equations that dont fit on a single line
"multline",
# 3.5 a group of consecutive equations when there is no alignment desired among them
"gather",
# 3.6 Used for two or more equations when vertical alignment is desired
"align",
# allows the horizontal space between equationsto be explicitly specified.
"alignat",
# stretches the space betweenthe equation columns to the maximum possible width
"flalign",
# 4.1 The pmatrix, bmatrix, Bmatrix, vmatrix and Vmatrix have (respectively)
# (),[],{},||,and ‖‖ delimiters built in.
"matrix",
"pmatrix",
"bmatrix",
"Bmatrix",
"vmatrix",
"Vmatrix",
# eqnarray is another math environment, it is not part of amsmath,
# and note that it is better to use align or equation+split instead
"eqnarray",
]
# other "non-top-level" environments:
# 3.4 the split environment is for single equations that are too long to fit on one line
# and hence must be split into multiple lines,
# it is intended for use only inside some other displayed equation structure,
# usually an equation, align, or gather environment
# 3.7 variants gathered, aligned,and alignedat are provided
# whose total width is the actual width of the contents;
# thus they can be used as a component in a containing expression
RE_OPEN = re.compile(r"\\begin\{(" + "|".join(ENVIRONMENTS) + r")([\*]?)\}")
def amsmath_plugin(md: MarkdownIt, *, renderer: Optional[Callable[[str], str]] = None):
"""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*}
:param renderer: Function to render content, by default escapes HTML
"""
md.block.ruler.before(
"blockquote",
"amsmath",
amsmath_block,
{"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
)
if renderer is None:
_renderer = lambda content: escapeHtml(content)
else:
_renderer = renderer
def render_amsmath_block(self, tokens, idx, options, env):
content = _renderer(str(tokens[idx].content))
return f'<div class="math amsmath">\n{content}\n</div>\n'
md.add_render_rule("amsmath", render_amsmath_block)
def match_environment(string):
match_open = RE_OPEN.match(string)
if not match_open:
return None
environment = match_open.group(1)
numbered = match_open.group(2)
match_close = re.search(
r"\\end\{" + environment + numbered.replace("*", r"\*") + "\\}", string
)
if not match_close:
return None
return (environment, numbered, match_close.end())
def amsmath_block(state: StateBlock, startLine: int, endLine: int, silent: bool):
# if it's indented more than 3 spaces, it should be a code block
if state.sCount[startLine] - state.blkIndent >= 4:
return False
begin = state.bMarks[startLine] + state.tShift[startLine]
outcome = match_environment(state.src[begin:])
if not outcome:
return False
environment, numbered, endpos = outcome
endpos += begin
line = startLine
while line < endLine:
if endpos >= state.bMarks[line] and endpos <= state.eMarks[line]:
# line for end of block math found ...
state.line = line + 1
break
line += 1
if not silent:
token = state.push("amsmath", "math", 0)
token.block = True
token.content = state.src[begin:endpos]
token.meta = {"environment": environment, "numbered": numbered}
token.map = [startLine, line]
return True

View File

@ -0,0 +1 @@
from .index import anchors_plugin # noqa F401

View File

@ -0,0 +1,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,
):
"""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,
):
def _anchor_func(state: StateCore):
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):
return re.sub(r"[^\w\u4e00-\u9fff\- ]", "", title.strip().lower().replace(" ", "-"))
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

View File

@ -0,0 +1 @@
from .index import attrs_plugin # noqa: F401

View File

@ -0,0 +1,123 @@
from typing import List, Optional
from markdown_it import MarkdownIt
from markdown_it.rules_inline import StateInline
from markdown_it.token import Token
from .parse import ParseError, parse
def attrs_plugin(
md: MarkdownIt,
*,
after=("image", "code_inline", "link_close", "span_close"),
spans=False,
span_after="link",
):
"""Parse inline attributes that immediately follow certain inline elements::
![alt](https://image.com){#id .a b=c}
This syntax is inspired by
`Djot spans
<https://htmlpreview.github.io/?https://github.com/jgm/djot/blob/master/doc/syntax.html#inline-attributes>`_.
Inside the curly braces, the following syntax is possible:
- `.foo` specifies foo as a class.
Multiple classes may be given in this way; they will be combined.
- `#foo` specifies foo as an identifier.
An element may have only one identifier;
if multiple identifiers are given, the last one is used.
- `key="value"` or `key=value` specifies a key-value attribute.
Quotes are not needed when the value consists entirely of
ASCII alphanumeric characters or `_` or `:` or `-`.
Backslash escapes may be used inside quoted values.
- `%` begins a comment, which ends with the next `%` or the end of the attribute (`}`).
Multiple attribute blocks are merged.
:param md: The MarkdownIt instance to modify.
:param after: The names of inline elements after which attributes may be specified.
This plugin does not support attributes after emphasis, strikethrough or text elements,
which all require post-parse processing.
:param spans: If True, also parse attributes after spans of text, encapsulated by `[]`.
Note Markdown link references take precedence over this syntax.
:param span_after: The name of an inline rule after which spans may be specified.
"""
def _attr_rule(state: StateInline, silent: bool):
if state.pending or not state.tokens:
return False
token = state.tokens[-1]
if token.type not in after:
return False
try:
new_pos, attrs = parse(state.src[state.pos :])
except ParseError:
return False
token_index = _find_opening(state.tokens, len(state.tokens) - 1)
if token_index is None:
return False
state.pos += new_pos + 1
if not silent:
attr_token = state.tokens[token_index]
if "class" in attrs and "class" in token.attrs:
attrs["class"] = f"{attr_token.attrs['class']} {attrs['class']}"
attr_token.attrs.update(attrs)
return True
if spans:
md.inline.ruler.after(span_after, "span", _span_rule)
md.inline.ruler.push("attr", _attr_rule)
def _find_opening(tokens: List[Token], index: int) -> Optional[int]:
"""Find the opening token index, if the token is closing."""
if tokens[index].nesting != -1:
return index
level = 0
while index >= 0:
level += tokens[index].nesting
if level == 0:
return index
index -= 1
return None
def _span_rule(state: StateInline, silent: bool):
if state.srcCharCode[state.pos] != 0x5B: # /* [ */
return False
maximum = state.posMax
labelStart = state.pos + 1
labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, False)
# parser failed to find ']', so it's not a valid span
if labelEnd < 0:
return False
pos = labelEnd + 1
# check not at end of inline
if pos >= maximum:
return False
try:
new_pos, attrs = parse(state.src[pos:])
except ParseError:
return False
pos += new_pos + 1
if not silent:
state.pos = labelStart
state.posMax = labelEnd
token = state.push("span_open", "span", 1)
token.attrs = attrs
state.md.inline.tokenize(state)
token = state.push("span_close", "span", -1)
state.pos = pos
state.posMax = maximum
return True

View File

@ -0,0 +1,265 @@
"""Parser for attributes::
attributes { id = "foo", class = "bar baz",
key1 = "val1", key2 = "val2" }
Adapted from:
https://github.com/jgm/djot/blob/fae7364b86bfce69bc6d5b5eede1f5196d845fd6/djot/attributes.lua#L1
syntax:
attributes <- '{' whitespace* attribute (whitespace attribute)* whitespace* '}'
attribute <- identifier | class | keyval
identifier <- '#' name
class <- '.' name
name <- (nonspace, nonpunctuation other than ':', '_', '-')+
keyval <- key '=' val
key <- (ASCII_ALPHANUM | ':' | '_' | '-')+
val <- bareval | quotedval
bareval <- (ASCII_ALPHANUM | ':' | '_' | '-')+
quotedval <- '"' ([^"] | '\"') '"'
"""
from __future__ import annotations
from enum import Enum
import re
from typing import Callable
class State(Enum):
START = 0
SCANNING = 1
SCANNING_ID = 2
SCANNING_CLASS = 3
SCANNING_KEY = 4
SCANNING_VALUE = 5
SCANNING_BARE_VALUE = 6
SCANNING_QUOTED_VALUE = 7
SCANNING_COMMENT = 8
SCANNING_ESCAPED = 9
DONE = 10
REGEX_SPACE = re.compile(r"\s")
REGEX_SPACE_PUNCTUATION = re.compile(r"[\s!\"#$%&'()*+,./;<=>?@[\]^`{|}~]")
REGEX_KEY_CHARACTERS = re.compile(r"[a-zA-Z\d_:-]")
class TokenState:
def __init__(self):
self._tokens = []
self.start: int = 0
def set_start(self, start: int) -> None:
self.start = start
def append(self, start: int, end: int, ttype: str):
self._tokens.append((start, end, ttype))
def compile(self, string: str) -> dict[str, str]:
"""compile the tokens into a dictionary"""
attributes = {}
classes = []
idx = 0
while idx < len(self._tokens):
start, end, ttype = self._tokens[idx]
if ttype == "id":
attributes["id"] = string[start:end]
elif ttype == "class":
classes.append(string[start:end])
elif ttype == "key":
key = string[start:end]
if idx + 1 < len(self._tokens):
start, end, ttype = self._tokens[idx + 1]
if ttype == "value":
if key == "class":
classes.append(string[start:end])
else:
attributes[key] = string[start:end]
idx += 1
idx += 1
if classes:
attributes["class"] = " ".join(classes)
return attributes
def __str__(self) -> str:
return str(self._tokens)
def __repr__(self) -> str:
return repr(self._tokens)
class ParseError(Exception):
def __init__(self, msg: str, pos: int) -> None:
self.pos = pos
super().__init__(msg + f" at position {pos}")
def parse(string: str) -> tuple[int, dict[str, str]]:
"""Parse attributes from start of string.
:returns: (length of parsed string, dict of attributes)
"""
pos = 0
state: State = State.START
tokens = TokenState()
while pos < len(string):
state = HANDLERS[state](string[pos], pos, tokens)
if state == State.DONE:
return pos, tokens.compile(string)
pos = pos + 1
return pos, tokens.compile(string)
def handle_start(char: str, pos: int, tokens: TokenState) -> State:
if char == "{":
return State.SCANNING
raise ParseError("Attributes must start with '{'", pos)
def handle_scanning(char: str, pos: int, tokens: TokenState) -> State:
if char == " " or char == "\t" or char == "\n" or char == "\r":
return State.SCANNING
if char == "}":
return State.DONE
if char == "#":
tokens.set_start(pos)
return State.SCANNING_ID
if char == "%":
tokens.set_start(pos)
return State.SCANNING_COMMENT
if char == ".":
tokens.set_start(pos)
return State.SCANNING_CLASS
if REGEX_KEY_CHARACTERS.fullmatch(char):
tokens.set_start(pos)
return State.SCANNING_KEY
raise ParseError(f"Unexpected character whilst scanning: {char}", pos)
def handle_scanning_comment(char: str, pos: int, tokens: TokenState) -> State:
if char == "%":
return State.SCANNING
return State.SCANNING_COMMENT
def handle_scanning_id(char: str, pos: int, tokens: TokenState) -> State:
if not REGEX_SPACE_PUNCTUATION.fullmatch(char):
return State.SCANNING_ID
if char == "}":
if (pos - 1) > tokens.start:
tokens.append(tokens.start + 1, pos, "id")
return State.DONE
if REGEX_SPACE.fullmatch(char):
if (pos - 1) > tokens.start:
tokens.append(tokens.start + 1, pos, "id")
return State.SCANNING
raise ParseError(f"Unexpected character whilst scanning id: {char}", pos)
def handle_scanning_class(char: str, pos: int, tokens: TokenState) -> State:
if not REGEX_SPACE_PUNCTUATION.fullmatch(char):
return State.SCANNING_CLASS
if char == "}":
if (pos - 1) > tokens.start:
tokens.append(tokens.start + 1, pos, "class")
return State.DONE
if REGEX_SPACE.fullmatch(char):
if (pos - 1) > tokens.start:
tokens.append(tokens.start + 1, pos, "class")
return State.SCANNING
raise ParseError(f"Unexpected character whilst scanning class: {char}", pos)
def handle_scanning_key(char: str, pos: int, tokens: TokenState) -> State:
if char == "=":
tokens.append(tokens.start, pos, "key")
return State.SCANNING_VALUE
if REGEX_KEY_CHARACTERS.fullmatch(char):
return State.SCANNING_KEY
raise ParseError(f"Unexpected character whilst scanning key: {char}", pos)
def handle_scanning_value(char: str, pos: int, tokens: TokenState) -> State:
if char == '"':
tokens.set_start(pos)
return State.SCANNING_QUOTED_VALUE
if REGEX_KEY_CHARACTERS.fullmatch(char):
tokens.set_start(pos)
return State.SCANNING_BARE_VALUE
raise ParseError(f"Unexpected character whilst scanning value: {char}", pos)
def handle_scanning_bare_value(char: str, pos: int, tokens: TokenState) -> State:
if REGEX_KEY_CHARACTERS.fullmatch(char):
return State.SCANNING_BARE_VALUE
if char == "}":
tokens.append(tokens.start, pos, "value")
return State.DONE
if REGEX_SPACE.fullmatch(char):
tokens.append(tokens.start, pos, "value")
return State.SCANNING
raise ParseError(f"Unexpected character whilst scanning bare value: {char}", pos)
def handle_scanning_escaped(char: str, pos: int, tokens: TokenState) -> State:
return State.SCANNING_QUOTED_VALUE
def handle_scanning_quoted_value(char: str, pos: int, tokens: TokenState) -> State:
if char == '"':
tokens.append(tokens.start + 1, pos, "value")
return State.SCANNING
if char == "\\":
return State.SCANNING_ESCAPED
if char == "{" or char == "}":
raise ParseError(
f"Unexpected character whilst scanning quoted value: {char}", pos
)
if char == "\n":
tokens.append(tokens.start + 1, pos, "value")
return State.SCANNING_QUOTED_VALUE
return State.SCANNING_QUOTED_VALUE
HANDLERS: dict[State, Callable[[str, int, TokenState], State]] = {
State.START: handle_start,
State.SCANNING: handle_scanning,
State.SCANNING_COMMENT: handle_scanning_comment,
State.SCANNING_ID: handle_scanning_id,
State.SCANNING_CLASS: handle_scanning_class,
State.SCANNING_KEY: handle_scanning_key,
State.SCANNING_VALUE: handle_scanning_value,
State.SCANNING_BARE_VALUE: handle_scanning_bare_value,
State.SCANNING_QUOTED_VALUE: handle_scanning_quoted_value,
State.SCANNING_ESCAPED: handle_scanning_escaped,
}

View File

@ -0,0 +1,132 @@
from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml, unescapeAll
from markdown_it.rules_block import StateBlock
def colon_fence_plugin(md: MarkdownIt):
"""This plugin directly mimics regular fences, but with `:` colons.
Example::
:::name
contained text
:::
"""
md.block.ruler.before(
"fence",
"colon_fence",
_rule,
{"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
)
md.add_render_rule("colon_fence", _render)
def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool):
haveEndMarker = False
pos = state.bMarks[startLine] + state.tShift[startLine]
maximum = state.eMarks[startLine]
# if it's indented more than 3 spaces, it should be a code block
if state.sCount[startLine] - state.blkIndent >= 4:
return False
if pos + 3 > maximum:
return False
marker = state.srcCharCode[pos]
# /* : */
if marker != 0x3A:
return False
# scan marker length
mem = pos
pos = state.skipChars(pos, marker)
length = pos - mem
if length < 3:
return False
markup = state.src[mem:pos]
params = state.src[pos:maximum]
# Since start is found, we can report success here in validation mode
if silent:
return True
# search end of block
nextLine = startLine
while True:
nextLine += 1
if nextLine >= endLine:
# unclosed block should be autoclosed by end of document.
# also block seems to be autoclosed by end of parent
break
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
maximum = state.eMarks[nextLine]
if pos < maximum and state.sCount[nextLine] < state.blkIndent:
# non-empty line with negative indent should stop the list:
# - ```
# test
break
if state.srcCharCode[pos] != marker:
continue
if state.sCount[nextLine] - state.blkIndent >= 4:
# closing fence should be indented less than 4 spaces
continue
pos = state.skipChars(pos, marker)
# closing code fence must be at least as long as the opening one
if pos - mem < length:
continue
# make sure tail has spaces only
pos = state.skipSpaces(pos)
if pos < maximum:
continue
haveEndMarker = True
# found!
break
# If a fence has heading spaces, they should be removed from its inner block
length = state.sCount[startLine]
state.line = nextLine + (1 if haveEndMarker else 0)
token = state.push("colon_fence", "code", 0)
token.info = params
token.content = state.getLines(startLine + 1, nextLine, length, True)
token.markup = markup
token.map = [startLine, state.line]
return True
def _render(self, tokens, idx, options, env):
token = tokens[idx]
info = unescapeAll(token.info).strip() if token.info else ""
content = escapeHtml(token.content)
block_name = ""
if info:
block_name = info.split()[0]
return (
"<pre><code"
+ (f' class="block-{block_name}" ' if block_name else "")
+ ">"
+ content
+ "</code></pre>\n"
)

View File

@ -0,0 +1,22 @@
Copyright (c) 2015 Vitaly Puzrin, Alex Kocharin.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,95 @@
# markdown-it-container
[![Build Status](https://img.shields.io/travis/markdown-it/markdown-it-container/master.svg?style=flat)](https://travis-ci.org/markdown-it/markdown-it-container)
[![NPM version](https://img.shields.io/npm/v/markdown-it-container.svg?style=flat)](https://www.npmjs.org/package/markdown-it-container)
[![Coverage Status](https://img.shields.io/coveralls/markdown-it/markdown-it-container/master.svg?style=flat)](https://coveralls.io/r/markdown-it/markdown-it-container?branch=master)
> Plugin for creating block-level custom containers for [markdown-it](https://github.com/markdown-it/markdown-it) markdown parser.
__v2.+ requires `markdown-it` v5.+, see changelog.__
With this plugin you can create block containers like:
```
::: warning
*here be dragons*
:::
```
.... and specify how they should be rendered. If no renderer defined, `<div>` with
container name class will be created:
```html
<div class="warning">
<em>here be dragons</em>
</div>
```
Markup is the same as for [fenced code blocks](http://spec.commonmark.org/0.18/#fenced-code-blocks).
Difference is, that marker use another character and content is rendered as markdown markup.
## Installation
node.js, browser:
```bash
$ npm install markdown-it-container --save
$ bower install markdown-it-container --save
```
## API
```js
var md = require('markdown-it')()
.use(require('markdown-it-container'), name [, options]);
```
Params:
- __name__ - container name (mandatory)
- __options:__
- __validate__ - optional, function to validate tail after opening marker, should
return `true` on success.
- __render__ - optional, renderer function for opening/closing tokens.
- __marker__ - optional (`:`), character to use in delimiter.
## Example
```js
var md = require('markdown-it')();
md.use(require('markdown-it-container'), 'spoiler', {
validate: function(params) {
return params.trim().match(/^spoiler\s+(.*)$/);
},
render: function (tokens, idx) {
var m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/);
if (tokens[idx].nesting === 1) {
// opening tag
return '<details><summary>' + md.utils.escapeHtml(m[1]) + '</summary>\n';
} else {
// closing tag
return '</details>\n';
}
}
});
console.log(md.render('::: spoiler click me\n*content*\n:::\n'));
// Output:
//
// <details><summary>click me</summary>
// <p><em>content</em></p>
// </details>
```
## License
[MIT](https://github.com/markdown-it/markdown-it-container/blob/master/LICENSE)

View File

@ -0,0 +1 @@
from .index import container_plugin # noqa F401

View File

@ -0,0 +1,174 @@
"""Process block-level custom containers."""
from math import floor
from typing import Callable, Optional
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: str,
marker: str = ":",
validate: Optional[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):
return params.strip().split(" ", 2)[0] == name
def renderDefault(self, tokens, idx, _options, env):
# add a class to the opening tag
if tokens[idx].nesting == 1:
tokens[idx].attrJoin("class", name)
return self.renderToken(tokens, idx, _options, env)
min_markers = 3
marker_str = marker
marker_char = charCodeAt(marker_str, 0)
marker_len = len(marker_str)
validate = validate or validateDefault
render = render or renderDefault
def container_func(state: StateBlock, startLine: int, endLine: int, silent: bool):
auto_closed = False
start = state.bMarks[startLine] + state.tShift[startLine]
maximum = state.eMarks[startLine]
# Check out the first character quickly,
# this should filter out most of non-containers
if marker_char != state.srcCharCode[start]:
return False
# Check out the rest of the marker string
pos = start + 1
while pos <= maximum:
try:
character = state.src[pos]
except IndexError:
break
if marker_str[(pos - start) % marker_len] != character:
break
pos += 1
marker_count = floor((pos - start) / marker_len)
if marker_count < min_markers:
return False
pos -= (pos - start) % marker_len
markup = state.src[start:pos]
params = state.src[pos:maximum]
assert validate is not None
if not validate(params, markup):
return False
# Since start is found, we can report success here in validation mode
if silent:
return True
# Search for the end of the block
nextLine = startLine
while True:
nextLine += 1
if nextLine >= endLine:
# unclosed block should be autoclosed by end of document.
# also block seems to be autoclosed by end of parent
break
start = state.bMarks[nextLine] + state.tShift[nextLine]
maximum = state.eMarks[nextLine]
if start < maximum and state.sCount[nextLine] < state.blkIndent:
# non-empty line with negative indent should stop the list:
# - ```
# test
break
if marker_char != state.srcCharCode[start]:
continue
if state.sCount[nextLine] - state.blkIndent >= 4:
# closing fence should be indented less than 4 spaces
continue
pos = start + 1
while pos <= maximum:
try:
character = state.src[pos]
except IndexError:
break
if marker_str[(pos - start) % marker_len] != character:
break
pos += 1
# closing code fence must be at least as long as the opening one
if floor((pos - start) / marker_len) < marker_count:
continue
# make sure tail has spaces only
pos -= (pos - start) % marker_len
pos = state.skipSpaces(pos)
if pos < maximum:
continue
# found!
auto_closed = True
break
old_parent = state.parentType
old_line_max = state.lineMax
state.parentType = "container"
# this will prevent lazy continuations from ever going past our end marker
state.lineMax = nextLine
token = state.push(f"container_{name}_open", "div", 1)
token.markup = markup
token.block = True
token.info = params
token.map = [startLine, nextLine]
state.md.block.tokenize(state, startLine + 1, nextLine)
token = state.push(f"container_{name}_close", "div", -1)
token.markup = state.src[start:pos]
token.block = True
state.parentType = old_parent
state.lineMax = old_line_max
state.line = nextLine + (1 if auto_closed else 0)
return True
md.block.ruler.before(
"fence",
"container_" + name,
container_func,
{"alt": ["paragraph", "reference", "blockquote", "list"]},
)
md.add_render_rule(f"container_{name}_open", render)
md.add_render_rule(f"container_{name}_close", render)

View File

@ -0,0 +1,5 @@
- package: markdown-it-container
commit: adb3defde3a1c56015895b47ce4c6591b8b1e3a2
date: Jun 2, 2020
version: 3.0.0
changes:

View File

@ -0,0 +1,22 @@
Copyright (c) 2014-2015 Vitaly Puzrin, Alex Kocharin.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,38 @@
# markdown-it-deflist
[![Build Status](https://img.shields.io/travis/markdown-it/markdown-it-deflist/master.svg?style=flat)](https://travis-ci.org/markdown-it/markdown-it-deflist)
[![NPM version](https://img.shields.io/npm/v/markdown-it-deflist.svg?style=flat)](https://www.npmjs.org/package/markdown-it-deflist)
[![Coverage Status](https://img.shields.io/coveralls/markdown-it/markdown-it-deflist/master.svg?style=flat)](https://coveralls.io/r/markdown-it/markdown-it-deflist?branch=master)
> Definition list (`<dl>`) tag plugin for [markdown-it](https://github.com/markdown-it/markdown-it) markdown parser.
__v2.+ requires `markdown-it` v5.+, see changelog.__
Syntax is based on [pandoc definition lists](http://johnmacfarlane.net/pandoc/README.html#definition-lists).
## Install
node.js, browser:
```bash
npm install markdown-it-deflist --save
bower install markdown-it-deflist --save
```
## Use
```js
var md = require('markdown-it')()
.use(require('markdown-it-deflist'));
md.render(/*...*/);
```
_Differences in browser._ If you load script directly into the page, without
package system, module will add itself globally as `window.markdownitDeflist`.
## License
[MIT](https://github.com/markdown-it/markdown-it-deflist/blob/master/LICENSE)

View File

@ -0,0 +1 @@
from .index import deflist_plugin # noqa F401

View File

@ -0,0 +1,253 @@
"""Process definition lists."""
from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock
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 # type: ignore
def skipMarker(state: StateBlock, line: int):
"""Search `[:~][\n ]`, returns next pos after marker on success or -1 on fail."""
start = state.bMarks[line] + state.tShift[line]
maximum = state.eMarks[line]
if start >= maximum:
return -1
# Check bullet
marker = state.srcCharCode[start]
start += 1
if marker != 0x7E and marker != 0x3A: # ~ :
return -1
pos = state.skipSpaces(start)
# require space after ":"
if start == pos:
return -1
# no empty definitions, e.g. " : "
if pos >= maximum:
return -1
return start
def markTightParagraphs(state: StateBlock, idx: int):
level = state.level + 2
i = idx + 2
l2 = len(state.tokens) - 2
while i < l2:
if (
state.tokens[i].level == level
and state.tokens[i].type == "paragraph_open"
):
state.tokens[i + 2].hidden = True
state.tokens[i].hidden = True
i += 2
i += 1
def deflist(state: StateBlock, startLine: int, endLine: int, silent: bool):
if silent:
# quirk: validation mode validates a dd block only, not a whole deflist
if state.ddIndent < 0:
return False
return skipMarker(state, startLine) >= 0
nextLine = startLine + 1
if nextLine >= endLine:
return False
if state.isEmpty(nextLine):
nextLine += 1
if nextLine >= endLine:
return False
if state.sCount[nextLine] < state.blkIndent:
return False
contentStart = skipMarker(state, nextLine)
if contentStart < 0:
return False
# Start list
listTokIdx = len(state.tokens)
tight = True
token = state.push("dl_open", "dl", 1)
token.map = listLines = [startLine, 0]
# Iterate list items
dtLine = startLine
ddLine = nextLine
# One definition list can contain multiple DTs,
# and one DT can be followed by multiple DDs.
#
# Thus, there is two loops here, and label is
# needed to break out of the second one
#
break_outer = False
while True:
prevEmptyEnd = False
token = state.push("dt_open", "dt", 1)
token.map = [dtLine, dtLine]
token = state.push("inline", "", 0)
token.map = [dtLine, dtLine]
token.content = state.getLines(
dtLine, dtLine + 1, state.blkIndent, False
).strip()
token.children = []
token = state.push("dt_close", "dt", -1)
while True:
token = state.push("dd_open", "dd", 1)
token.map = itemLines = [nextLine, 0]
pos = contentStart
maximum = state.eMarks[ddLine]
offset = (
state.sCount[ddLine]
+ contentStart
- (state.bMarks[ddLine] + state.tShift[ddLine])
)
while pos < maximum:
ch = state.srcCharCode[pos]
if isSpace(ch):
if ch == 0x09:
offset += 4 - offset % 4
else:
offset += 1
else:
break
pos += 1
contentStart = pos
oldTight = state.tight
oldDDIndent = state.ddIndent
oldIndent = state.blkIndent
oldTShift = state.tShift[ddLine]
oldSCount = state.sCount[ddLine]
oldParentType = state.parentType
state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2
state.tShift[ddLine] = contentStart - state.bMarks[ddLine]
state.sCount[ddLine] = offset
state.tight = True
state.parentType = "deflist"
state.md.block.tokenize(state, ddLine, endLine, True)
# If any of list item is tight, mark list as tight
if not state.tight or prevEmptyEnd:
tight = False
# Item become loose if finish with empty line,
# but we should filter last element, because it means list finish
prevEmptyEnd = (state.line - ddLine) > 1 and state.isEmpty(
state.line - 1
)
state.tShift[ddLine] = oldTShift
state.sCount[ddLine] = oldSCount
state.tight = oldTight
state.parentType = oldParentType
state.blkIndent = oldIndent
state.ddIndent = oldDDIndent
token = state.push("dd_close", "dd", -1)
itemLines[1] = nextLine = state.line
if nextLine >= endLine:
break_outer = True
break
if state.sCount[nextLine] < state.blkIndent:
break_outer = True
break
contentStart = skipMarker(state, nextLine)
if contentStart < 0:
break
ddLine = nextLine
# go to the next loop iteration:
# insert DD tag and repeat checking
if break_outer:
break_outer = False
break
if nextLine >= endLine:
break
dtLine = nextLine
if state.isEmpty(dtLine):
break
if state.sCount[dtLine] < state.blkIndent:
break
ddLine = dtLine + 1
if ddLine >= endLine:
break
if state.isEmpty(ddLine):
ddLine += 1
if ddLine >= endLine:
break
if state.sCount[ddLine] < state.blkIndent:
break
contentStart = skipMarker(state, ddLine)
if contentStart < 0:
break
# go to the next loop iteration:
# insert DT and DD tags and repeat checking
# Finalise list
token = state.push("dl_close", "dl", -1)
listLines[1] = nextLine
state.line = nextLine
# mark paragraphs tight if needed
if tight:
markTightParagraphs(state, listTokIdx)
return True
md.block.ruler.before(
"paragraph",
"deflist",
deflist,
{"alt": ["paragraph", "reference", "blockquote"]},
)

View File

@ -0,0 +1,5 @@
- package: markdown-it-deflist
commit: 20db400948520308291da029a23b0751cb30f3a0
date: July 12, 2017
version: 2.0.3
changes:

View File

@ -0,0 +1 @@
from .index import dollarmath_plugin # noqa F401

View File

@ -0,0 +1,339 @@
import re
from typing import Any, Callable, Dict, Optional
from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml, isWhiteSpace
from markdown_it.rules_block import StateBlock
from markdown_it.rules_inline import StateInline
def dollarmath_plugin(
md: MarkdownIt,
*,
allow_labels: bool = True,
allow_space: bool = True,
allow_digits: bool = True,
double_inline: bool = False,
label_normalizer: Optional[Callable[[str], str]] = None,
renderer: Optional[Callable[[str, Dict[str, Any]], str]] = None,
label_renderer: Optional[Callable[[str], str]] = None,
) -> None:
"""Plugin for parsing dollar enclosed math,
e.g. inline: ``$a=1$``, block: ``$$b=2$$``
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 $``
:param allow_digits: Parse inline math when there is a digit
before/after the opening/closing ``$``, e.g. ``1$`` or ``$2``.
This is useful when also using currency.
:param double_inline: Search for double-dollar math within inline contexts
:param label_normalizer: Function to normalize the label,
by default replaces whitespace with `-`
:param renderer: Function to render content: `(str, {"display_mode": bool}) -> str`,
by default escapes HTML
:param label_renderer: Function to render labels, by default creates anchor
"""
if label_normalizer is None:
label_normalizer = lambda label: re.sub(r"\s+", "-", label)
md.inline.ruler.before(
"escape",
"math_inline",
math_inline_dollar(allow_space, allow_digits, double_inline),
)
md.block.ruler.before(
"fence", "math_block", math_block_dollar(allow_labels, label_normalizer)
)
# TODO the current render rules are really just for testing
# would be good to allow "proper" math rendering,
# e.g. https://github.com/roniemartinez/latex2mathml
if renderer is None:
_renderer = lambda content, _: escapeHtml(content)
else:
_renderer = renderer
if label_renderer is None:
_label_renderer = (
lambda label: f'<a href="#{label}" class="mathlabel" title="Permalink to this equation">¶</a>' # noqa: E501
)
else:
_label_renderer = label_renderer
def render_math_inline(self, tokens, idx, options, env) -> str:
content = _renderer(str(tokens[idx].content).strip(), {"display_mode": False})
return f'<span class="math inline">{content}</span>'
def render_math_inline_double(self, tokens, idx, options, env) -> str:
content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True})
return f'<div class="math inline">{content}</div>'
def render_math_block(self, tokens, idx, options, env) -> str:
content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True})
return f'<div class="math block">\n{content}\n</div>\n'
def render_math_block_label(self, tokens, idx, options, env) -> str:
content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True})
_id = tokens[idx].info
label = _label_renderer(tokens[idx].info)
return f'<div id="{_id}" class="math block">\n{label}\n{content}\n</div>\n'
md.add_render_rule("math_inline", render_math_inline)
md.add_render_rule("math_inline_double", render_math_inline_double)
md.add_render_rule("math_block", render_math_block)
md.add_render_rule("math_block_label", render_math_block_label)
def is_escaped(state: StateInline, back_pos: int, mod: int = 0) -> bool:
"""Test if dollar is escaped."""
# count how many \ are before the current position
backslashes = 0
while back_pos >= 0:
back_pos = back_pos - 1
if state.srcCharCode[back_pos] == 0x5C: # /* \ */
backslashes += 1
else:
break
if not backslashes:
return False
# if an odd number of \ then ignore
if (backslashes % 2) != mod:
return True
return False
def math_inline_dollar(
allow_space: bool = True, allow_digits: bool = True, allow_double: bool = False
) -> Callable[[StateInline, bool], bool]:
"""Generate inline dollar rule.
:param allow_space: Parse inline math when there is space
after/before the opening/closing ``$``, e.g. ``$ a $``
:param allow_digits: Parse inline math when there is a digit
before/after the opening/closing ``$``, e.g. ``1$`` or ``$2``.
This is useful when also using currency.
:param allow_double: Search for double-dollar math within inline contexts
"""
def _math_inline_dollar(state: StateInline, silent: bool) -> bool:
"""Inline dollar rule.
- Initial check:
- check if first character is a $
- check if the first character is escaped
- check if the next character is a space (if not allow_space)
- check if the next character is a digit (if not allow_digits)
- Advance one, if allow_double
- Find closing (advance one, if allow_double)
- Check closing:
- check if the previous character is a space (if not allow_space)
- check if the next character is a digit (if not allow_digits)
- Check empty content
"""
# TODO options:
# even/odd backslash escaping
if state.srcCharCode[state.pos] != 0x24: # /* $ */
return False
if not allow_space:
# whitespace not allowed straight after opening $
try:
if isWhiteSpace(state.srcCharCode[state.pos + 1]):
return False
except IndexError:
return False
if not allow_digits:
# digit not allowed straight before opening $
try:
if state.src[state.pos - 1].isdigit():
return False
except IndexError:
pass
if is_escaped(state, state.pos):
return False
try:
is_double = allow_double and state.srcCharCode[state.pos + 1] == 0x24
except IndexError:
return False
# find closing $
pos = state.pos + 1 + (1 if is_double else 0)
found_closing = False
while not found_closing:
try:
end = state.srcCharCode.index(0x24, pos)
except ValueError:
return False
if is_escaped(state, end):
pos = end + 1
continue
try:
if is_double and not state.srcCharCode[end + 1] == 0x24:
pos = end + 1
continue
except IndexError:
return False
if is_double:
end += 1
found_closing = True
if not found_closing:
return False
if not allow_space:
# whitespace not allowed straight before closing $
try:
if isWhiteSpace(state.srcCharCode[end - 1]):
return False
except IndexError:
return False
if not allow_digits:
# digit not allowed straight after closing $
try:
if state.src[end + 1].isdigit():
return False
except IndexError:
pass
text = (
state.src[state.pos + 2 : end - 1]
if is_double
else state.src[state.pos + 1 : end]
)
# ignore empty
if not text:
return False
if not silent:
token = state.push(
"math_inline_double" if is_double else "math_inline", "math", 0
)
token.content = text
token.markup = "$$" if is_double else "$"
state.pos = end + 1
return True
return _math_inline_dollar
# reversed end of block dollar equation, with equation label
DOLLAR_EQNO_REV = re.compile(r"^\s*\)([^)$\r\n]+?)\(\s*\${2}")
def math_block_dollar(
allow_labels: bool = True,
label_normalizer: Optional[Callable[[str], str]] = None,
) -> Callable[[StateBlock, int, int, bool], bool]:
"""Generate block dollar rule."""
def _math_block_dollar(
state: StateBlock, startLine: int, endLine: int, silent: bool
) -> bool:
# TODO internal backslash escaping
haveEndMarker = False
startPos = state.bMarks[startLine] + state.tShift[startLine]
end = state.eMarks[startLine]
# if it's indented more than 3 spaces, it should be a code block
if state.sCount[startLine] - state.blkIndent >= 4:
return False
if startPos + 2 > end:
return False
if (
state.srcCharCode[startPos] != 0x24
or state.srcCharCode[startPos + 1] != 0x24
): # /* $ */
return False
# search for end of block
nextLine = startLine
label = None
# search for end of block on same line
lineText = state.src[startPos:end]
if len(lineText.strip()) > 3:
if lineText.strip().endswith("$$"):
haveEndMarker = True
end = end - 2 - (len(lineText) - len(lineText.strip()))
elif allow_labels:
# reverse the line and match
eqnoMatch = DOLLAR_EQNO_REV.match(lineText[::-1])
if eqnoMatch:
haveEndMarker = True
label = eqnoMatch.group(1)[::-1]
end = end - eqnoMatch.end()
# search for end of block on subsequent line
if not haveEndMarker:
while True:
nextLine += 1
if nextLine >= endLine:
break
start = state.bMarks[nextLine] + state.tShift[nextLine]
end = state.eMarks[nextLine]
if end - start < 2:
continue
lineText = state.src[start:end]
if lineText.strip().endswith("$$"):
haveEndMarker = True
end = end - 2 - (len(lineText) - len(lineText.strip()))
break
# reverse the line and match
if allow_labels:
eqnoMatch = DOLLAR_EQNO_REV.match(lineText[::-1])
if eqnoMatch:
haveEndMarker = True
label = eqnoMatch.group(1)[::-1]
end = end - eqnoMatch.end()
break
if not haveEndMarker:
return False
state.line = nextLine + (1 if haveEndMarker else 0)
token = state.push("math_block_label" if label else "math_block", "math", 0)
token.block = True
token.content = state.src[startPos + 2 : end]
token.markup = "$$"
token.map = [startLine, state.line]
if label:
token.info = label if label_normalizer is None else label_normalizer(label)
return True
return _math_block_dollar

View File

@ -0,0 +1,208 @@
"""Field list plugin"""
from contextlib import contextmanager
from typing import Tuple
from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock
def fieldlist_plugin(md: MarkdownIt):
"""Field lists are mappings from field names to field bodies, based on the
`reStructureText syntax
<https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#field-lists>`_.
.. code-block:: md
:name *markup*:
:name1: body content
:name2: paragraph 1
paragraph 2
:name3:
paragraph 1
paragraph 2
A field name may consist of any characters except colons (":").
Inline markup is parsed in field names.
The field name is followed by whitespace and the field body.
The field body may be empty or contain multiple body elements.
The field body is aligned either by the start of the body on the first line or,
if no body content is on the first line, by 2 spaces.
"""
md.block.ruler.before(
"paragraph",
"fieldlist",
_fieldlist_rule,
{"alt": ["paragraph", "reference", "blockquote"]},
)
def parseNameMarker(state: StateBlock, startLine: int) -> Tuple[int, str]:
"""Parse field name: `:name:`
:returns: position after name marker, name text
"""
start = state.bMarks[startLine] + state.tShift[startLine]
pos = start
maximum = state.eMarks[startLine]
# marker should have at least 3 chars (colon + character + colon)
if pos + 2 >= maximum:
return -1, ""
# first character should be ':'
if state.src[pos] != ":":
return -1, ""
# scan name length
name_length = 1
found_close = False
for ch in state.src[pos + 1 :]:
if ch == "\n":
break
if ch == ":":
# TODO backslash escapes
found_close = True
break
name_length += 1
if not found_close:
return -1, ""
# get name
name_text = state.src[pos + 1 : pos + name_length]
# name should contain at least one character
if not name_text.strip():
return -1, ""
return pos + name_length + 1, name_text
@contextmanager
def set_parent_type(state: StateBlock, name: str):
"""Temporarily set parent type to `name`"""
oldParentType = state.parentType
state.parentType = name
yield
state.parentType = oldParentType
def _fieldlist_rule(state: StateBlock, startLine: int, endLine: int, silent: bool):
# adapted from markdown_it/rules_block/list.py::list_block
# if it's indented more than 3 spaces, it should be a code block
if state.sCount[startLine] - state.blkIndent >= 4:
return False
posAfterName, name_text = parseNameMarker(state, startLine)
if posAfterName < 0:
return False
# For validation mode we can terminate immediately
if silent:
return True
# start field list
token = state.push("field_list_open", "dl", 1)
token.attrSet("class", "field-list")
token.map = listLines = [startLine, 0]
# iterate list items
nextLine = startLine
with set_parent_type(state, "fieldlist"):
while nextLine < endLine:
# create name tokens
token = state.push("fieldlist_name_open", "dt", 1)
token.map = [startLine, startLine]
token = state.push("inline", "", 0)
token.map = [startLine, startLine]
token.content = name_text
token.children = []
token = state.push("fieldlist_name_close", "dt", -1)
# set indent positions
pos = posAfterName
maximum = state.eMarks[nextLine]
offset = (
state.sCount[nextLine]
+ posAfterName
- (state.bMarks[startLine] + state.tShift[startLine])
)
# find indent to start of body on first line
while pos < maximum:
ch = state.srcCharCode[pos]
if ch == 0x09: # \t
offset += 4 - (offset + state.bsCount[nextLine]) % 4
elif ch == 0x20: # \s
offset += 1
else:
break
pos += 1
contentStart = pos
# set indent for body text
if contentStart >= maximum:
# no body on first line, so use constant indentation
# TODO adapt to indentation of subsequent lines?
indent = 2
else:
indent = offset
# Run subparser on the field body
token = state.push("fieldlist_body_open", "dd", 1)
token.map = itemLines = [startLine, 0]
# change current state, then restore it after parser subcall
oldTShift = state.tShift[startLine]
oldSCount = state.sCount[startLine]
oldBlkIndent = state.blkIndent
state.tShift[startLine] = contentStart - state.bMarks[startLine]
state.sCount[startLine] = offset
state.blkIndent = indent
state.md.block.tokenize(state, startLine, endLine)
state.blkIndent = oldBlkIndent
state.tShift[startLine] = oldTShift
state.sCount[startLine] = oldSCount
token = state.push("fieldlist_body_close", "dd", -1)
nextLine = startLine = state.line
itemLines[1] = nextLine
if nextLine >= endLine:
break
contentStart = state.bMarks[startLine]
# Try to check if list is terminated or continued.
if state.sCount[nextLine] < state.blkIndent:
break
# if it's indented more than 3 spaces, it should be a code block
if state.sCount[startLine] - state.blkIndent >= 4:
break
# get next field item
posAfterName, name_text = parseNameMarker(state, startLine)
if posAfterName < 0:
break
# Finalize list
token = state.push("field_list_close", "dl", -1)
listLines[1] = nextLine
state.line = nextLine
return True

View File

@ -0,0 +1,22 @@
Copyright (c) 2014-2015 Vitaly Puzrin, Alex Kocharin.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1 @@
from .index import footnote_plugin # noqa: F401

View File

@ -0,0 +1,430 @@
# Process footnotes
#
from typing import List, Optional
from markdown_it import MarkdownIt
from markdown_it.common.utils import isSpace
from markdown_it.helpers import parseLinkLabel
from markdown_it.rules_block import StateBlock
from markdown_it.rules_inline import StateInline
from markdown_it.token import Token
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"]}
)
md.inline.ruler.after("image", "footnote_inline", footnote_inline)
md.inline.ruler.after("footnote_inline", "footnote_ref", footnote_ref)
md.core.ruler.after("inline", "footnote_tail", footnote_tail)
md.add_render_rule("footnote_ref", render_footnote_ref)
md.add_render_rule("footnote_block_open", render_footnote_block_open)
md.add_render_rule("footnote_block_close", render_footnote_block_close)
md.add_render_rule("footnote_open", render_footnote_open)
md.add_render_rule("footnote_close", render_footnote_close)
md.add_render_rule("footnote_anchor", render_footnote_anchor)
# helpers (only used in other rules, no tokens are attached to those)
md.add_render_rule("footnote_caption", render_footnote_caption)
md.add_render_rule("footnote_anchor_name", render_footnote_anchor_name)
# ## RULES ##
def footnote_def(state: StateBlock, startLine: int, endLine: int, silent: bool):
"""Process footnote block definition"""
start = state.bMarks[startLine] + state.tShift[startLine]
maximum = state.eMarks[startLine]
# line should be at least 5 chars - "[^x]:"
if start + 4 > maximum:
return False
if state.srcCharCode[start] != 0x5B: # /* [ */
return False
if state.srcCharCode[start + 1] != 0x5E: # /* ^ */
return False
pos = start + 2
while pos < maximum:
if state.srcCharCode[pos] == 0x20:
return False
if state.srcCharCode[pos] == 0x5D: # /* ] */
break
pos += 1
if pos == start + 2: # no empty footnote labels
return False
pos += 1
if pos >= maximum or state.srcCharCode[pos] != 0x3A: # /* : */
return False
if silent:
return True
pos += 1
label = state.src[start + 2 : pos - 2]
state.env.setdefault("footnotes", {}).setdefault("refs", {})[":" + label] = -1
open_token = Token("footnote_reference_open", "", 1)
open_token.meta = {"label": label}
open_token.level = state.level
state.level += 1
state.tokens.append(open_token)
oldBMark = state.bMarks[startLine]
oldTShift = state.tShift[startLine]
oldSCount = state.sCount[startLine]
oldParentType = state.parentType
posAfterColon = pos
initial = offset = (
state.sCount[startLine]
+ pos
- (state.bMarks[startLine] + state.tShift[startLine])
)
while pos < maximum:
ch = state.srcCharCode[pos]
if isSpace(ch):
if ch == 0x09:
offset += 4 - offset % 4
else:
offset += 1
else:
break
pos += 1
state.tShift[startLine] = pos - posAfterColon
state.sCount[startLine] = offset - initial
state.bMarks[startLine] = posAfterColon
state.blkIndent += 4
state.parentType = "footnote"
if state.sCount[startLine] < state.blkIndent:
state.sCount[startLine] += state.blkIndent
state.md.block.tokenize(state, startLine, endLine, True)
state.parentType = oldParentType
state.blkIndent -= 4
state.tShift[startLine] = oldTShift
state.sCount[startLine] = oldSCount
state.bMarks[startLine] = oldBMark
open_token.map = [startLine, state.line]
token = Token("footnote_reference_close", "", -1)
state.level -= 1
token.level = state.level
state.tokens.append(token)
return True
def footnote_inline(state: StateInline, silent: bool):
"""Process inline footnotes (^[...])"""
maximum = state.posMax
start = state.pos
if start + 2 >= maximum:
return False
if state.srcCharCode[start] != 0x5E: # /* ^ */
return False
if state.srcCharCode[start + 1] != 0x5B: # /* [ */
return False
labelStart = start + 2
labelEnd = parseLinkLabel(state, start + 1)
# parser failed to find ']', so it's not a valid note
if labelEnd < 0:
return False
# We found the end of the link, and know for a fact it's a valid link
# so all that's left to do is to call tokenizer.
#
if not silent:
refs = state.env.setdefault("footnotes", {}).setdefault("list", {})
footnoteId = len(refs)
tokens: List[Token] = []
state.md.inline.parse(
state.src[labelStart:labelEnd], state.md, state.env, tokens
)
token = state.push("footnote_ref", "", 0)
token.meta = {"id": footnoteId}
refs[footnoteId] = {"content": state.src[labelStart:labelEnd], "tokens": tokens}
state.pos = labelEnd + 1
state.posMax = maximum
return True
def footnote_ref(state: StateInline, silent: bool):
"""Process footnote references ([^...])"""
maximum = state.posMax
start = state.pos
# should be at least 4 chars - "[^x]"
if start + 3 > maximum:
return False
if "footnotes" not in state.env or "refs" not in state.env["footnotes"]:
return False
if state.srcCharCode[start] != 0x5B: # /* [ */
return False
if state.srcCharCode[start + 1] != 0x5E: # /* ^ */
return False
pos = start + 2
while pos < maximum:
if state.srcCharCode[pos] == 0x20:
return False
if state.srcCharCode[pos] == 0x0A:
return False
if state.srcCharCode[pos] == 0x5D: # /* ] */
break
pos += 1
if pos == start + 2: # no empty footnote labels
return False
if pos >= maximum:
return False
pos += 1
label = state.src[start + 2 : pos - 1]
if (":" + label) not in state.env["footnotes"]["refs"]:
return False
if not silent:
if "list" not in state.env["footnotes"]:
state.env["footnotes"]["list"] = {}
if state.env["footnotes"]["refs"][":" + label] < 0:
footnoteId = len(state.env["footnotes"]["list"])
state.env["footnotes"]["list"][footnoteId] = {"label": label, "count": 0}
state.env["footnotes"]["refs"][":" + label] = footnoteId
else:
footnoteId = state.env["footnotes"]["refs"][":" + label]
footnoteSubId = state.env["footnotes"]["list"][footnoteId]["count"]
state.env["footnotes"]["list"][footnoteId]["count"] += 1
token = state.push("footnote_ref", "", 0)
token.meta = {"id": footnoteId, "subId": footnoteSubId, "label": label}
state.pos = pos
state.posMax = maximum
return True
def footnote_tail(state: StateBlock, *args, **kwargs):
"""Post-processing step, to move footnote tokens to end of the token stream.
Also removes un-referenced tokens.
"""
insideRef = False
refTokens = {}
if "footnotes" not in state.env:
return
current: List[Token] = []
tok_filter = []
for tok in state.tokens:
if tok.type == "footnote_reference_open":
insideRef = True
current = []
currentLabel = tok.meta["label"]
tok_filter.append(False)
continue
if tok.type == "footnote_reference_close":
insideRef = False
# prepend ':' to avoid conflict with Object.prototype members
refTokens[":" + currentLabel] = current
tok_filter.append(False)
continue
if insideRef:
current.append(tok)
tok_filter.append((not insideRef))
state.tokens = [t for t, f in zip(state.tokens, tok_filter) if f]
if "list" not in state.env.get("footnotes", {}):
return
foot_list = state.env["footnotes"]["list"]
token = Token("footnote_block_open", "", 1)
state.tokens.append(token)
for i, foot_note in foot_list.items():
token = Token("footnote_open", "", 1)
token.meta = {"id": i, "label": foot_note.get("label", None)}
# TODO propagate line positions of original foot note
# (but don't store in token.map, because this is used for scroll syncing)
state.tokens.append(token)
if "tokens" in foot_note:
tokens = []
token = Token("paragraph_open", "p", 1)
token.block = True
tokens.append(token)
token = Token("inline", "", 0)
token.children = foot_note["tokens"]
token.content = foot_note["content"]
tokens.append(token)
token = Token("paragraph_close", "p", -1)
token.block = True
tokens.append(token)
elif "label" in foot_note:
tokens = refTokens[":" + foot_note["label"]]
state.tokens.extend(tokens)
if state.tokens[len(state.tokens) - 1].type == "paragraph_close":
lastParagraph: Optional[Token] = state.tokens.pop()
else:
lastParagraph = None
t = (
foot_note["count"]
if (("count" in foot_note) and (foot_note["count"] > 0))
else 1
)
j = 0
while j < t:
token = Token("footnote_anchor", "", 0)
token.meta = {"id": i, "subId": j, "label": foot_note.get("label", None)}
state.tokens.append(token)
j += 1
if lastParagraph:
state.tokens.append(lastParagraph)
token = Token("footnote_close", "", -1)
state.tokens.append(token)
token = Token("footnote_block_close", "", -1)
state.tokens.append(token)
########################################
# Renderer partials
def render_footnote_anchor_name(self, tokens, idx, options, env):
n = str(tokens[idx].meta["id"] + 1)
prefix = ""
doc_id = env.get("docId", None)
if isinstance(doc_id, str):
prefix = f"-{doc_id}-"
return prefix + n
def render_footnote_caption(self, tokens, idx, options, env):
n = str(tokens[idx].meta["id"] + 1)
if tokens[idx].meta.get("subId", -1) > 0:
n += ":" + str(tokens[idx].meta["subId"])
return "[" + n + "]"
def render_footnote_ref(self, tokens, idx, options, env):
ident = self.rules["footnote_anchor_name"](tokens, idx, options, env)
caption = self.rules["footnote_caption"](tokens, idx, options, env)
refid = ident
if tokens[idx].meta.get("subId", -1) > 0:
refid += ":" + str(tokens[idx].meta["subId"])
return (
'<sup class="footnote-ref"><a href="#fn'
+ ident
+ '" id="fnref'
+ refid
+ '">'
+ caption
+ "</a></sup>"
)
def render_footnote_block_open(self, tokens, idx, options, env):
return (
(
'<hr class="footnotes-sep" />\n'
if options.xhtmlOut
else '<hr class="footnotes-sep">\n'
)
+ '<section class="footnotes">\n'
+ '<ol class="footnotes-list">\n'
)
def render_footnote_block_close(self, tokens, idx, options, env):
return "</ol>\n</section>\n"
def render_footnote_open(self, tokens, idx, options, env):
ident = self.rules["footnote_anchor_name"](tokens, idx, options, env)
if tokens[idx].meta.get("subId", -1) > 0:
ident += ":" + tokens[idx].meta["subId"]
return '<li id="fn' + ident + '" class="footnote-item">'
def render_footnote_close(self, tokens, idx, options, env):
return "</li>\n"
def render_footnote_anchor(self, tokens, idx, options, env):
ident = self.rules["footnote_anchor_name"](tokens, idx, options, env)
if tokens[idx].meta["subId"] > 0:
ident += ":" + str(tokens[idx].meta["subId"])
# ↩ with escape code to prevent display as Apple Emoji on iOS
return ' <a href="#fnref' + ident + '" class="footnote-backref">\u21a9\uFE0E</a>'

View File

@ -0,0 +1,4 @@
- package: markdown-it-footnote
commit: cab6665ba39c6eb517cbbae3baeb549004bf740c
date: Jul 9, 2019
version: 3.0.2

View File

@ -0,0 +1,22 @@
Copyright (c) 2016-2020 ParkSB.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1 @@
from .index import front_matter_plugin # noqa: F401

View File

@ -0,0 +1,138 @@
# Process front matter and pass to cb
from math import floor
from markdown_it import MarkdownIt
from markdown_it.common.utils import charCodeAt
from markdown_it.rules_block import StateBlock
def front_matter_plugin(md: MarkdownIt):
"""Plugin ported from
`markdown-it-front-matter <https://github.com/ParkSB/markdown-it-front-matter>`__.
It parses initial metadata, stored between opening/closing dashes:
.. code-block:: md
---
valid-front-matter: true
---
"""
frontMatter = make_front_matter_rule()
md.block.ruler.before(
"table",
"front_matter",
frontMatter,
{"alt": ["paragraph", "reference", "blockquote", "list"]},
)
def make_front_matter_rule():
min_markers = 3
marker_str = "-"
marker_char = charCodeAt(marker_str, 0)
marker_len = len(marker_str)
def frontMatter(state: StateBlock, startLine: int, endLine: int, silent: bool):
auto_closed = False
start = state.bMarks[startLine] + state.tShift[startLine]
maximum = state.eMarks[startLine]
src_len = len(state.src)
# Check out the first character of the first line quickly,
# this should filter out non-front matter
if startLine != 0 or marker_char != state.srcCharCode[0]:
return False
# Check out the rest of the marker string
# while pos <= 3
pos = start + 1
while pos <= maximum and pos < src_len:
if marker_str[(pos - start) % marker_len] != state.src[pos]:
break
pos += 1
marker_count = floor((pos - start) / marker_len)
if marker_count < min_markers:
return False
pos -= (pos - start) % marker_len
# Since start is found, we can report success here in validation mode
if silent:
return True
# Search for the end of the block
nextLine = startLine
while True:
nextLine += 1
if nextLine >= endLine:
# unclosed block should be autoclosed by end of document.
return False
if state.src[start:maximum] == "...":
break
start = state.bMarks[nextLine] + state.tShift[nextLine]
maximum = state.eMarks[nextLine]
if start < maximum and state.sCount[nextLine] < state.blkIndent:
# non-empty line with negative indent should stop the list:
# - ```
# test
break
if marker_char != state.srcCharCode[start]:
continue
if state.sCount[nextLine] - state.blkIndent >= 4:
# closing fence should be indented less than 4 spaces
continue
pos = start + 1
while pos < maximum:
if marker_str[(pos - start) % marker_len] != state.src[pos]:
break
pos += 1
# closing code fence must be at least as long as the opening one
if floor((pos - start) / marker_len) < marker_count:
continue
# make sure tail has spaces only
pos -= (pos - start) % marker_len
pos = state.skipSpaces(pos)
if pos < maximum:
continue
# found!
auto_closed = True
break
old_parent = state.parentType
old_line_max = state.lineMax
state.parentType = "container"
# this will prevent lazy continuations from ever going past our end marker
state.lineMax = nextLine
token = state.push("front_matter", "", 0)
token.hidden = True
token.markup = marker_str * min_markers
token.content = state.src[
state.bMarks[startLine + 1] : state.eMarks[nextLine - 1]
]
token.block = True
state.parentType = old_parent
state.lineMax = old_line_max
state.line = nextLine + (1 if auto_closed else 0)
token.map = [startLine, state.line]
return True
return frontMatter

View File

@ -0,0 +1,4 @@
- package: markdown-it-front-matter
commit: b404f5d8fd536e7e9ddb276267ae0b6f76e9cf9d
date: Feb 7, 2020
version: 0.2.1

View File

@ -0,0 +1 @@
from .index import myst_block_plugin # noqa: F401

View File

@ -0,0 +1,153 @@
import itertools
from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml, isSpace
from markdown_it.rules_block import StateBlock
def myst_block_plugin(md: MarkdownIt):
"""Parse MyST targets (``(name)=``), blockquotes (``% comment``) and block breaks (``+++``)."""
md.block.ruler.before(
"blockquote",
"myst_line_comment",
line_comment,
{"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
)
md.block.ruler.before(
"hr",
"myst_block_break",
block_break,
{"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
)
md.block.ruler.before(
"hr",
"myst_target",
target,
{"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
)
md.add_render_rule("myst_target", render_myst_target)
md.add_render_rule("myst_line_comment", render_myst_line_comment)
def line_comment(state: StateBlock, startLine: int, endLine: int, silent: bool):
pos = state.bMarks[startLine] + state.tShift[startLine]
maximum = state.eMarks[startLine]
# if it's indented more than 3 spaces, it should be a code block
if state.sCount[startLine] - state.blkIndent >= 4:
return False
if state.src[pos] != "%":
return False
if silent:
return True
token = state.push("myst_line_comment", "", 0)
token.attrSet("class", "myst-line-comment")
token.content = state.src[pos + 1 : maximum].rstrip()
token.markup = "%"
# search end of block while appending lines to `token.content`
for nextLine in itertools.count(startLine + 1):
if nextLine >= endLine:
break
pos = state.bMarks[nextLine] + state.tShift[nextLine]
maximum = state.eMarks[nextLine]
if state.src[pos] != "%":
break
token.content += "\n" + state.src[pos + 1 : maximum].rstrip()
state.line = nextLine
token.map = [startLine, nextLine]
return True
def block_break(state: StateBlock, startLine: int, endLine: int, silent: bool):
pos = state.bMarks[startLine] + state.tShift[startLine]
maximum = state.eMarks[startLine]
# if it's indented more than 3 spaces, it should be a code block
if state.sCount[startLine] - state.blkIndent >= 4:
return False
marker = state.srcCharCode[pos]
pos += 1
# Check block marker /* + */
if marker != 0x2B:
return False
# markers can be mixed with spaces, but there should be at least 3 of them
cnt = 1
while pos < maximum:
ch = state.srcCharCode[pos]
if ch != marker and not isSpace(ch):
break
if ch == marker:
cnt += 1
pos += 1
if cnt < 3:
return False
if silent:
return True
state.line = startLine + 1
token = state.push("myst_block_break", "hr", 0)
token.attrSet("class", "myst-block")
token.content = state.src[pos:maximum].strip()
token.map = [startLine, state.line]
token.markup = chr(marker) * cnt
return True
def target(state: StateBlock, startLine: int, endLine: int, silent: bool):
pos = state.bMarks[startLine] + state.tShift[startLine]
maximum = state.eMarks[startLine]
# if it's indented more than 3 spaces, it should be a code block
if state.sCount[startLine] - state.blkIndent >= 4:
return False
text = state.src[pos:maximum].strip()
if not text.startswith("("):
return False
if not text.endswith(")="):
return False
if not text[1:-2]:
return False
if silent:
return True
state.line = startLine + 1
token = state.push("myst_target", "", 0)
token.attrSet("class", "myst-target")
token.content = text[1:-2]
token.map = [startLine, state.line]
return True
def render_myst_target(self, tokens, idx, options, env):
label = tokens[idx].content
class_name = "myst-target"
target = f'<a href="#{label}">({label})=</a>'
return f'<div class="{class_name}">{target}</div>'
def render_myst_line_comment(self, tokens, idx, options, env):
# Strip leading whitespace from all lines
content = "\n".join(line.lstrip() for line in tokens[idx].content.split("\n"))
return f"<!-- {escapeHtml(content)} -->"

View File

@ -0,0 +1 @@
from .index import myst_role_plugin # noqa: F401

View File

@ -0,0 +1,65 @@
import re
from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml
from markdown_it.rules_inline import StateInline
VALID_NAME_PATTERN = re.compile(r"^\{([a-zA-Z0-9\_\-\+\:]+)\}")
def myst_role_plugin(md: MarkdownIt):
"""Parse ``{role-name}`content```"""
md.inline.ruler.before("backticks", "myst_role", myst_role)
md.add_render_rule("myst_role", render_myst_role)
def myst_role(state: StateInline, silent: bool):
# check name
match = VALID_NAME_PATTERN.match(state.src[state.pos :])
if not match:
return False
name = match.group(1)
# check for starting backslash escape
try:
if state.srcCharCode[state.pos - 1] == 0x5C: # /* \ */
# escaped (this could be improved in the case of edge case '\\{')
return False
except IndexError:
pass
# scan opening tick length
start = pos = state.pos + match.end()
try:
while state.src[pos] == "`":
pos += 1
except IndexError:
return False
tick_length = pos - start
if not tick_length:
return False
# search for closing ticks
match = re.search("`" * tick_length, state.src[pos + 1 :])
if not match:
return False
content = state.src[pos : pos + match.start() + 1].replace("\n", " ")
if not silent:
token = state.push("myst_role", "", 0)
token.meta = {"name": name}
token.content = content
state.pos = pos + match.end() + 1
return True
def render_myst_role(self, tokens, idx, options, env):
token = tokens[idx]
name = token.meta.get("name", "unknown")
return (
'<code class="myst role">' f"{{{name}}}[{escapeHtml(token.content)}]" "</code>"
)

1
mdit_py_plugins/py.typed Normal file
View File

@ -0,0 +1 @@
# Marker file for PEP 561

View File

@ -0,0 +1,113 @@
from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock
from markdown_it.rules_inline import StateInline
def substitution_plugin(
md: MarkdownIt, start_delimiter: str = "{", end_delimiter: str = "}"
):
"""A plugin to create substitution tokens.
These, token should be handled by the renderer.
Example::
{{ block }}
a {{ inline }} b
"""
start_char = ord(start_delimiter)
end_char = ord(end_delimiter)
def _substitution_inline(state: StateInline, silent: bool):
try:
if (
state.srcCharCode[state.pos] != start_char
or state.srcCharCode[state.pos + 1] != start_char
):
return False
except IndexError:
return False
pos = state.pos + 2
found_closing = False
while True:
try:
end = state.srcCharCode.index(end_char, pos)
except ValueError:
return False
try:
if state.srcCharCode[end + 1] == end_char:
found_closing = True
break
except IndexError:
return False
pos = end + 2
if not found_closing:
return False
text = state.src[state.pos + 2 : end].strip()
state.pos = end + 2
if silent:
return True
token = state.push("substitution_inline", "span", 0)
token.block = False
token.content = text
token.attrSet("class", "substitution")
token.attrSet("text", text)
token.markup = f"{start_delimiter}{end_delimiter}"
return True
def _substitution_block(
state: StateBlock, startLine: int, endLine: int, silent: bool
):
startPos = state.bMarks[startLine] + state.tShift[startLine]
end = state.eMarks[startLine]
# if it's indented more than 3 spaces, it should be a code block
if state.sCount[startLine] - state.blkIndent >= 4:
return False
lineText = state.src[startPos:end].strip()
try:
if (
lineText[0] != start_delimiter
or lineText[1] != start_delimiter
or lineText[-1] != end_delimiter
or lineText[-2] != end_delimiter
or len(lineText) < 5
):
return False
except IndexError:
return False
text = lineText[2:-2].strip()
# special case if multiple on same line, e.g. {{a}}{{b}}
if (end_delimiter * 2) in text:
return False
state.line = startLine + 1
if silent:
return True
token = state.push("substitution_block", "div", 0)
token.block = True
token.content = text
token.attrSet("class", "substitution")
token.attrSet("text", text)
token.markup = f"{start_delimiter}{end_delimiter}"
token.map = [startLine, state.line]
return True
md.block.ruler.before("fence", "substitution_block", _substitution_block)
md.inline.ruler.before("escape", "substitution_inline", _substitution_inline)

View File

@ -0,0 +1,153 @@
"""Builds task/todo lists out of markdown lists with items starting with [ ] or [x]"""
# Ported by Wolmar Nyberg Åkerström from https://github.com/revin/markdown-it-task-lists
# ISC License
# Copyright (c) 2016, Revin Guillen
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import re
from typing import List
from uuid import uuid4
from markdown_it import MarkdownIt
from markdown_it.token import Token
# Regex string to match a whitespace character, as specified in
# https://github.github.com/gfm/#whitespace-character
# (spec version 0.29-gfm (2019-04-06))
_GFM_WHITESPACE_RE = r"[ \t\n\v\f\r]"
def tasklists_plugin(
md: MarkdownIt,
enabled: bool = False,
label: bool = False,
label_after: bool = False,
):
"""Plugin for building task/todo lists out of markdown lists with items starting with [ ] or [x]
.. Nothing else
For example::
- [ ] An item that needs doing
- [x] An item that is complete
The rendered HTML checkboxes are disabled; to change this, pass a truthy value into the enabled
property of the plugin options.
:param enabled: True enables the rendered checkboxes
:param label: True wraps the rendered list items in a <label> element for UX purposes,
:param label_after: True adds the <label> element after the checkbox.
"""
disable_checkboxes = not enabled
use_label_wrapper = label
use_label_after = label_after
def fcn(state):
tokens: List[Token] = state.tokens
for i in range(2, len(tokens) - 1):
if is_todo_item(tokens, i):
todoify(tokens[i], tokens[i].__class__)
tokens[i - 2].attrSet(
"class",
"task-list-item" + (" enabled" if not disable_checkboxes else ""),
)
tokens[parent_token(tokens, i - 2)].attrSet(
"class", "contains-task-list"
)
md.core.ruler.after("inline", "github-tasklists", fcn)
def parent_token(tokens, index):
target_level = tokens[index].level - 1
for i in range(1, index + 1):
if tokens[index - i].level == target_level:
return index - i
return -1
def is_todo_item(tokens, index):
return (
is_inline(tokens[index])
and is_paragraph(tokens[index - 1])
and is_list_item(tokens[index - 2])
and starts_with_todo_markdown(tokens[index])
)
def todoify(token: Token, token_constructor):
assert token.children is not None
token.children.insert(0, make_checkbox(token, token_constructor))
token.children[1].content = token.children[1].content[3:]
token.content = token.content[3:]
if use_label_wrapper:
if use_label_after:
token.children.pop()
# Replaced number generator from original plugin with uuid.
checklist_id = f"task-item-{uuid4()}"
token.children[0].content = (
token.children[0].content[0:-1] + f' id="{checklist_id}">'
)
token.children.append(
after_label(token.content, checklist_id, token_constructor)
)
else:
token.children.insert(0, begin_label(token_constructor))
token.children.append(end_label(token_constructor))
def make_checkbox(token, token_constructor):
checkbox = token_constructor("html_inline", "", 0)
disabled_attr = 'disabled="disabled"' if disable_checkboxes else ""
if token.content.startswith("[ ] "):
checkbox.content = (
'<input class="task-list-item-checkbox" '
f'{disabled_attr} type="checkbox">'
)
elif token.content.startswith("[x] ") or token.content.startswith("[X] "):
checkbox.content = (
'<input class="task-list-item-checkbox" checked="checked" '
f'{disabled_attr} type="checkbox">'
)
return checkbox
def begin_label(token_constructor):
token = token_constructor("html_inline", "", 0)
token.content = "<label>"
return token
def end_label(token_constructor):
token = token_constructor("html_inline", "", 0)
token.content = "</label>"
return token
def after_label(content, checkbox_id, token_constructor):
token = token_constructor("html_inline", "", 0)
token.content = (
f'<label class="task-list-item-label" for="{checkbox_id}">{content}</label>'
)
token.attrs = [{"for": checkbox_id}]
return token
def is_inline(token):
return token.type == "inline"
def is_paragraph(token):
return token.type == "paragraph_open"
def is_list_item(token):
return token.type == "list_item_open"
def starts_with_todo_markdown(token):
# leading whitespace in a list item is already trimmed off by markdown-it
return re.match(rf"\[[ xX]]{_GFM_WHITESPACE_RE}+", token.content)

View File

@ -0,0 +1,6 @@
- package: markdown-it-task-lists
commit: 8233e000559fae5a6306009e55332a54a9d3f606
date: 6 Mar 2018
version: 2.1.1
changes:
- Replaced number generator from original plugin with uuid

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-17 Stefan Goessner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,137 @@
[![License](https://img.shields.io/github/license/goessner/markdown-it-texmath.svg)](https://github.com/goessner/markdown-it-texmath/blob/master/licence.txt)
[![npm](https://img.shields.io/npm/v/markdown-it-texmath.svg)](https://www.npmjs.com/package/markdown-it-texmath)
[![npm](https://img.shields.io/npm/dt/markdown-it-texmath.svg)](https://www.npmjs.com/package/markdown-it-texmath)
# markdown-it-texmath
Add TeX math equations to your Markdown documents rendered by [markdown-it](https://github.com/markdown-it/markdown-it) parser. [KaTeX](https://github.com/Khan/KaTeX) is used as a fast math renderer.
## Features
Simplify the process of authoring markdown documents containing math formulas.
This extension is a comfortable tool for scientists, engineers and students with markdown as their first choice document format.
* Macro support
* Simple formula numbering
* Inline math with tables, lists and blockquote.
* User setting delimiters:
* `'dollars'` (default)
* inline: `$...$`
* display: `$$...$$`
* display + equation number: `$$...$$ (1)`
* `'brackets'`
* inline: `\(...\)`
* display: `\[...\]`
* display + equation number: `\[...\] (1)`
* `'gitlab'`
* inline: ``$`...`$``
* display: `` ```math ... ``` ``
* display + equation number: `` ```math ... ``` (1)``
* `'julia'`
* inline: `$...$` or ``` ``...`` ```
* display: `` ```math ... ``` ``
* display + equation number: `` ```math ... ``` (1)``
* `'kramdown'`
* inline: ``$$...$$``
* display: `$$...$$`
* display + equation number: `$$...$$ (1)`
## Show me
View a [test table](https://goessner.github.io/markdown-it-texmath/index.html).
[try it out ...](https://goessner.github.io/markdown-it-texmath/markdown-it-texmath-demo.html)
## Use with `node.js`
Install the extension. Verify having `markdown-it` and `katex` already installed .
```
npm install markdown-it-texmath
```
Use it with JavaScript.
```js
let kt = require('katex'),
tm = require('markdown-it-texmath').use(kt),
md = require('markdown-it')().use(tm,{delimiters:'dollars',macros:{"\\RR": "\\mathbb{R}"}});
md.render('Euler\'s identity \(e^{i\pi}+1=0\) is a beautiful formula in $\\RR 2$.')
```
## Use in Browser
```html
<html>
<head>
<meta charset='utf-8'>
<link rel="stylesheet" href="katex.min.css">
<link rel="stylesheet" href="texmath.css">
<script src="markdown-it.min.js"></script>
<script src="katex.min.js"></script>
<script src="texmath.js"></script>
</head>
<body>
<div id="out"></div>
<script>
let md;
document.addEventListener("DOMContentLoaded", () => {
const tm = texmath.use(katex);
md = markdownit().use(tm,{delimiters:'dollars',macros:{"\\RR": "\\mathbb{R}"}});
out.innerHTML = md.render('Euler\'s identity $e^{i\pi}+1=0$ is a beautiful formula in //RR 2.');
})
</script>
</body>
</html>
```
## CDN
Use following links for `texmath.js` and `texmath.css`
* `https://gitcdn.xyz/cdn/goessner/markdown-it-texmath/master/texmath.js`
* `https://gitcdn.xyz/cdn/goessner/markdown-it-texmath/master/texmath.css`
## Dependencies
* [`markdown-it`](https://github.com/markdown-it/markdown-it): Markdown parser done right. Fast and easy to extend.
* [`katex`](https://github.com/Khan/KaTeX): This is where credits for fast rendering TeX math in HTML go to.
## ToDo
nothing yet
## FAQ
* __`markdown-it-texmath` with React Native does not work, why ?__
* `markdown-it-texmath` is using regular expressions with `y` [(sticky) property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky) and cannot avoid this. The use of the `y` flag in regular expressions means the plugin is not compatible with React Native (which as of now doesn't support it and throws an error `Invalid flags supplied to RegExp constructor`).
## CHANGELOG
### [0.6.0] on October 04, 2019
* Add support for [Julia Markdown](https://docs.julialang.org/en/v1/stdlib/Markdown/) on [request](https://github.com/goessner/markdown-it-texmath/issues/15).
### [0.5.5] on February 07, 2019
* Remove [rendering bug with brackets delimiters](https://github.com/goessner/markdown-it-texmath/issues/9).
### [0.5.4] on January 20, 2019
* Remove pathological [bug within blockquotes](https://github.com/goessner/mdmath/issues/50).
### [0.5.3] on November 11, 2018
* Add support for Tex macros (https://katex.org/docs/supported.html#macros) .
* Bug with [brackets delimiters](https://github.com/goessner/markdown-it-texmath/issues/9) .
### [0.5.2] on September 07, 2018
* Add support for [Kramdown](https://kramdown.gettalong.org/) .
### [0.5.0] on August 15, 2018
* Fatal blockquote bug investigated. Implemented workaround to vscode bug, which has finally gone with vscode 1.26.0 .
### [0.4.6] on January 05, 2018
* Escaped underscore bug removed.
### [0.4.5] on November 06, 2017
* Backslash bug removed.
### [0.4.4] on September 27, 2017
* Modifying the `block` mode regular expression with `gitlab` delimiters, so removing the `newline` bug.
## License
`markdown-it-texmath` is licensed under the [MIT License](./license.txt)
© [Stefan Gössner](https://github.com/goessner)

View File

@ -0,0 +1 @@
from .index import texmath_plugin # noqa F401

View File

@ -0,0 +1,307 @@
import re
from typing import Optional
from markdown_it import MarkdownIt
from markdown_it.common.utils import charCodeAt
def texmath_plugin(md: MarkdownIt, delimiters="dollars", macros: Optional[dict] = None):
"""Plugin ported from
`markdown-it-texmath <https://github.com/goessner/markdown-it-texmath>`__.
It parses TeX math equations set inside opening and closing delimiters:
.. code-block:: md
$\\alpha = \\frac{1}{2}$
:param delimiters: one of: brackets, dollars, gitlab, julia, kramdown
"""
macros = macros or {}
if delimiters in rules:
for rule_inline in rules[delimiters]["inline"]:
md.inline.ruler.before(
"escape", rule_inline["name"], make_inline_func(rule_inline)
)
def render_math_inline(self, tokens, idx, options, env):
return rule_inline["tmpl"].format(
render(tokens[idx].content, False, macros)
)
md.add_render_rule(rule_inline["name"], render_math_inline)
for rule_block in rules[delimiters]["block"]:
md.block.ruler.before(
"fence", rule_block["name"], make_block_func(rule_block)
)
def render_math_block(self, tokens, idx, options, env):
return rule_block["tmpl"].format(
render(tokens[idx].content, True, macros), tokens[idx].info
)
md.add_render_rule(rule_block["name"], render_math_block)
def applyRule(rule, string: str, begin, inBlockquote):
if not (
string.startswith(rule["tag"], begin)
and (rule["pre"](string, begin) if "pre" in rule else True)
):
return False
match = rule["rex"].match(string[begin:]) # type: re.Match
if not match or match.start() != 0:
return False
lastIndex = match.end() + begin - 1
if "post" in rule:
if not (
rule["post"](string, lastIndex) # valid post-condition
# remove evil blockquote bug (https:#github.com/goessner/mdmath/issues/50)
and (not inBlockquote or "\n" not in match.group(1))
):
return False
return match
def make_inline_func(rule):
def _func(state, silent):
res = applyRule(rule, state.src, state.pos, False)
if res:
if not silent:
token = state.push(rule["name"], "math", 0)
token.content = res[1] # group 1 from regex ..
token.markup = rule["tag"]
state.pos += res.end()
return bool(res)
return _func
def make_block_func(rule):
def _func(state, begLine, endLine, silent):
begin = state.bMarks[begLine] + state.tShift[begLine]
res = applyRule(rule, state.src, begin, state.parentType == "blockquote")
if res:
if not silent:
token = state.push(rule["name"], "math", 0)
token.block = True
token.content = res[1]
token.info = res[len(res.groups())]
token.markup = rule["tag"]
line = begLine
endpos = begin + res.end() - 1
while line < endLine:
if endpos >= state.bMarks[line] and endpos <= state.eMarks[line]:
# line for end of block math found ...
state.line = line + 1
break
line += 1
state.pos = begin + res.end()
return bool(res)
return _func
def dollar_pre(str, beg):
prv = charCodeAt(str[beg - 1], 0) if beg > 0 else False
return (
(not prv) or prv != 0x5C and (prv < 0x30 or prv > 0x39) # no backslash,
) # no decimal digit .. before opening '$'
def dollar_post(string, end):
try:
nxt = string[end + 1] and charCodeAt(string[end + 1], 0)
except IndexError:
return True
return (
(not nxt) or (nxt < 0x30) or (nxt > 0x39)
) # no decimal digit .. after closing '$'
def render(tex, displayMode, macros):
return tex
# TODO better HTML renderer port for math
# try:
# res = katex.renderToString(tex,{throwOnError:False,displayMode,macros})
# except:
# res = tex+": "+err.message.replace("<","&lt;")
# return res
# def use(katex): # math renderer used ...
# texmath.katex = katex; # ... katex solely at current ...
# return texmath;
# }
# All regexes areg global (g) and sticky (y), see:
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky
rules: dict = {
"brackets": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^\\\((.+?)\\\)", re.DOTALL),
"tmpl": "<eq>{0}</eq>",
"tag": "\\(",
}
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(
r"^\\\[(((?!\\\]|\\\[)[\s\S])+?)\\\]\s*?\(([^)$\r\n]+?)\)", re.M
),
"tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>',
"tag": "\\[",
},
{
"name": "math_block",
"rex": re.compile(r"^\\\[([\s\S]+?)\\\]", re.M),
"tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n",
"tag": "\\[",
},
],
},
"gitlab": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^\$`(.+?)`\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$`",
}
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(
r"^`{3}math\s+?([^`]+?)\s+?`{3}\s*?\(([^)$\r\n]+?)\)", re.M
),
"tmpl": '<section class="eqno">\n<eqn>{0}</eqn><span>({1})</span>\n</section>\n', # noqa: E501
"tag": "```math",
},
{
"name": "math_block",
"rex": re.compile(r"^`{3}math\s+?([^`]+?)\s+?`{3}", re.M),
"tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n",
"tag": "```math",
},
],
},
"julia": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^`{2}([^`]+?)`{2}"),
"tmpl": "<eq>{0}</eq>",
"tag": "``",
},
{
"name": "math_inline",
"rex": re.compile(r"^\$(\S[^$\r\n]*?[^\s\\]{1}?)\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$",
"pre": dollar_pre,
"post": dollar_post,
},
{
"name": "math_single",
"rex": re.compile(r"^\$([^$\s\\]{1}?)\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$",
"pre": dollar_pre,
"post": dollar_post,
},
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(
r"^`{3}math\s+?([^`]+?)\s+?`{3}\s*?\(([^)$\r\n]+?)\)", re.M
),
"tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>',
"tag": "```math",
},
{
"name": "math_block",
"rex": re.compile(r"^`{3}math\s+?([^`]+?)\s+?`{3}", re.M),
"tmpl": "<section><eqn>{0}</eqn></section>",
"tag": "```math",
},
],
},
"kramdown": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^\${2}([^$\r\n]*?)\${2}"),
"tmpl": "<eq>{0}</eq>",
"tag": "$$",
}
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(r"^\${2}([^$]*?)\${2}\s*?\(([^)$\r\n]+?)\)", re.M),
"tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>',
"tag": "$$",
},
{
"name": "math_block",
"rex": re.compile(r"^\${2}([^$]*?)\${2}", re.M),
"tmpl": "<section><eqn>{0}</eqn></section>",
"tag": "$$",
},
],
},
"dollars": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^\$(\S[^$]*?[^\s\\]{1}?)\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$",
"pre": dollar_pre,
"post": dollar_post,
},
{
"name": "math_single",
"rex": re.compile(r"^\$([^$\s\\]{1}?)\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$",
"pre": dollar_pre,
"post": dollar_post,
},
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(r"^\${2}([^$]*?)\${2}\s*?\(([^)$\r\n]+?)\)", re.M),
"tmpl": '<section class="eqno">\n<eqn>{0}</eqn><span>({1})</span>\n</section>\n', # noqa: E501
"tag": "$$",
},
{
"name": "math_block",
"rex": re.compile(r"^\${2}([^$]*?)\${2}", re.M),
"tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n",
"tag": "$$",
},
],
},
}

View File

@ -0,0 +1,7 @@
- package: markdown-it-texmath
commit: 78c548829ce2ef85c73dc71e680d01e5ae41ffbf
date: Oct 4, 2019
version: 0.6
changes: |
both dollars/math_inline and brackets/math_inline regexes have been changed,
to allow (single) line breaks

View File

@ -0,0 +1,58 @@
import string
from typing import Callable, List
from markdown_it import MarkdownIt
from markdown_it.rules_core import StateCore
def basic_count(text: str) -> int:
"""Split the string and ignore punctuation only elements."""
return sum([el.strip(string.punctuation).isalpha() for el in text.split()])
def wordcount_plugin(
md: MarkdownIt,
*,
per_minute: int = 200,
count_func: Callable[[str], int] = basic_count,
store_text: bool = False
):
"""Plugin for computing and storing the word count.
Stores in the ``env`` e.g.::
env["wordcount"] = {
"words": 200
"minutes": 1,
}
If "wordcount" is already in the env, it will update it.
:param per_minute: Words per minute reading speed
:param store_text: store all text under a "text" key, as a list of strings
"""
def _word_count_rule(state: StateCore) -> None:
text: List[str] = []
words = 0
for token in state.tokens:
if token.type == "text":
words += count_func(token.content)
if store_text:
text.append(token.content)
elif token.type == "inline":
for child in token.children or ():
if child.type == "text":
words += count_func(child.content)
if store_text:
text.append(child.content)
data = state.env.setdefault("wordcount", {})
if store_text:
data.setdefault("text", [])
data["text"] += text
data.setdefault("words", 0)
data["words"] += words
data["minutes"] = int(round(data["words"] / per_minute))
md.core.ruler.push("wordcount", _word_count_rule)

67
pyproject.toml Normal file
View File

@ -0,0 +1,67 @@
[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "mdit-py-plugins"
dynamic = ["version"]
description = "Collection of plugins for markdown-it-py"
readme = "README.md"
authors = [{name = "Chris Sewell", email = "chrisj_sewell@hotmail.com"}]
license = {file = "LICENSE"}
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Text Processing :: Markup",
]
keywords = ["markdown", "markdown-it", "lexer", "parser", "development"]
requires-python = ">=3.7"
dependencies = ["markdown-it-py>=1.0.0,<3.0.0"]
[project.urls]
Homepage = "https://github.com/executablebooks/mdit-py-plugins"
Documentation = "https://markdown-it-py.readthedocs.io"
[project.optional-dependencies]
code_style = ["pre-commit"]
testing = [
"coverage",
"pytest",
"pytest-cov",
"pytest-regressions",
]
rtd = [
"attrs",
"myst-parser~=0.16.1",
"sphinx-book-theme~=0.1.0",
]
[tool.flit.module]
name = "mdit_py_plugins"
[tool.flit.sdist]
exclude = [
"docs/",
"tests/",
]
[tool.isort]
profile = "black"
force_sort_within_sections = true
known_first_party = ["mdit_py_plugins", "tests"]
[tool.mypy]
show_error_codes = true
warn_unused_ignores = true
warn_redundant_casts = true
no_implicit_optional = true
strict_equality = true

269
tests/fixtures/admon.md vendored Normal file
View File

@ -0,0 +1,269 @@
Simple admonition
.
!!! note
*content*
.
<div class="admonition note">
<p class="admonition-title">Note</p>
<p><em>content</em></p>
</div>
.
Could contain block elements too
.
!!! note
### heading
-----------
.
<div class="admonition note">
<p class="admonition-title">Note</p>
<h3>heading</h3>
<hr>
</div>
.
Shows custom title
.
!!! note Custom title
Some text
.
<div class="admonition note">
<p class="admonition-title">Custom title</p>
<p>Some text</p>
</div>
.
Shows no title
.
!!! note ""
Some text
.
<div class="admonition note">
<p>Some text</p>
</div>
.
Closes block after 2 empty lines
.
!!! note
Some text
A code block
.
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Some text</p>
</div>
<pre><code>A code block
</code></pre>
.
Nested blocks
.
!!! note
!!! note
Some text
code block
.
<div class="admonition note">
<p class="admonition-title">Note</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Some text</p>
<pre><code>code block
</code></pre>
</div>
</div>
.
Consecutive admonitions
.
!!! note
!!! warning
.
<div class="admonition note">
<p class="admonition-title">Note</p>
</div>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
</div>
.
Marker may be indented up to 3 chars
.
!!! note
content
.
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>content</p>
</div>
.
But that's a code block
.
!!! note
content
.
<pre><code>!!! note
content
</code></pre>
.
Some more indent checks
.
!!! note
not a code block
code block
.
<div class="admonition note">
<p class="admonition-title">Note</p>
</div>
<p>not a code block</p>
<pre><code>code block
</code></pre>
.
Type could be adjacent to marker
.
!!!note
xxx
.
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>xxx</p>
</div>
.
Type could be adjacent to marker and content may be shifted up to 3 chars
.
!!!note
xxx
.
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>xxx</p>
</div>
.
Or several spaces apart
.
!!! note
xxx
.
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>xxx</p>
</div>
.
Admonitions self-close at the end of the document
.
!!! note
xxx
.
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>xxx</p>
</div>
.
They could be nested in lists
.
- !!! note
- a
- b
- !!! warning
- c
- d
.
<ul>
<li>
<div class="admonition note">
<p class="admonition-title">Note</p>
<ul>
<li>a</li>
<li>b</li>
</ul>
</div>
</li>
<li>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<ul>
<li>c</li>
<li>d</li>
</ul>
</div>
</li>
</ul>
.
Or in blockquotes
.
> !!! note
> xxx
> > yyy
> zzz
>
.
<blockquote>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>xxx</p>
<blockquote>
<p>yyy
zzz</p>
</blockquote>
</div>
</blockquote>
.
Renders unknown admonition type
.
!!! unknown title
content
.
<div class="admonition unknown">
<p class="admonition-title">title</p>
<p>content</p>
</div>
.
Does not render
.
!!!
content
.
<p>!!!
content</p>
.

223
tests/fixtures/amsmath.md vendored Normal file
View File

@ -0,0 +1,223 @@
equation environment:
.
\begin{equation}
a = 1
\end{equation}
.
<div class="math amsmath">
\begin{equation}
a = 1
\end{equation}
</div>
.
equation* environment:
.
\begin{equation*}
a = 1
\end{equation*}
.
<div class="math amsmath">
\begin{equation*}
a = 1
\end{equation*}
</div>
.
multline environment:
.
\begin{multline}
a = 1
\end{multline}
.
<div class="math amsmath">
\begin{multline}
a = 1
\end{multline}
</div>
.
multline* environment:
.
\begin{multline*}
a = 1
\end{multline*}
.
<div class="math amsmath">
\begin{multline*}
a = 1
\end{multline*}
</div>
.
gather environment:
.
\begin{gather}
a = 1
\end{gather}
.
<div class="math amsmath">
\begin{gather}
a = 1
\end{gather}
</div>
.
gather* environment:
.
\begin{gather*}
a = 1
\end{gather*}
.
<div class="math amsmath">
\begin{gather*}
a = 1
\end{gather*}
</div>
.
align environment:
.
\begin{align}
a = 1
\end{align}
.
<div class="math amsmath">
\begin{align}
a = 1
\end{align}
</div>
.
align* environment:
.
\begin{align*}
a = 1
\end{align*}
.
<div class="math amsmath">
\begin{align*}
a = 1
\end{align*}
</div>
.
alignat environment:
.
\begin{alignat}
a = 1
\end{alignat}
.
<div class="math amsmath">
\begin{alignat}
a = 1
\end{alignat}
</div>
.
alignat* environment:
.
\begin{alignat*}
a = 1
\end{alignat*}
.
<div class="math amsmath">
\begin{alignat*}
a = 1
\end{alignat*}
</div>
.
flalign environment:
.
\begin{flalign}
a = 1
\end{flalign}
.
<div class="math amsmath">
\begin{flalign}
a = 1
\end{flalign}
</div>
.
flalign* environment:
.
\begin{flalign*}
a = 1
\end{flalign*}
.
<div class="math amsmath">
\begin{flalign*}
a = 1
\end{flalign*}
</div>
.
equation environment, with before/after paragraphs:
.
before
\begin{equation}
a = 1
\end{equation}
after
.
<p>before</p>
<div class="math amsmath">
\begin{equation}
a = 1
\end{equation}
</div>
<p>after</p>
.
equation environment, in list:
.
- \begin{equation}
a = 1
\end{equation}
.
<ul>
<li>
<div class="math amsmath">
\begin{equation}
a = 1
\end{equation}
</div>
</li>
</ul>
.
`alignat` environment and HTML escaping
.
\begin{alignat}{3}
& d = \frac{1}{1 + 0.2316419x} \quad && a_1 = 0.31938153 \quad && a_2 = -0.356563782 \\
& a_3 = 1.781477937 \quad && a_4 = -1.821255978 \quad && a_5 = 1.330274429
\end{alignat}
.
<div class="math amsmath">
\begin{alignat}{3}
&amp; d = \frac{1}{1 + 0.2316419x} \quad &amp;&amp; a_1 = 0.31938153 \quad &amp;&amp; a_2 = -0.356563782 \\
&amp; a_3 = 1.781477937 \quad &amp;&amp; a_4 = -1.821255978 \quad &amp;&amp; a_5 = 1.330274429
\end{alignat}
</div>
.
`alignat*` environment and HTML escaping
.
\begin{alignat*}{3}
& m \quad && \text{módulo} \quad && m>0\\
& a \quad && \text{multiplicador} \quad && 0<a<m\\
& c \quad && \text{constante aditiva} \quad && 0\leq c<m\\
& x_0 \quad && \text{valor inicial} \quad && 0\leq x_0 <m
\end{alignat*}
.
<div class="math amsmath">
\begin{alignat*}{3}
&amp; m \quad &amp;&amp; \text{módulo} \quad &amp;&amp; m&gt;0\\
&amp; a \quad &amp;&amp; \text{multiplicador} \quad &amp;&amp; 0&lt;a&lt;m\\
&amp; c \quad &amp;&amp; \text{constante aditiva} \quad &amp;&amp; 0\leq c&lt;m\\
&amp; x_0 \quad &amp;&amp; \text{valor inicial} \quad &amp;&amp; 0\leq x_0 &lt;m
\end{alignat*}
</div>
.

65
tests/fixtures/anchors.md vendored Normal file
View File

@ -0,0 +1,65 @@
basic (max level 2):
.
# H1
## H2
### H3
.
<h1 id="h1">H1</h1>
<h2 id="h2">H2</h2>
<h3>H3</h3>
.
space:
.
# a b c
.
<h1 id="a-b--c">a b c</h1>
.
characters:
.
# a,b\cβÊ
.
<h1 id="abcβê">a,b\cβÊ</h1>
.
emoji:
.
# 🚀a
.
<h1 id="a">🚀a</h1>
.
html entity:
.
# foo&amp;bar
.
<h1 id="foobar">foo&amp;bar</h1>
.
uniqueness:
.
# a
# a
## a
.
<h1 id="a">a</h1>
<h1 id="a-1">a</h1>
<h2 id="a-2">a</h2>
.
standard (permalink after):
.
# a
.
<h1 id="a">a <a class="header-anchor" href="#a"></a></h1>
.
standard (permalink before):
.
# a
.
<h1 id="a"><a class="header-anchor" href="#a"></a> a</h1>
.

169
tests/fixtures/attrs.md vendored Normal file
View File

@ -0,0 +1,169 @@
simple reference link
.
[text *emphasis*](a){#id .a}
.
<p><a href="a" id="id" class="a">text <em>emphasis</em></a></p>
.
simple definition link
.
[a][]{#id .b}
[a]: /url
.
<p><a href="/url" id="id" class="b">a</a></p>
.
simple image
.
![a](b){#id .a b=c}
.
<p><img src="b" alt="a" id="id" b="c" class="a"></p>
.
simple inline code
.
`a`{#id .a b=c}
.
<p><code id="id" b="c" class="a">a</code></p>
.
ignore if space
.
![a](b) {#id key="*"}
.
<p><img src="b" alt="a"> {#id key=&quot;*&quot;}</p>
.
ignore if text
.
![a](b)b{#id key="*"}
.
<p><img src="b" alt="a">b{#id key=&quot;*&quot;}</p>
.
multi-line
.
![a](b){
#id .a
b=c
}
more
.
<p><img src="b" alt="a" id="id" b="c" class="a">
more</p>
.
merging attributes
.
![a](b){#a .a}{.b class=x other=h}{#x class="x g" other=a}
.
<p><img src="b" alt="a" id="x" class="a b x x g" other="a"></p>
.
spans: simple
.
[a]{#id .b}c
.
<p><span id="id" class="b">a</span>c</p>
.
spans: end of inline before attrs
.
[a]
.
<p>[a]</p>
.
spans: space between brace and attrs
.
[a] {.b}
.
<p>[a] {.b}</p>
.
spans: escaped span start
.
\[a]{.b}
.
<p>[a]{.b}</p>
.
spans: escaped span end
.
[a\]{.b}
.
<p>[a]{.b}</p>
.
spans: escaped span attribute
.
[a]\{.b}
.
<p>[a]{.b}</p>
.
spans: nested text syntax
.
[*a*]{.b}c
.
<p><span class="b"><em>a</em></span>c</p>
.
spans: nested span
.
*[a]{.b}c*
.
<p><em><span class="b">a</span>c</em></p>
.
spans: multi-line
.
x [a
b]{#id
b=c} y
.
<p>x <span id="id" b="c">a
b</span> y</p>
.
spans: nested spans
.
[[a]{.b}]{.c}
.
<p><span class="c"><span class="b">a</span></span></p>
.
spans: short link takes precedence over span
.
[a]{#id .b}
[a]: /url
.
<p><a href="/url" id="id" class="b">a</a></p>
.
spans: long link takes precedence over span
.
[a][a]{#id .b}
[a]: /url
.
<p><a href="/url" id="id" class="b">a</a></p>
.
spans: link inside span
.
[[a]]{#id .b}
[a]: /url
.
<p><span id="id" class="b"><a href="/url">a</a></span></p>
.
spans: merge attributes
.
[a]{#a .a}{#b .a .b other=c}{other=d}
.
<p><span id="b" class="a b" other="d">a</span></p>
.

423
tests/fixtures/colon_fence.md vendored Normal file
View File

@ -0,0 +1,423 @@
# The initial tests are adapted from the test for normal code fences in tests/test_port/fixtures/commonmark_spec.md
src line: 1638
.
:::
<
>
:::
.
<pre><code>&lt;
&gt;
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1665
.
::
foo
::
.
<p>::
foo
::</p>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1676
.
:::
aaa
~~~
:::
.
<pre><code>aaa
~~~
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1688
.
:::
aaa
```
:::
.
<pre><code>aaa
```
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1702
.
::::
aaa
:::
::::::
.
<pre><code>aaa
:::
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1729
.
:::
.
<pre><code></code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1736
.
:::::
:::
aaa
.
<pre><code>
:::
aaa
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1749
.
> :::
> aaa
bbb
.
<blockquote>
<pre><code>aaa
</code></pre>
</blockquote>
<p>bbb</p>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1765
.
:::
:::
.
<pre><code>
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1779
.
:::
:::
.
<pre><code></code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1791
.
:::
aaa
aaa
:::
.
<pre><code>aaa
aaa
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1803
.
:::
aaa
aaa
aaa
:::
.
<pre><code>aaa
aaa
aaa
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1817
.
:::
aaa
aaa
aaa
:::
.
<pre><code>aaa
aaa
aaa
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1833
.
:::
aaa
:::
.
<pre><code>:::
aaa
:::
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1848
.
:::
aaa
:::
.
<pre><code>aaa
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1858
.
:::
aaa
:::
.
<pre><code>aaa
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1870
.
:::
aaa
:::
.
<pre><code>aaa
:::
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1884
.
::: :::
aaa
.
<pre><code class="block-:::" >aaa
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1907
.
foo
:::
bar
:::
baz
.
<p>foo</p>
<pre><code>bar
</code></pre>
<p>baz</p>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1946
.
:::ruby
def foo(x)
return 3
end
:::
.
<pre><code class="block-ruby" >def foo(x)
return 3
end
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1974
.
::::;
::::
.
<pre><code class="block-;" ></code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 1984
.
::: aa :::
foo
.
<pre><code class="block-aa" >foo
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 2007
.
:::
::: aaa
:::
.
<pre><code>::: aaa
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src line: 2007
.
:::
::: aaa
:::
.
<pre><code>::: aaa
</code></pre>
.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ending marker could be longer
.
::::: name :::::
hello world
::::::::::::::::
.
<pre><code class="block-name" > hello world
</code></pre>
.
Nested blocks
.
::::: name
:::: name
xxx
::::
:::::
.
<pre><code class="block-name" >:::: name
xxx
::::
</code></pre>
.
Name could be adjacent to marker
.
:::name
xxx
:::
.
<pre><code class="block-name" >xxx
</code></pre>
.
They should terminate paragraphs
.
blah blah
::: name
content
:::
.
<p>blah blah</p>
<pre><code class="block-name" >content
</code></pre>
.
They could be nested in lists
.
- ::: name
- xxx
:::
.
<ul>
<li>
<pre><code class="block-name" >- xxx
</code></pre>
</li>
</ul>
.
Or in blockquotes
.
> ::: name
> xxx
>> yyy
> zzz
> :::
.
<blockquote>
<pre><code class="block-name" >xxx
&gt; yyy
zzz
</code></pre>
</blockquote>
.
List indentation quirks
.
- ::: name
xxx
yyy
:::
- ::: name
xxx
yyy
:::
.
<ul>
<li>
<pre><code class="block-name" >xxx
yyy
</code></pre>
</li>
</ul>
<pre><code>
- ::: name
xxx
yyy
</code></pre>
.

286
tests/fixtures/container.md vendored Normal file
View File

@ -0,0 +1,286 @@
Simple container
.
::: name
*content*
:::
.
<div class="name">
<p><em>content</em></p>
</div>
.
Delimiters too short
.
:: name
*content*
::
.
<p>:: name
<em>content</em>
::</p>
.
Could contain block elements too
.
::: name
### heading
-----------
:::
.
<div class="name">
<h3>heading</h3>
<hr>
</div>
.
Ending marker could be longer
.
::::: name :::::
hello world
::::::::::::::::
.
<div class="name">
<p>hello world</p>
</div>
.
Nested blocks
.
::::: name
:::: name
xxx
::::
:::::
.
<div class="name">
<div class="name">
<p>xxx</p>
</div>
</div>
.
Incorrectly nested blocks
.
:::: name
this block is closed with 5 markers below
::::: name
auto-closed block
:::::
::::
.
<div class="name">
<p>this block is closed with 5 markers below</p>
<div class="name">
<p>auto-closed block</p>
</div>
</div>
<p>::::</p>
.
Marker could be indented up to 3 spaces
.
::: name
content
:::
:::
.
<div class="name">
<p>content
:::</p>
</div>
.
But that's a code block
.
::: name
content
:::
.
<pre><code>::: name
content
:::
</code></pre>
.
Some more indent checks
.
::: name
not a code block
code block
:::
.
<div class="name">
<p>not a code block</p>
<pre><code>code block
</code></pre>
</div>
.
Name could be adjacent to marker
.
:::name
xxx
:::
.
<div class="name">
<p>xxx</p>
</div>
.
Or several spaces apart
.
::: name
xxx
:::
.
<div class="name">
<p>xxx</p>
</div>
.
It could contain params
.
::: name arg=123 foo=456
xxx
:::
.
<div class="name">
<p>xxx</p>
</div>
.
But closing marker can't
.
::: name
xxx
::: arg=123
.
<div class="name">
<p>xxx
::: arg=123</p>
</div>
.
This however isn't a valid one
.
::: namename
xxx
:::
.
<p>::: namename
xxx
:::</p>
.
Containers self-close at the end of the document
.
::: name
xxx
.
<div class="name">
<p>xxx</p>
</div>
.
They should terminate paragraphs
.
blah blah
::: name
content
:::
.
<p>blah blah</p>
<div class="name">
<p>content</p>
</div>
.
They could be nested in lists
.
- ::: name
- xxx
:::
.
<ul>
<li>
<div class="name">
<ul>
<li>xxx</li>
</ul>
</div>
</li>
</ul>
.
Or in blockquotes
.
> ::: name
> xxx
>> yyy
> zzz
> :::
.
<blockquote>
<div class="name">
<p>xxx</p>
<blockquote>
<p>yyy
zzz</p>
</blockquote>
</div>
</blockquote>
.
List indentation quirks
.
- ::: name
xxx
yyy
:::
- ::: name
xxx
yyy
:::
.
<ul>
<li>
<div class="name">
<p>xxx
yyy</p>
</div>
</li>
</ul>
<p>:::</p>
<ul>
<li>
<div class="name">
<p>xxx</p>
</div>
</li>
</ul>
<p>yyy
:::</p>
.

247
tests/fixtures/deflist.md vendored Normal file
View File

@ -0,0 +1,247 @@
Pandoc (with slightly changed indents):
.
Term 1
: Definition 1
Term 2 with *inline markup*
: Definition 2
{ some code, part of Definition 2 }
Third paragraph of definition 2.
.
<dl>
<dt>Term 1</dt>
<dd>
<p>Definition 1</p>
</dd>
<dt>Term 2 with <em>inline markup</em></dt>
<dd>
<p>Definition 2</p>
<pre><code>{ some code, part of Definition 2 }
</code></pre>
<p>Third paragraph of definition 2.</p>
</dd>
</dl>
.
Pandoc again:
.
Term 1
: Definition
with lazy continuation.
Second paragraph of the definition.
.
<dl>
<dt>Term 1</dt>
<dd>
<p>Definition
with lazy continuation.</p>
<p>Second paragraph of the definition.</p>
</dd>
</dl>
.
Well, I might just copy-paste the third one while I'm at it:
.
Term 1
~ Definition 1
Term 2
~ Definition 2a
~ Definition 2b
.
<dl>
<dt>Term 1</dt>
<dd>Definition 1</dd>
<dt>Term 2</dt>
<dd>Definition 2a</dd>
<dd>Definition 2b</dd>
</dl>
.
Now, with our custom ones. Spaces after a colon:
.
Term 1
: paragraph
Term 2
: code block
.
<dl>
<dt>Term 1</dt>
<dd>paragraph</dd>
<dt>Term 2</dt>
<dd>
<pre><code>code block
</code></pre>
</dd>
</dl>
.
There should be something after a colon by the way:
.
Non-term 1
:
Non-term 2
:
.
<p>Non-term 1
:</p>
<p>Non-term 2
:</p>
.
List is tight iff all dts are tight:
.
Term 1
: foo
: bar
Term 2
: foo
: bar
.
<dl>
<dt>Term 1</dt>
<dd>
<p>foo</p>
</dd>
<dd>
<p>bar</p>
</dd>
<dt>Term 2</dt>
<dd>
<p>foo</p>
</dd>
<dd>
<p>bar</p>
</dd>
</dl>
.
Regression test (first paragraphs shouldn't be tight):
.
Term 1
: foo
bar
Term 2
: foo
.
<dl>
<dt>Term 1</dt>
<dd>
<p>foo</p>
<p>bar
Term 2</p>
</dd>
<dd>
<p>foo</p>
</dd>
</dl>
.
Definition lists should be second last in the queue. Exemplī grātiā, this isn't a valid one:
.
# test
: just a paragraph with a colon
.
<h1>test</h1>
<p>: just a paragraph with a colon</p>
.
Nested definition lists:
.
test
: foo
: bar
: baz
: bar
: foo
.
<dl>
<dt>test</dt>
<dd>
<dl>
<dt>foo</dt>
<dd>
<dl>
<dt>bar</dt>
<dd>baz</dd>
</dl>
</dd>
<dd>bar</dd>
</dl>
</dd>
<dd>foo</dd>
</dl>
.
Regression test, tabs
.
Term 1
: code block
.
<dl>
<dt>Term 1</dt>
<dd>
<pre><code>code block
</code></pre>
</dd>
</dl>
.
Regression test (blockquote inside deflist)
.
foo
: > bar
: baz
.
<dl>
<dt>foo</dt>
<dd>
<blockquote>
<p>bar</p>
</blockquote>
</dd>
<dd>baz</dd>
</dl>
.
Coverage, 1 blank line
.
test
.
<p>test</p>
.
Coverage, 2 blank lines
.
test
.
<p>test</p>
.

552
tests/fixtures/dollar_math.md vendored Normal file
View File

@ -0,0 +1,552 @@
single dollar
.
$
.
<p>$</p>
.
double-dollar
.
$$
.
<p>$$</p>
.
single character inline equation. (valid=True)
.
$a$
.
<p><span class="math inline">a</span></p>
.
inline equation with single greek character (valid=True)
.
$\\varphi$
.
<p><span class="math inline">\\varphi</span></p>
.
simple equation starting and ending with numbers. (valid=True)
.
$1+1=2$
.
<p><span class="math inline">1+1=2</span></p>
.
simple equation including special html character. (valid=True)
.
$1+1<3$
.
<p><span class="math inline">1+1&lt;3</span></p>
.
equation including backslashes. (valid=True)
.
$a \\backslash$
.
<p><span class="math inline">a \\backslash</span></p>
.
use of currency symbol, i.e. digits before/after opening/closing (valid=True)
.
3$1+2$ $1+2$3
.
<p>3$1+2$ $1+2$3</p>
.
use of currency symbol (valid=True)
.
If you solve $1+2$ you get $3
.
<p>If you solve <span class="math inline">1+2</span> you get $3</p>
.
inline fraction (valid=True)
.
$\\frac{1}{2}$
.
<p><span class="math inline">\\frac{1}{2}</span></p>
.
inline column vector (valid=True)
.
$\\begin{pmatrix}x\\\\y\\end{pmatrix}$
.
<p><span class="math inline">\\begin{pmatrix}x\\\\y\\end{pmatrix}</span></p>
.
inline bold vector notation (valid=True)
.
${\\tilde\\bold e}_\\alpha$
.
<p><span class="math inline">{\\tilde\\bold e}_\\alpha</span></p>
.
exponentiation (valid=True)
.
$a^{b}$
.
<p><span class="math inline">a^{b}</span></p>
.
conjugate complex (valid=True)
.
$a^\*b$ with $a^\*$
.
<p><span class="math inline">a^\*b</span> with <span class="math inline">a^\*</span></p>
.
Inline multi-line (valid=True)
.
a $a
\not=1$ b
.
<p>a <span class="math inline">a
\not=1</span> b</p>
.
Inline multi-line with newline (valid=False)
.
a $a
\not=1$ b
.
<p>a $a</p>
<p>\not=1$ b</p>
.
single block equation, greek index (valid=True)
.
$$e_\\alpha$$
.
<div class="math block">
e_\\alpha
</div>
.
display equation on its own single line. (valid=True)
.
$$1+1=2$$
.
<div class="math block">
1+1=2
</div>
.
display equation with number on its own single line. (valid=True)
.
$$1+1=2$$ (2)
.
<div id="2" class="math block">
<a href="#2" class="mathlabel" title="Permalink to this equation"></a>
1+1=2
</div>
.
inline equation followed by block equation. (valid=True)
.
${e}_x$
$$e_\\alpha$$
.
<p><span class="math inline">{e}_x</span></p>
<div class="math block">
e_\\alpha
</div>
.
underline tests (valid=True)
.
$$c{\\bold e}_x = a{\\bold e}_\\alpha - b\\tilde{\\bold e}_\\alpha$$
.
<div class="math block">
c{\\bold e}_x = a{\\bold e}_\\alpha - b\\tilde{\\bold e}_\\alpha
</div>
.
non-numeric character before opening $ or
after closing $ or both is allowed. (valid=True)
.
a$1+1=2$
$1+1=2$b
c$x$d
.
<p>a<span class="math inline">1+1=2</span>
<span class="math inline">1+1=2</span>b
c<span class="math inline">x</span>d</p>
.
following dollar character '$' is allowed. (valid=True)
.
$x$ $
.
<p><span class="math inline">x</span> $</p>
.
consecutive inline equations. (valid=True)
.
$x$ $y$
.
<p><span class="math inline">x</span> <span class="math inline">y</span></p>
.
inline equation after '-' sign in text. (valid=True)
.
so-what is $x$
.
<p>so-what is <span class="math inline">x</span></p>
.
display equation with line breaks. (valid=True)
.
$$
1+1=2
$$
.
<div class="math block">
1+1=2
</div>
.
multiple equations (valid=True)
.
$$
a = 1
$$
$$
b = 2
$$
.
<div class="math block">
a = 1
</div>
<div class="math block">
b = 2
</div>
.
equation followed by a labelled equation (valid=True)
.
$$
a = 1
$$
$$
b = 2
$$ (1)
.
<div class="math block">
a = 1
</div>
<div id="1" class="math block">
<a href="#1" class="mathlabel" title="Permalink to this equation"></a>
b = 2
</div>
.
multiline equation. (valid=True)
.
$$\\begin{matrix}
f & = & 2 + x + 3 \\
& = & 5 + x
\\end{matrix}$$
.
<div class="math block">
\\begin{matrix}
f &amp; = &amp; 2 + x + 3 \\
&amp; = &amp; 5 + x
\\end{matrix}
</div>
.
vector equation. (valid=True)
.
$$\\begin{pmatrix}x_2 \\\\ y_2 \\end{pmatrix} =
\\begin{pmatrix} A & B \\\\ C & D \\end{pmatrix}\\cdot
\\begin{pmatrix} x_1 \\\\ y_1 \\end{pmatrix}$$
.
<div class="math block">
\\begin{pmatrix}x_2 \\\\ y_2 \\end{pmatrix} =
\\begin{pmatrix} A &amp; B \\\\ C &amp; D \\end{pmatrix}\\cdot
\\begin{pmatrix} x_1 \\\\ y_1 \\end{pmatrix}
</div>
.
display equation with equation number. (valid=True)
.
$$f(x) = x^2 - 1$$ (1)
.
<div id="1" class="math block">
<a href="#1" class="mathlabel" title="Permalink to this equation"></a>
f(x) = x^2 - 1
</div>
.
inline equation following code section. (valid=True)
.
`code`$a-b$
.
<p><code>code</code><span class="math inline">a-b</span></p>
.
equation following code block. (valid=True)
.
```
code
```
$$a+b$$
.
<pre><code>code
</code></pre>
<div class="math block">
a+b
</div>
.
numbered equation following code block. (valid=True)
.
```
code
```
$$a+b$$(1)
.
<pre><code>code
</code></pre>
<div id="1" class="math block">
<a href="#1" class="mathlabel" title="Permalink to this equation"></a>
a+b
</div>
.
Equations in list. (valid=True)
.
1. $1+2$
2. $2+3$
1. $3+4$
.
<ol>
<li><span class="math inline">1+2</span></li>
<li><span class="math inline">2+3</span>
<ol>
<li><span class="math inline">3+4</span></li>
</ol>
</li>
</ol>
.
Inline sum. (valid=True)
.
$\\sum\_{i=1}^n$
.
<p><span class="math inline">\\sum\_{i=1}^n</span></p>
.
Sum without equation number. (valid=True)
.
$$\\sum\_{i=1}^n$$
.
<div class="math block">
\\sum\_{i=1}^n
</div>
.
Sum with equation number. (valid=True)
.
$$\\sum\_{i=1}\^n$$ (2)
.
<div id="2" class="math block">
<a href="#2" class="mathlabel" title="Permalink to this equation"></a>
\\sum\_{i=1}\^n
</div>
.
equation number always vertically aligned. (valid=True)
.
$${\\bold e}(\\varphi) = \\begin{pmatrix}
\\cos\\varphi\\\\\\sin\\varphi
\\end{pmatrix}$$ (3)
.
<div id="3" class="math block">
<a href="#3" class="mathlabel" title="Permalink to this equation"></a>
{\\bold e}(\\varphi) = \\begin{pmatrix}
\\cos\\varphi\\\\\\sin\\varphi
\\end{pmatrix}
</div>
.
inline equations in blockquote. (valid=True)
.
> see $a = b + c$
> $c^2=a^2+b^2$ (2)
> $c^2=a^2+b^2$
.
<blockquote>
<p>see <span class="math inline">a = b + c</span>
<span class="math inline">c^2=a^2+b^2</span> (2)
<span class="math inline">c^2=a^2+b^2</span></p>
</blockquote>
.
display equation in blockquote. (valid=True)
.
> formula
>
> $$ a+b=c$$ (2)
>
> in blockquote.
.
<blockquote>
<p>formula</p>
<div id="2" class="math block">
<a href="#2" class="mathlabel" title="Permalink to this equation"></a>
a+b=c
</div>
<p>in blockquote.</p>
</blockquote>
.
mixed syntax:
.
$$
a=1 \\
b=2
$$ (abc)
- ab $c=1$ d
.
<div id="abc" class="math block">
<a href="#abc" class="mathlabel" title="Permalink to this equation"></a>
a=1 \\
b=2
</div>
<ul>
<li>ab <span class="math inline">c=1</span> d</li>
</ul>
.
escaped dollars '\\$' are interpreted as
dollar '$' characters. (valid=True)
.
\\$1+1=2$
$1+1=2\\$
.
<p>\<span class="math inline">1+1=2</span>
<span class="math inline">1+1=2\\</span></p>
.
empty line between text and display formula is required. (valid=False)
.
some text
\$\\$a+b=c\$\$
.
<p>some text
$\$a+b=c$$</p>
.
whitespace character after opening $
or before closing $ is not allowed. (valid=False)
.
$ $
$ x$
$x $
.
<p>$ $
$ x$
$x $</p>
.
new line in blockquote block (valid=False):
.
> \$\$ a+b\n=c\$\$
.
<blockquote>
<p>$$ a+b\n=c$$</p>
</blockquote>
.
math-escaping: escaped start $:
.
\$p_2 = $a
.
<p>$p_2 = $a</p>
.
math-escaping: escaped end $:
.
$p_2 = \$a
.
<p>$p_2 = $a</p>
.
math-escaping: internal escaped $:
.
$p_2 = \$1$
.
<p><span class="math inline">p_2 = \$1</span></p>
.
math-escaping: double-escaped start $:
.
\\$p_2 = 1$
.
<p>\<span class="math inline">p_2 = 1</span></p>
.
math-escaping: double-escaped end $:
.
$p_2 = \\$a
.
<p><span class="math inline">p_2 = \\</span>a</p>
.
Inline double-dollar start:
.
$$a=1$$ b
.
<p><div class="math inline">a=1</div> b</p>
.
Inline double-dollar end:
.
a $$a=1$$
.
<p>a <div class="math inline">a=1</div></p>
.
Inline double-dollar enclosed:
.
a $$a=1$$ (1) b
.
<p>a <div class="math inline">a=1</div> (1) b</p>
.
Inline double-dollar, escaped:
.
a \$$a=1$$ b
.
<p>a $<span class="math inline">a=1</span>$ b</p>
.
Inline mixed single/double dollars:
.
Hence, for $\alpha \in (0, 1)$,
$$
\mathbb P (\alpha \bar{X} \ge \mu) \le \alpha;
$$
i.e., $[\alpha \bar{X}, \infty)$ is a lower 1-sided $1-\alpha$ confidence bound for $\mu$.
.
<p>Hence, for <span class="math inline">\alpha \in (0, 1)</span>,
<div class="math inline">\mathbb P (\alpha \bar{X} \ge \mu) \le \alpha;</div>
i.e., <span class="math inline">[\alpha \bar{X}, \infty)</span> is a lower 1-sided <span class="math inline">1-\alpha</span> confidence bound for <span class="math inline">\mu</span>.</p>
.
display equation with label containing whitespace. (valid=True)
.
$$1+1=2$$ (a b)
.
<div id="a-b" class="math block">
<a href="#a-b" class="mathlabel" title="Permalink to this equation"></a>
1+1=2
</div>
.

252
tests/fixtures/field_list.md vendored Normal file
View File

@ -0,0 +1,252 @@
Body alignment:
.
:no body:
:single line: content
:paragraph: content
running onto new line
:body inline: paragraph 1
paragraph 2
paragraph 3
:body on 2nd line:
paragraph 1
paragraph 2
:body on 3rd line:
paragraph 1
paragraph 2
.
<dl class="field-list">
<dt>no body</dt>
<dd></dd>
<dt>single line</dt>
<dd>
<p>content</p>
</dd>
<dt>paragraph</dt>
<dd>
<p>content
running onto new line</p>
</dd>
<dt>body inline</dt>
<dd>
<p>paragraph 1</p>
<p>paragraph 2</p>
<p>paragraph 3</p>
</dd>
<dt>body on 2nd line</dt>
<dd>
<p>paragraph 1</p>
<p>paragraph 2</p>
</dd>
<dt>body on 3rd line</dt>
<dd>
<p>paragraph 1</p>
<p>paragraph 2</p>
</dd>
</dl>
.
Empty name:
.
::
.
<p>::</p>
.
Whitespace only name:
.
: :
.
<p>: :</p>
.
Name markup:
.
:inline *markup*:
.
<dl class="field-list">
<dt>inline <em>markup</em></dt>
<dd></dd>
</dl>
.
Content paragraph markup:
.
:name: body *markup*
.
<dl class="field-list">
<dt>name</dt>
<dd>
<p>body <em>markup</em></p>
</dd>
</dl>
.
Body list:
.
:name:
- item 1
- item 2
:name: - item 1
- item 2
.
<dl class="field-list">
<dt>name</dt>
<dd>
<ul>
<li>item 1</li>
<li>item 2</li>
</ul>
</dd>
<dt>name</dt>
<dd>
<ul>
<li>item 1</li>
<li>item 2</li>
</ul>
</dd>
</dl>
.
Body code block
.
:name:
code
:name: body
code
.
<dl class="field-list">
<dt>name</dt>
<dd>
<pre><code>code
</code></pre>
</dd>
<dt>name</dt>
<dd>
<p>body</p>
<pre><code>code
</code></pre>
</dd>
</dl>
.
Body blockquote:
.
:name:
> item 1
> item 2
:name: > item 1
> item 2
.
<dl class="field-list">
<dt>name</dt>
<dd>
<blockquote>
<p>item 1
item 2</p>
</blockquote>
</dd>
<dt>name</dt>
<dd>
<blockquote>
<p>item 1
item 2</p>
</blockquote>
</dd>
</dl>
.
Body fence:
.
:name:
```python
code
```
.
<dl class="field-list">
<dt>name</dt>
<dd>
<pre><code class="language-python">code
</code></pre>
</dd>
</dl>
.
Following blocks:
.
:name: body
- item 1
:name: body
> block quote
:name: body
```python
code
```
.
<dl class="field-list">
<dt>name</dt>
<dd>
<p>body</p>
</dd>
</dl>
<ul>
<li>item 1</li>
</ul>
<dl class="field-list">
<dt>name</dt>
<dd>
<p>body</p>
</dd>
</dl>
<blockquote>
<p>block quote</p>
</blockquote>
<dl class="field-list">
<dt>name</dt>
<dd>
<p>body</p>
</dd>
</dl>
<pre><code class="language-python">code
</code></pre>
.
In list:
.
- :name: body
- item 2
.
<ul>
<li>
<dl class="field-list">
<dt>name</dt>
<dd>
<p>body</p>
</dd>
</dl>
</li>
<li>item 2</li>
</ul>
.
In blockquote:
.
> :name: body
.
<blockquote>
<dl class="field-list">
<dt>name</dt>
<dd>
<p>body</p>
</dd>
</dl>
</blockquote>
.

346
tests/fixtures/footnote.md vendored Normal file
View File

@ -0,0 +1,346 @@
Pandoc example:
.
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.
{ some.code }
The whole paragraph can be indented, or just the first
line. In this way, multi-paragraph footnotes work like
multi-paragraph list items.
This paragraph won't be part of the note, because it
isn't indented.
.
<p>Here is a footnote reference,<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> and another.<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p>
<p>This paragraph won't be part of the note, because it
isn't indented.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Here is the footnote. <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>Here's one with multiple blocks.</p>
<p>Subsequent paragraphs are indented to show that they
belong to the previous footnote.</p>
<pre><code>{ some.code }
</code></pre>
<p>The whole paragraph can be indented, or just the first
line. In this way, multi-paragraph footnotes work like
multi-paragraph list items. <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
They could terminate each other:
.
[^1][^2][^3]
[^1]: foo
[^2]: bar
[^3]: baz
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup><sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup><sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>foo <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>bar <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p>baz <a href="#fnref3" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
They could be inside blockquotes, and are lazy:
.
[^foo]
> [^foo]: bar
baz
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<blockquote></blockquote>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>bar
baz <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
Their labels could not contain spaces or newlines:
.
[^ foo]: bar baz
[^foo
]: bar baz
.
<p>[^ foo]: bar baz</p>
<p>[^foo
]: bar baz</p>
.
We support inline notes too (pandoc example):
.
Here is an inline note.^[Inlines notes are easier to write, since
you don't have to pick an identifier and move down to type the
note.]
.
<p>Here is an inline note.<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Inlines notes are easier to write, since
you don't have to pick an identifier and move down to type the
note. <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
They could have arbitrary markup:
.
foo^[ *bar* ]
.
<p>foo<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p> <em>bar</em> <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
Duplicate footnotes:
.
[^xxxxx] [^xxxxx]
[^xxxxx]: foo
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> <sup class="footnote-ref"><a href="#fn1" id="fnref1:1">[1:1]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>foo <a href="#fnref1" class="footnote-backref">↩︎</a> <a href="#fnref1:1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
Indents:
.
[^xxxxx] [^yyyyy]
[^xxxxx]: foo
---
[^yyyyy]: foo
---
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> <sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p>
<hr>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><h2>foo</h2>
<a href="#fnref1" class="footnote-backref">↩︎</a></li>
<li id="fn2" class="footnote-item"><p>foo <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
Indents for the first line:
.
[^xxxxx] [^yyyyy]
[^xxxxx]: foo
[^yyyyy]: foo
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> <sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>foo <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><pre><code>foo
</code></pre>
<a href="#fnref2" class="footnote-backref"></a></li>
</ol>
</section>
.
Indents for the first line (tabs):
.
[^xxxxx]
[^xxxxx]: foo
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>foo <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
Security 1
.
[^__proto__]
[^__proto__]: blah
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>blah <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
Security 2
.
[^hasOwnProperty]
[^hasOwnProperty]: blah
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>blah <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
Should allow links in inline footnotes
.
Example^[this is another example https://github.com]
.
<p>Example<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>this is another example https://github.com <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
Nested blocks:
.
[^a]
[^a]: abc
def
hij
- list
> block
terminates here
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<p>terminates here</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>abc</p>
<p>def
hij</p>
<ul>
<li>list</li>
</ul>
<blockquote>
<p>block</p>
</blockquote>
<a href="#fnref1" class="footnote-backref">↩︎</a></li>
</ol>
</section>
.
Empty lines after blockquote+footnote (markdown-it-py#133)
.
> b [^1]
Some text
> c
[^1]: d
.
<blockquote>
<p>b <sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
</blockquote>
<p>Some text</p>
<blockquote>
<p>c</p>
</blockquote>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>d <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
.
Newline after footnote identifier
.
[^a]
[^a]:
b
.
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<p>b</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"> <a href="#fnref1" class="footnote-backref"><-</a></li>
</ol>
</section>
.

87
tests/fixtures/front_matter.md vendored Normal file
View File

@ -0,0 +1,87 @@
should parse empty front matter:
.
---
---
# Head
.
<h1>Head</h1>
.
should parse basic front matter:
.
---
x: 1
---
# Head
.
<h1>Head</h1>
.
should parse until triple dots:
.
---
x: 1
...
# Head
.
<h1>Head</h1>
.
should parse front matter with indentation:
.
---
title: Associative arrays
people:
name: John Smith
age: 33
morePeople: { name: Grace Jones, age: 21 }
---
# Head
.
<h1>Head</h1>
.
should ignore spaces after front matter delimiters:
.
---
title: Associative arrays
people:
name: John Smith
age: 33
morePeople: { name: Grace Jones, age: 21 }
---
# Head
.
<h1>Head</h1>
.
should ignore front matter with less than 3 opening dashes:
.
--
x: 1
--
# Head
.
<h2>--
x: 1</h2>
<h1>Head</h1>
.
should require front matter have matching number of opening and closing dashes:
.
----
x: 1
---
# Head
.
<hr>
<h2>x: 1</h2>
<h1>Head</h1>
.

149
tests/fixtures/myst_block.md vendored Normal file
View File

@ -0,0 +1,149 @@
Block Break:
.
+++
.
<hr class="myst-block">
.
Block Break Split Markers:
.
+ + + + xfsdfsdf
.
<hr class="myst-block">
.
Block Break Too Few Markers:
.
++
.
<p>++</p>
.
Block Break terminates other blocks:
.
a
+++
- b
+++
> c
+++
.
<p>a</p>
<hr class="myst-block">
<ul>
<li>b</li>
</ul>
<hr class="myst-block">
<blockquote>
<p>c</p>
</blockquote>
<hr class="myst-block">
.
Target:
.
(a)=
.
<div class="myst-target"><a href="#a">(a)=</a></div>
.
Target characters:
.
(a bc |@<>*./_-+:)=
.
<div class="myst-target"><a href="#a bc |@<>*./_-+:">(a bc |@<>*./_-+:)=</a></div>
.
Empty target:
.
()=
.
<p>()=</p>
.
Escaped target:
.
\(a)=
.
<p>(a)=</p>
.
Indented target:
.
(a)=
.
<div class="myst-target"><a href="#a">(a)=</a></div>
.
Target terminates other blocks:
.
a
(a-b)=
- b
(a b)=
> c
(a)=
.
<p>a</p>
<div class="myst-target"><a href="#a-b">(a-b)=</a></div><ul>
<li>b</li>
</ul>
<div class="myst-target"><a href="#a b">(a b)=</a></div><blockquote>
<p>c</p>
</blockquote>
<div class="myst-target"><a href="#a">(a)=</a></div>
.
Comment:
.
% abc
.
<!-- abc -->
.
Comment terminates other blocks:
.
a
% abc
- b
% abc
> c
% abc
.
<p>a</p>
<!-- abc --><ul>
<li>b</li>
</ul>
<!-- abc --><blockquote>
<p>c</p>
</blockquote>
<!-- abc -->
.
Multiline comment:
.
a
% abc
% def
- b
% abc
%def
> c
%abc
%
%def
.
<p>a</p>
<!-- abc
def --><ul>
<li>b</li>
</ul>
<!-- abc
def --><blockquote>
<p>c</p>
</blockquote>
<!-- abc
def -->
.

104
tests/fixtures/myst_role.md vendored Normal file
View File

@ -0,0 +1,104 @@
Basic:
.
{abc}`xyz`
.
<p><code class="myst role">{abc}[xyz]</code></p>
.
Multiple:
.
{abc}`xyz`{def}`uvw`
.
<p><code class="myst role">{abc}[xyz]</code><code class="myst role">{def}[uvw]</code></p>
.
Surrounding Text:
.
a {abc}`xyz` b
.
<p>a <code class="myst role">{abc}[xyz]</code> b</p>
.
New lines:
.
{abc}`xy
z` `d
e`
.
<p><code class="myst role">{abc}[xy z]</code> <code>d e</code></p>
.
In Code:
.
`` {abc}`xyz` ``
.
<p><code>{abc}`xyz`</code></p>
.
Empty content:
.
{name}`` a
.
<p>{name}`` a</p>
.
Surrounding Code:
.
`a` {abc}`xyz` `b`
.
<p><code>a</code> <code class="myst role">{abc}[xyz]</code> <code>b</code></p>
.
In list:
.
- {abc}`xyz`
.
<ul>
<li><code class="myst role">{abc}[xyz]</code></li>
</ul>
.
In Quote:
.
> {abc}`xyz` b
.
<blockquote>
<p><code class="myst role">{abc}[xyz]</code> b</p>
</blockquote>
.
Multiple ticks:
.
{abc}``xyz``
.
<p><code class="myst role">{abc}[xyz]</code></p>
.
Inner tick:
.
{abc}``x`yz``
.
<p><code class="myst role">{abc}[x`yz]</code></p>
.
Unbalanced ticks:
.
{abc}``xyz`
.
<p>{abc}``xyz`</p>
.
Space in name:
.
{ab c}`xyz`
.
<p>{ab c}<code>xyz</code></p>
.
Escaped:
.
\{abc}`xyz`
.
<p>{abc}<code>xyz</code></p>
.

0
tests/fixtures/span.md vendored Normal file
View File

125
tests/fixtures/substitution.md vendored Normal file
View File

@ -0,0 +1,125 @@
Basic (space):
.
{{ block }}
a {{ inline }} b
.
<div class="substitution" text="block" />
<p>a <span class="substitution" text="inline" /> b</p>
.
Basic (no space):
.
{{block}}
a {{inline}} b
.
<div class="substitution" text="block" />
<p>a <span class="substitution" text="inline" /> b</p>
.
Same line:
.
{{a}}{{b}}
.
<p><span class="substitution" text="a" /><span class="substitution" text="b" /></p>
.
No closing:
.
{{ a
{{b}
{{c} }
.
<p>{{ a</p>
<p>{{b}</p>
<p>{{c} }</p>
.
Inside code
.
`{{ a }}`
```python
{{b}}
```
.
<p><code>{{ a }}</code></p>
<pre><code class="language-python">{{b}}
</code></pre>
.
New line:
.
{{a}}
{{b}}
.
<div class="substitution" text="a" />
<div class="substitution" text="b" />
.
Blocks:
.
- {{a}}
> {{b}}
1. {{c}}
.
<ul>
<li>
<div class="substitution" text="a" />
</li>
</ul>
<blockquote>
<div class="substitution" text="b" />
</blockquote>
<ol>
<li>
<div class="substitution" text="c" />
</li>
</ol>
.
Inline:
.
- {{a}} x
> {{b}} y
1. {{c}} z
.
<ul>
<li><span class="substitution" text="a" /> x</li>
</ul>
<blockquote>
<p><span class="substitution" text="b" /> y</p>
</blockquote>
<ol>
<li><span class="substitution" text="c" /> z</li>
</ol>
.
Tables:
.
| | |
|-|-|
|{{a}}|{{b}} c|
.
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="substitution" text="a" /></td>
<td><span class="substitution" text="b" /> c</td>
</tr>
</tbody>
</table>
.

131
tests/fixtures/tasklists.md vendored Normal file
View File

@ -0,0 +1,131 @@
bullet.md:
.
- [ ] unchecked item 1
- [ ] unchecked item 2
- [ ] unchecked item 3
- [x] checked item 4
.
<ul class="contains-task-list">
<li class="task-list-item"><input class="task-list-item-checkbox" disabled="disabled" type="checkbox"> unchecked item 1</li>
<li class="task-list-item"><input class="task-list-item-checkbox" disabled="disabled" type="checkbox"> unchecked item 2</li>
<li class="task-list-item"><input class="task-list-item-checkbox" disabled="disabled" type="checkbox"> unchecked item 3</li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox"> checked item 4</li>
</ul>
.
dirty.md:
.
- [ ] unchecked todo item 1
- [ ]
- [ ] not a todo item 2
- [ x] not a todo item 3
- [x ] not a todo item 4
- [ x ] not a todo item 5
- [x] todo item 6
.
<ul class="contains-task-list">
<li class="task-list-item"><input class="task-list-item-checkbox" disabled="disabled" type="checkbox"> unchecked todo item 1</li>
<li>[ ]</li>
<li>[ ] not a todo item 2</li>
<li>[ x] not a todo item 3</li>
<li>[x ] not a todo item 4</li>
<li>[ x ] not a todo item 5</li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox"> todo item 6</li>
</ul>
.
mixed-nested.md:
.
# Test 1
1. foo
* [ ] nested unchecked item 1
* not a todo item 2
* not a todo item 3
* [x] nested checked item 4
2. bar
3. spam
# Test 2
- foo
- [ ] nested unchecked item 1
- [ ] nested unchecked item 2
- [x] nested checked item 3
- [X] nested checked item 4
.
<h1>Test 1</h1>
<ol>
<li>foo
<ul class="contains-task-list">
<li class="task-list-item"><input class="task-list-item-checkbox" disabled="disabled" type="checkbox"> nested unchecked item 1</li>
<li>not a todo item 2</li>
<li>not a todo item 3</li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox"> nested checked item 4</li>
</ul>
</li>
<li>bar</li>
<li>spam</li>
</ol>
<h1>Test 2</h1>
<ul>
<li>foo
<ul class="contains-task-list">
<li class="task-list-item"><input class="task-list-item-checkbox" disabled="disabled" type="checkbox"> nested unchecked item 1</li>
<li class="task-list-item"><input class="task-list-item-checkbox" disabled="disabled" type="checkbox"> nested unchecked item 2</li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox"> nested checked item 3</li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox"> nested checked item 4</li>
</ul>
</li>
</ul>
.
oedered.md:
.
1. [x] checked ordered 1
2. [ ] unchecked ordered 2
3. [x] checked ordered 3
4. [ ] unchecked ordered 4
.
<ol class="contains-task-list">
<li class="task-list-item"><input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox"> checked ordered 1</li>
<li class="task-list-item"><input class="task-list-item-checkbox" disabled="disabled" type="checkbox"> unchecked ordered 2</li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox"> checked ordered 3</li>
<li class="task-list-item"><input class="task-list-item-checkbox" disabled="disabled" type="checkbox"> unchecked ordered 4</li>
</ol>
.
Tab after task list item marker
.
+ [x] item 1
+ [ ] item 2
.
<ul class="contains-task-list">
<li class="task-list-item"> item 1</li>
<li class="task-list-item"> item 2</li>
</ul>
.
Form feed after task list item marker
.
+ [x] item 1
+ [ ] item 2
.
<ul class="contains-task-list">
<li class="task-list-item"> item 1</li>
<li class="task-list-item"> item 2</li>
</ul>
.

387
tests/fixtures/texmath_bracket.md vendored Normal file
View File

@ -0,0 +1,387 @@
single character inline equation. (valid=True)
.
\(a\)
.
<p><eq>a</eq></p>
.
inline equation with single greek character (valid=True)
.
\(\\varphi\)
.
<p><eq>\\varphi</eq></p>
.
simple equation starting and ending with numbers. (valid=True)
.
\(1+1=2\)
.
<p><eq>1+1=2</eq></p>
.
simple equation including special html character. (valid=True)
.
\(1+1<3\)
.
<p><eq>1+1<3</eq></p>
.
equation including backslashes. (valid=True)
.
\(a \\backslash\)
.
<p><eq>a \\backslash</eq></p>
.
use of currency symbol (valid=True)
.
You get 3$ if you solve \(1+2\)
.
<p>You get 3$ if you solve <eq>1+2</eq></p>
.
use of currency symbol (valid=True)
.
If you solve \(1+2\) you get $3
.
<p>If you solve <eq>1+2</eq> you get $3</p>
.
inline fraction (valid=True)
.
\(\\frac{1}{2}\)
.
<p><eq>\\frac{1}{2}</eq></p>
.
inline column vector (valid=True)
.
\(\\begin{pmatrix}x\\\\y\\end{pmatrix}\)
.
<p><eq>\\begin{pmatrix}x\\\\y\\end{pmatrix}</eq></p>
.
inline bold vector notation (valid=True)
.
\({\\tilde\\bold e}_\\alpha\)
.
<p><eq>{\\tilde\\bold e}_\\alpha</eq></p>
.
exponentiation (valid=True)
.
\(a^{b}\)
.
<p><eq>a^{b}</eq></p>
.
conjugate complex (valid=True)
.
\(a^\*b\) with \(a^\*\)
.
<p><eq>a^\*b</eq> with <eq>a^\*</eq></p>
.
Inline multi-line (valid=True)
.
a \(a
\not=1\) b
.
<p>a <eq>a
\not=1</eq> b</p>
.
Inline multi-line with newline (valid=False)
.
a \(a
\not=1\) b
.
<p>a (a</p>
<p>\not=1) b</p>
.
single block equation, greek index (valid=True)
.
\[e_\\alpha\]
.
<section>
<eqn>e_\\alpha</eqn>
</section>
.
display equation on its own single line. (valid=True)
.
\[1+1=2\]
.
<section>
<eqn>1+1=2</eqn>
</section>
.
inline equation followed by block equation. (valid=True)
.
\({e}_x\)
\[e_\\alpha\]
.
<p><eq>{e}_x</eq></p>
<section>
<eqn>e_\\alpha</eqn>
</section>
.
underline tests (valid=True)
.
\[c{\\bold e}_x = a{\\bold e}_\\alpha - b\\tilde{\\bold e}_\\alpha\]
.
<section>
<eqn>c{\\bold e}_x = a{\\bold e}_\\alpha - b\\tilde{\\bold e}_\\alpha</eqn>
</section>
.
non-numeric character before opening $ or
after closing $ or both is allowed. (valid=True)
.
a\(1+1=2\)
\(1+1=2\)b
c\(x\)d
.
<p>a<eq>1+1=2</eq>
<eq>1+1=2</eq>b
c<eq>x</eq>d</p>
.
following dollar character '$' is allowed. (valid=True)
.
\(x\) $
.
<p><eq>x</eq> $</p>
.
consecutive inline equations. (valid=True)
.
\(x\) \(y\)
.
<p><eq>x</eq> <eq>y</eq></p>
.
inline equation after '-' sign in text. (valid=True)
.
so-what is \(x\)
.
<p>so-what is <eq>x</eq></p>
.
display equation with line breaks. (valid=True)
.
\[
1+1=2
\]
.
<section>
<eqn>
1+1=2
</eqn>
</section>
.
multiple equations (valid=True)
.
\[
a = 1
\]
\[
b = 2
\]
.
<section>
<eqn>
a = 1
</eqn>
</section>
<section>
<eqn>
b = 2
</eqn>
</section>
.
equation followed by a labelled equation (valid=True)
.
\[
a = 1
\]
\[
b = 2
\] (1)
.
<section>
<eqn>
a = 1
</eqn>
</section>
<section>
<eqn>
b = 2
</eqn>
</section>
.
multiline equation. (valid=True)
.
\[\\begin{matrix}
f & = & 2 + x + 3 \\
& = & 5 + x
\\end{matrix}\]
.
<section>
<eqn>\\begin{matrix}
f & = & 2 + x + 3 \\
& = & 5 + x
\\end{matrix}</eqn>
</section>
.
vector equation. (valid=True)
.
\[\\begin{pmatrix}x_2 \\\\ y_2 \\end{pmatrix} =
\\begin{pmatrix} A & B \\\\ C & D \\end{pmatrix}\\cdot
\\begin{pmatrix} x_1 \\\\ y_1 \\end{pmatrix}\]
.
<section>
<eqn>\\begin{pmatrix}x_2 \\\\ y_2 \\end{pmatrix} =
\\begin{pmatrix} A & B \\\\ C & D \\end{pmatrix}\\cdot
\\begin{pmatrix} x_1 \\\\ y_1 \\end{pmatrix}</eqn>
</section>
.
display equation with equation number. (valid=True)
.
\[f(x) = x^2 - 1\] (1)
.
<section>
<eqn>f(x) = x^2 - 1</eqn>
</section>
.
inline equation following code section. (valid=True)
.
`code`\(a-b\)
.
<p><code>code</code><eq>a-b</eq></p>
.
equation following code block. (valid=True)
.
```
code
```
\[a+b\]
.
<pre><code>code
</code></pre>
<section>
<eqn>a+b</eqn>
</section>
.
numbered equation following code block. (valid=True)
.
```
code
```
\[a+b\](1)
.
<pre><code>code
</code></pre>
<section>
<eqn>a+b</eqn>
</section>
.
Equations in list. (valid=True)
.
1. \(1+2\)
2. \(2+3\)
1. \(3+4\)
.
<ol>
<li><eq>1+2</eq></li>
<li><eq>2+3</eq>
<ol>
<li><eq>3+4</eq></li>
</ol>
</li>
</ol>
.
Inline sum. (valid=True)
.
\(\\sum\_{i=1}^n\)
.
<p><eq>\\sum\_{i=1}^n</eq></p>
.
Sum without equation number. (valid=True)
.
\[\\sum\_{i=1}^n\]
.
<section>
<eqn>\\sum\_{i=1}^n</eqn>
</section>
.
Sum with equation number. (valid=True)
.
\[\\sum\_{i=1}\^n\] \(2\)
.
<section>
<eqn>\\sum\_{i=1}\^n</eqn>
</section>
.
equation number always vertically aligned. (valid=True)
.
\[{\\bold e}(\\varphi) = \\begin{pmatrix}
\\cos\\varphi\\\\\\sin\\varphi
\\end{pmatrix}\] (3)
.
<section>
<eqn>{\\bold e}(\\varphi) = \\begin{pmatrix}
\\cos\\varphi\\\\\\sin\\varphi
\\end{pmatrix}</eqn>
</section>
.
inline equations in blockquote. (valid=True)
.
> see \(a = b + c\)
> \(c^2=a^2+b^2\) (2)
> \(c^2=a^2+b^2\)
.
<blockquote>
<p>see <eq>a = b + c</eq>
<eq>c^2=a^2+b^2</eq> (2)
<eq>c^2=a^2+b^2</eq></p>
</blockquote>
.
display equation in blockquote. (valid=True)
.
> formula
>
> \[ a+b=c\] (2)
>
> in blockquote.
.
<blockquote>
<p>formula</p>
<section>
<eqn> a+b=c</eqn>
</section>
<p>in blockquote.</p>
</blockquote>
.

438
tests/fixtures/texmath_dollar.md vendored Normal file
View File

@ -0,0 +1,438 @@
single character inline equation. (valid=True)
.
$a$
.
<p><eq>a</eq></p>
.
inline equation with single greek character (valid=True)
.
$\\varphi$
.
<p><eq>\\varphi</eq></p>
.
simple equation starting and ending with numbers. (valid=True)
.
$1+1=2$
.
<p><eq>1+1=2</eq></p>
.
simple equation including special html character. (valid=True)
.
$1+1<3$
.
<p><eq>1+1<3</eq></p>
.
equation including backslashes. (valid=True)
.
$a \\backslash$
.
<p><eq>a \\backslash</eq></p>
.
use of currency symbol (valid=True)
.
You get 3$ if you solve $1+2$
.
<p>You get 3$ if you solve <eq>1+2</eq></p>
.
use of currency symbol (valid=True)
.
If you solve $1+2$ you get $3
.
<p>If you solve <eq>1+2</eq> you get $3</p>
.
inline fraction (valid=True)
.
$\\frac{1}{2}$
.
<p><eq>\\frac{1}{2}</eq></p>
.
inline column vector (valid=True)
.
$\\begin{pmatrix}x\\\\y\\end{pmatrix}$
.
<p><eq>\\begin{pmatrix}x\\\\y\\end{pmatrix}</eq></p>
.
inline bold vector notation (valid=True)
.
${\\tilde\\bold e}_\\alpha$
.
<p><eq>{\\tilde\\bold e}_\\alpha</eq></p>
.
exponentiation (valid=True)
.
$a^{b}$
.
<p><eq>a^{b}</eq></p>
.
conjugate complex (valid=True)
.
$a^\*b$ with $a^\*$
.
<p><eq>a^\*b</eq> with <eq>a^\*</eq></p>
.
Inline multi-line (valid=True)
.
a $a
\not=1$ b
.
<p>a <eq>a
\not=1</eq> b</p>
.
Inline multi-line with newline (valid=False)
.
a $a
\not=1$ b
.
<p>a $a</p>
<p>\not=1$ b</p>
.
single block equation, greek index (valid=True)
.
$$e_\\alpha$$
.
<section>
<eqn>e_\\alpha</eqn>
</section>
.
display equation on its own single line. (valid=True)
.
$$1+1=2$$
.
<section>
<eqn>1+1=2</eqn>
</section>
.
inline equation followed by block equation. (valid=True)
.
${e}_x$
$$e_\\alpha$$
.
<p><eq>{e}_x</eq></p>
<section>
<eqn>e_\\alpha</eqn>
</section>
.
underline tests (valid=True)
.
$$c{\\bold e}_x = a{\\bold e}_\\alpha - b\\tilde{\\bold e}_\\alpha$$
.
<section>
<eqn>c{\\bold e}_x = a{\\bold e}_\\alpha - b\\tilde{\\bold e}_\\alpha</eqn>
</section>
.
non-numeric character before opening $ or
after closing $ or both is allowed. (valid=True)
.
a$1+1=2$
$1+1=2$b
c$x$d
.
<p>a<eq>1+1=2</eq>
<eq>1+1=2</eq>b
c<eq>x</eq>d</p>
.
following dollar character '$' is allowed. (valid=True)
.
$x$ $
.
<p><eq>x</eq> $</p>
.
consecutive inline equations. (valid=True)
.
$x$ $y$
.
<p><eq>x</eq> <eq>y</eq></p>
.
inline equation after '-' sign in text. (valid=True)
.
so-what is $x$
.
<p>so-what is <eq>x</eq></p>
.
display equation with line breaks. (valid=True)
.
$$
1+1=2
$$
.
<section>
<eqn>
1+1=2
</eqn>
</section>
.
multiple equations (valid=True)
.
$$
a = 1
$$
$$
b = 2
$$
.
<section>
<eqn>
a = 1
</eqn>
</section>
<section>
<eqn>
b = 2
</eqn>
</section>
.
equation followed by a labelled equation (valid=True)
.
$$
a = 1
$$
$$
b = 2
$$ (1)
.
<section>
<eqn>
a = 1
</eqn>
</section>
<section>
<eqn>
b = 2
</eqn>
</section>
.
multiline equation. (valid=True)
.
$$\\begin{matrix}
f & = & 2 + x + 3 \\
& = & 5 + x
\\end{matrix}$$
.
<section>
<eqn>\\begin{matrix}
f & = & 2 + x + 3 \\
& = & 5 + x
\\end{matrix}</eqn>
</section>
.
vector equation. (valid=True)
.
$$\\begin{pmatrix}x_2 \\\\ y_2 \\end{pmatrix} =
\\begin{pmatrix} A & B \\\\ C & D \\end{pmatrix}\\cdot
\\begin{pmatrix} x_1 \\\\ y_1 \\end{pmatrix}$$
.
<section>
<eqn>\\begin{pmatrix}x_2 \\\\ y_2 \\end{pmatrix} =
\\begin{pmatrix} A & B \\\\ C & D \\end{pmatrix}\\cdot
\\begin{pmatrix} x_1 \\\\ y_1 \\end{pmatrix}</eqn>
</section>
.
display equation with equation number. (valid=True)
.
$$f(x) = x^2 - 1$$ (1)
.
<section>
<eqn>f(x) = x^2 - 1</eqn>
</section>
.
inline equation following code section. (valid=True)
.
`code`$a-b$
.
<p><code>code</code><eq>a-b</eq></p>
.
equation following code block. (valid=True)
.
```
code
```
$$a+b$$
.
<pre><code>code
</code></pre>
<section>
<eqn>a+b</eqn>
</section>
.
numbered equation following code block. (valid=True)
.
```
code
```
$$a+b$$(1)
.
<pre><code>code
</code></pre>
<section>
<eqn>a+b</eqn>
</section>
.
Equations in list. (valid=True)
.
1. $1+2$
2. $2+3$
1. $3+4$
.
<ol>
<li><eq>1+2</eq></li>
<li><eq>2+3</eq>
<ol>
<li><eq>3+4</eq></li>
</ol>
</li>
</ol>
.
Inline sum. (valid=True)
.
$\\sum\_{i=1}^n$
.
<p><eq>\\sum\_{i=1}^n</eq></p>
.
Sum without equation number. (valid=True)
.
$$\\sum\_{i=1}^n$$
.
<section>
<eqn>\\sum\_{i=1}^n</eqn>
</section>
.
Sum with equation number. (valid=True)
.
$$\\sum\_{i=1}\^n$$ \(2\)
.
<section>
<eqn>\\sum\_{i=1}\^n</eqn>
</section>
.
equation number always vertically aligned. (valid=True)
.
$${\\bold e}(\\varphi) = \\begin{pmatrix}
\\cos\\varphi\\\\\\sin\\varphi
\\end{pmatrix}$$ (3)
.
<section>
<eqn>{\\bold e}(\\varphi) = \\begin{pmatrix}
\\cos\\varphi\\\\\\sin\\varphi
\\end{pmatrix}</eqn>
</section>
.
inline equations in blockquote. (valid=True)
.
> see $a = b + c$
> $c^2=a^2+b^2$ (2)
> $c^2=a^2+b^2$
.
<blockquote>
<p>see <eq>a = b + c</eq>
<eq>c^2=a^2+b^2</eq> (2)
<eq>c^2=a^2+b^2</eq></p>
</blockquote>
.
display equation in blockquote. (valid=True)
.
> formula
>
> $$ a+b=c$$ (2)
>
> in blockquote.
.
<blockquote>
<p>formula</p>
<section>
<eqn> a+b=c</eqn>
</section>
<p>in blockquote.</p>
</blockquote>
.
mixed syntax:
.
$$
a=1 \\
b=2
$$ (abc)
- ab $c=1$ d
.
<section>
<eqn>
a=1 \\
b=2
</eqn>
</section>
<ul>
<li>ab <eq>c=1</eq> d</li>
</ul>
.
escaped dollars '\\$' are interpreted as
dollar '$' characters. (valid=True)
.
\\$1+1=2$
$1+1=2\\$
.
<p>\$1+1=2$
$1+1=2\$</p>
.
empty line between text and display formula is required. (valid=False)
.
some text
\$\\$a+b=c\$\$
.
<p>some text
$\$a+b=c$$</p>
.
whitespace character after opening $
or before closing $ is not allowed. (valid=False)
.
$ $
$ x$
$x $
.
<p>$ $
$ x$
$x $</p>
.

51
tests/fixtures/wordcount.md vendored Normal file
View File

@ -0,0 +1,51 @@
syntax (text)
.
one two
three four
- five six
> seven eight
[nine ten](link)
.
{
"minutes": 0,
"text": [
"one two",
"three four",
"five six",
"seven eight",
"nine ten"
],
"words": 10
}
.
non-words
.
Geeksforgeeks, is best @# Computer Science Portal.!!!
.
{
"minutes": 0,
"words": 6
}
.
lore ipsum
.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent imperdiet hendrerit dictum. Etiam diam turpis, cursus in varius dignissim, imperdiet nec nibh. Nulla nec finibus dui. Phasellus fermentum venenatis placerat. Donec ut dui in sem rhoncus molestie. Sed auctor sem dapibus augue varius facilisis. Maecenas at suscipit dolor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vestibulum ornare dui ac tristique ultricies. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque arcu erat, ultricies ac lorem at, semper ornare nisl.
Donec massa magna, commodo et ultrices ac, rutrum non nulla. Nunc fermentum fringilla ultrices. Morbi ante nibh, accumsan ac viverra quis, gravida rutrum mi. Integer lobortis, purus id laoreet ornare, sapien odio placerat massa, vel vestibulum dolor ante id mi. Donec ex leo, ultricies non ante eget, pharetra dictum orci. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas vitae tortor ut nisi cursus egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Morbi et nunc posuere, pharetra est aliquet, iaculis libero. Aliquam leo nibh, posuere eget eros a, convallis bibendum nibh. Phasellus vulputate bibendum lacus sit amet varius. Integer ut rutrum dolor, ac finibus neque. Maecenas ultrices pretium augue vitae mollis. Fusce semper lorem eu mauris iaculis pulvinar.
Morbi ac pretium nunc, ac faucibus enim. Duis consequat nibh metus, at sodales sem luctus nec. Donec id finibus ante. Duis tincidunt vulputate efficitur. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean porttitor augue consectetur, feugiat odio in, rutrum velit. Aliquam et mi lacinia, efficitur nisl nec, rutrum mauris. Mauris efficitur eros in maximus tempor. Suspendisse potenti. Quisque cursus non libero in faucibus. Etiam dignissim urna vel nibh feugiat, at vehicula dui vulputate.
Praesent malesuada arcu quis neque condimentum vestibulum. Aliquam pretium eleifend neque, eget vulputate erat faucibus at. Quisque egestas nunc ac hendrerit fringilla. Vestibulum at tristique lacus, eget placerat risus. Aenean a metus ullamcorper, eleifend ante ut, feugiat lacus. Proin eget semper purus, ac vehicula nisl. Suspendisse eu mi enim. Nullam aliquam purus eu orci iaculis suscipit. Mauris dapibus non neque eu hendrerit. Sed eros purus, finibus ut ex ac, ultricies luctus enim. Quisque non lacus arcu. Ut dictum mauris ac tristique pulvinar. Aenean ut nisl massa. Donec nec dui scelerisque, egestas arcu sit amet, tempor eros.
Donec sit amet faucibus tellus. Cras auctor mi id quam rhoncus, eget porttitor magna ultrices. Sed tristique ut augue in facilisis. Duis in finibus diam. In hac habitasse platea dictumst. Vestibulum in pulvinar orci. Sed a justo cursus enim ultrices egestas sed sit amet leo. Donec sed auctor urna. Praesent vitae dapibus ipsum. Nulla facilisi. Pellentesque non nisi sem. Sed ac mi rutrum, blandit purus ut, facilisis ipsum.
.
{
"minutes": 2,
"words": 458
}
.

21
tests/test_admon.py Normal file
View File

@ -0,0 +1,21 @@
from pathlib import Path
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.admon import admon_plugin
FIXTURE_PATH = Path(__file__).parent
@pytest.mark.parametrize(
"line,title,input,expected",
read_fixture_file(FIXTURE_PATH.joinpath("fixtures", "admon.md")),
)
def test_all(line, title, input, expected):
md = MarkdownIt("commonmark").use(admon_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()

39
tests/test_amsmath.py Normal file
View File

@ -0,0 +1,39 @@
from pathlib import Path
from textwrap import dedent
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.amsmath import amsmath_plugin
FIXTURE_PATH = Path(__file__).parent
def test_plugin_parse(data_regression):
md = MarkdownIt().use(amsmath_plugin)
tokens = md.parse(
dedent(
"""\
a
\\begin{equation}
b=1
c=2
\\end{equation}
d
"""
)
)
data_regression.check([t.as_dict() for t in tokens])
@pytest.mark.parametrize(
"line,title,input,expected",
read_fixture_file(FIXTURE_PATH.joinpath("fixtures", "amsmath.md")),
)
def test_fixtures(line, title, input, expected):
md = MarkdownIt("commonmark").use(amsmath_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()

View File

@ -0,0 +1,135 @@
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map:
- 0
- 1
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: a
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: a
hidden: false
info: ''
level: 1
map:
- 0
- 1
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: '\begin{equation}
b=1
c=2
\end{equation}'
hidden: false
info: ''
level: 0
map:
- 1
- 4
markup: ''
meta:
environment: equation
numbered: ''
nesting: 0
tag: math
type: amsmath
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map:
- 5
- 6
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: d
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: d
hidden: false
info: ''
level: 1
map:
- 5
- 6
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close

23
tests/test_anchors.py Normal file
View File

@ -0,0 +1,23 @@
from pathlib import Path
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.anchors import anchors_plugin
FIXTURE_PATH = Path(__file__).parent
@pytest.mark.parametrize(
"line,title,input,expected",
read_fixture_file(FIXTURE_PATH.joinpath("fixtures", "anchors.md")),
)
def test_fixtures(line, title, input, expected):
md = MarkdownIt("commonmark").use(
anchors_plugin,
permalink="(permalink" in title,
permalinkBefore="before)" in title,
)
text = md.render(input)
assert text.rstrip() == expected.rstrip()

20
tests/test_attrs.py Normal file
View File

@ -0,0 +1,20 @@
from pathlib import Path
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.attrs import attrs_plugin
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
@pytest.mark.parametrize(
"line,title,input,expected", read_fixture_file(FIXTURE_PATH / "attrs.md")
)
def test_attrs(line, title, input, expected):
md = MarkdownIt("commonmark").use(attrs_plugin, spans=True)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()

36
tests/test_colon_fence.py Normal file
View File

@ -0,0 +1,36 @@
from pathlib import Path
from textwrap import dedent
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.colon_fence import colon_fence_plugin
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "colon_fence.md")
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_fixtures(line, title, input, expected):
md = MarkdownIt("commonmark").use(colon_fence_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
try:
assert text.rstrip() == expected.rstrip()
except AssertionError:
print(text)
raise
def test_plugin_parse(data_regression):
md = MarkdownIt().use(colon_fence_plugin)
tokens = md.parse(
dedent(
"""\
::: name
*content*
:::
"""
)
)
data_regression.check([t.as_dict() for t in tokens])

View File

@ -0,0 +1,17 @@
- attrs: null
block: true
children: null
content: '*content*
'
hidden: false
info: ' name'
level: 0
map:
- 0
- 3
markup: ':::'
meta: {}
nesting: 0
tag: code
type: colon_fence

48
tests/test_container.py Normal file
View File

@ -0,0 +1,48 @@
from pathlib import Path
from textwrap import dedent
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.container import container_plugin
def test_plugin_parse(data_regression):
md = MarkdownIt().use(container_plugin, "name")
tokens = md.parse(
dedent(
"""\
::: name
*content*
:::
"""
)
)
data_regression.check([t.as_dict() for t in tokens])
def test_no_new_line_issue(data_regression):
"""Fixed an IndexError when no newline on final line."""
md = MarkdownIt().use(container_plugin, "name")
tokens = md.parse(
dedent(
"""\
::: name
*content*
:::"""
)
)
data_regression.check([t.as_dict() for t in tokens])
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "container.md")
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_all(line, title, input, expected):
md = MarkdownIt("commonmark").use(container_plugin, "name")
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()

View File

@ -0,0 +1,110 @@
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ' name'
level: 0
map:
- 0
- 2
markup: ':::'
meta: {}
nesting: 1
tag: div
type: container_name_open
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 1
- 2
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: '*'
meta: {}
nesting: 1
tag: em
type: em_open
- attrs: null
block: false
children: null
content: content
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
- attrs: null
block: false
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: '*'
meta: {}
nesting: -1
tag: em
type: em_close
content: '*content*'
hidden: false
info: ''
level: 2
map:
- 1
- 2
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: ':::'
meta: {}
nesting: -1
tag: div
type: container_name_close

View File

@ -0,0 +1,110 @@
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ' name'
level: 0
map:
- 0
- 2
markup: ':::'
meta: {}
nesting: 1
tag: div
type: container_name_open
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 1
- 2
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: '*'
meta: {}
nesting: 1
tag: em
type: em_open
- attrs: null
block: false
children: null
content: content
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
- attrs: null
block: false
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: '*'
meta: {}
nesting: -1
tag: em
type: em_close
content: '*content*'
hidden: false
info: ''
level: 2
map:
- 1
- 2
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: ':::'
meta: {}
nesting: -1
tag: div
type: container_name_close

37
tests/test_deflist.py Normal file
View File

@ -0,0 +1,37 @@
from pathlib import Path
from textwrap import dedent
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.deflist import deflist_plugin
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "deflist.md")
def test_plugin_parse(data_regression):
md = MarkdownIt().use(deflist_plugin)
tokens = md.parse(
dedent(
"""\
Term 1
: Definition 1
Term 2
~ Definition 2a
~ Definition 2b
"""
)
)
data_regression.check([t.as_dict() for t in tokens])
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_all(line, title, input, expected):
md = MarkdownIt("commonmark").use(deflist_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()

View File

@ -0,0 +1,392 @@
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map:
- 0
- 7
markup: ''
meta: {}
nesting: 1
tag: dl
type: dl_open
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 0
- 0
markup: ''
meta: {}
nesting: 1
tag: dt
type: dt_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: Term 1
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: Term 1
hidden: false
info: ''
level: 2
map:
- 0
- 0
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: dt
type: dt_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 2
- 4
markup: ''
meta: {}
nesting: 1
tag: dd
type: dd_open
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map:
- 2
- 3
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: Definition 1
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: Definition 1
hidden: false
info: ''
level: 3
map:
- 2
- 3
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: dd
type: dd_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 4
- 4
markup: ''
meta: {}
nesting: 1
tag: dt
type: dt_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: Term 2
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: Term 2
hidden: false
info: ''
level: 2
map:
- 4
- 4
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: dt
type: dt_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 4
- 6
markup: ''
meta: {}
nesting: 1
tag: dd
type: dd_open
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map:
- 5
- 6
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: Definition 2a
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: Definition 2a
hidden: false
info: ''
level: 3
map:
- 5
- 6
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: dd
type: dd_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 6
- 7
markup: ''
meta: {}
nesting: 1
tag: dd
type: dd_open
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map:
- 6
- 7
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: Definition 2b
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: Definition 2b
hidden: false
info: ''
level: 3
map:
- 6
- 7
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: dd
type: dd_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: -1
tag: dl
type: dl_close

96
tests/test_dollarmath.py Normal file
View File

@ -0,0 +1,96 @@
from pathlib import Path
from textwrap import dedent
from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock
from markdown_it.rules_inline import StateInline
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.dollarmath import dollarmath_plugin
from mdit_py_plugins.dollarmath import index as main
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
def test_inline_func():
inline_func = main.math_inline_dollar()
md = MarkdownIt()
src = r"$a=1$ $b=2$"
tokens = []
state = StateInline(src, md, {}, tokens)
inline_func(state, False)
assert tokens[0].as_dict() == {
"type": "math_inline",
"tag": "math",
"nesting": 0,
"attrs": None,
"map": None,
"level": 0,
"children": None,
"content": "a=1",
"markup": "$",
"info": "",
"meta": {},
"block": False,
"hidden": False,
}
assert state.pos == 5
def test_block_func():
block_func = main.math_block_dollar()
md = MarkdownIt()
src = r"$$\na=1\n\nc\nb=2$$ (abc)"
tokens = []
state = StateBlock(src, md, {}, tokens)
block_func(state, 0, 10, False)
print(tokens[0].as_dict())
assert tokens[0].as_dict() == {
"type": "math_block_label",
"tag": "math",
"nesting": 0,
"attrs": None,
"map": [0, 1],
"level": 0,
"children": None,
"content": "\\na=1\\n\\nc\\nb=2",
"markup": "$$",
"info": "abc",
"meta": {},
"block": True,
"hidden": False,
}
def test_plugin_parse(data_regression):
md = MarkdownIt().use(dollarmath_plugin)
tokens = md.parse(
dedent(
"""\
$$
a=1
b=2
$$ (abc)
- ab $c=1$ d
"""
)
)
data_regression.check([t.as_dict() for t in tokens])
@pytest.mark.parametrize(
"line,title,input,expected",
read_fixture_file(FIXTURE_PATH.joinpath("dollar_math.md")),
)
def test_dollarmath_fixtures(line, title, input, expected):
md = MarkdownIt("commonmark").use(
dollarmath_plugin, allow_space=False, allow_digits=False, double_inline=True
)
md.options.xhtmlOut = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()

View File

@ -0,0 +1,159 @@
- attrs: null
block: true
children: null
content: '
a=1
b=2
'
hidden: false
info: abc
level: 0
map:
- 0
- 4
markup: $$
meta: {}
nesting: 0
tag: math
type: math_block_label
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map:
- 5
- 6
markup: '-'
meta: {}
nesting: 1
tag: ul
type: bullet_list_open
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 5
- 6
markup: '-'
meta: {}
nesting: 1
tag: li
type: list_item_open
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map:
- 5
- 6
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: 'ab '
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
- attrs: null
block: false
children: null
content: c=1
hidden: false
info: ''
level: 0
map: null
markup: $
meta: {}
nesting: 0
tag: math
type: math_inline
- attrs: null
block: false
children: null
content: ' d'
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: ab $c=1$ d
hidden: false
info: ''
level: 3
map:
- 5
- 6
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: '-'
meta: {}
nesting: -1
tag: li
type: list_item_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: '-'
meta: {}
nesting: -1
tag: ul
type: bullet_list_close

32
tests/test_field_list.py Normal file
View File

@ -0,0 +1,32 @@
from pathlib import Path
from textwrap import dedent
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.field_list import fieldlist_plugin
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "field_list.md")
def test_plugin_parse(data_regression):
md = MarkdownIt().use(fieldlist_plugin)
tokens = md.parse(
dedent(
"""\
:abc: Content
:def: Content
"""
)
)
data_regression.check([t.as_dict() for t in tokens])
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_all(line, title, input, expected):
md = MarkdownIt("commonmark").use(fieldlist_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()

View File

@ -0,0 +1,310 @@
- attrs:
- - class
- field-list
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map:
- 0
- 2
markup: ''
meta: {}
nesting: 1
tag: dl
type: field_list_open
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 0
- 0
markup: ''
meta: {}
nesting: 1
tag: dt
type: fieldlist_name_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: abc
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: abc
hidden: false
info: ''
level: 2
map:
- 0
- 0
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: dt
type: fieldlist_name_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 0
- 1
markup: ''
meta: {}
nesting: 1
tag: dd
type: fieldlist_body_open
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 2
map:
- 0
- 1
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: Content
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: Content
hidden: false
info: ''
level: 3
map:
- 0
- 1
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 2
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: dd
type: fieldlist_body_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 1
- 1
markup: ''
meta: {}
nesting: 1
tag: dt
type: fieldlist_name_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: def
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: def
hidden: false
info: ''
level: 2
map:
- 1
- 1
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: dt
type: fieldlist_name_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 1
- 2
markup: ''
meta: {}
nesting: 1
tag: dd
type: fieldlist_body_open
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 2
map:
- 1
- 2
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: Content
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: Content
hidden: false
info: ''
level: 3
map:
- 1
- 2
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 2
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: ''
meta: {}
nesting: -1
tag: dd
type: fieldlist_body_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: -1
tag: dl
type: field_list_close

462
tests/test_footnote.py Normal file
View File

@ -0,0 +1,462 @@
from pathlib import Path
from textwrap import dedent
from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock
from markdown_it.rules_inline import StateInline
from markdown_it.token import Token
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.footnote import footnote_plugin, index
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "footnote.md")
def test_footnote_def():
md = MarkdownIt()
src = r"[^a]: xyz"
tokens = []
state = StateBlock(src, md, {}, tokens)
index.footnote_def(state, 0, 1, False)
assert [t.as_dict() for t in tokens] == [
{
"type": "footnote_reference_open",
"tag": "",
"nesting": 1,
"attrs": None,
"map": [0, 1],
"level": 0,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {"label": "a"},
"block": False,
"hidden": False,
},
{
"type": "paragraph_open",
"tag": "p",
"nesting": 1,
"attrs": None,
"map": [0, 1],
"level": 1,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {},
"block": True,
"hidden": False,
},
{
"type": "inline",
"tag": "",
"nesting": 0,
"attrs": None,
"map": [0, 1],
"level": 2,
"children": [],
"content": "xyz",
"markup": "",
"info": "",
"meta": {},
"block": True,
"hidden": False,
},
{
"type": "paragraph_close",
"tag": "p",
"nesting": -1,
"attrs": None,
"map": None,
"level": 1,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {},
"block": True,
"hidden": False,
},
{
"type": "footnote_reference_close",
"tag": "",
"nesting": -1,
"attrs": None,
"map": None,
"level": 0,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {},
"block": False,
"hidden": False,
},
]
assert state.env == {"footnotes": {"refs": {":a": -1}}}
def test_footnote_ref():
md = MarkdownIt()
src = r"[^a]"
tokens = []
state = StateInline(src, md, {}, tokens)
state.env = {"footnotes": {"refs": {":a": -1}}}
index.footnote_ref(state, False)
# print([t.as_dict() for t in tokens])
assert [t.as_dict() for t in tokens] == [
{
"type": "footnote_ref",
"tag": "",
"nesting": 0,
"attrs": None,
"map": None,
"level": 0,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {"id": 0, "subId": 0, "label": "a"},
"block": False,
"hidden": False,
}
]
assert state.env == {
"footnotes": {"refs": {":a": 0}, "list": {0: {"label": "a", "count": 1}}}
}
def test_footnote_inline():
md = MarkdownIt().use(footnote_plugin)
src = r"^[a]"
tokens = []
state = StateInline(src, md, {}, tokens)
state.env = {"footnotes": {"refs": {":a": -1}}}
index.footnote_inline(state, False)
# print([t.as_dict() for t in tokens])
assert [t.as_dict() for t in tokens] == [
{
"type": "footnote_ref",
"tag": "",
"nesting": 0,
"attrs": None,
"map": None,
"level": 0,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {"id": 0},
"block": False,
"hidden": False,
}
]
assert state.env == {
"footnotes": {
"refs": {":a": -1},
"list": {
0: {
"content": "a",
"tokens": [
Token(
type="text",
tag="",
nesting=0,
attrs=None,
map=None,
level=0,
children=None,
content="a",
markup="",
info="",
meta={},
block=False,
hidden=False,
)
],
}
},
}
}
def test_footnote_tail():
md = MarkdownIt()
tokens = [
Token(
**{
"type": "footnote_reference_open",
"tag": "",
"nesting": 1,
"attrs": None,
"map": None,
"level": 0,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {"label": "a"},
"block": False,
"hidden": False,
}
),
Token(
**{
"type": "paragraph_open",
"tag": "p",
"nesting": 1,
"attrs": None,
"map": [0, 1],
"level": 1,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {},
"block": True,
"hidden": False,
}
),
Token(
**{
"type": "inline",
"tag": "",
"nesting": 0,
"attrs": None,
"map": [0, 1],
"level": 2,
"children": [],
"content": "xyz",
"markup": "",
"info": "",
"meta": {},
"block": True,
"hidden": False,
}
),
Token(
**{
"type": "paragraph_close",
"tag": "p",
"nesting": -1,
"attrs": None,
"map": None,
"level": 1,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {},
"block": True,
"hidden": False,
}
),
Token(
**{
"type": "footnote_reference_close",
"tag": "",
"nesting": -1,
"attrs": None,
"map": None,
"level": 0,
"children": None,
"content": "",
"markup": "",
"info": "",
"meta": {},
"block": False,
"hidden": False,
}
),
Token("other", "", 0),
]
env = {"footnotes": {"refs": {":a": 0}, "list": {0: {"label": "a", "count": 1}}}}
state = StateBlock("", md, env, tokens)
index.footnote_tail(state)
assert state.tokens == [
Token(
type="other",
tag="",
nesting=0,
attrs=None,
map=None,
level=0,
children=None,
content="",
markup="",
info="",
meta={},
block=False,
hidden=False,
),
Token(
type="footnote_block_open",
tag="",
nesting=1,
attrs=None,
map=None,
level=0,
children=None,
content="",
markup="",
info="",
meta={},
block=False,
hidden=False,
),
Token(
type="footnote_open",
tag="",
nesting=1,
attrs=None,
map=None,
level=0,
children=None,
content="",
markup="",
info="",
meta={"id": 0, "label": "a"},
block=False,
hidden=False,
),
Token(
type="paragraph_open",
tag="p",
nesting=1,
attrs=None,
map=[0, 1],
level=1,
children=None,
content="",
markup="",
info="",
meta={},
block=True,
hidden=False,
),
Token(
type="inline",
tag="",
nesting=0,
attrs=None,
map=[0, 1],
level=2,
children=[],
content="xyz",
markup="",
info="",
meta={},
block=True,
hidden=False,
),
Token(
type="footnote_anchor",
tag="",
nesting=0,
attrs=None,
map=None,
level=0,
children=None,
content="",
markup="",
info="",
meta={"id": 0, "subId": 0, "label": "a"},
block=False,
hidden=False,
),
Token(
type="paragraph_close",
tag="p",
nesting=-1,
attrs=None,
map=None,
level=1,
children=None,
content="",
markup="",
info="",
meta={},
block=True,
hidden=False,
),
Token(
type="footnote_close",
tag="",
nesting=-1,
attrs=None,
map=None,
level=0,
children=None,
content="",
markup="",
info="",
meta={},
block=False,
hidden=False,
),
Token(
type="footnote_block_close",
tag="",
nesting=-1,
attrs=None,
map=None,
level=0,
children=None,
content="",
markup="",
info="",
meta={},
block=False,
hidden=False,
),
]
def test_plugin_render():
md = MarkdownIt().use(footnote_plugin)
text = md.render(
dedent(
"""\
[^1] ^[a] [^a] [^a]
[^1]: abc
[^a]: xyz
"""
)
)
assert text == (
dedent(
"""\
<p><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> <sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> <sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup> <sup class="footnote-ref"><a href="#fn3" id="fnref3:1">[3:1]</a></sup></p>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>abc <a href="#fnref1" class="footnote-backref"></a></p>
</li>
<li id="fn2" class="footnote-item"><p>a <a href="#fnref2" class="footnote-backref"></a></p>
</li>
<li id="fn3" class="footnote-item"><p>xyz <a href="#fnref3" class="footnote-backref"></a> <a href="#fnref3:1" class="footnote-backref"></a></p>
</li>
</ol>
</section>
""" # noqa: E501
)
)
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_all(line, title, input, expected):
md = MarkdownIt("commonmark").use(footnote_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip().replace("↩︎", "<-").replace(
"", "<-"
) == expected.rstrip().replace("↩︎", "<-").replace("", "<-")

View File

@ -0,0 +1,49 @@
from pathlib import Path
from markdown_it import MarkdownIt
from markdown_it.token import Token
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.front_matter import front_matter_plugin
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "front_matter.md")
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_all(line, title, input, expected):
md = MarkdownIt("commonmark").use(front_matter_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()
def test_token():
md = MarkdownIt("commonmark").use(front_matter_plugin)
tokens = md.parse("---\na: 1\n---")
# print(tokens)
assert tokens == [
Token(
type="front_matter",
tag="",
nesting=0,
attrs=None,
map=[0, 3],
level=0,
children=None,
content="a: 1",
markup="---",
info="",
meta={},
block=True,
hidden=True,
)
]
def test_short_source():
md = MarkdownIt("commonmark").use(front_matter_plugin)
# The code should not raise an IndexError.
assert md.parse("-")

79
tests/test_myst_block.py Normal file
View File

@ -0,0 +1,79 @@
from pathlib import Path
from markdown_it import MarkdownIt
from markdown_it.token import Token
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.myst_blocks import myst_block_plugin
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "myst_block.md")
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_all(line, title, input, expected):
md = MarkdownIt("commonmark").use(myst_block_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()
def test_block_token():
md = MarkdownIt("commonmark").use(myst_block_plugin)
tokens = md.parse("+++")
expected_token = Token(
type="myst_block_break",
tag="hr",
nesting=0,
map=[0, 1],
level=0,
children=None,
content="",
markup="+++",
info="",
meta={},
block=True,
hidden=False,
)
expected_token.attrSet("class", "myst-block")
assert tokens == [expected_token]
tokens = md.parse("\n+ + + abc")
expected_token = Token(
type="myst_block_break",
tag="hr",
nesting=0,
map=[1, 2],
level=0,
children=None,
content="abc",
markup="+++",
info="",
meta={},
block=True,
hidden=False,
)
expected_token.attrSet("class", "myst-block")
assert tokens == [expected_token]
def test_comment_token():
md = MarkdownIt("commonmark").use(myst_block_plugin)
tokens = md.parse("\n\n% abc \n%def")
expected_token = Token(
type="myst_line_comment",
tag="",
nesting=0,
map=[2, 4],
level=0,
children=None,
content=" abc\ndef",
markup="%",
info="",
meta={},
block=True,
hidden=False,
)
expected_token.attrSet("class", "myst-line-comment")
assert tokens == [expected_token]

89
tests/test_myst_role.py Normal file
View File

@ -0,0 +1,89 @@
from pathlib import Path
from markdown_it import MarkdownIt
from markdown_it.token import Token
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.myst_role import myst_role_plugin
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "myst_role.md")
def test_basic():
md = MarkdownIt().use(myst_role_plugin)
src = "{abc}``` a ```"
tokens = md.parse(src)
print(tokens)
assert tokens == [
Token(
type="paragraph_open",
tag="p",
nesting=1,
attrs=None,
map=[0, 1],
level=0,
children=None,
content="",
markup="",
info="",
meta={},
block=True,
hidden=False,
),
Token(
type="inline",
tag="",
nesting=0,
attrs=None,
map=[0, 1],
level=1,
children=[
Token(
type="myst_role",
tag="",
nesting=0,
attrs=None,
map=None,
level=0,
children=None,
content=" a ",
markup="",
info="",
meta={"name": "abc"},
block=False,
hidden=False,
)
],
content="{abc}``` a ```",
markup="",
info="",
meta={},
block=True,
hidden=False,
),
Token(
type="paragraph_close",
tag="p",
nesting=-1,
attrs=None,
map=None,
level=0,
children=None,
content="",
markup="",
info="",
meta={},
block=True,
hidden=False,
),
]
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_all(line, title, input, expected):
md = MarkdownIt("commonmark").use(myst_role_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()

View File

@ -0,0 +1,33 @@
from pathlib import Path
from textwrap import dedent
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
# from markdown_it.token import Token
from mdit_py_plugins.substitution import substitution_plugin
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "substitution.md")
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_fixtures(line, title, input, expected):
md = MarkdownIt("commonmark").enable("table").use(substitution_plugin)
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()
def test_tokens(data_regression):
md = MarkdownIt().use(substitution_plugin)
tokens = md.parse(
dedent(
"""\
{{ block }}
a {{ inline }} b
"""
)
)
data_regression.check([t.as_dict() for t in tokens])

View File

@ -0,0 +1,105 @@
- attrs:
- - class
- substitution
- - text
- block
block: true
children: null
content: block
hidden: false
info: ''
level: 0
map:
- 0
- 1
markup: '{}'
meta: {}
nesting: 0
tag: div
type: substitution_block
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map:
- 2
- 3
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: 'a '
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
- attrs:
- - class
- substitution
- - text
- inline
block: false
children: null
content: inline
hidden: false
info: ''
level: 0
map: null
markup: '{}'
meta: {}
nesting: 0
tag: span
type: substitution_inline
- attrs: null
block: false
children: null
content: ' b'
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: a {{ inline }} b
hidden: false
info: ''
level: 1
map:
- 2
- 3
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close

34
tests/test_tasklists.py Normal file
View File

@ -0,0 +1,34 @@
from pathlib import Path
from textwrap import dedent
from markdown_it import MarkdownIt
from markdown_it.utils import read_fixture_file
import pytest
from mdit_py_plugins.tasklists import tasklists_plugin
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "tasklists.md")
def test_plugin_parse(data_regression):
md = MarkdownIt().use(tasklists_plugin)
tokens = md.parse(
dedent(
"""\
* [ ] Task incomplete
* [x] Task complete
* [ ] Indented task incomplete
* [x] Indented task complete
"""
)
)
data_regression.check([t.as_dict() for t in tokens])
@pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH))
def test_all(line, title, input, expected):
md = MarkdownIt("commonmark").use(tasklists_plugin)
md.options["xhtmlOut"] = False
text = md.render(input)
print(text)
assert text.rstrip() == expected.rstrip()

View File

@ -0,0 +1,458 @@
- attrs:
- - class
- contains-task-list
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map:
- 0
- 4
markup: '*'
meta: {}
nesting: 1
tag: ul
type: bullet_list_open
- attrs:
- - class
- task-list-item
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 0
- 1
markup: '*'
meta: {}
nesting: 1
tag: li
type: list_item_open
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map:
- 0
- 1
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: <input class="task-list-item-checkbox" disabled="disabled" type="checkbox">
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: html_inline
- attrs: null
block: false
children: null
content: ' Task incomplete'
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: ' Task incomplete'
hidden: false
info: ''
level: 3
map:
- 0
- 1
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: '*'
meta: {}
nesting: -1
tag: li
type: list_item_close
- attrs:
- - class
- task-list-item
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map:
- 1
- 4
markup: '*'
meta: {}
nesting: 1
tag: li
type: list_item_open
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map:
- 1
- 2
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: <input class="task-list-item-checkbox" checked="checked" disabled="disabled"
type="checkbox">
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: html_inline
- attrs: null
block: false
children: null
content: ' Task complete'
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: ' Task complete'
hidden: false
info: ''
level: 3
map:
- 1
- 2
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 2
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs:
- - class
- contains-task-list
block: true
children: null
content: ''
hidden: false
info: ''
level: 2
map:
- 2
- 4
markup: '*'
meta: {}
nesting: 1
tag: ul
type: bullet_list_open
- attrs:
- - class
- task-list-item
block: true
children: null
content: ''
hidden: false
info: ''
level: 3
map:
- 2
- 3
markup: '*'
meta: {}
nesting: 1
tag: li
type: list_item_open
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 4
map:
- 2
- 3
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: <input class="task-list-item-checkbox" disabled="disabled" type="checkbox">
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: html_inline
- attrs: null
block: false
children: null
content: ' Indented task incomplete'
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: ' Indented task incomplete'
hidden: false
info: ''
level: 5
map:
- 2
- 3
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 4
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 3
map: null
markup: '*'
meta: {}
nesting: -1
tag: li
type: list_item_close
- attrs:
- - class
- task-list-item
block: true
children: null
content: ''
hidden: false
info: ''
level: 3
map:
- 3
- 4
markup: '*'
meta: {}
nesting: 1
tag: li
type: list_item_open
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 4
map:
- 3
- 4
markup: ''
meta: {}
nesting: 1
tag: p
type: paragraph_open
- attrs: null
block: true
children:
- attrs: null
block: false
children: null
content: <input class="task-list-item-checkbox" checked="checked" disabled="disabled"
type="checkbox">
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: html_inline
- attrs: null
block: false
children: null
content: ' Indented task complete'
hidden: false
info: ''
level: 0
map: null
markup: ''
meta: {}
nesting: 0
tag: ''
type: text
content: ' Indented task complete'
hidden: false
info: ''
level: 5
map:
- 3
- 4
markup: ''
meta: {}
nesting: 0
tag: ''
type: inline
- attrs: null
block: true
children: null
content: ''
hidden: true
info: ''
level: 4
map: null
markup: ''
meta: {}
nesting: -1
tag: p
type: paragraph_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 3
map: null
markup: '*'
meta: {}
nesting: -1
tag: li
type: list_item_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 2
map: null
markup: '*'
meta: {}
nesting: -1
tag: ul
type: bullet_list_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 1
map: null
markup: '*'
meta: {}
nesting: -1
tag: li
type: list_item_close
- attrs: null
block: true
children: null
content: ''
hidden: false
info: ''
level: 0
map: null
markup: '*'
meta: {}
nesting: -1
tag: ul
type: bullet_list_close

Some files were not shown because too many files have changed in this diff Show More