forked from openkylin/myst-parser
Import Upstream version 0.18.1
This commit is contained in:
commit
aff43e4ee7
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Script to convert package setup to myst-docutils."""
|
||||
import sys
|
||||
|
||||
import tomlkit
|
||||
|
||||
|
||||
def modify_toml(content: str) -> str:
|
||||
"""Modify `pyproject.toml`."""
|
||||
doc = tomlkit.parse(content)
|
||||
|
||||
# change name of package
|
||||
doc["project"]["name"] = "myst-docutils"
|
||||
|
||||
# move dependency on docutils and sphinx to extra
|
||||
dependencies = []
|
||||
sphinx_extra = []
|
||||
for dep in doc["project"]["dependencies"]:
|
||||
if dep.startswith("docutils") or dep.startswith("sphinx"):
|
||||
sphinx_extra.append(dep)
|
||||
else:
|
||||
dependencies.append(dep)
|
||||
doc["project"]["dependencies"] = dependencies
|
||||
doc["project"]["optional-dependencies"]["sphinx"] = sphinx_extra
|
||||
|
||||
return tomlkit.dumps(doc)
|
||||
|
||||
|
||||
def modify_readme(content: str) -> str:
|
||||
"""Modify README.md."""
|
||||
content = content.replace("myst-parser", "myst-docutils")
|
||||
content = content.replace(
|
||||
"# MyST-Parser",
|
||||
"# MyST-Parser\n\nNote: myst-docutils is identical to myst-parser, "
|
||||
"but without installation requirements on sphinx",
|
||||
)
|
||||
content = content.replace("myst-docutils.readthedocs", "myst-parser.readthedocs")
|
||||
content = content.replace(
|
||||
"readthedocs.org/projects/myst-docutils", "readthedocs.org/projects/myst-parser"
|
||||
)
|
||||
return content
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
project_path = sys.argv[1]
|
||||
readme_path = sys.argv[2]
|
||||
with open(project_path) as f:
|
||||
content = f.read()
|
||||
content = modify_toml(content)
|
||||
with open(project_path, "w") as f:
|
||||
f.write(content)
|
||||
with open(readme_path) as f:
|
||||
content = f.read()
|
||||
content = modify_readme(content)
|
||||
with open(readme_path, "w") as f:
|
||||
f.write(content)
|
|
@ -0,0 +1,152 @@
|
|||
name: continuous-integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+*"
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: "3.8"
|
||||
- uses: pre-commit/action@v2.0.0
|
||||
|
||||
tests:
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
sphinx: [">=5,<6"]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
python-version: "3.8"
|
||||
sphinx: ">=4,<5"
|
||||
- os: windows-latest
|
||||
python-version: "3.8"
|
||||
sphinx: ">=4,<5"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e .[linkify,testing]
|
||||
pip install --upgrade-strategy "only-if-needed" "sphinx${{ matrix.sphinx }}"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest --cov=myst_parser --cov-report=xml --cov-report=term-missing
|
||||
coverage xml
|
||||
- name: Upload to Codecov
|
||||
if: github.repository == 'executablebooks/MyST-Parser' && matrix.python-version == 3.8
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
name: myst-parser-pytests
|
||||
flags: pytests
|
||||
file: ./coverage.xml
|
||||
fail_ci_if_error: true
|
||||
|
||||
check-myst-docutils:
|
||||
|
||||
name: Check myst-docutils install
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
docutils-version: ["0.17", "0.18", "0.19"]
|
||||
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: "3.8"
|
||||
- name: Install setup
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tomlkit
|
||||
- name: Modify setup
|
||||
run: python .github/workflows/docutils_setup.py pyproject.toml README.md
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install .
|
||||
pip install pytest~=6.2 pytest-param-files~=0.3.3 pygments docutils==${{ matrix.docutils-version }}
|
||||
- name: ensure sphinx is not installed
|
||||
run: |
|
||||
python -c "\
|
||||
try:
|
||||
import sphinx
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError()"
|
||||
- name: Run pytest for docutils-only tests
|
||||
run: pytest tests/test_docutils.py tests/test_renderers/test_fixtures_docutils.py tests/test_renderers/test_include_directive.py
|
||||
- name: Run docutils CLI
|
||||
run: echo "test" | myst-docutils-html
|
||||
|
||||
publish:
|
||||
|
||||
name: Publish myst-parser to PyPi
|
||||
needs: [pre-commit, tests, check-myst-docutils]
|
||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: "3.8"
|
||||
- name: install flit
|
||||
run: |
|
||||
pip install flit~=3.4
|
||||
- name: Build and publish
|
||||
run: |
|
||||
flit publish
|
||||
env:
|
||||
FLIT_USERNAME: __token__
|
||||
FLIT_PASSWORD: ${{ secrets.PYPI_KEY }}
|
||||
|
||||
publish-docutils:
|
||||
|
||||
name: Publish myst-docutils to PyPi
|
||||
needs: [publish]
|
||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: "3.8"
|
||||
- name: install flit and tomlkit
|
||||
run: |
|
||||
pip install flit~=3.4 tomlkit
|
||||
- name: Modify setup
|
||||
run: python .github/workflows/docutils_setup.py pyproject.toml README.md
|
||||
- name: Build and publish
|
||||
run: |
|
||||
flit publish
|
||||
env:
|
||||
FLIT_USERNAME: __token__
|
||||
FLIT_PASSWORD: ${{ secrets.PYPI_KEY_DOCUTILS }}
|
|
@ -0,0 +1,135 @@
|
|||
# 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
|
||||
!tests/**/*.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/
|
||||
|
||||
_archive/
|
||||
|
||||
.vscode/
|
||||
.DS_Store
|
|
@ -0,0 +1,59 @@
|
|||
# Install pre-commit hooks via
|
||||
# pre-commit install
|
||||
|
||||
exclude: >
|
||||
(?x)^(
|
||||
\.vscode/settings\.json|
|
||||
tests/test_commonmark/commonmark\.json|
|
||||
.*\.xml|
|
||||
tests/.*/.*\.md
|
||||
)$
|
||||
|
||||
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/asottile/pyupgrade
|
||||
rev: v2.38.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py37-plus]
|
||||
|
||||
- repo: https://github.com/PyCQA/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: 5.0.4
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-comprehensions
|
||||
- flake8-bugbear
|
||||
# - flake8-self~=0.2.2
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.971
|
||||
hooks:
|
||||
- id: mypy
|
||||
args: [--config-file=pyproject.toml]
|
||||
additional_dependencies:
|
||||
- sphinx~=5.0
|
||||
- markdown-it-py>=1.0.0,<3.0.0
|
||||
- mdit-py-plugins~=0.3.1
|
||||
files: >
|
||||
(?x)^(
|
||||
myst_parser/.*py|
|
||||
)$
|
|
@ -0,0 +1,14 @@
|
|||
version: 2
|
||||
|
||||
python:
|
||||
version: "3"
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- linkify
|
||||
- rtd
|
||||
|
||||
sphinx:
|
||||
builder: html
|
||||
fail_on_warning: true
|
|
@ -0,0 +1,728 @@
|
|||
# Changelog
|
||||
|
||||
## 0.18.1 - 2022-27-09
|
||||
|
||||
Full Changelog: [v0.18.0...v0.18.1](https://github.com/executablebooks/MyST-Parser/compare/v0.18.0...v0.18.1)
|
||||
|
||||
- ⬆️ UPGRADE: docutils 0.19 support (#611)
|
||||
- ✨ NEW: Add `attrs_image` (experimental) extension (#620)
|
||||
- e.g. `![image](image.png){#id .class width=100px}`
|
||||
- See: [Optional syntax section](docs/syntax/optional.md)
|
||||
- **Important**: This is an experimental extension, and may change in future releases
|
||||
|
||||
## 0.18.0 - 2022-06-07
|
||||
|
||||
Full Changelog: [v0.17.2...v0.18.0](https://github.com/executablebooks/MyST-Parser/compare/v0.17.2...v0.18.0)
|
||||
|
||||
This release adds support for Sphinx v5 (dropping v3), restructures the code base into modules, and also restructures the documentation, to make it easier for developers/users to follow.
|
||||
|
||||
It also introduces **document-level configuration** *via* the Markdown top-matter, under the `myst` key.
|
||||
See the [Local configuration](docs/configuration.md) section for more information.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
This should not be breaking, for general users of the sphinx extension (with `sphinx>3`),
|
||||
but will be for anyone directly using the Python API, mainly just requiring changes in import module paths.
|
||||
|
||||
The `to_docutils`, `to_html`, `to_tokens` (from `myst_parser/main.py`) and `mock_sphinx_env`/`parse` (from `myst_parser.sphinx_renderer.py`) functions have been removed, since these were primarily for internal testing.
|
||||
Instead, for single page builds, users should use the docutils parser API/CLI (see [](docs/docutils.md)),
|
||||
and for testing, functionality has been moved to <https://github.com/chrisjsewell/sphinx-pytest>.
|
||||
|
||||
The top-level `html_meta` and `substitutions` top-matter keys have also been deprecated (i.e. they will still work but will emit a warning), as they now form part of the `myst` config, e.g.
|
||||
|
||||
```yaml
|
||||
---
|
||||
html_meta:
|
||||
"description lang=en": "metadata description"
|
||||
substitutions:
|
||||
key1: I'm a **substitution**
|
||||
---
|
||||
```
|
||||
|
||||
is replaced by:
|
||||
|
||||
```yaml
|
||||
---
|
||||
myst:
|
||||
html_meta:
|
||||
"description lang=en": "metadata description"
|
||||
substitutions:
|
||||
key1: I'm a **substitution**
|
||||
---
|
||||
```
|
||||
|
||||
### Key PRs
|
||||
|
||||
- ♻️📚 Restructure code base and documentation (#566)
|
||||
- ⬆️ Drop Sphinx 3 and add Sphinx 5 support (#579)
|
||||
- 🐛 FIX: `parse_directive_text` when body followed by options (#580)
|
||||
- 🐛 FIX: floor table column widths to integers (#568), thanks to @Jean-Abou-Samra!
|
||||
|
||||
## 0.17.2 - 2022-04-17
|
||||
|
||||
Full Changelog: [v0.17.1...v0.17.2](https://github.com/executablebooks/MyST-Parser/compare/v0.17.1...v0.17.2)
|
||||
|
||||
- ♻️ REFACTOR: Replace `attrs` by `dataclasses` for configuration (#557)
|
||||
|
||||
## 0.17.1 - 2022-04-15
|
||||
|
||||
Full Changelog: [v0.17.0...v0.17.1](https://github.com/executablebooks/MyST-Parser/compare/v0.17.0...v0.17.1)
|
||||
|
||||
- 🐛 FIX: Heading anchor resolution for parallel builds (#525)
|
||||
- 🔧 MAINTAIN: Move packaging from setuptools to flit (#553)
|
||||
- 🔧 MAINTAIN: Directly specify attrs dependency (#555)
|
||||
|
||||
## 0.17.0 - 2022-02-11
|
||||
|
||||
This release contains a number of breaking improvements.
|
||||
|
||||
Full Changelog: [v0.16.1...v0.17.0](https://github.com/executablebooks/MyST-Parser/compare/v0.16.1...v0.17.0)
|
||||
|
||||
### ‼️ Markdown link resolution improvements
|
||||
|
||||
**WARNING: This is a breaking change for links that rely on auto-generated anchor links**. You should now [manually enable auto-generated anchor links](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html?highlight=anchor#auto-generated-header-anchors) if you see errors like `WARNING reference target not found`.
|
||||
|
||||
Markdown links are of the format `[text](link)`.
|
||||
MyST-Parser looks to smartly resolve such links, by identifying if they are:
|
||||
|
||||
1. A link to an external resource, e.g. `[text](http://example.com)`
|
||||
2. A link to another source document, e.g. `[text](file.md)`
|
||||
- If `header-anchors` are enabled, anchor links are also supported, e.g. `[text](file.md#anchor)`
|
||||
3. A link to an internal sphinx cross-reference, e.g. `[text](my-reference)`
|
||||
|
||||
an additional situation is now supported:
|
||||
|
||||
4. A link to a source file, which is not a document, e.g. `[text](file.js)`. This behaves similarly to the sphinx `download` role.
|
||||
|
||||
In addition, configuration to more finely tune this behaviour has been added.
|
||||
|
||||
- `myst_all_links_external=True`, will make all links be treated as (1)
|
||||
- `myst_url_schemes=("http", "https")`, sets what URL schemes are treated as (1)
|
||||
- `myst_ref_domains=("std", "py")`, sets what Sphinx reference domains are checked, when handling (3)
|
||||
|
||||
See [Markdown Links and Referencing](docs/syntax/syntax.md#markdown-links-and-referencing) for more information.
|
||||
|
||||
### ‼️ Dollarmath is now disabled by default
|
||||
|
||||
**WARNING: This is a breaking change for dollar math**. You should now manually enable dollar math (see below).
|
||||
|
||||
The default configuration is now `myst_enable_extensions=()`, instead of `myst_enable_extensions=("dollarmath",)`.
|
||||
If you are using math enclosed in `$` or `$$` in your documents, you should enable `dollarmath` explicitly.
|
||||
|
||||
See [Dollar delimited math](docs/syntax/optional.md#math-shortcuts) for more information.
|
||||
|
||||
### ⬆️ Drop Python 3.6 support
|
||||
|
||||
MyST-Parser now supports, and is tested against, Python 3.7 to 3.10.
|
||||
|
||||
### ✨ Add the `strikethrough` extension and `myst_gfm_only` configuration
|
||||
|
||||
The `strikethrough` extension allows text within `~~` delimiters to have a strike-through (horizontal line) placed over it.
|
||||
For example, `~~strikethrough with *emphasis*~~` renders as: ~~strikethrough with *emphasis*~~.
|
||||
|
||||
**Important**: This extension is currently only supported for HTML output.
|
||||
|
||||
See [Strikethrough](docs/syntax/optional.md#strikethrough) for more information.
|
||||
|
||||
The `myst_gfm_only=True` configuration sets up specific configuration, to enable compliance only with [GitHub-flavored Markdown](https://github.github.com/gfm/), including enabling the `strikethrough`, `tasklist` and `linkify` extensions, but disabling support for roles and directives.
|
||||
|
||||
### ✨ Add `myst_title_to_header` configuration
|
||||
|
||||
Setting `myst_title_to_header=True`, allows for a `title` key in the frontmatter to be used as the document title.
|
||||
for example:
|
||||
|
||||
```md
|
||||
---
|
||||
title: My Title with *emphasis*
|
||||
---
|
||||
```
|
||||
|
||||
would be equivalent to:
|
||||
|
||||
```md
|
||||
# My Title with *emphasis*
|
||||
```
|
||||
|
||||
See [Front matter](docs/syntax/syntax.md#front-matter) for more information.
|
||||
|
||||
### 👌 Internal improvements
|
||||
|
||||
👌 IMPROVE: Convert nested headings to rubrics.
|
||||
Headings within directives are not directly supported by sphinx, since they break the structure of the document. Previously myst-parser would emit a `myst.nested_header` warning, but still generate the heading, leading to unexpected outcomes.
|
||||
Now the warning is still emitted, but also the heading is rendered as a [rubric](https://docutils.sourceforge.io/docs/ref/rst/directives.html#rubric) non-structural heading (i.e. it will not show in the ToC).
|
||||
|
||||
Other internal improvements primarily focused in improving support for the for "docutils-only" use, introduced in `v0.16`:
|
||||
|
||||
- ♻️ REFACTOR: `default_parser` -> `create_md_parser` in [#474](https://github.com/executablebooks/MyST-Parser/pull/474)
|
||||
- 👌 IMPROVE: Add `bullet` attribute to `bullet_list` node in [#465](https://github.com/executablebooks/MyST-Parser/pull/465)
|
||||
- 👌 IMPROVE: Use correct renderer for `state.inline_text` in [#466](https://github.com/executablebooks/MyST-Parser/pull/466)
|
||||
- 👌 IMPROVE: Docutils parser settings in [#476](https://github.com/executablebooks/MyST-Parser/pull/476)
|
||||
- 🐛 FIX: front-matter rendering with docutils in [#477](https://github.com/executablebooks/MyST-Parser/pull/477)
|
||||
- 👌 IMPROVE: Code block highlighting in [#478](https://github.com/executablebooks/MyST-Parser/pull/478)
|
||||
- 👌 IMPROVE: `note_refname` for docutils internal links in [#481](https://github.com/executablebooks/MyST-Parser/pull/481)
|
||||
- 🐛 FIX: Ordered list starting number in [#483](https://github.com/executablebooks/MyST-Parser/pull/483)
|
||||
- 👌 IMPROVE: Propagate enumerated list suffix in [#484](https://github.com/executablebooks/MyST-Parser/pull/484)
|
||||
- 👌 IMPROVE: `DocutilsRenderer.create_highlighted_code_block` in [#488](https://github.com/executablebooks/MyST-Parser/pull/488)
|
||||
- 🐛 FIX: Source line reporting for nested parsing in [#490](https://github.com/executablebooks/MyST-Parser/pull/490)
|
||||
- 🔧 MAINTAIN: Implement `MockInliner.parse` in [#504](https://github.com/executablebooks/MyST-Parser/pull/504)
|
||||
|
||||
## 0.16.1 - 2021-12-16
|
||||
|
||||
✨ NEW: Add `myst_linkify_fuzzy_links` option.
|
||||
When using the [`linkify` extension](docs/syntax/optional.md#linkify), this option can be used to disable matching of links that do not contain a schema (such as `http://`).
|
||||
|
||||
## 0.16.0 - 2021-12-06
|
||||
|
||||
This release contains a number of exciting improvements:
|
||||
|
||||
### Upgrade of Markdown parser
|
||||
|
||||
`markdown-it-py` has been upgraded to [v2.0.0](https://github.com/executablebooks/markdown-it-py/releases/tag/v2.0.0).
|
||||
This upgrade brings full compliance with the [CommonMark v0.30 specification](https://spec.commonmark.org/0.30/).
|
||||
|
||||
Additionally, `mdit-py-plugins` has been upgraded to [v0.3.0](https://github.com/executablebooks/mdit-py-plugins/releases/tag/v0.3.0).
|
||||
This improves the parsing of the MyST target syntax, to allow for spaces and additional special characters in the target name,
|
||||
for example this is now valid:
|
||||
|
||||
```md
|
||||
(a bc |@<>*./_-+:)=
|
||||
|
||||
# Header
|
||||
```
|
||||
|
||||
Also MyST role syntax now supports unlimited length in the role name and new lines in the content.
|
||||
For example, this is now valid:
|
||||
|
||||
```md
|
||||
{abc}`xy
|
||||
new line`
|
||||
```
|
||||
|
||||
### Improvements for Docutils-only use
|
||||
|
||||
MyST now allows for Docutils-only use (outside of Sphinx), that allows for MyST configuration options to be set via the `docutils.conf` file, or on the command line.
|
||||
|
||||
On installing MyST-Parser, the following CLI-commands are made available:
|
||||
|
||||
- `myst-docutils-html`: converts MyST to HTML
|
||||
- `myst-docutils-html5`: converts MyST to HTML5
|
||||
- `myst-docutils-latex`: converts MyST to LaTeX
|
||||
- `myst-docutils-xml`: converts MyST to docutils-native XML
|
||||
- `myst-docutils-pseudoxml`: converts MyST to pseudo-XML (to visualise the AST structure)
|
||||
|
||||
You can also install the [myst-docutils](https://pypi.org/project/myst-docutils/) package from `pip`,
|
||||
which includes no direct install requirements on docutils or sphinx.
|
||||
|
||||
See [MyST with Docutils](docs/docutils.md) for more information.
|
||||
|
||||
Thanks to help from [@cpitclaudel](https://github.com/cpitclaudel)!
|
||||
|
||||
### Include MyST files in RST files
|
||||
|
||||
With `docutils>=0.17`, the `include` directive has a `parser` option.
|
||||
This can be used with myst-parser to include MyST files in RST files.
|
||||
|
||||
```md
|
||||
Parse using the docutils only parser:
|
||||
|
||||
.. include:: include.md
|
||||
:parser: myst_parser.docutils_
|
||||
|
||||
Parse using the sphinx parser:
|
||||
|
||||
.. include:: include.md
|
||||
:parser: myst_parser.sphinx_
|
||||
```
|
||||
|
||||
### Addition of the `fieldlist` syntax extension
|
||||
|
||||
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):
|
||||
|
||||
```rst
|
||||
:name only:
|
||||
:name: body
|
||||
:name:
|
||||
Multiple
|
||||
|
||||
Paragraphs
|
||||
```
|
||||
|
||||
This should eventually allow for MyST Markdown docstrings! (see <https://github.com/executablebooks/MyST-Parser/issues/228>)
|
||||
|
||||
See [Field Lists syntax](docs/syntax/optional.md#field-lists) for more information.
|
||||
|
||||
### Improvements to table rendering
|
||||
|
||||
Tables with no body are now allowed, for example:
|
||||
|
||||
```md
|
||||
| abc | def |
|
||||
| --- | --- |
|
||||
```
|
||||
|
||||
Also cell alignment HTML classes have now been changed to: `text-left`, `text-center`, or `text-right`, for example:
|
||||
|
||||
```md
|
||||
| left | center | right |
|
||||
| :--- | :----: | ----: |
|
||||
| a | b | c |
|
||||
```
|
||||
|
||||
is converted to:
|
||||
|
||||
```html
|
||||
<table class="colwidths-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left head"><p>left</p></th>
|
||||
<th class="text-center head"><p>center</p></th>
|
||||
<th class="text-right head"><p>right</p></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-left"><p>a</p></td>
|
||||
<td class="text-center"><p>b</p></td>
|
||||
<td class="text-right"><p>c</p></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
These classes should be supported by most sphinx HTML themes.
|
||||
|
||||
See [Tables syntax](docs/syntax/syntax.md#tables) for more information.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
- 🐛 FIX: Add mandatory attributes on `enumerated_list` by @cpitclaudel in [#418](https://github.com/executablebooks/MyST-Parser/pull/418)
|
||||
- 📚 DOCS: Add reference to MySTyc in landing page by @astrojuanlu in [#413](https://github.com/executablebooks/MyST-Parser/pull/413)
|
||||
- ⬆️ UPGRADE: markdown-it-py v2, mdit-py-plugins v0.3 by @chrisjsewell in [#449](https://github.com/executablebooks/MyST-Parser/pull/449)
|
||||
- 👌 IMPROVE: Table rendering by @chrisjsewell in [#450](https://github.com/executablebooks/MyST-Parser/pull/450)
|
||||
- 🐛 FIX: Ensure parent files are re-built if `include` file changes by @chrisjsewell in [#451](https://github.com/executablebooks/MyST-Parser/pull/451)
|
||||
- 🐛 FIX: Convert empty directive option to `None` by @chrisjsewell in [#452](https://github.com/executablebooks/MyST-Parser/pull/452)
|
||||
- 👌 IMPROVE: Add `\\` for hard-breaks in latex by @chrisjsewell in [#453](https://github.com/executablebooks/MyST-Parser/pull/453)
|
||||
- 🔧 MAINTAIN: Remove empty "sphinx" extra by @hukkin in [#350](https://github.com/executablebooks/MyST-Parser/pull/350)
|
||||
- ✨ NEW: Add `fieldlist` extension by @chrisjsewell in [#455](https://github.com/executablebooks/MyST-Parser/pull/455)
|
||||
- ✨ NEW: Add Docutils MyST config and CLI by @cpitclaudel in [#426](https://github.com/executablebooks/MyST-Parser/pull/426)
|
||||
- 🔧 MAINTAIN: Add publishing job for `myst-docutils` by @chrisjsewell in [#456](https://github.com/executablebooks/MyST-Parser/pull/456)
|
||||
- 🧪 TESTS: Add for `gettext_additional_targets` by @jpmckinney in [#459](https://github.com/executablebooks/MyST-Parser/pull/459)
|
||||
|
||||
### New Contributors
|
||||
|
||||
- @cpitclaudel made their first contribution in [#418](https://github.com/executablebooks/MyST-Parser/pull/418)
|
||||
- @astrojuanlu made their first contribution in [#413](https://github.com/executablebooks/MyST-Parser/pull/413)
|
||||
|
||||
**Full Changelog**: <https://github.com/executablebooks/MyST-Parser/compare/v0.15.2...v0.16.0>
|
||||
|
||||
## 0.15.2 - 2021-08-26
|
||||
|
||||
This is mainly a maintenance release that fixes some incompatibilities with `sphinx<3.1`, improvements for compatibility
|
||||
with `docutils=0.17`, and improvements to robustness.
|
||||
|
||||
## 0.15.1 - 2021-06-18
|
||||
|
||||
👌 IMPROVE: MathJax compatibility with `nbsphinx`
|
||||
|
||||
`nbsphinx` also overrides the MathJax configuration.
|
||||
For compatibility, `output_area` is added to the list of default processed classes, and the override warning is allowed to be suppressed with `suppress_warnings = ["myst.mathjax"]`.
|
||||
|
||||
## 0.15.0 - 2021-06-13
|
||||
|
||||
### Upgraded to `sphinx` v4 ⬆️
|
||||
|
||||
A principe change in this release is to updates the requirements of myst-parser from `sphinx>=2,<4` to `sphinx>=3,<5`.
|
||||
|
||||
### Changed MathJax handling ♻️
|
||||
|
||||
Instead of removing all `$` processing for the whole project,
|
||||
during MyST document parsing, the top-level section is now given the classes `tex2jax_ignore` and `mathjax_ignore` (turning off default MathJax processing of all HTML elements)
|
||||
and MathJax is then configured to process elements with the `tex2jax_process|mathjax_process|math` classes.
|
||||
|
||||
See [the math syntax guide](docs/syntax/optional.md#math-shortcuts) for further information.
|
||||
|
||||
### Set URL scheme defaults ‼️
|
||||
|
||||
The `myst_url_schemes` default is now: `("http", "https", "mailto", "ftp")`.
|
||||
This means that only these URL will be considered as external (e.g. `[](https://example.com)`),
|
||||
and references like `[](prefix:main)` will be considered as internal references.
|
||||
Set `myst_url_schemes = None`, to revert to the previous default.
|
||||
|
||||
### Added `myst_heading_slug_func` option 👌
|
||||
|
||||
Use this option to specify a custom function to auto-generate heading anchors (see [Auto-generated header anchors](docs/syntax/optional.md#auto-generated-header-anchors)).
|
||||
|
||||
Thanks to [@jpmckinney](https://github.com/jpmckinney)!
|
||||
|
||||
## 0.14.0 - 2021-05-04
|
||||
|
||||
### Upgrade to `markdown-it-py` v1.0 ⬆️
|
||||
|
||||
This release updates the code-base to fully support the [markdown-it-py](https://markdown-it-py.readthedocs.io) `v1.0.0` release.
|
||||
In particular for users, this update alters the parsing of tables to be consistent with the [Github Flavoured Markdown (GFM) specification](https://github.github.com/gfm/#tables-extension-).
|
||||
|
||||
### New Features ✨
|
||||
|
||||
- **Task lists** utilise the [markdown-it-py tasklists plugin](markdown_it:md/plugins), and are applied to Markdown list items starting with `[ ]` or `[x]`.
|
||||
|
||||
```markdown
|
||||
- [ ] An item that needs doing
|
||||
- [x] An item that is complete
|
||||
```
|
||||
|
||||
Add "tasklist" to the `myst_enable_extensions` configuration to enable.
|
||||
|
||||
See [the optional syntax guide](docs/syntax/optional.md#task-lists) for further information.
|
||||
|
||||
- The **`sub-ref`** role has been added for use identical to ReST's `|name|` syntax.
|
||||
|
||||
This allows one to access Sphinx's built-in `|today|`, `|release|` and `|version|` substitutions, and also introduces two new substitutions: `wordcount-words` and `wordcount-minutes`, computed by the markdown-it-py [`wordcount_plugin`](https://github.com/executablebooks/mdit-py-plugins/pull/20).
|
||||
|
||||
```markdown
|
||||
> {sub-ref}`today` | {sub-ref}`wordcount-words` words | {sub-ref}`wordcount-minutes` min read
|
||||
```
|
||||
|
||||
See [the roles syntax guide](docs/syntax/syntax.md) for further information.
|
||||
|
||||
- The **`dmath_double_inline`** configuration option allows display math (i.e. `$$`) within an inline context.
|
||||
See [the math syntax guide](docs/syntax/optional.md#math-shortcuts) for further information.
|
||||
|
||||
### Remove v0.13 deprecations ‼️
|
||||
|
||||
The deprecations made to extension configurations and colon fences in `0.13.0` (see below) have now been removed:
|
||||
|
||||
- Configuration variables: `myst_admonition_enable`, `myst_figure_enable`, `myst_dmath_enable`, `myst_amsmath_enable`, `myst_deflist_enable`, `myst_html_img_enable`
|
||||
- `:::{admonition,class}` -> `:::{admonition}\n:class: class`
|
||||
- `:::{figure}` -> `:::{figure-md}`
|
||||
|
||||
### Fix extraction of nested footnotes 🐛
|
||||
|
||||
Previously footnote definitions in block elements like lists would crash the parsing:
|
||||
|
||||
```markdown
|
||||
- [^e]: footnote definition in a block element
|
||||
```
|
||||
|
||||
These are now correctly extracted.
|
||||
|
||||
## 0.13.7 - 2021-04-25
|
||||
|
||||
👌 IMPROVE: Add warning for nested headers:
|
||||
|
||||
Nested headers are not supported within most elements (this is a limitation of the docutils/sphinx document structure), and can lead to unexpected outcomes.
|
||||
For example in admonitions:
|
||||
|
||||
````markdown
|
||||
```{note}
|
||||
# Unsupported Header
|
||||
```
|
||||
````
|
||||
|
||||
A warning (of type `myst.nested_header`) is now emitted when this occurs.
|
||||
|
||||
🔧 MAINTAIN: Python 3.9 is now officially supported.
|
||||
|
||||
## 0.13.6 - 2021-04-10
|
||||
|
||||
🐛 FIX: docutils `v0.17` compatibility
|
||||
|
||||
## 0.13.5 - 2021-02-15
|
||||
|
||||
- ⬆️ UPGRADE: required markdown-it-py to `v0.6.2`:
|
||||
In particular, this fixes missing source line mappings for table rows and their children
|
||||
- 👌 IMPROVE: Store `rawtext` in AST nodes:
|
||||
We now ensure that the raw text is propagated from the Markdown tokens to the Sphinx AST.
|
||||
In particular, this is required by the `gettext` builder, to generate translation POT templates.
|
||||
Thanks to [@jpmckinney](https://github.com/jpmckinney)!
|
||||
- ✨ NEW: Add warning types `myst.subtype`:
|
||||
All parsing warnings are assigned a type/subtype, and also the messages are appended with them.
|
||||
These warning types can be suppressed with the sphinx `suppress_warnings` config option.
|
||||
See [How-to suppress warnings](howto/warnings) for more information.
|
||||
|
||||
## 0.13.3 - 2021-01-20
|
||||
|
||||
Minor fixes:
|
||||
|
||||
- 🐛 FIX: front-matter parsing for bibliographic keys
|
||||
- 🐛 FIX: directive/role name translations
|
||||
- 👌 IMPROVE: Add warning for multiple footnote definitions
|
||||
|
||||
## 0.13.2 - 2021-01-20
|
||||
|
||||
✨ NEW: Add `html_admonition` extension
|
||||
|
||||
: By adding `"html_admonition"` to `myst_enable_extensions`, you can enable parsing of `<div class="admonition">` HTML blocks to sphinx admonitions.
|
||||
: This is helpful when you care about viewing the "source" Markdown, such as in Jupyter Notebooks.
|
||||
: For example:
|
||||
```html
|
||||
<div class="admonition note" name="html-admonition">
|
||||
<p class="title">This is the **title**</p>
|
||||
This is the *content*
|
||||
</div>
|
||||
```
|
||||
: See [the optional syntax guide](docs/syntax/optional.md) for further information.
|
||||
|
||||
👌 IMPROVE: Footnotes
|
||||
|
||||
: If the label is an integer, then it will always use this integer for the rendered label (i.e. they are manually numbered).
|
||||
: Add `myst_footnote_transition` configuration, to turn on/off transition line.
|
||||
: Add `footnotes` class to transition `<hr>` in HTML.
|
||||
: See [the syntax guide](docs/syntax/syntax.md) for further information.
|
||||
|
||||
👌 IMPROVE: `substitution` extension logic
|
||||
|
||||
: Parse inline substitutions without block rules, unless the substitution starts with a directive.
|
||||
|
||||
🐛 FIX: Render front-matter as `field_list`
|
||||
|
||||
: To improve use by sphinx extensions).
|
||||
|
||||
👌 IMPROVE: Code quality
|
||||
|
||||
: Add isort and mypy type checking to code base.
|
||||
|
||||
(thanks to contributors @akhmerov, @tfiers)
|
||||
|
||||
## 0.13.1 - 2020-12-31
|
||||
|
||||
👌 Directives can now be used for inline substitutions, e.g.
|
||||
|
||||
```md
|
||||
---
|
||||
substitutions:
|
||||
key: |
|
||||
```{image} img/fun-fish.png
|
||||
:alt: fishy
|
||||
:height: 20px
|
||||
```
|
||||
---
|
||||
|
||||
An inline image: {{ key }}
|
||||
```
|
||||
|
||||
## 0.13.0 - 2020-12-18
|
||||
|
||||
This release makes some major updates to the optional syntaxes.
|
||||
For full details see [Optional MyST Syntaxes](docs/syntax/optional.md).
|
||||
|
||||
### 🗑 Deprecations
|
||||
|
||||
`myst_enable_extensions = ["dollarmath", ...]` now replaces and deprecates individual enable configuration variables: `admonition_enable` -> `"colon_fence"`, `figure_enable` -> `"colon_fence"`, `dmath_enable` -> `"dollarmath"`, `amsmath` -> `"colon_fence"`, `deflist_enable` -> `"deflist"`, `html_img_enable` -> `"html_image"`.
|
||||
|
||||
The `colon_fence` extension (replacing `admonition_enable`) now works exactly the same as normal ```` ``` ```` code fences, but using `:::` delimiters. This is helpful for directives that contain Markdown text, for example:
|
||||
|
||||
```md
|
||||
:::{admonition} The title
|
||||
:class: note
|
||||
|
||||
This note contains *Markdown*
|
||||
:::
|
||||
```
|
||||
|
||||
### ✨ New
|
||||
|
||||
The `smartquotes` extension will automatically convert standard quotations to their opening/closing variants:
|
||||
|
||||
- `'single quotes'`: ‘single quotes’
|
||||
- `"double quotes"`: “double quotes”
|
||||
|
||||
The `linkify` extension will automatically identify “bare” web URLs, like `www.example.com`, and add hyperlinks; www.example.com.
|
||||
This extension requires that [linkify-it-py](https://github.com/tsutsu3/linkify-it-py) is installed.
|
||||
|
||||
The `replacements` extension will automatically convert some common typographic texts, such as `+-` -> `±`.
|
||||
|
||||
The `substitution` extension allows you to specify "substitution definitions" in either the `conf.py` (as `myst_substitutions`) and/or individual file's front-matter (front-matter takes precedence), which will then replace substitution references. For example:
|
||||
|
||||
```md
|
||||
---
|
||||
substitutions:
|
||||
key1: definition
|
||||
---
|
||||
{{ key1 }}
|
||||
```
|
||||
|
||||
The substitutions are assessed as [jinja2 expressions](http://jinja.palletsprojects.com/) and includes the [Sphinx Environment](https://www.sphinx-doc.org/en/master/extdev/envapi.html) as `env`, so you can do powerful thinks like:
|
||||
|
||||
```
|
||||
{{ [key1, env.docname] | join('/') }}
|
||||
```
|
||||
|
||||
The `figure-md` directive has been added (replacing `enable_figure`), which parses a "Markdown friendly" figure (used with the `colon_fence` extension):
|
||||
|
||||
```md
|
||||
:::{figure-md} fig-target
|
||||
:class: myclass
|
||||
|
||||
<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
|
||||
|
||||
This is a caption in **Markdown**
|
||||
:::
|
||||
```
|
||||
|
||||
### 👌 Improvements
|
||||
|
||||
Using the `html_image` extension, HTML images are now processed for both blocks and (now) inline.
|
||||
|
||||
So you can correctly do, for example:
|
||||
|
||||
```md
|
||||
I’m an inline image: <img src="img/fun-fish.png" height="20px">
|
||||
|
||||
| table column |
|
||||
| ----------------------------------------- |
|
||||
| <img src="img/fun-fish.png" width="20px"> |
|
||||
```
|
||||
|
||||
## 0.12.10 - 2020-09-21
|
||||
|
||||
🐛 FIX: allow dates to be parsed in frontmatter.
|
||||
: This fixes a bug that would raise errors at parse time if non-string date objects were in front-matter YAML. See [#253](https://github.com/executablebooks/MyST-Parser/pull/253)
|
||||
|
||||
## 0.12.9 - 2020-09-08
|
||||
|
||||
✨ NEW: Auto-generate heading anchors.
|
||||
: This utilises `markdown-it-py`'s `anchors-plugin`, to generate unique anchor "slugs" for each header (up to a certain level),
|
||||
and allows them to be referenced *via* a relative path, e.g. `[](./file.md#header-anchor)`, or in the same document, e.g. `[](#header-anchor)`.
|
||||
|
||||
Slugs are generated in the GitHub style ([see here](https://github.com/Flet/github-slugger)); lower-case text, removing punctuation, replacing spaces with `-`, enforce uniqueness *via* suffix enumeration `-1`.
|
||||
|
||||
It is enabled in your `conf.py` *via* `myst_heading_anchors = 2` (sets maximum heading level).
|
||||
|
||||
See [the documentation here](docs/syntax/optional.md#auto-generated-header-anchors).
|
||||
|
||||
🐛 FIX: doc reference resolution for singlehtml/latex.
|
||||
: These reference resolutions are passed to the "missing-reference" event, and require the `node["refdoc"]` attribute to be available, which was missing for `[text](./path/to/file.md)` type references.
|
||||
|
||||
## 0.12.7 - 2020-08-31
|
||||
|
||||
✨ NEW: Want to include your README.md in the documentation?
|
||||
: See [including a file from outside the docs folder](howto/include-readme).
|
||||
|
||||
(👌 added `relative-docs` option in 0.12.8)
|
||||
|
||||
## 0.12.5 - 2020-08-28
|
||||
|
||||
✨ NEW: Add Markdown figure syntax
|
||||
: Setting `myst_figure_enable = True` in your sphinx `conf.py`, combines the above two extended syntaxes,
|
||||
to create a fully Markdown compliant version of the `figure` directive.
|
||||
See [Markdown Figures](docs/syntax/optional.md#markdown-figures) for details.
|
||||
|
||||
(👌 formatting of caption improved in 0.12.6)
|
||||
|
||||
## 0.12.4 - 2020-08-27
|
||||
|
||||
👌 IMPROVE: the mathjax extension is now only overridden if strictly necessary (to support dollar and ams math), and the override is more precise, to mitigate any unwanted side-effects
|
||||
|
||||
## 0.12.3 - 2020-08-26
|
||||
|
||||
✨ NEW: Add definition lists.
|
||||
: This addition, enabled by `myst_deflist_enable = True`, allows for "Pandoc style" definition lists to be parsed and rendered, e.g.
|
||||
|
||||
```md
|
||||
Term 1
|
||||
: Definition
|
||||
```
|
||||
|
||||
See the [Definition Lists documentation](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html#definition-lists) for further details.
|
||||
|
||||
👌 IMPROVE: mathjax_config override.
|
||||
: Only `mathjax_config["tex2jax"]` will now be overridden, in order to not interfere with other user configurations, such as adding TeX macros.
|
||||
The configuration name has also changed from `myst_override_mathjax` to `myst_update_mathjax`.
|
||||
See [Mathjax and math parsing](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#mathjax-and-math-parsing) for further details.
|
||||
|
||||
## 0.12.2 - 2020-08-25
|
||||
|
||||
✨ NEW: Add the `eval-rst` directive
|
||||
|
||||
: This directive parses its contents as ReStructuredText, which integrates back into the rest of the document, e.g. for cross-referencing. See [this documentation](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#how-directives-parse-content) for further explanation.
|
||||
|
||||
In particular, this addition solves some outstanding user requests:
|
||||
|
||||
- How-to [include rST files into a Markdown file](https://myst-parser.readthedocs.io/en/latest/using/howto.html#include-rst-files-into-a-markdown-file)
|
||||
- How-to [Use sphinx.ext.autodoc in Markdown files](https://myst-parser.readthedocs.io/en/latest/using/howto.html#use-sphinx-ext-autodoc-in-markdown-files)
|
||||
|
||||
Thanks to [@stephenroller](https://github.com/stephenroller) for the contribution 🎉
|
||||
|
||||
## 0.12.1 - 2020-08-19
|
||||
|
||||
✨ NEW: Add `myst_commonmark_only` config option, for restricting the parser to strict CommonMark (no extensions).
|
||||
|
||||
## 0.12.0 - 2020-08-19
|
||||
|
||||
### ‼️ BREAKING
|
||||
|
||||
If you are using math in your documents, be sure to read the updated [Math syntax guide](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#math-shortcuts)!
|
||||
In particular, the Mathjax configuration is now overridden, such that LaTeX environments will only be rendered if `myst_amsmath_enable=True` is set.
|
||||
|
||||
The `myst_math_delimiters` option has also been removed (please open an issue if you would like brackets math parsing to be re-implemented).
|
||||
|
||||
In addition the `myst_html_img` option name has been changed to `myst_html_img_enable`.
|
||||
|
||||
Some underlying code has also been refactored, to centralise handling of configuration options (see [commit 98573b9](https://github.com/executablebooks/MyST-Parser/commit/98573b9c6e3602ab31d627b5266ae5c1ba2c9e5f)).
|
||||
|
||||
### Improved 👌
|
||||
|
||||
More configuration options for math parsing (see [MyST configuration options](https://myst-parser.readthedocs.io/en/latest/using/intro.html#myst-configuration-options)).
|
||||
|
||||
## 0.11.2 - 2020-07-13
|
||||
|
||||
### Added ✨
|
||||
|
||||
- `<img src="file.png" width="200px">` tag parsing to sphinx representation, see [the image syntax guide](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#images)
|
||||
|
||||
### Improved 👌
|
||||
|
||||
- `[title](link)` syntax now works with intersphinx references.
|
||||
Recognised URI schemas can also be configured, see the [configuration options](https://myst-parser.readthedocs.io/en/latest/using/intro.html#myst-configuration-options)
|
||||
|
||||
## 0.11.1 - 2020-07-12
|
||||
|
||||
### Fix
|
||||
|
||||
- Correctly pin required minimum markdown-it-py version
|
||||
|
||||
## 0.11.0 - 2020-07-12
|
||||
|
||||
### Added ✨
|
||||
|
||||
* Special admonition directive syntax (optional):
|
||||
|
||||
```md
|
||||
:::{note}
|
||||
This text is **standard** _Markdown_
|
||||
:::
|
||||
```
|
||||
|
||||
See [the syntax guide section](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#admonition-directives-special-syntax-optional) for details.
|
||||
|
||||
* Direct parsing of [amsmath](https://ctan.org/pkg/amsmath) LaTeX equations (optional).
|
||||
See [the syntax guide section](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#direct-latex-math-optional) for details.
|
||||
|
||||
### Breaking ‼️
|
||||
|
||||
* Sphinx configuration options are now set as separate variables, rather than a single dict.
|
||||
See [MyST configuration options](https://myst-parser.readthedocs.io/en/latest/using/intro.html#myst-configuration-options) for details.
|
||||
|
||||
## 0.10.0 - 2020-07-08
|
||||
|
||||
([full changelog](https://github.com/executablebooks/MyST-Parser/compare/v0.9.1...aaed58808af485c29bbbf73c5aac10697bfa08b9))
|
||||
|
||||
### Improved 👌
|
||||
|
||||
* Support Sphinx version 3 [#197](https://github.com/executablebooks/MyST-Parser/pull/197) ([@chrisjsewell](https://github.com/chrisjsewell))
|
||||
* Update Trove Classifiers [#192](https://github.com/executablebooks/MyST-Parser/pull/192) ([@chrisjsewell](https://github.com/chrisjsewell))
|
||||
* Add functionality to use docutils specialized role [#189](https://github.com/executablebooks/MyST-Parser/pull/189) ([@chrisjsewell](https://github.com/chrisjsewell))
|
||||
|
||||
### Contributors to this release
|
||||
|
||||
([GitHub contributors page for this release](https://github.com/executablebooks/MyST-Parser/graphs/contributors?from=2020-07-20&to=2020-08-07&type=c))
|
||||
|
||||
[@AakashGfude](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3AAakashGfude+updated%3A2020-07-20..2020-08-07&type=Issues) | [@asmeurer](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Aasmeurer+updated%3A2020-07-20..2020-08-07&type=Issues) | [@choldgraf](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acholdgraf+updated%3A2020-07-20..2020-08-07&type=Issues) | [@chrisjsewell](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Achrisjsewell+updated%3A2020-07-20..2020-08-07&type=Issues) | [@codecov](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acodecov+updated%3A2020-07-20..2020-08-07&type=Issues) | [@webknjaz](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Awebknjaz+updated%3A2020-07-20..2020-08-07&type=Issues) | [@welcome](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Awelcome+updated%3A2020-07-20..2020-08-07&type=Issues)
|
||||
|
||||
## Past Releases
|
||||
|
||||
### Contributors
|
||||
|
||||
([GitHub contributors page for these releases](https://github.com/executablebooks/MyST-Parser/graphs/contributors?from=2020-01-01&to=2020-07-20&type=c))
|
||||
|
||||
[@akhmerov](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Aakhmerov+updated%3A2020-01-01..2020-07-20&type=Issues) | [@asmeurer](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Aasmeurer+updated%3A2020-01-01..2020-07-20&type=Issues) | [@certik](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acertik+updated%3A2020-01-01..2020-07-20&type=Issues) | [@choldgraf](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acholdgraf+updated%3A2020-01-01..2020-07-20&type=Issues) | [@chrisjsewell](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Achrisjsewell+updated%3A2020-01-01..2020-07-20&type=Issues) | [@codecov](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acodecov+updated%3A2020-01-01..2020-07-20&type=Issues) | [@dhermes](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Adhermes+updated%3A2020-01-01..2020-07-20&type=Issues) | [@filippo82](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Afilippo82+updated%3A2020-01-01..2020-07-20&type=Issues) | [@jlperla](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Ajlperla+updated%3A2020-01-01..2020-07-20&type=Issues) | [@jstac](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Ajstac+updated%3A2020-01-01..2020-07-20&type=Issues) | [@martinagvilas](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Amartinagvilas+updated%3A2020-01-01..2020-07-20&type=Issues) | [@mlncn](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Amlncn+updated%3A2020-01-01..2020-07-20&type=Issues) | [@mmcky](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Ammcky+updated%3A2020-01-01..2020-07-20&type=Issues) | [@moorepants](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Amoorepants+updated%3A2020-01-01..2020-07-20&type=Issues) | [@najuzilu](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Anajuzilu+updated%3A2020-01-01..2020-07-20&type=Issues) | [@nathancarter](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Anathancarter+updated%3A2020-01-01..2020-07-20&type=Issues) | [@pauleveritt](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Apauleveritt+updated%3A2020-01-01..2020-07-20&type=Issues) | [@phaustin](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Aphaustin+updated%3A2020-01-01..2020-07-20&type=Issues) | [@rossbar](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Arossbar+updated%3A2020-01-01..2020-07-20&type=Issues) | [@rowanc1](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Arowanc1+updated%3A2020-01-01..2020-07-20&type=Issues) | [@sbliven](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Asbliven+updated%3A2020-01-01..2020-07-20&type=Issues) | [@webknjaz](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Awebknjaz+updated%3A2020-01-01..2020-07-20&type=Issues)
|
|
@ -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.
|
|
@ -0,0 +1,64 @@
|
|||
# MyST-Parser
|
||||
|
||||
[![Github-CI][github-ci]][github-link]
|
||||
[![Coverage Status][codecov-badge]][codecov-link]
|
||||
[![Documentation Status][rtd-badge]][rtd-link]
|
||||
[![Code style: black][black-badge]][black-link]
|
||||
[![PyPI][pypi-badge]][pypi-link]
|
||||
[![Conda][conda-badge]][conda-link]
|
||||
[![PyPI - Downloads][install-badge]][install-link]
|
||||
|
||||
|
||||
**MyST is a rich and extensible flavor of Markdown meant for technical documentation and publishing**.
|
||||
|
||||
MyST is a flavor of markdown that is designed for simplicity, flexibility, and extensibility.
|
||||
This repository serves as the reference implementation of MyST Markdown, as well as a collection of tools to support working with MyST in Python and Sphinx.
|
||||
It contains an extended [CommonMark](https://commonmark.org)-compliant parser using [`markdown-it-py`](https://markdown-it-py.readthedocs.io/), as well as a [Sphinx](https://www.sphinx-doc.org) extension that allows you to write MyST Markdown in Sphinx.
|
||||
|
||||
[**See the MyST Parser documentation for more information**](https://myst-parser.readthedocs.io/en/latest/).
|
||||
|
||||
## Installation
|
||||
|
||||
To install the MyST parser, run the following in a
|
||||
[Conda environment](https://docs.conda.io) (recommended):
|
||||
|
||||
```bash
|
||||
conda install -c conda-forge myst-parser
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
pip install myst-parser
|
||||
```
|
||||
|
||||
Or for package development:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/executablebooks/MyST-Parser
|
||||
cd MyST-Parser
|
||||
git checkout master
|
||||
pip install -e .[code_style,testing,rtd]
|
||||
```
|
||||
|
||||
To use the MyST parser in Sphinx, simply add: `extensions = ["myst_parser"]` to your `conf.py`.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome all contributions!
|
||||
See the [Contributing Guide](https://myst-parser.readthedocs.io/en/latest/develop/index.html) for more details.
|
||||
|
||||
[github-ci]: https://github.com/executablebooks/MyST-Parser/workflows/continuous-integration/badge.svg?branch=master
|
||||
[github-link]: https://github.com/executablebooks/MyST-Parser
|
||||
[codecov-badge]: https://codecov.io/gh/executablebooks/MyST-Parser/branch/master/graph/badge.svg
|
||||
[codecov-link]: https://codecov.io/gh/executablebooks/MyST-Parser
|
||||
[rtd-badge]: https://readthedocs.org/projects/myst-parser/badge/?version=latest
|
||||
[rtd-link]: https://myst-parser.readthedocs.io/en/latest/?badge=latest
|
||||
[black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
[pypi-badge]: https://img.shields.io/pypi/v/myst-parser.svg
|
||||
[pypi-link]: https://pypi.org/project/myst-parser
|
||||
[conda-badge]: https://anaconda.org/conda-forge/myst-parser/badges/version.svg
|
||||
[conda-link]: https://anaconda.org/conda-forge/myst-parser
|
||||
[black-link]: https://github.com/ambv/black
|
||||
[install-badge]: https://img.shields.io/pypi/dw/myst-parser?label=pypi%20installs
|
||||
[install-link]: https://pypistats.org/packages/myst-parser
|
|
@ -0,0 +1,10 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 90%
|
||||
threshold: 0.5%
|
||||
patch:
|
||||
default:
|
||||
target: 75%
|
||||
threshold: 0.5%
|
|
@ -0,0 +1,2 @@
|
|||
_build/
|
||||
_api/
|
|
@ -0,0 +1,27 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
# raise warnings to errors
|
||||
html-strict:
|
||||
@$(SPHINXBUILD) -b html -nW --keep-going "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O)
|
||||
|
||||
clean:
|
||||
rm -r $(BUILDDIR)
|
|
@ -0,0 +1,24 @@
|
|||
/** Add a counter before subsections **/
|
||||
h1 {
|
||||
counter-reset: subsection;
|
||||
text-decoration: underline;
|
||||
}
|
||||
h2 {
|
||||
counter-reset: subsubsection;
|
||||
}
|
||||
h2::before {
|
||||
counter-increment: subsection;
|
||||
content: counter(subsection) ". ";
|
||||
}
|
||||
h3::before {
|
||||
counter-increment: subsubsection;
|
||||
content: counter(subsection) "." counter(subsubsection) ". ";
|
||||
}
|
||||
|
||||
/** No icon for admonitions with no-icon class */
|
||||
.admonition > .admonition-title, div.admonition.no-icon > .admonition-title::before {
|
||||
content: "";
|
||||
}
|
||||
.admonition > .admonition-title, div.admonition.no-icon > .admonition-title {
|
||||
padding-left: .6rem;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 81.78 73.9"><defs><style>.cls-1{fill:#616161;}.cls-2{fill:#f37726;}</style></defs><title>Artboard 4</title><path class="cls-1" d="M14.66,41.7V38.09l4.67-.79V4.44l-4.67-.8V0h12.2L40.3,31.66h.17L53.47,0H66.05V3.64l-4.67.8V37.3l4.67.79V41.7h-15V38.09L56,37.3V7.7l-.15,0L42.3,40.22H38.55l-14-32.49-.14,0,.18,17.43V37.3l5.05.79V41.7Z"/><path class="cls-2" d="M37.88,73.9q0-8.82-10.13-8.82H19.33q-7.83,0-12.53-3.79T0,50.25l4.8-1.63q2.7,8.46,13.78,8.75h9.17q9.63,0,13.13,7.53Q44.3,57.37,54,57.37h8.45q11.76,0,14.55-8.72l4.78,1.6Q79.71,57.61,75,61.33T62.63,65.08H53.88q-10,0-10,8.82"/></svg>
|
After Width: | Height: | Size: 670 B |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.3 KiB |
|
@ -0,0 +1,96 @@
|
|||
.. _api/main:
|
||||
|
||||
==========
|
||||
Python API
|
||||
==========
|
||||
|
||||
Source text parsers
|
||||
-------------------
|
||||
|
||||
.. _api/docutils_parser:
|
||||
|
||||
Docutils
|
||||
........
|
||||
|
||||
.. autoclass:: myst_parser.docutils_.Parser
|
||||
:members: parse
|
||||
:undoc-members:
|
||||
:member-order: bysource
|
||||
:show-inheritance:
|
||||
|
||||
.. _api/sphinx_parser:
|
||||
|
||||
Sphinx
|
||||
......
|
||||
|
||||
.. autoclass:: myst_parser.parsers.sphinx_.MystParser
|
||||
:members: supported, parse
|
||||
:undoc-members:
|
||||
:member-order: bysource
|
||||
:show-inheritance:
|
||||
:exclude-members: __init__
|
||||
|
||||
.. _api/renderers:
|
||||
|
||||
Markdown-it to docutils
|
||||
-----------------------
|
||||
|
||||
These renderers take the markdown-it parsed token stream and convert it to
|
||||
the docutils AST. The sphinx renderer is a subclass of the docutils one,
|
||||
with some additional methods only available *via* sphinx e.g. multi-document cross-referencing.
|
||||
|
||||
|
||||
Docutils
|
||||
........
|
||||
|
||||
.. autoclass:: myst_parser.mdit_to_docutils.base.DocutilsRenderer
|
||||
:special-members: __output__, __init__
|
||||
:members: render, nested_render_text, add_line_and_source_path, current_node_context
|
||||
:undoc-members:
|
||||
:member-order: bysource
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Sphinx
|
||||
......
|
||||
|
||||
.. autoclass:: myst_parser.mdit_to_docutils.sphinx_.SphinxRenderer
|
||||
:special-members: __output__
|
||||
:members: render_internal_link, render_math_block_label
|
||||
:undoc-members:
|
||||
:member-order: alphabetical
|
||||
:show-inheritance:
|
||||
|
||||
.. _api/directive:
|
||||
|
||||
Directive and role processing
|
||||
-----------------------------
|
||||
|
||||
This module processes the content of a directive:
|
||||
|
||||
.. automodule:: myst_parser.parsers.directives
|
||||
:members:
|
||||
|
||||
These classes are parsed to sphinx roles and directives,
|
||||
to mimic the original docutls rST specific parser elements,
|
||||
but instead run nested parsing with the markdown parser.
|
||||
|
||||
.. autoclass:: myst_parser.mocking.MockInliner
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: myst_parser.mocking.MockState
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: myst_parser.mocking.MockStateMachine
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: myst_parser.mocking.MockIncludeDirective
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,169 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
from datetime import date
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
from myst_parser import __version__
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = "MyST Parser"
|
||||
copyright = f"{date.today().year}, Executable Book Project"
|
||||
author = "Executable Book Project"
|
||||
version = __version__
|
||||
|
||||
master_doc = "index"
|
||||
language = "en"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"myst_parser",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx_design",
|
||||
"sphinxext.rediraffe",
|
||||
"sphinxcontrib.mermaid",
|
||||
"sphinxext.opengraph",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = "sphinx_book_theme"
|
||||
html_logo = "_static/logo-wide.svg"
|
||||
html_favicon = "_static/logo-square.svg"
|
||||
html_title = ""
|
||||
html_theme_options = {
|
||||
"home_page_in_toc": True,
|
||||
"github_url": "https://github.com/executablebooks/MyST-Parser",
|
||||
"repository_url": "https://github.com/executablebooks/MyST-Parser",
|
||||
"repository_branch": "master",
|
||||
"path_to_docs": "docs",
|
||||
"use_repository_button": True,
|
||||
"use_edit_page_button": True,
|
||||
}
|
||||
# OpenGraph metadata
|
||||
ogp_site_url = "https://myst-parser.readthedocs.io/en/latest"
|
||||
# This is the image that GitHub stores for our social media previews
|
||||
ogp_image = "https://repository-images.githubusercontent.com/240151150/316bc480-cc23-11eb-96fc-4ab2f981a65d" # noqa: E501
|
||||
ogp_custom_meta_tags = [
|
||||
'<meta name="twitter:card" content="summary_large_image">',
|
||||
]
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
|
||||
myst_enable_extensions = [
|
||||
"dollarmath",
|
||||
"amsmath",
|
||||
"deflist",
|
||||
"fieldlist",
|
||||
"html_admonition",
|
||||
"html_image",
|
||||
"colon_fence",
|
||||
"smartquotes",
|
||||
"replacements",
|
||||
"linkify",
|
||||
"strikethrough",
|
||||
"substitution",
|
||||
"tasklist",
|
||||
"attrs_image",
|
||||
]
|
||||
myst_number_code_blocks = ["typescript"]
|
||||
myst_heading_anchors = 2
|
||||
myst_footnote_transition = True
|
||||
myst_dmath_double_inline = True
|
||||
|
||||
rediraffe_redirects = {
|
||||
"using/intro.md": "sphinx/intro.md",
|
||||
"sphinx/intro.md": "intro.md",
|
||||
"using/use_api.md": "api/index.md",
|
||||
"api/index.md": "api/reference.rst",
|
||||
"using/syntax.md": "syntax/syntax.md",
|
||||
"using/syntax-optional.md": "syntax/optional.md",
|
||||
"using/reference.md": "syntax/reference.md",
|
||||
"sphinx/reference.md": "configuration.md",
|
||||
"sphinx/index.md": "faq/index.md",
|
||||
"sphinx/use.md": "faq/index.md",
|
||||
"sphinx/faq.md": "faq/index.md",
|
||||
"explain/index.md": "develop/background.md",
|
||||
}
|
||||
|
||||
suppress_warnings = ["myst.strikethrough"]
|
||||
|
||||
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3.7", None),
|
||||
"sphinx": ("https://www.sphinx-doc.org/en/master", None),
|
||||
"markdown_it": ("https://markdown-it-py.readthedocs.io/en/latest", None),
|
||||
}
|
||||
|
||||
# autodoc_default_options = {
|
||||
# "show-inheritance": True,
|
||||
# "special-members": "__init__, __enter__, __exit__",
|
||||
# "members": True,
|
||||
# # 'exclude-members': '',
|
||||
# "undoc-members": True,
|
||||
# # 'inherited-members': True
|
||||
# }
|
||||
autodoc_member_order = "bysource"
|
||||
nitpicky = True
|
||||
nitpick_ignore = [
|
||||
("py:class", "docutils.nodes.document"),
|
||||
("py:class", "docutils.nodes.docinfo"),
|
||||
("py:class", "docutils.nodes.Element"),
|
||||
("py:class", "docutils.nodes.Node"),
|
||||
("py:class", "docutils.nodes.field_list"),
|
||||
("py:class", "docutils.nodes.problematic"),
|
||||
("py:class", "docutils.nodes.pending"),
|
||||
("py:class", "docutils.nodes.system_message"),
|
||||
("py:class", "docutils.statemachine.StringList"),
|
||||
("py:class", "docutils.parsers.rst.directives.misc.Include"),
|
||||
("py:class", "docutils.parsers.rst.Parser"),
|
||||
("py:class", "docutils.utils.Reporter"),
|
||||
("py:class", "nodes.Element"),
|
||||
("py:class", "nodes.Node"),
|
||||
("py:class", "nodes.system_message"),
|
||||
("py:class", "Directive"),
|
||||
("py:class", "Include"),
|
||||
("py:class", "StringList"),
|
||||
("py:class", "DocutilsRenderer"),
|
||||
("py:class", "MockStateMachine"),
|
||||
]
|
||||
|
||||
|
||||
def setup(app: Sphinx):
|
||||
"""Add functions to the Sphinx setup."""
|
||||
from myst_parser._docs import (
|
||||
DirectiveDoc,
|
||||
DocutilsCliHelpDirective,
|
||||
MystConfigDirective,
|
||||
)
|
||||
|
||||
app.add_css_file("custom.css")
|
||||
app.add_directive("myst-config", MystConfigDirective)
|
||||
app.add_directive("docutils-cli-help", DocutilsCliHelpDirective)
|
||||
app.add_directive("doc-directive", DirectiveDoc)
|
|
@ -0,0 +1,106 @@
|
|||
(sphinx/config-options)=
|
||||
# Configuration
|
||||
|
||||
MyST parsing can be configured at both the global and individual document level,
|
||||
with the most specific configuration taking precedence.
|
||||
|
||||
## Global configuration
|
||||
|
||||
Overriding the default configuration at the global level is achieved by specifying variables in the Sphinx `conf.py` file.
|
||||
All `myst_parser` global configuration variables are prefixed with `myst_`, e.g.
|
||||
|
||||
```python
|
||||
myst_enable_extensions = ["deflist"]
|
||||
```
|
||||
|
||||
:::{seealso}
|
||||
Configuration in Docutils, in the [](docutils.md) section.
|
||||
:::
|
||||
|
||||
```{myst-config}
|
||||
:sphinx:
|
||||
:scope: global
|
||||
```
|
||||
|
||||
### Extensions
|
||||
|
||||
Configuration specific to syntax extensions:
|
||||
|
||||
```{myst-config}
|
||||
:sphinx:
|
||||
:extensions:
|
||||
:scope: global
|
||||
```
|
||||
|
||||
## Local configuration
|
||||
|
||||
```{versionadded} 0.18
|
||||
```
|
||||
|
||||
The following configuration variables are available at the document level.
|
||||
These can be set in the document [front matter](syntax/frontmatter), under the `myst` key, e.g.
|
||||
|
||||
```yaml
|
||||
---
|
||||
myst:
|
||||
enable_extensions: ["deflist"]
|
||||
---
|
||||
```
|
||||
|
||||
```{myst-config}
|
||||
:sphinx:
|
||||
:scope: local
|
||||
```
|
||||
|
||||
### Extensions
|
||||
|
||||
Configuration specific to syntax extensions:
|
||||
|
||||
```{myst-config}
|
||||
:sphinx:
|
||||
:extensions:
|
||||
:scope: local
|
||||
```
|
||||
|
||||
## List of syntax extensions
|
||||
|
||||
Full details in the [](syntax/extensions) section.
|
||||
|
||||
amsmath
|
||||
: enable direct parsing of [amsmath](https://ctan.org/pkg/amsmath) LaTeX equations
|
||||
|
||||
colon_fence
|
||||
: Enable code fences using `:::` delimiters, [see here](syntax/colon_fence) for details
|
||||
|
||||
deflist
|
||||
: Enable definition lists, [see here](syntax/definition-lists) for details
|
||||
|
||||
dollarmath
|
||||
: Enable parsing of dollar `$` and `$$` encapsulated math
|
||||
|
||||
fieldlist
|
||||
: Enable field lists, [see here](syntax/fieldlists) for details
|
||||
|
||||
html_admonition
|
||||
: Convert `<div class="admonition">` elements to sphinx admonition nodes, see the [HTML admonition syntax](syntax/html-admonition) for details
|
||||
|
||||
html_image
|
||||
: Convert HTML `<img>` elements to sphinx image nodes, [see here](syntax/images) for details
|
||||
|
||||
linkify
|
||||
: Automatically identify "bare" web URLs and add hyperlinks
|
||||
|
||||
replacements
|
||||
: Automatically convert some common typographic texts
|
||||
|
||||
smartquotes
|
||||
: Automatically convert standard quotations to their opening/closing variants
|
||||
|
||||
strikethrough
|
||||
: Enable strikethrough syntax, [see here](syntax/strikethrough) for details
|
||||
|
||||
substitution
|
||||
: Substitute keys, [see here](syntax/substitutions) for details
|
||||
|
||||
tasklist
|
||||
: Add check-boxes to the start of list items, [see here](syntax/tasklists) for details
|
|
@ -0,0 +1,4 @@
|
|||
```{include} ../../CHANGELOG.md
|
||||
:relative-docs: docs/
|
||||
:relative-images:
|
||||
```
|
|
@ -0,0 +1,27 @@
|
|||
# The MyST implementation architecture
|
||||
|
||||
This page describes implementation details to help you understand the structure
|
||||
of the project.
|
||||
|
||||
## A Renderer for markdown-it tokens
|
||||
|
||||
At a high level, the MyST parser is an extension of th project. Markdown-It-Py
|
||||
is a well-structured Python parser for CommonMark text. It also defines an extension
|
||||
point to include more syntax in parsed files. The MyST parser uses this extension
|
||||
point to define its own syntax options (e.g., for Sphinx roles and directives).
|
||||
|
||||
The result of this parser is a markdown-it token stream.
|
||||
|
||||
## A docutils renderer
|
||||
|
||||
The MyST parser also defines a docutils renderer for the markdown-it token stream.
|
||||
This allows us to convert parsed elements of a MyST markdown file into docutils.
|
||||
|
||||
## A Sphinx parser
|
||||
|
||||
Finally, the MyST parser provides a parser for Sphinx, the documentation generation
|
||||
system. This parser does the following:
|
||||
|
||||
* Parse markdown files with the markdown-it parser, including MyST specific plugins
|
||||
* Convert these files into docutils objects using the MyST docutils renderer
|
||||
* Provide these to Sphinx in order to use in building your site.
|
|
@ -0,0 +1,82 @@
|
|||
# Background
|
||||
|
||||
These sections discuss high-level questions about the MyST ecosystem, and explain a few decisions made in the project.
|
||||
|
||||
## Why did we create MyST markdown?
|
||||
|
||||
While markdown is ubiquitous, it is not powerful enough for writing modern,
|
||||
fully-featured documentation. Some flavors of markdown support features needed for this,
|
||||
but there is no community standard around various syntactic choices for these features.
|
||||
|
||||
Sphinx is a documentation generation framework written in Python. It heavily-utilizes
|
||||
reStructuredText syntax, which is another markup language for writing documents. In
|
||||
particular, Sphinx defines two extension points that are extremely useful:
|
||||
**{ref}`in-line roles<sphinx:rst-roles-alt>`** and **{ref}`block-level directives <sphinx:rst-directives>`**.
|
||||
|
||||
**This project is an attempt at combining the simplicity and readability of Markdown
|
||||
with the power and flexibility of reStructuredText and the Sphinx platform.** It
|
||||
starts with the [CommonMark markdown specification][commonmark], and selectively adds a few extra
|
||||
syntax pieces to utilize the most powerful parts of reStructuredText.
|
||||
|
||||
```{note}
|
||||
The CommonMark community has been discussing an "official" extension syntax for many
|
||||
years now (for example, see
|
||||
[this seven-year-old thread about directives](https://talk.commonmark.org/t/generic-directives-plugins-syntax/444) as well as
|
||||
[this more recent converstaion](https://talk.commonmark.org/t/support-for-extension-token/2771),
|
||||
and [this comment listing several more threads on this topic](https://talk.commonmark.org/t/extension-terminology-and-rules/1233)).
|
||||
|
||||
We have chosen a "roles and directives" syntax that seems reasonable and follows other
|
||||
common conventions in Markdown flavors. However, if the CommonMark community ever
|
||||
decides on an "official" extension syntax, we will likely utilize this syntax for
|
||||
MyST.
|
||||
```
|
||||
|
||||
## The relationship between MyST, reStructuredText, and Sphinx
|
||||
|
||||
MyST markdown provides a markdown equivalent of the reStructuredText syntax,
|
||||
meaning that you can do anything in MyST that you can do with reStructuredText.
|
||||
|
||||
The Sphinx documentation engine supports a number of different input types. By default,
|
||||
Sphinx reads **reStructuredText** (`.rst`) files. Sphinx uses a **parser** to parse input files
|
||||
into its own internal document model (which is provided by a core Python project,
|
||||
[docutils](https://docutils.sourceforge.io/)).
|
||||
|
||||
Developers can *extend Sphinx* to support other kinds of input files. Any content file
|
||||
can be read into the Sphinx document structure, provided that somebody writes a
|
||||
**parser** for that file. Once a content file has been parsed into Sphinx, it behaves
|
||||
nearly the same way as any other content file, regardless of the language in which it
|
||||
was written.
|
||||
|
||||
The MyST-parser is a Sphinx parser for the MyST markdown language.
|
||||
When you use it, Sphinx will know how to parse content files that contain MyST markdown (by default, Sphinx will assume any files ending in `.md` are written in MyST markdown). Once a document has been parsed into Sphinx, it behaves the same way regardless of whether it has been written in rST or MyST markdown.
|
||||
|
||||
```
|
||||
myst markdown (.md) ------> myst parser ---+
|
||||
|
|
||||
+-->Sphinx document (docutils)
|
||||
|
|
||||
reStructuredText (.rst) --> rst parser ----+
|
||||
```
|
||||
|
||||
For example, here's how you'd write a `toctree` directive in MyST markdown:
|
||||
|
||||
````
|
||||
```{toctree}
|
||||
My page name <page1>
|
||||
page2
|
||||
```
|
||||
````
|
||||
|
||||
and here's the same in rST:
|
||||
|
||||
```
|
||||
.. toctree::
|
||||
|
||||
My page name <page1>
|
||||
page2
|
||||
```
|
||||
|
||||
They will both behave the same in Sphinx.
|
||||
|
||||
|
||||
[commonmark]: https://commonmark.org/
|
|
@ -0,0 +1,85 @@
|
|||
# Contributing
|
||||
|
||||
[![Github-CI][github-ci]][github-link]
|
||||
[![Coverage Status][codecov-badge]][codecov-link]
|
||||
[![Documentation Status][rtd-badge]][rtd-link]
|
||||
[![Code style: black][black-badge]][black-link]
|
||||
|
||||
We welcome all contributions!
|
||||
See the [EBP Contributing Guide](https://executablebooks.org/en/latest/contributing.html) for general details, and below for guidance specific to MyST-Parser.
|
||||
|
||||
## Install for development
|
||||
|
||||
To install `myst-parser` for development, take the following steps:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/executablebooks/MyST-Parser
|
||||
cd MyST-Parser
|
||||
git checkout master
|
||||
pip install -e .[code_style,testing,rtd]
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
Code style is tested using [flake8](http://flake8.pycqa.org),
|
||||
with the configuration set in `.flake8`,
|
||||
and code formatted with [black](https://github.com/ambv/black).
|
||||
|
||||
Installing with `myst-parser[code_style]` makes the [pre-commit](https://pre-commit.com/)
|
||||
package available, which will ensure this style is met before commits are submitted, by reformatting the code
|
||||
and testing for lint errors.
|
||||
It can be setup by:
|
||||
|
||||
```shell
|
||||
>> cd MyST-Parser
|
||||
>> pre-commit install
|
||||
```
|
||||
|
||||
Optionally you can run `black` and `flake8` separately:
|
||||
|
||||
```shell
|
||||
>> black .
|
||||
>> flake8 .
|
||||
```
|
||||
|
||||
Editors like VS Code also have automatic code reformat utilities, which can adhere to this standard.
|
||||
|
||||
All functions and class methods should be annotated with types and include a docstring. The preferred docstring format is outlined in `MyST-Parser/docstring.fmt.mustache` and can be used automatically with the
|
||||
[autodocstring](https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring) VS Code extension.
|
||||
|
||||
## Testing
|
||||
|
||||
For code tests, myst-parser uses [pytest](https://docs.pytest.org):
|
||||
|
||||
```shell
|
||||
>> cd MyST-Parser
|
||||
>> pytest
|
||||
```
|
||||
|
||||
You can also use [tox](https://tox.readthedocs.io), to run the tests in multiple isolated environments (see the `tox.ini` file for available test environments):
|
||||
|
||||
```shell
|
||||
>> cd MyST-Parser
|
||||
>> tox
|
||||
```
|
||||
|
||||
For documentation build tests:
|
||||
|
||||
```shell
|
||||
>> cd MyST-Parser/docs
|
||||
>> make clean
|
||||
>> make html-strict
|
||||
```
|
||||
|
||||
```{seealso}
|
||||
{ref}`develop/testing`
|
||||
```
|
||||
|
||||
[github-ci]: https://github.com/executablebooks/MyST-Parser/workflows/continuous-integration/badge.svg?branch=master
|
||||
[github-link]: https://github.com/executablebooks/MyST-Parser
|
||||
[codecov-badge]: https://codecov.io/gh/executablebooks/MyST-Parser/branch/master/graph/badge.svg
|
||||
[codecov-link]: https://codecov.io/gh/executablebooks/MyST-Parser
|
||||
[rtd-badge]: https://readthedocs.org/projects/myst-parser/badge/?version=latest
|
||||
[rtd-link]: https://myst-parser.readthedocs.io/en/latest/?badge=latest
|
||||
[black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
[black-link]: https://github.com/ambv/black
|
|
@ -0,0 +1,15 @@
|
|||
# Contribute
|
||||
|
||||
This section covers documentation relevant to developing and maintaining the MyST
|
||||
codebase, and some guidelines for how you can contribute.
|
||||
|
||||
```{toctree}
|
||||
contributing.md
|
||||
architecture.md
|
||||
test_infrastructure.md
|
||||
```
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
The MyST-parser project follows the
|
||||
[Executable Book Project code of conduct](https://github.com/executablebooks/.github/blob/master/CODE_OF_CONDUCT.md).
|
|
@ -0,0 +1,53 @@
|
|||
(develop/testing)=
|
||||
|
||||
# Testing Infrastructure
|
||||
|
||||
Where possible, additions to the code should be carried out in a
|
||||
[test-driven development](https://en.wikipedia.org/wiki/Test-driven_development)
|
||||
manner:
|
||||
|
||||
> **Write failing tests that the code should pass, then write code to pass the tests**.
|
||||
|
||||
The tests are run using [pytest](https://docs.pytest.org)/[GitHub Actions](https://github.com/features/actions) for unit tests, and [readthedocs](https://readthedocs.org/) for documentation build tests.
|
||||
|
||||
The tests are ordered in a hierarchical fashion:
|
||||
|
||||
1. In `tests/test_commonmark` the [CommonMark](https://github.com/commonmark/CommonMark.git) test set is run to check that the parser is complying with the CommonMark specification.
|
||||
2. In `tests/test_renderers` are tests that check that the Markdown AST is being correctly converted to the docutils/sphinx AST. This includes testing that roles and directives are correctly parsed and run.
|
||||
3. In `tests/test_sphinx` are tests that check that minimal sphinx project builds are running correctly, to convert MyST markdown files to HTML.
|
||||
4. In `.circleci` the package documentation (written in MyST format) is built and tested for build errors/warnings.
|
||||
|
||||
## Test tools
|
||||
|
||||
[**pytest-regressions**](https://pytest-regressions.readthedocs.io) is a pytest plugin
|
||||
that is used in the test suite, to maintain tests that generate lots of data.
|
||||
In particular, they are used in the syntax testing to generate tests for AST trees
|
||||
which may change in the future due to changes/additions to the data captured by the parser.
|
||||
For example, after writing:
|
||||
|
||||
```python
|
||||
def test_example_dict(data_regression):
|
||||
data_regression.check({
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"more": "data...",
|
||||
})
|
||||
def test_example_str(file_regression):
|
||||
file_regression.check("a very long string...")
|
||||
```
|
||||
|
||||
Running the following will initially fail,
|
||||
but will also generate a file (per test) of expected output:
|
||||
|
||||
```console
|
||||
$ pytest -k test_example
|
||||
```
|
||||
|
||||
Subsequent times the tests are run, the tests output will now be validated against these stored files.
|
||||
|
||||
After a change to the syntax parser, all failing tests can then be 'regenerated' with the new
|
||||
expected output, by running:
|
||||
|
||||
```console
|
||||
$ pytest --force-regen
|
||||
```
|
|
@ -0,0 +1,82 @@
|
|||
(myst-docutils)=
|
||||
|
||||
# Single Page Builds
|
||||
|
||||
```{versionadded} 0.16.0
|
||||
```
|
||||
|
||||
Sphinx, and thus MyST-Parser, is built on top of the [Docutils](https://docutils.sourceforge.io/docs/) package.
|
||||
MyST-Parser offers a renderer, parser and CLI-interface for working directly with Docutils, independent of Sphinx, as described below.
|
||||
|
||||
:::{note}
|
||||
Since these tools are independent of Sphinx, this means they cannot parse any Sphinx or Sphinx extensions specific roles or directives.
|
||||
:::
|
||||
|
||||
On installing MyST-Parser, the following CLI-commands are made available:
|
||||
|
||||
- `myst-docutils-html`: converts MyST to HTML
|
||||
- `myst-docutils-html5`: converts MyST to HTML5
|
||||
- `myst-docutils-latex`: converts MyST to LaTeX
|
||||
- `myst-docutils-xml`: converts MyST to docutils-native XML
|
||||
- `myst-docutils-pseudoxml`: converts MyST to pseudo-XML (to visualise the AST structure)
|
||||
|
||||
Each command can be piped stdin or take a file path as an argument:
|
||||
|
||||
```console
|
||||
$ myst-docutils-html --help
|
||||
$ echo "Hello World" | myst-docutils-html
|
||||
$ myst-docutils-html hello-world.md
|
||||
```
|
||||
|
||||
The commands are based on the [Docutils Front-End Tools](https://docutils.sourceforge.io/docs/user/tools.html), and so follow the same argument and options structure, included many of the MyST specific options detailed in [](sphinx/config-options).
|
||||
|
||||
:::{dropdown} Shared Docutils CLI Options
|
||||
```{docutils-cli-help}
|
||||
```
|
||||
:::
|
||||
|
||||
The CLI commands can also utilise the [`docutils.conf` configuration file](https://docutils.sourceforge.io/docs/user/config.html) to configure the behaviour of the CLI commands. For example:
|
||||
|
||||
```
|
||||
# These entries affect all processing:
|
||||
[general]
|
||||
myst-enable-extensions: deflist,linkify
|
||||
myst-footnote-transition: no
|
||||
|
||||
# These entries affect specific HTML output:
|
||||
[html writers]
|
||||
embed-stylesheet: no
|
||||
|
||||
[html5 writer]
|
||||
stylesheet-dirs: path/to/html5_polyglot/
|
||||
stylesheet-path: minimal.css, responsive.css
|
||||
```
|
||||
|
||||
You can also use the {py:class}`myst_parser.docutils_.Parser` class programmatically with the [Docutils publisher API](https://docutils.sourceforge.io/docs/api/publisher.html):
|
||||
|
||||
```python
|
||||
from docutils.core import publish_string
|
||||
from myst_parser.docutils_ import Parser
|
||||
|
||||
source = "hallo world\n: Definition"
|
||||
output = publish_string(
|
||||
source=source,
|
||||
writer_name="html5",
|
||||
settings_overrides={
|
||||
"myst_enable_extensions": ["deflist"],
|
||||
"embed_stylesheet": False,
|
||||
},
|
||||
parser=Parser(),
|
||||
)
|
||||
```
|
||||
|
||||
Finally, you can include MyST Markdown files within a RestructuredText file, using the [`include` directive](https://docutils.sourceforge.io/docs/ref/rst/directives.html#include):
|
||||
|
||||
```rst
|
||||
.. include:: include.md
|
||||
:parser: myst_parser.docutils_
|
||||
```
|
||||
|
||||
```{important}
|
||||
The `parser` option requires `docutils>=0.17`
|
||||
```
|
|
@ -0,0 +1,267 @@
|
|||
(myst-sphinx)=
|
||||
|
||||
# FAQ
|
||||
|
||||
## How-tos
|
||||
|
||||
These sections describe some common scenarios and use-cases for writing MyST with Sphinx.
|
||||
|
||||
(howto/include-rst)=
|
||||
### Include rST files into a Markdown file
|
||||
|
||||
As explained in [this section](syntax/directives/parsing), all MyST directives will parse their content as Markdown.
|
||||
Therefore, using the conventional `include` directive, will parse the file contents as Markdown:
|
||||
|
||||
````md
|
||||
```{include} snippets/include-md.md
|
||||
```
|
||||
````
|
||||
|
||||
```{include} snippets/include-md.md
|
||||
```
|
||||
|
||||
To include rST, we must first "wrap" the directive in the [eval-rst directive](syntax/directives/parsing):
|
||||
|
||||
````md
|
||||
```{eval-rst}
|
||||
.. include:: snippets/include-rst.rst
|
||||
```
|
||||
````
|
||||
|
||||
```{eval-rst}
|
||||
.. include:: snippets/include-rst.rst
|
||||
```
|
||||
|
||||
(howto/include-md)=
|
||||
### Include Markdown files into an rST file
|
||||
|
||||
To include a MyST file within a ReStructuredText file, we can use the `parser` option of the `include` directive:
|
||||
|
||||
```rst
|
||||
.. include:: include.md
|
||||
:parser: myst_parser.sphinx_
|
||||
```
|
||||
|
||||
```{important}
|
||||
The `parser` option requires `docutils>=0.17`
|
||||
```
|
||||
|
||||
### Use MyST in Jupyter Notebooks
|
||||
|
||||
The [MyST-NB](https://myst-nb.readthedocs.io) tool provides a Sphinx extension for parsing **Jupyter Notebooks written with MyST Markdown**. It includes features like automatically executing notebooks during documentation builds, storing notebook cell outputs in order to insert them elsewhere in your documentation, and more. See the [MyST-NB documentation](https://myst-nb.readthedocs.io) for more information.
|
||||
|
||||
(howto/include-readme)=
|
||||
### Include a file from outside the docs folder (like README.md)
|
||||
|
||||
You can include a file, including one from outside the project using e.g.:
|
||||
|
||||
````md
|
||||
```{include} ../README.md
|
||||
```
|
||||
````
|
||||
|
||||
**However**, including a file will not usually resolve local links correctly, like `![](my-image.png)`, since it treats the text as if it originated from the "including file".
|
||||
|
||||
As of myst-parser version 0.12.7, a new, experimental feature has been added to resolve such links.
|
||||
You can now use for example:
|
||||
|
||||
````md
|
||||
Source:
|
||||
```{literalinclude} ../../example.md
|
||||
:language: md
|
||||
```
|
||||
Included:
|
||||
```{include} ../../example.md
|
||||
:relative-docs: docs/
|
||||
:relative-images:
|
||||
```
|
||||
````
|
||||
|
||||
Source:
|
||||
|
||||
```{literalinclude} ../../example-include.md
|
||||
:language: md
|
||||
```
|
||||
|
||||
Included:
|
||||
|
||||
```{include} ../../example-include.md
|
||||
:relative-docs: docs/
|
||||
:relative-images:
|
||||
```
|
||||
|
||||
The include here attempts to re-write local links, to reference them from the correct location!
|
||||
The `relative-docs` must be given the prefix of any links to re-write, to distinguish them from sphinx cross-references.
|
||||
|
||||
:::{important}
|
||||
The current functionality only works for Markdown style images and links.
|
||||
|
||||
If you encounter any issues with this feature, please don't hesitate to report it.
|
||||
:::
|
||||
|
||||
(howto/autodoc)=
|
||||
### Use `sphinx.ext.autodoc` in Markdown files
|
||||
|
||||
The [Sphinx extension `autodoc`](sphinx:sphinx.ext.autodoc), which pulls in code documentation from docstrings, is currently hard-coded to parse reStructuredText.
|
||||
It is therefore incompatible with MyST's Markdown parser.
|
||||
However, the special [`eval-rst` directive](syntax/directives/parsing) can be used to "wrap" `autodoc` directives:
|
||||
|
||||
````md
|
||||
```{eval-rst}
|
||||
.. autoclass:: myst_parser.mocking.MockRSTParser
|
||||
:show-inheritance:
|
||||
:members: parse
|
||||
```
|
||||
````
|
||||
|
||||
```{eval-rst}
|
||||
.. autoclass:: myst_parser.mocking.MockRSTParser
|
||||
:show-inheritance:
|
||||
:members: parse
|
||||
```
|
||||
|
||||
As with other objects in MyST, this can then be referenced:
|
||||
|
||||
- Using the role `` {py:class}`myst_parser.mocking.MockRSTParser` ``: {py:class}`myst_parser.mocking.MockRSTParser`
|
||||
- Using the Markdown syntax `[MockRSTParser](myst_parser.mocking.MockRSTParser)`: [MockRSTParser](myst_parser.mocking.MockRSTParser)
|
||||
|
||||
```{warning}
|
||||
This expects docstrings to be written in reStructuredText.
|
||||
We hope to support Markdown in the future, see [GitHub issue #228](https://github.com/executablebooks/MyST-Parser/issues/228).
|
||||
```
|
||||
|
||||
(howto/autosectionlabel)=
|
||||
### Automatically create targets for section headers
|
||||
|
||||
:::{important}
|
||||
|
||||
New in `v0.13.0` ✨, myst-parser now provides a separate implementation of `autosectionlabel`, which implements GitHub Markdown style bookmark anchors, like `[](file.md#header-anchor)`.
|
||||
|
||||
See the [](syntax/header-anchors) section of extended syntaxes.
|
||||
|
||||
:::
|
||||
|
||||
If you'd like to *automatically* generate targets for each of your section headers,
|
||||
check out the [`autosectionlabel`](https://www.sphinx-doc.org/en/master/usage/extensions/autosectionlabel.html)
|
||||
sphinx feature. You can activate it in your Sphinx site by adding the following to your
|
||||
`conf.py` file:
|
||||
|
||||
```python
|
||||
extensions = [
|
||||
'sphinx.ext.autosectionlabel',
|
||||
]
|
||||
|
||||
# Prefix document path to section labels, to use:
|
||||
# `path/to/file:heading` instead of just `heading`
|
||||
autosectionlabel_prefix_document = True
|
||||
```
|
||||
|
||||
So, if you have a page at `myfolder/mypage.md` (relative to your documentation root)
|
||||
with the following structure:
|
||||
|
||||
```md
|
||||
# Title
|
||||
|
||||
## My Subtitle
|
||||
```
|
||||
|
||||
Then the `autosectionlabel` feature will allow you to reference the section headers
|
||||
like so:
|
||||
|
||||
```md
|
||||
{ref}`path/to/file_1:My Subtitle`
|
||||
```
|
||||
|
||||
(howto/warnings)=
|
||||
### Suppress warnings
|
||||
|
||||
In general, if your build logs any warnings, you should either fix them or [raise an Issue](https://github.com/executablebooks/MyST-Parser/issues/new/choose) if you think the warning is erroneous.
|
||||
However, in some circumstances if you wish to suppress the warning you can use the [`suppress_warnings`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-suppress_warnings) configuration option.
|
||||
All myst-parser warnings are prepended by their type, e.g. to suppress:
|
||||
|
||||
```md
|
||||
# Title
|
||||
### Subtitle
|
||||
```
|
||||
|
||||
```
|
||||
WARNING: Non-consecutive header level increase; H1 to H3 [myst.header]
|
||||
```
|
||||
|
||||
Add to your `conf.py`:
|
||||
|
||||
```python
|
||||
suppress_warnings = ["myst.header"]
|
||||
```
|
||||
|
||||
|
||||
### Sphinx-specific page front matter
|
||||
|
||||
Sphinx intercepts front matter and stores them within the global environment
|
||||
(as discussed [in the deflists documentation](https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html)).
|
||||
There are certain front-matter keys (or their translations) that are also recognised specifically by docutils and parsed to inline Markdown:
|
||||
|
||||
- `author`
|
||||
- `authors`
|
||||
- `organization`
|
||||
- `address`
|
||||
- `contact`
|
||||
- `version`
|
||||
- `revision`
|
||||
- `status`
|
||||
- `date`
|
||||
- `copyright`
|
||||
- `dedication`
|
||||
- `abstract`
|
||||
|
||||
A classic use-case is to specify 'orphan' documents, that are not specified in any toctrees.
|
||||
For example, inserting the following syntax at the top of a page will cause Sphinx to treat it as an orphan page:
|
||||
|
||||
```md
|
||||
---
|
||||
orphan: true
|
||||
---
|
||||
|
||||
This is an orphan document, not specified in any toctrees.
|
||||
```
|
||||
|
||||
### Migrate pre-existing rST into MyST
|
||||
|
||||
If you've already got some reStructuredText files that you'd like to convert into MyST Markdown, try the [`rst-to-myst`](https://github.com/executablebooks/rst-to-myst) tool, which allows you to convert single rST files to MyST markdown documents.
|
||||
|
||||
## Disable Markdown syntax for the parser
|
||||
|
||||
If you'd like to either enable or disable custom markdown syntax, use `myst_disable_syntax`.
|
||||
Anything in this list will no longer be parsed by the MyST parser.
|
||||
|
||||
For example, to disable the `emphasis` in-line syntax, use this configuration:
|
||||
|
||||
```python
|
||||
myst_disable_syntax = ["emphasis"]
|
||||
```
|
||||
|
||||
emphasis syntax will now be disabled. For example, the following will be rendered
|
||||
*without* any italics:
|
||||
|
||||
```md
|
||||
*emphasis is now disabled*
|
||||
```
|
||||
|
||||
For a list of all the syntax elements you can disable, see the [markdown-it parser guide](markdown_it:using).
|
||||
|
||||
## Common errors and questions
|
||||
|
||||
These are common issues and gotchas that people may experience when using the MyST Sphinx extension.
|
||||
|
||||
### What markup language should I use inside directives?
|
||||
|
||||
If you need to parse content *inside* of another block of content (for example, the
|
||||
content inside a **note directive**), note that the MyST parser will be used for this
|
||||
nested parsing as well.
|
||||
|
||||
### Why doesn't my role/directive recognize markdown link syntax?
|
||||
|
||||
There are some roles/directives that _hard-code_ syntax into
|
||||
their behavior. For example, many roles allow you to supply titles for links like so:
|
||||
`` {role}`My title <myref>` ``. While this looks like reStructuredText, the role may
|
||||
be explicitly expecting the `My title <myref>` structure, and so MyST will behave the same way.
|
|
@ -0,0 +1 @@
|
|||
Hallo I'm from a Markdown file, [with a reference](howto/autodoc).
|
|
@ -0,0 +1 @@
|
|||
Hallo I'm from an rST file, :ref:`with a reference <howto/autodoc>`.
|
|
@ -0,0 +1,160 @@
|
|||
---
|
||||
sd_hide_title: true
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
::::{grid}
|
||||
:reverse:
|
||||
:gutter: 3 4 4 4
|
||||
:margin: 1 2 1 2
|
||||
|
||||
:::{grid-item}
|
||||
:columns: 12 4 4 4
|
||||
|
||||
```{image} _static/logo-square.svg
|
||||
:width: 200px
|
||||
:class: sd-m-auto
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::{grid-item}
|
||||
:columns: 12 8 8 8
|
||||
:child-align: justify
|
||||
:class: sd-fs-5
|
||||
|
||||
```{rubric} MyST - Markedly Structured Text - Parser
|
||||
```
|
||||
|
||||
A Sphinx and Docutils extension to parse MyST,
|
||||
a rich and extensible flavour of Markdown for authoring technical and scientific documentation.
|
||||
|
||||
```{button-ref} intro
|
||||
:ref-type: doc
|
||||
:color: primary
|
||||
:class: sd-rounded-pill
|
||||
|
||||
Get Started
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
---
|
||||
|
||||
::::{grid} 1 2 2 3
|
||||
:gutter: 1 1 1 2
|
||||
|
||||
:::{grid-item-card} {octicon}`markdown;1.5em;sd-mr-1` CommonMark-plus
|
||||
:link: syntax/core
|
||||
:link-type: ref
|
||||
|
||||
MyST extends the CommonMark syntax specification, to support technical authoring features such as tables and footnotes.
|
||||
|
||||
+++
|
||||
[Learn more »](syntax/core)
|
||||
:::
|
||||
|
||||
:::{grid-item-card} {octicon}`plug;1.5em;sd-mr-1` Sphinx compatible
|
||||
:link: roles-directives
|
||||
:link-type: ref
|
||||
|
||||
Use the MyST role and directive syntax to harness the full capability of Sphinx, such as admonitions and figures, and all existing Sphinx extensions.
|
||||
|
||||
+++
|
||||
[Learn more »](roles-directives)
|
||||
:::
|
||||
|
||||
:::{grid-item-card} {octicon}`tools;1.5em;sd-mr-1` Highly configurable
|
||||
:link: configuration
|
||||
:link-type: doc
|
||||
|
||||
MyST-parser can be configured at both the global and individual document level,
|
||||
to modify parsing behaviour and access extended syntax features.
|
||||
|
||||
+++
|
||||
[Learn more »](configuration)
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
---
|
||||
|
||||
```{rubric} Additional resources
|
||||
```
|
||||
|
||||
[MyST-Markdown VS Code extension](https://marketplace.visualstudio.com/items?itemName=ExecutableBookProject.myst-highlight)
|
||||
: For MyST extended syntax highlighting and authoring tools.
|
||||
|
||||
[Convert existing ReStructuredText files to Markdown][rst-to-myst]
|
||||
: Use the [rst-to-myst] CLI or [the MySTyc interactive web interface](https://astrojuanlu.github.io/mystyc/).
|
||||
|
||||
[MyST-NB](https://myst-nb.readthedocs.io)
|
||||
: A Sphinx and Docutils extension for compiling Jupyter Notebooks into high quality documentation formats, built on top of the MyST-Parser.
|
||||
|
||||
[Jupyter Book](https://jupyterbook.org)
|
||||
: An open source project for building beautiful, publication-quality books and documents from computational material, built on top of the MyST-Parser and MyST-NB.
|
||||
|
||||
[The Jupyter Book gallery](https://executablebooks.org/en/latest/gallery.html)
|
||||
: Examples of documents built with MyST.
|
||||
|
||||
[Javascript MyST parser][mystjs]
|
||||
: The [mystjs] Javascript parser, allows you to parse MyST in websites.
|
||||
|
||||
[markdown-it-py]
|
||||
: A CommonMark-compliant and extensible Markdown parser, used by MyST-Parser to parse source text to tokens.
|
||||
|
||||
```{rubric} Acknowledgements
|
||||
```
|
||||
|
||||
The MyST markdown language and MyST parser are both supported by the open community,
|
||||
[The Executable Book Project](https://executablebooks.org).
|
||||
|
||||
```{toctree}
|
||||
:hidden:
|
||||
intro.md
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:hidden:
|
||||
:caption: Guides
|
||||
|
||||
syntax/syntax
|
||||
syntax/optional
|
||||
syntax/roles-and-directives.md
|
||||
configuration.md
|
||||
docutils.md
|
||||
faq/index.md
|
||||
develop/index.md
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:hidden:
|
||||
:caption: Reference
|
||||
|
||||
develop/_changelog.md
|
||||
syntax/reference
|
||||
develop/background.md
|
||||
api/reference.rst
|
||||
```
|
||||
|
||||
[commonmark]: https://commonmark.org/
|
||||
[github-ci]: https://github.com/executablebooks/MyST-Parser/workflows/continuous-integration/badge.svg?branch=master
|
||||
[github-link]: https://github.com/executablebooks/MyST-Parser
|
||||
[codecov-badge]: https://codecov.io/gh/executablebooks/MyST-Parser/branch/master/graph/badge.svg
|
||||
[codecov-link]: https://codecov.io/gh/executablebooks/MyST-Parser
|
||||
[rtd-badge]: https://readthedocs.org/projects/myst-parser/badge/?version=latest
|
||||
[rtd-link]: https://myst-parser.readthedocs.io/en/latest/?badge=latest
|
||||
[black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
[pypi-badge]: https://img.shields.io/pypi/v/myst-parser.svg
|
||||
[pypi-link]: https://pypi.org/project/myst-parser
|
||||
[conda-badge]: https://anaconda.org/conda-forge/myst-parser/badges/version.svg
|
||||
[conda-link]: https://anaconda.org/conda-forge/myst-parser
|
||||
[black-link]: https://github.com/ambv/black
|
||||
[github-badge]: https://img.shields.io/github/stars/executablebooks/myst-parser?label=github
|
||||
[markdown-it-py]: https://markdown-it-py.readthedocs.io/
|
||||
[markdown-it]: https://markdown-it.github.io/
|
||||
[rst-to-myst]: https://rst-to-myst.readthedocs.io
|
||||
[mystjs]: https://github.com/executablebooks/mystjs
|
|
@ -0,0 +1,250 @@
|
|||
(intro/get-started)=
|
||||
# Get Started
|
||||
|
||||
This page describes how to get started with the MyST parser, with a focus on enabling it in the Sphinx documentation engine.
|
||||
|
||||
## Installation
|
||||
|
||||
[![PyPI][pypi-badge]][pypi-link]
|
||||
[![Conda][conda-badge]][conda-link]
|
||||
|
||||
To install use [pip](https://pip.pypa.io):
|
||||
|
||||
```bash
|
||||
pip install myst-parser
|
||||
```
|
||||
|
||||
or [Conda](https://docs.conda.io):
|
||||
|
||||
```bash
|
||||
conda install -c conda-forge myst-parser
|
||||
```
|
||||
|
||||
[pypi-badge]: https://img.shields.io/pypi/v/myst-parser.svg
|
||||
[pypi-link]: https://pypi.org/project/myst-parser
|
||||
[conda-badge]: https://anaconda.org/conda-forge/myst-parser/badges/version.svg
|
||||
[conda-link]: https://anaconda.org/conda-forge/myst-parser
|
||||
|
||||
(intro/sphinx)=
|
||||
## Enable MyST in Sphinx
|
||||
|
||||
To get started with Sphinx, see their [Quickstart Guide](https://www.sphinx-doc.org/en/master/usage/quickstart.html).
|
||||
|
||||
To use the MyST parser in Sphinx, simply add the following to your `conf.py` file:
|
||||
|
||||
```python
|
||||
extensions = ["myst_parser"]
|
||||
```
|
||||
|
||||
This will activate the MyST Parser extension, causing all documents with the `.md` extension to be parsed as MyST.
|
||||
|
||||
:::{tip}
|
||||
To parse single documents, see the [](docutils.md) section
|
||||
:::
|
||||
|
||||
(intro/writing)=
|
||||
## Write a CommonMark document
|
||||
|
||||
MyST is an extension of [CommonMark Markdown](https://commonmark.org/),
|
||||
that includes [additional syntax](../syntax/syntax.md) for technical authoring,
|
||||
which integrates with Docutils and Sphinx.
|
||||
|
||||
To start off, create an empty file called `myfile.md` and give it a markdown title and text.
|
||||
|
||||
```md
|
||||
# My nifty title
|
||||
|
||||
Some **text**!
|
||||
```
|
||||
|
||||
To parse to HTML, try the CLI:
|
||||
|
||||
```html
|
||||
$ myst-docutils-html5 --stylesheet= myfile.md
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<title>My nifty title</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<main id="my-nifty-title">
|
||||
<h1 class="title">My nifty title</h1>
|
||||
|
||||
<p>Some <strong>text</strong>!</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
To include this document within a Sphinx project,
|
||||
include `myfile.md` in a [`toctree` directive](sphinx:toctree-directive) on an index page.
|
||||
|
||||
## Extend CommonMark with roles and directives
|
||||
|
||||
MyST allows any Sphinx role or directive to be used in a document.
|
||||
These are extensions points allowing for richer features, such as admonitions and figures.
|
||||
|
||||
For example, add an `admonition` directive and `sup` role to your Markdown page, like so:
|
||||
|
||||
````md
|
||||
# My nifty title
|
||||
|
||||
Some **text**!
|
||||
|
||||
```{admonition} Here's my title
|
||||
:class: tip
|
||||
|
||||
Here's my admonition content.{sup}`1`
|
||||
```
|
||||
````
|
||||
|
||||
Then convert to HTML:
|
||||
|
||||
```html
|
||||
$ myst-docutils-html5 --stylesheet= myfile.md
|
||||
...
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Here's my title</p>
|
||||
<p>Here's my admonition content.<sup>1</sup></p>
|
||||
</div>
|
||||
...
|
||||
```
|
||||
|
||||
:::{seealso}
|
||||
The full [](syntax/roles-and-directives.md) section
|
||||
:::
|
||||
|
||||
(intro/reference)=
|
||||
## Cross-referencing
|
||||
|
||||
MyST-Parser offers powerful cross-referencing features, to link to documents, headers, figures and more.
|
||||
|
||||
For example, to add a section *reference target*, and reference it:
|
||||
|
||||
```md
|
||||
(header-label)=
|
||||
# A header
|
||||
|
||||
[My reference](header-label)
|
||||
```
|
||||
|
||||
```html
|
||||
$ myst-docutils-html5 --stylesheet= myfile.md
|
||||
...
|
||||
<span id="header-label"></span>
|
||||
<h1 class="title">A header</h1>
|
||||
|
||||
<p><a class="reference internal" href="#header-label">My reference</a></p>
|
||||
...
|
||||
```
|
||||
|
||||
:::{seealso}
|
||||
The [](syntax/referencing) section,\
|
||||
and the [ReadTheDocs cross-referencing](https://docs.readthedocs.io/en/stable/guides/cross-referencing-with-sphinx.html) documentation
|
||||
:::
|
||||
|
||||
## Configuring MyST-Parser
|
||||
|
||||
The [](configuration.md) section contains a complete list of configuration options for the MyST-Parser.
|
||||
|
||||
These can be applied globally, e.g. in the sphinx `conf.py`:
|
||||
|
||||
```python
|
||||
myst_enable_extensions = [
|
||||
"colon_fence",
|
||||
]
|
||||
```
|
||||
|
||||
Or they can be applied to specific documents, at the top of the document:
|
||||
|
||||
```yaml
|
||||
---
|
||||
myst:
|
||||
enable_extensions: ["colon_fence"]
|
||||
---
|
||||
```
|
||||
|
||||
## Extending Sphinx
|
||||
|
||||
The other way to extend MyST in Sphinx is to install Sphinx extensions that define new roles, directives, etc.
|
||||
|
||||
For example, let's install the `sphinxcontrib.mermaid` extension,
|
||||
which will allow us to generate [Mermaid diagrams](https://mermaid-js.github.io/mermaid/#/) with MyST.
|
||||
|
||||
First, install `sphinxcontrib.mermaid`:
|
||||
|
||||
```shell
|
||||
pip install sphinxcontrib-mermaid
|
||||
```
|
||||
|
||||
Next, add it to your list of extensions in `conf.py`:
|
||||
|
||||
```python
|
||||
extensions = [
|
||||
"myst_parser",
|
||||
"sphinxcontrib.mermaid",
|
||||
]
|
||||
```
|
||||
|
||||
Now, add a **mermaid directive** to your markdown file.
|
||||
For example:
|
||||
|
||||
````md
|
||||
# My nifty title
|
||||
|
||||
Some **text**!
|
||||
|
||||
```{admonition} Here's my title
|
||||
:class: warning
|
||||
|
||||
Here's my admonition content
|
||||
```
|
||||
|
||||
(section-two)=
|
||||
## Here's another section
|
||||
|
||||
And some more content.
|
||||
|
||||
% This comment won't make it into the outputs!
|
||||
And here's {ref}`a reference to this section <section-two>`.
|
||||
I can also reference the section {ref}`section-two` without specifying my title.
|
||||
|
||||
:::{note}
|
||||
And here's a note with a colon fence!
|
||||
:::
|
||||
|
||||
And finally, here's a cool mermaid diagram!
|
||||
|
||||
```{mermaid}
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
Alice->John: Hello John, how are you?
|
||||
loop Healthcheck
|
||||
John->John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts <br/>prevail...
|
||||
John-->Alice: Great!
|
||||
John->Bob: How about you?
|
||||
Bob-->John: Jolly good!
|
||||
```
|
||||
````
|
||||
|
||||
When you build your documentation, you should see something like this:
|
||||
|
||||
```{mermaid}
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
Alice->John: Hello John, how are you?
|
||||
loop Healthcheck
|
||||
John->John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts <br/>prevail...
|
||||
John-->Alice: Great!
|
||||
John->Bob: How about you?
|
||||
Bob-->John: Jolly good!
|
||||
```
|
|
@ -0,0 +1 @@
|
|||
Hallo!
|
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,261 @@
|
|||
(syntax-tokens)=
|
||||
# Syntax tokens
|
||||
|
||||
This page serves as a reference for the syntax that makes of MyST Markdown.
|
||||
|
||||
:::{seealso}
|
||||
For more description and explanation of MyST syntax, see the [syntax guide](syntax.md).
|
||||
:::
|
||||
|
||||
## Block (Multi-line) Tokens
|
||||
|
||||
Block tokens span multiple lines of content. They are broken down into two sections:
|
||||
|
||||
- {ref}`extended-block-tokens` contains *extra* tokens that are not in CommonMark.
|
||||
- {ref}`commonmark-block-tokens` contains CommonMark tokens that also work, for reference.
|
||||
|
||||
:::{note}
|
||||
Because MyST markdown was inspired by functionality that exists in reStructuredText,
|
||||
we have shown equivalent rST syntax for many MyST markdown features below.
|
||||
:::
|
||||
|
||||
(extended-block-tokens)=
|
||||
### Extended block tokens
|
||||
|
||||
`````{list-table}
|
||||
:header-rows: 1
|
||||
:widths: 10 20 20
|
||||
|
||||
* - Token
|
||||
- Description
|
||||
- Example
|
||||
* - FrontMatter
|
||||
- A YAML block at the start of the document enclosed by `---`
|
||||
- ```yaml
|
||||
---
|
||||
key: value
|
||||
---
|
||||
```
|
||||
* - Directives
|
||||
- enclosed in 3 or more backticks followed by the directive name wrapped
|
||||
in curly brackets `{}`. See {ref}`syntax/directives` for more details.
|
||||
- ````md
|
||||
```{directive}
|
||||
:option: value
|
||||
|
||||
content
|
||||
```
|
||||
````
|
||||
* - Math
|
||||
- `$$` (default) or `\[`...`\]` characters wrapping multi-line math, or even direct [amsmath](https://ctan.org/pkg/amsmath) LaTeX equations (optional).
|
||||
See {ref}`syntax/math` for more information.
|
||||
- ```latex
|
||||
$$
|
||||
a=1
|
||||
$$
|
||||
```
|
||||
* - Table
|
||||
- Standard markdown table style, with pipe separation.
|
||||
- ```md
|
||||
| a | b |
|
||||
| :--- | ---: |
|
||||
| c | d |
|
||||
```
|
||||
* - LineComment
|
||||
- A commented line. See {ref}`syntax/comments` for more information.
|
||||
- ```latex
|
||||
% this is a comment
|
||||
```
|
||||
* - BlockBreak
|
||||
- Define blocks of text. See {ref}`syntax/blockbreaks` for more information.
|
||||
- ```md
|
||||
+++ {"meta": "data"}
|
||||
```
|
||||
* - Footnote
|
||||
- A definition for a referencing footnote, that is placed at the bottom of the document.
|
||||
See {ref}`syntax/footnotes` for more details.
|
||||
- ```md
|
||||
[^ref]: Some footnote text
|
||||
```
|
||||
* - Admonitions (optional)
|
||||
- An alternative approach for admonition style directives only, which has the benefit of allowing the content to be rendered in standard markdown editors.
|
||||
See [admonition directives](syntax/admonitions) for more details.
|
||||
- ````md
|
||||
:::{note}
|
||||
*content*
|
||||
:::
|
||||
````
|
||||
`````
|
||||
|
||||
(commonmark-block-tokens)=
|
||||
### CommonMark tokens
|
||||
|
||||
`````{list-table}
|
||||
:header-rows: 1
|
||||
:widths: 10 20 20
|
||||
|
||||
* - Token
|
||||
- Description
|
||||
- Example
|
||||
* - HTMLBlock
|
||||
- Any valid HTML (rendered in HTML output only)
|
||||
- ```html
|
||||
<p>some text</p>
|
||||
```
|
||||
* - BlockCode
|
||||
- indented text (4 spaces or a tab)
|
||||
- ```md
|
||||
included as literal *text*
|
||||
```
|
||||
* - Heading
|
||||
- Level 1-6 headings, denoted by number of `#`
|
||||
- ```md
|
||||
### Heading level 3
|
||||
```
|
||||
* - SetextHeading
|
||||
- Underlined header (using multiple `=` or `-`)
|
||||
- ```md
|
||||
Header
|
||||
======
|
||||
```
|
||||
* - Quote
|
||||
- Quoted text
|
||||
- ```md
|
||||
> this is a quote
|
||||
```
|
||||
* - CodeFence
|
||||
- Enclosed in 3 or more `` ` `` or `~` with an optional language name.
|
||||
See {ref}`syntax/code-blocks` for more information.
|
||||
- ````md
|
||||
```python
|
||||
print('this is python')
|
||||
```
|
||||
````
|
||||
* - ThematicBreak
|
||||
- Creates a horizontal line in the output
|
||||
- ```md
|
||||
---
|
||||
```
|
||||
* - List
|
||||
- bullet points or enumerated.
|
||||
- ```md
|
||||
- item
|
||||
- nested item
|
||||
1. numbered item
|
||||
```
|
||||
* - LinkDefinition
|
||||
- A substitution for an inline link, which can have a reference target (no spaces), and an optional title (in `"`)
|
||||
- ```md
|
||||
[key]: https://www.google.com "a title"
|
||||
```
|
||||
* - Paragraph
|
||||
- General inline text
|
||||
- ```md
|
||||
any *text*
|
||||
```
|
||||
`````
|
||||
|
||||
## Span (Inline) Tokens
|
||||
|
||||
Span (or inline) tokens are defined on a single line of content. They are broken down into two
|
||||
sections below:
|
||||
|
||||
- {ref}`extended-span-tokens` contains *extra* tokens that are not in CommonMark.
|
||||
- {ref}`commonmark-span-tokens` contains CommonMark tokens that also work, for reference.
|
||||
|
||||
(extended-span-tokens)=
|
||||
### Extended inline tokens
|
||||
|
||||
`````{list-table}
|
||||
:header-rows: 1
|
||||
:widths: 10 20 20
|
||||
|
||||
* - Token
|
||||
- Description
|
||||
- Example
|
||||
* - Role
|
||||
- See {ref}`syntax/roles` for more information.
|
||||
- ```md
|
||||
{rolename}`interpreted text`
|
||||
```
|
||||
* - Target
|
||||
- Precedes element to target, e.g. header. See
|
||||
{ref}`syntax/targets` for more information.
|
||||
- ```md
|
||||
(target)=
|
||||
```
|
||||
* - Math
|
||||
- `$` (default) or `\(`...`\)` enclosed math. See
|
||||
{ref}`syntax/math` for more information.
|
||||
- ```latex
|
||||
$a=1$ or $$a=1$$
|
||||
```
|
||||
* - FootReference
|
||||
- Reference a footnote. See {ref}`syntax/footnotes` for more details.
|
||||
- ```md
|
||||
[^abc]
|
||||
```
|
||||
`````
|
||||
|
||||
(commonmark-span-tokens)=
|
||||
### CommonMark inline tokens
|
||||
|
||||
`````{list-table}
|
||||
:header-rows: 1
|
||||
:widths: 10 20 20
|
||||
|
||||
* - Token
|
||||
- Description
|
||||
- Example
|
||||
* - HTMLSpan
|
||||
- Any valid HTML (rendered in HTML output only)
|
||||
- ```html
|
||||
<p>some text</p>
|
||||
```
|
||||
* - EscapeSequence
|
||||
- Escaped symbols (to avoid them being interpreted as other syntax elements)
|
||||
- ```md
|
||||
\*
|
||||
```
|
||||
* - AutoLink
|
||||
- Link that is shown in final output
|
||||
- ```md
|
||||
<http://www.google.com>
|
||||
```
|
||||
* - InlineCode
|
||||
- Literal text
|
||||
- ```md
|
||||
`a=1`
|
||||
```
|
||||
* - LineBreak
|
||||
- Soft or hard (ends with spaces or backslash)
|
||||
- ```md
|
||||
A hard break\
|
||||
```
|
||||
* - Image
|
||||
- Link to an image.
|
||||
You can also use HTML syntax, to include image size etc, [see here](syntax/images) for details
|
||||
- ```md
|
||||
![alt](src "title")
|
||||
```
|
||||
* - Link
|
||||
- Reference `LinkDefinitions`. See {ref}`syntax/referencing` for more details.
|
||||
- ```md
|
||||
[text](target "title") or [text][key]
|
||||
```
|
||||
* - Strong
|
||||
- Bold text
|
||||
- ```md
|
||||
**strong**
|
||||
```
|
||||
* - Emphasis
|
||||
- Italic text
|
||||
- ```md
|
||||
*emphasis*
|
||||
```
|
||||
* - RawText
|
||||
- Any text
|
||||
- ```md
|
||||
any text
|
||||
```
|
||||
`````
|
|
@ -0,0 +1,421 @@
|
|||
(roles-directives)=
|
||||
|
||||
# Roles and Directives
|
||||
|
||||
Roles and directives provide a way to extend the syntax of MyST in an unbound manner,
|
||||
by interpreting a chuck of text as a specific type of markup, according to its name.
|
||||
|
||||
Mostly all [docutils roles](https://docutils.sourceforge.io/docs/ref/rst/roles.html), [docutils directives](https://docutils.sourceforge.io/docs/ref/rst/directives.html), [sphinx roles](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html), or [sphinx directives](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html) can be used in MyST.
|
||||
|
||||
## Syntax
|
||||
|
||||
(syntax/directives)=
|
||||
|
||||
### Directives - a block-level extension point
|
||||
|
||||
Directives syntax is defined with triple-backticks and curly-brackets.
|
||||
It is effectively a Markdown code fence with curly brackets around the language, and a directive name in place of a language name.
|
||||
Here is the basic structure:
|
||||
|
||||
`````{list-table}
|
||||
---
|
||||
header-rows: 1
|
||||
---
|
||||
* - MyST
|
||||
- reStructuredText
|
||||
* - ````md
|
||||
```{directivename} arguments
|
||||
---
|
||||
key1: val1
|
||||
key2: val2
|
||||
---
|
||||
This is
|
||||
directive content
|
||||
```
|
||||
````
|
||||
- ```rst
|
||||
.. directivename:: arguments
|
||||
:key1: val1
|
||||
:key2: val2
|
||||
|
||||
This is
|
||||
directive content
|
||||
```
|
||||
`````
|
||||
|
||||
For example, the following code:
|
||||
|
||||
````md
|
||||
```{admonition} This is my admonition
|
||||
This is my note
|
||||
```
|
||||
````
|
||||
|
||||
Will generate this admonition:
|
||||
|
||||
```{admonition} This is my admonition
|
||||
This is my note
|
||||
```
|
||||
|
||||
#### Parameterizing directives
|
||||
|
||||
For directives that take parameters as input, there are two ways to parameterize them.
|
||||
In each case, the options themselves are given as `key: value` pairs. An example of
|
||||
each is shown below:
|
||||
|
||||
**Using YAML frontmatter**. A block of YAML front-matter just after the
|
||||
first line of the directive will be parsed as options for the directive. This needs to be
|
||||
surrounded by `---` lines. Everything in between will be parsed by YAML and
|
||||
passed as keyword arguments to your directive. For example:
|
||||
|
||||
````md
|
||||
```{code-block} python
|
||||
---
|
||||
lineno-start: 10
|
||||
emphasize-lines: 1, 3
|
||||
caption: |
|
||||
This is my
|
||||
multi-line caption. It is *pretty nifty* ;-)
|
||||
---
|
||||
a = 2
|
||||
print('my 1st line')
|
||||
print(f'my {a}nd line')
|
||||
```
|
||||
````
|
||||
|
||||
```{code-block} python
|
||||
---
|
||||
lineno-start: 10
|
||||
emphasize-lines: 1, 3
|
||||
caption: |
|
||||
This is my
|
||||
multi-line caption. It is *pretty nifty* ;-)
|
||||
---
|
||||
a = 2
|
||||
print('my 1st line')
|
||||
print(f'my {a}nd line')
|
||||
```
|
||||
|
||||
**Short-hand options with `:` characters**. If you only need one or two options for your
|
||||
directive and wish to save lines, you may also specify directive options as a collection
|
||||
of lines just after the first line of the directive, each preceding with `:`. Then the
|
||||
leading `:` is removed from each line, and the rest is parsed as YAML.
|
||||
|
||||
For example:
|
||||
|
||||
````md
|
||||
```{code-block} python
|
||||
:lineno-start: 10
|
||||
:emphasize-lines: 1, 3
|
||||
|
||||
a = 2
|
||||
print('my 1st line')
|
||||
print(f'my {a}nd line')
|
||||
```
|
||||
````
|
||||
|
||||
(syntax/directives/parsing)=
|
||||
|
||||
#### How directives parse content
|
||||
|
||||
Some directives parse the content that is in their content block.
|
||||
MyST parses this content **as Markdown**.
|
||||
|
||||
This means that MyST markdown can be written in the content areas of any directives written in MyST markdown. For example:
|
||||
|
||||
````md
|
||||
```{admonition} My markdown link
|
||||
Here is [markdown link syntax](https://jupyter.org)
|
||||
```
|
||||
````
|
||||
|
||||
```{admonition} My markdown link
|
||||
Here is [markdown link syntax](https://jupyter.org)
|
||||
```
|
||||
|
||||
As a short-hand for directives that require no arguments, and when no parameter options are used (see below),
|
||||
you may start the content directly after the directive name.
|
||||
|
||||
````md
|
||||
```{note} Notes require **no** arguments, so content can start here.
|
||||
```
|
||||
````
|
||||
|
||||
```{note} Notes require **no** arguments, so content can start here.
|
||||
```
|
||||
|
||||
For special cases, MySt also offers the `eval-rst` directive.
|
||||
This will parse the content **as ReStructuredText**:
|
||||
|
||||
````md
|
||||
```{eval-rst}
|
||||
.. figure:: img/fun-fish.png
|
||||
:width: 100px
|
||||
:name: rst-fun-fish
|
||||
|
||||
Party time!
|
||||
|
||||
A reference from inside: :ref:`rst-fun-fish`
|
||||
|
||||
A reference from outside: :ref:`syntax/directives/parsing`
|
||||
```
|
||||
````
|
||||
|
||||
```{eval-rst}
|
||||
.. figure:: img/fun-fish.png
|
||||
:width: 100px
|
||||
:name: rst-fun-fish
|
||||
|
||||
Party time!
|
||||
|
||||
A reference from inside: :ref:`rst-fun-fish`
|
||||
|
||||
A reference from outside: :ref:`syntax/directives/parsing`
|
||||
```
|
||||
|
||||
Note how the text is integrated into the rest of the document, so we can also reference [party fish](rst-fun-fish) anywhere else in the documentation.
|
||||
|
||||
#### Nesting directives
|
||||
|
||||
You can nest directives by ensuring that the tick-lines corresponding to the
|
||||
outermost directive are longer than the tick-lines for the inner directives.
|
||||
For example, nest a warning inside a note block like so:
|
||||
|
||||
`````md
|
||||
````{note}
|
||||
The next info should be nested
|
||||
```{warning}
|
||||
Here's my warning
|
||||
```
|
||||
````
|
||||
`````
|
||||
|
||||
Here's how it looks rendered:
|
||||
|
||||
````{note}
|
||||
The next info should be nested
|
||||
```{warning}
|
||||
Here's my warning
|
||||
```
|
||||
````
|
||||
|
||||
You can indent inner-code fences, so long as they aren't indented by more than 3 spaces.
|
||||
Otherwise, they will be rendered as "raw code" blocks:
|
||||
|
||||
`````md
|
||||
````{note}
|
||||
The warning block will be properly-parsed
|
||||
|
||||
```{warning}
|
||||
Here's my warning
|
||||
```
|
||||
|
||||
But the next block will be parsed as raw text
|
||||
|
||||
```{warning}
|
||||
Here's my raw text warning that isn't parsed...
|
||||
```
|
||||
````
|
||||
`````
|
||||
|
||||
````{note}
|
||||
The warning block will be properly-parsed
|
||||
|
||||
```{warning}
|
||||
Here's my warning
|
||||
```
|
||||
|
||||
But the next block will be parsed as raw text
|
||||
|
||||
```{warning}
|
||||
Here's my raw text warning that isn't parsed...
|
||||
```
|
||||
````
|
||||
|
||||
This can really be abused if you'd like ;-)
|
||||
|
||||
``````{note}
|
||||
The next info should be nested
|
||||
`````{warning}
|
||||
Here's my warning
|
||||
````{admonition} Yep another admonition
|
||||
```python
|
||||
# All this fuss was about this boring python?!
|
||||
print('yep!')
|
||||
```
|
||||
````
|
||||
`````
|
||||
``````
|
||||
|
||||
#### Markdown-friendly directives
|
||||
|
||||
Want to use syntax that renders correctly in standard Markdown editors?
|
||||
See [the extended syntax option](syntax/colon_fence).
|
||||
|
||||
```md
|
||||
:::{note}
|
||||
This text is **standard** *Markdown*
|
||||
:::
|
||||
```
|
||||
|
||||
:::{note}
|
||||
This text is **standard** *Markdown*
|
||||
:::
|
||||
|
||||
(syntax/roles)=
|
||||
|
||||
### Roles - an in-line extension point
|
||||
|
||||
Roles are similar to directives - they allow you to define arbitrary new functionality, but they are used *in-line*.
|
||||
To define an in-line role, use the following form:
|
||||
|
||||
````{list-table}
|
||||
---
|
||||
header-rows: 1
|
||||
---
|
||||
* - MyST
|
||||
- reStructuredText
|
||||
* - ````md
|
||||
{role-name}`role content`
|
||||
````
|
||||
- ```rst
|
||||
:role-name:`role content`
|
||||
```
|
||||
````
|
||||
|
||||
For example, the following code:
|
||||
|
||||
```md
|
||||
Since Pythagoras, we know that {math}`a^2 + b^2 = c^2`
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
Since Pythagoras, we know that {math}`a^2 + b^2 = c^2`
|
||||
|
||||
You can use roles to do things like reference equations and other items in
|
||||
your book. For example:
|
||||
|
||||
````md
|
||||
```{math} e^{i\pi} + 1 = 0
|
||||
---
|
||||
label: euler
|
||||
---
|
||||
```
|
||||
|
||||
Euler's identity, equation {math:numref}`euler`, was elected one of the
|
||||
most beautiful mathematical formulas.
|
||||
````
|
||||
|
||||
Becomes:
|
||||
|
||||
```{math} e^{i\pi} + 1 = 0
|
||||
---
|
||||
label: euler
|
||||
---
|
||||
```
|
||||
|
||||
Euler's identity, equation {math:numref}`euler`, was elected one of the
|
||||
most beautiful mathematical formulas.
|
||||
|
||||
#### How roles parse content
|
||||
|
||||
The content of roles is parsed differently depending on the role that you've used.
|
||||
Some roles expect inputs that will be used to change functionality. For example,
|
||||
the `ref` role will assume that input content is a reference to some other part of the
|
||||
site. However, other roles may use the MyST parser to parse the input as content.
|
||||
|
||||
Some roles also **extend their functionality** depending on the content that you pass.
|
||||
For example, following the `ref` example above, if you pass a string like this:
|
||||
`Content to display <myref>`, then the `ref` will display `Content to display` and use
|
||||
`myref` as the reference to look up.
|
||||
|
||||
How roles parse this content depends on the author that created the role.
|
||||
|
||||
## Common roles and directives
|
||||
|
||||
:::{admonition} {material-regular}`engineering;1.5rem;sd-mr-1` Currently Under Construction
|
||||
:class: no-icon
|
||||
Check back for more...
|
||||
:::
|
||||
|
||||
### ToC Trees
|
||||
|
||||
```{doc-directive} contents
|
||||
Insert a table of contents tree of the documents headings.
|
||||
```
|
||||
|
||||
```{doc-directive} toctree
|
||||
Inserts a Sphinx "Table of Contents" tree, containing a list of (relative) child document paths.
|
||||
```
|
||||
|
||||
### Admonitions
|
||||
|
||||
```{doc-directive} admonition
|
||||
Create a generic "callout" box, containing the content.
|
||||
```
|
||||
|
||||
```{doc-directive} note
|
||||
Create a "callout" box, specific to notes, containing the content.
|
||||
```
|
||||
|
||||
Other admonitions (same structure as `note`): `attention`, `caution`, `danger`, `error`, `hint`, `important`, `tip`, `warning`.
|
||||
|
||||
Sphinx only: `deprecated`, `versionadded`, `versionchanged`.
|
||||
|
||||
### Images and Figures
|
||||
|
||||
```{doc-directive} image
|
||||
Insert an image, from a (relative) path or URL.
|
||||
```
|
||||
|
||||
```{doc-directive} figure
|
||||
Insert an image, from a (relative) path or URL,
|
||||
with a caption (first paragraph), and optional legend (subsequent content).
|
||||
```
|
||||
|
||||
```{doc-directive} table
|
||||
Insert a (MyST) table with a caption.
|
||||
```
|
||||
|
||||
### Tables
|
||||
|
||||
```{doc-directive} list-table
|
||||
Create a table from data in a uniform two-level bullet list.
|
||||
```
|
||||
|
||||
```{doc-directive} csv-table
|
||||
Create a table from CSV (comma-separated values) data.
|
||||
```
|
||||
|
||||
### Code
|
||||
|
||||
```{doc-directive} code-block
|
||||
Syntax highlight a block of code, according to the language.
|
||||
```
|
||||
|
||||
(syntax/roles/special)=
|
||||
|
||||
### MyST only
|
||||
|
||||
This section contains information about special roles and directives that come bundled with the MyST Parser Sphinx extension.
|
||||
|
||||
#### Insert the date and reading time
|
||||
|
||||
```{versionadded} 0.14.0
|
||||
The `sub-ref` role and word counting.
|
||||
```
|
||||
|
||||
You may insert the "last updated" date and estimated reading time into your document via substitution definitions, which can be accessed *via* the `sub-ref` role.
|
||||
|
||||
For example:
|
||||
|
||||
```markdown
|
||||
> {sub-ref}`today` | {sub-ref}`wordcount-words` words | {sub-ref}`wordcount-minutes` min read
|
||||
```
|
||||
|
||||
> {sub-ref}`today` | {sub-ref}`wordcount-words` words | {sub-ref}`wordcount-minutes` min read
|
||||
|
||||
`today` is replaced by either the date on which the document is parsed, with the format set by [`today_fmt`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-today_fmt), or the `today` variable if set in the configuration file.
|
||||
|
||||
The reading speed is computed using the `myst_words_per_minute` configuration (see the [Sphinx configuration options](sphinx/config-options)).
|
|
@ -0,0 +1,491 @@
|
|||
(syntax/core)=
|
||||
|
||||
# Core Syntax
|
||||
|
||||
## Introduction
|
||||
|
||||
MyST is a strict superset of the [CommonMark syntax specification](https://spec.commonmark.org/).
|
||||
It adds features focussed on scientific and technical documentation authoring, as detailed below.
|
||||
|
||||
In addition, the roles and directives syntax provide inline/block-level extension points for plugins.
|
||||
This is detailed further in the [Roles and Directives](roles-directives) section.
|
||||
|
||||
:::{seealso}
|
||||
The [syntax token reference tables](syntax-tokens)
|
||||
:::
|
||||
|
||||
(syntax/commonmark)=
|
||||
|
||||
## CommonMark
|
||||
|
||||
The [CommonMark syntax specification](https://spec.commonmark.org/) details the full set of syntax rules.
|
||||
Here we provide a summary of most features:
|
||||
|
||||
Element | Syntax
|
||||
--------------- | -------------------------------------------
|
||||
Heading | `# H1` to `###### H6`
|
||||
Bold | `**bold**`
|
||||
Italic | `*italic*`
|
||||
Inline Code | `` `code` ``
|
||||
Autolink | `<https://www.example.com>`
|
||||
URL Link | `[title](https://www.example.com)`
|
||||
Image | `![alt](https://www.example.com/image.png)`
|
||||
Reference Link | `[title][link]`
|
||||
Link Definition | `[link]: https://www.example.com`
|
||||
Thematic break | `---`
|
||||
Blockquote | `> quote`
|
||||
Ordered List | `1. item`
|
||||
Unordered List | `- item`
|
||||
Code Fence | opening ```` ```lang ```` to closing ```` ``` ````
|
||||
|
||||
(syntax/frontmatter)=
|
||||
|
||||
## Front Matter
|
||||
|
||||
This is a [YAML](https://en.wikipedia.org/wiki/YAML) block at the start of the document, as used for example in [jekyll](https://jekyllrb.com/docs/front-matter/).
|
||||
The document should start with three or more `---` markers, and YAML is parsed until a closing `---` marker is found:
|
||||
|
||||
```yaml
|
||||
---
|
||||
key1: value
|
||||
key2: [value1, value2]
|
||||
key3:
|
||||
subkey1: value
|
||||
---
|
||||
```
|
||||
|
||||
:::{seealso}
|
||||
Top-matter is also used for the [substitution syntax extension](syntax/substitutions),
|
||||
and can be used to store information for blog posting (see [ablog's myst-parser support](https://ablog.readthedocs.io/en/latest/manual/markdown/)).
|
||||
:::
|
||||
|
||||
### Setting a title
|
||||
|
||||
```{versionadded} 0.17.0
|
||||
```
|
||||
|
||||
If `myst_title_to_header` is set to `True`, and a `title` key is present in the front matter,
|
||||
then the title will be used as the document's header (parsed as Markdown).
|
||||
For example:
|
||||
|
||||
```md
|
||||
---
|
||||
title: My Title with *emphasis*
|
||||
---
|
||||
```
|
||||
|
||||
would be equivalent to:
|
||||
|
||||
```md
|
||||
# My Title with *emphasis*
|
||||
```
|
||||
|
||||
(syntax/html_meta)=
|
||||
|
||||
### Setting HTML Metadata
|
||||
|
||||
The front-matter can contain the special key `html_meta`; a dict with data to add to the generated HTML as [`<meta>` elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta).
|
||||
This is equivalent to using the [RST `meta` directive](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#html-metadata).
|
||||
|
||||
HTML metadata can also be added globally in the `conf.py` *via* the `myst_html_meta` variable, in which case it will be added to all MyST documents.
|
||||
For each document, the `myst_html_meta` dict will be updated by the document level front-matter `html_meta`, with the front-matter taking precedence.
|
||||
|
||||
::::{tab-set}
|
||||
:::{tab-item} Sphinx Configuration
|
||||
|
||||
```python
|
||||
language = "en"
|
||||
myst_html_meta = {
|
||||
"description lang=en": "metadata description",
|
||||
"description lang=fr": "description des métadonnées",
|
||||
"keywords": "Sphinx, MyST",
|
||||
"property=og:locale": "en_US"
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} MyST Front-Matter
|
||||
|
||||
```yaml
|
||||
---
|
||||
myst:
|
||||
html_meta:
|
||||
"description lang=en": "metadata description"
|
||||
"description lang=fr": "description des métadonnées"
|
||||
"keywords": "Sphinx, MyST"
|
||||
"property=og:locale": "en_US"
|
||||
---
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} RestructuredText
|
||||
|
||||
```restructuredtext
|
||||
.. meta::
|
||||
:description lang=en: metadata description
|
||||
:description lang=fr: description des métadonnées
|
||||
:keywords: Sphinx, MyST
|
||||
:property=og:locale: en_US
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} HTML Output
|
||||
|
||||
```html
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta content="metadata description" lang="en" name="description" xml:lang="en" />
|
||||
<meta content="description des métadonnées" lang="fr" name="description" xml:lang="fr" />
|
||||
<meta name="keywords" content="Sphinx, MyST">
|
||||
<meta content="en_US" property="og:locale" />
|
||||
```
|
||||
|
||||
:::
|
||||
::::
|
||||
|
||||
(syntax/comments)=
|
||||
|
||||
## Comments
|
||||
|
||||
You may add comments by putting the `%` character at the beginning of a line. This will
|
||||
prevent the line from being parsed into the output document.
|
||||
|
||||
For example, this code:
|
||||
|
||||
```md
|
||||
% my comment
|
||||
```
|
||||
|
||||
Is below, but it won't be parsed into the document.
|
||||
|
||||
% my comment
|
||||
|
||||
````{important}
|
||||
Since comments are a block-level entity, they will terminate the previous block.
|
||||
In practical terms, this means that the following lines
|
||||
will be broken up into two paragraphs, resulting in a new line between them:
|
||||
|
||||
```
|
||||
a line
|
||||
% a comment
|
||||
another line
|
||||
```
|
||||
|
||||
a line
|
||||
% a comment
|
||||
another line
|
||||
````
|
||||
|
||||
:::{tip}
|
||||
Comments are equivalent to the RST syntax: `.. my comment`.
|
||||
:::
|
||||
|
||||
(syntax/blockbreaks)=
|
||||
|
||||
## Block Breaks
|
||||
|
||||
You may add a block break by putting `+++` at the beginning of a line.
|
||||
This constuct's intended use case is for mapping to cell based document formats,
|
||||
like [jupyter notebooks](https://jupyter.org/),
|
||||
to indicate a new text cell. It will not show up in the rendered text,
|
||||
but is stored in the internal document structure for use by developers.
|
||||
|
||||
For example, this code:
|
||||
|
||||
```md
|
||||
+++ some text
|
||||
```
|
||||
|
||||
Is below, but it won't be parsed into the document.
|
||||
|
||||
+++
|
||||
|
||||
(syntax/referencing)=
|
||||
|
||||
## Markdown Links and Referencing
|
||||
|
||||
Markdown links are of the form: `[text](link)`.
|
||||
|
||||
If you set the configuration `myst_all_links_external = True` (`False` by default),
|
||||
then all links will be treated simply as "external" links.
|
||||
For example, in HTML outputs, `[text](link)` will be rendered as `<a href="link">text</a>`.
|
||||
|
||||
Otherwise, links will only be treated as "external" links if they are prefixed with a scheme,
|
||||
configured with `myst_url_schemes` (by default, `http`, `https`, `ftp`, or `mailto`).
|
||||
For example, `[example.com](https://example.com)` becomes [example.com](https://example.com).
|
||||
|
||||
:::{note}
|
||||
The `text` will be parsed as nested Markdown, for example `[here's some *emphasised text*](https://example.com)` will be parsed as [here's some *emphasised text*](https://example.com).
|
||||
:::
|
||||
|
||||
For "internal" links, myst-parser in Sphinx will attempt to resolve the reference to either a relative document path, or a cross-reference to a target (see [](syntax/targets)):
|
||||
|
||||
- `[this doc](syntax.md)` will link to a rendered source document: [this doc](syntax.md)
|
||||
- This is similar to `` {doc}`this doc <syntax>` ``; {doc}`this doc <syntax>`, but allows for document extensions, and parses nested Markdown text.
|
||||
- `[example text](example.txt)` will link to a non-source (downloadable) file: [example text](example.txt)
|
||||
- The linked document itself will be copied to the build directory.
|
||||
- This is similar to `` {download}`example text <example.txt>` ``; {download}`example text <example.txt>`, but parses nested Markdown text.
|
||||
- `[reference](syntax/referencing)` will link to an internal cross-reference: [reference](syntax/referencing)
|
||||
- This is similar to `` {any}`reference <syntax/referencing>` ``; {any}`reference <syntax/referencing>`, but parses nested Markdown text.
|
||||
- You can limit the scope of the cross-reference to specific [sphinx domains](sphinx:domain), by using the `myst_ref_domains` configuration.
|
||||
For example, `myst_ref_domains = ("std", "py")` will only allow cross-references to `std` and `py` domains.
|
||||
|
||||
Additionally, only if [](syntax/header-anchors) are enabled, then internal links to document headers can be used.
|
||||
For example `[a header](syntax.md#markdown-links-and-referencing)` will link to a header anchor: [a header](syntax.md#markdown-links-and-referencing).
|
||||
|
||||
(syntax/targets)=
|
||||
|
||||
## Targets and Cross-Referencing
|
||||
|
||||
Targets are used to define custom anchors that you can refer to elsewhere in your
|
||||
documentation. They generally go before section titles so that you can easily refer
|
||||
to them.
|
||||
|
||||
:::{tip}
|
||||
|
||||
If you'd like to *automatically* generate targets for each of your section headers,
|
||||
check out the [](syntax/header-anchors) section of extended syntaxes.
|
||||
|
||||
:::
|
||||
|
||||
Target headers are defined with this syntax:
|
||||
|
||||
```md
|
||||
(header_target)=
|
||||
```
|
||||
|
||||
They can then be referred to with the [ref inline role](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-ref):
|
||||
|
||||
```md
|
||||
{ref}`header_target`
|
||||
```
|
||||
|
||||
By default, the reference will use the text of the target (such as the section title), but also you can directly specify the text:
|
||||
|
||||
```md
|
||||
{ref}`my text <header_target>`
|
||||
```
|
||||
|
||||
For example, see this ref: {ref}`syntax/targets`, and here's a ref back to the top of this page: {ref}`my text <syntax/core>`.
|
||||
|
||||
Alternatively using the markdown syntax:
|
||||
|
||||
```md
|
||||
[my text](header_target)
|
||||
```
|
||||
|
||||
is equivalent to using the [any inline role](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-any):
|
||||
|
||||
```md
|
||||
{any}`my text <header_target>`
|
||||
```
|
||||
|
||||
but can also accept "nested" syntax (like bold text) and will recognise document paths that include extensions (e.g. `syntax/syntax` or `syntax/syntax.md`)
|
||||
|
||||
Using the same example, see this ref: [](syntax/targets), here is a reference back to the top of
|
||||
this page: [my text with **nested** $\alpha$ syntax](syntax/core), and here is a reference to another page (`[](../intro.md)`): [](../intro.md).
|
||||
|
||||
```{note}
|
||||
If you wish to have the target's title inserted into your text, you can
|
||||
leave the "text" section of the markdown link empty. For example, this
|
||||
markdown: `[](syntax.md)` will result in: [](syntax.md).
|
||||
```
|
||||
|
||||
(syntax/code-blocks)=
|
||||
## Code syntax highlighting
|
||||
|
||||
Code blocks contain a language identifier, which is used to determine the language of the code.
|
||||
This language is used to determine the syntax highlighting, using an available [pygments lexer](https://pygments.org/docs/lexers/).
|
||||
|
||||
````markdown
|
||||
```python
|
||||
from a import b
|
||||
c = "string"
|
||||
```
|
||||
````
|
||||
|
||||
```python
|
||||
from a import b
|
||||
c = "string"
|
||||
```
|
||||
|
||||
You can create and register your own lexer, using the [`pygments.lexers` entry point](https://pygments.org/docs/plugins/#register-plugins),
|
||||
or within a sphinx extension, with the [`app.add_lexer` method](sphinx:sphinx.application.Sphinx.add_lexer).
|
||||
|
||||
Using the `myst_number_code_blocks` configuration option, you can also control whether code blocks are numbered by line.
|
||||
For example, using `myst_number_code_blocks = ["typescript"]`:
|
||||
|
||||
```typescript
|
||||
type MyBool = true | false;
|
||||
|
||||
interface User {
|
||||
name: string;
|
||||
id: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Show backticks inside raw markdown blocks
|
||||
|
||||
If you'd like to show backticks inside of your markdown, you can do so by nesting them
|
||||
in backticks of a greater length. Markdown will treat the outer-most backticks as the
|
||||
edges of the "raw" block and everything inside will show up. For example:
|
||||
|
||||
``` `` `hi` `` ``` will be rendered as: `` `hi` ``
|
||||
|
||||
and
|
||||
|
||||
`````
|
||||
````
|
||||
```
|
||||
hi
|
||||
```
|
||||
````
|
||||
`````
|
||||
|
||||
will be rendered as:
|
||||
|
||||
````
|
||||
```
|
||||
hi
|
||||
```
|
||||
````
|
||||
|
||||
## Tables
|
||||
|
||||
Tables can be written using the standard [Github Flavoured Markdown syntax](https://github.github.com/gfm/#tables-extension-):
|
||||
|
||||
```md
|
||||
| foo | bar |
|
||||
| --- | --- |
|
||||
| baz | bim |
|
||||
```
|
||||
|
||||
| foo | bar |
|
||||
| --- | --- |
|
||||
| baz | bim |
|
||||
|
||||
Cells in a column can be aligned using the `:` character:
|
||||
|
||||
```md
|
||||
| left | center | right |
|
||||
| :--- | :----: | ----: |
|
||||
| a | b | c |
|
||||
```
|
||||
|
||||
| left | center | right |
|
||||
| :--- | :----: | ----: |
|
||||
| a | b | c |
|
||||
|
||||
:::{note}
|
||||
|
||||
Text is aligned by assigning `text-left`, `text-center`, or `text-right` to the cell.
|
||||
It is then necessary for the theme you are using to include the appropriate css styling.
|
||||
|
||||
```html
|
||||
<table class="colwidths-auto table">
|
||||
<thead>
|
||||
<tr><th class="text-left head"><p>left</p></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td class="text-left"><p>a</p></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Images
|
||||
|
||||
MyST provides a few different syntaxes for including images in your documentation.
|
||||
|
||||
The standard Markdown syntax is:
|
||||
|
||||
```md
|
||||
![fishy](img/fun-fish.png)
|
||||
```
|
||||
|
||||
![fishy](img/fun-fish.png)
|
||||
|
||||
But you can also enable extended image syntaxes, to control attributes like width and captions.
|
||||
See the [extended image syntax guide](syntax/images).
|
||||
|
||||
(syntax/footnotes)=
|
||||
## Footnotes
|
||||
|
||||
Footnotes use the [pandoc specification](https://pandoc.org/MANUAL.html#footnotes).
|
||||
Their labels **start with `^`** and can then be any alphanumeric string (no spaces), which is case-insensitive.
|
||||
|
||||
- If the label is an integer, then it will always use that integer for the rendered label (i.e. they are manually numbered).
|
||||
- For any other labels, they will be auto-numbered in the order which they are referenced, skipping any manually numbered labels.
|
||||
|
||||
All footnote definitions are collected, and displayed at the bottom of the page (in the order they are referenced).
|
||||
Note that un-referenced footnote definitions will not be displayed.
|
||||
|
||||
```md
|
||||
- This is a manually-numbered footnote reference.[^3]
|
||||
- This is an auto-numbered footnote reference.[^myref]
|
||||
|
||||
[^myref]: This is an auto-numbered footnote definition.
|
||||
[^3]: This is a manually-numbered footnote definition.
|
||||
```
|
||||
|
||||
- This is a manually-numbered footnote reference.[^3]
|
||||
- This is an auto-numbered footnote reference.[^myref]
|
||||
|
||||
[^myref]: This is an auto-numbered footnote definition.
|
||||
[^3]: This is a manually-numbered footnote definition.
|
||||
|
||||
Any preceding text after a footnote definitions, which is
|
||||
indented by four or more spaces, will also be included in the footnote definition, and the text is rendered as MyST Markdown, e.g.
|
||||
|
||||
```md
|
||||
A longer footnote definition.[^mylongdef]
|
||||
|
||||
[^mylongdef]: This is the _**footnote definition**_.
|
||||
|
||||
That continues for all indented lines
|
||||
|
||||
- even other block elements
|
||||
|
||||
Plus any preceding unindented lines,
|
||||
that are not separated by a blank line
|
||||
|
||||
This is not part of the footnote.
|
||||
```
|
||||
|
||||
A longer footnote definition.[^mylongdef]
|
||||
|
||||
[^mylongdef]: This is the _**footnote definition**_.
|
||||
|
||||
That continues for all indented lines
|
||||
|
||||
- even other block elements
|
||||
|
||||
Plus any subsequent unindented lines,
|
||||
that are not separated by a blank line
|
||||
|
||||
This is not part of the footnote.
|
||||
|
||||
````{important}
|
||||
Although footnote references can be used just fine within directives, e.g.[^myref],
|
||||
it is recommended that footnote definitions are not set within directives,
|
||||
unless they will only be referenced within that same directive:
|
||||
|
||||
```md
|
||||
[^other]
|
||||
|
||||
[^other]: A definition within a directive
|
||||
```
|
||||
|
||||
[^other]
|
||||
|
||||
[^other]: A definition within a directive
|
||||
|
||||
This is because, in the current implementation, they may not be available to reference in text above that particular directive.
|
||||
````
|
||||
|
||||
By default, a transition line (with a `footnotes` class) will be placed before any footnotes.
|
||||
This can be turned off by adding `myst_footnote_transition = False` to the config file.
|
|
@ -0,0 +1,2 @@
|
|||
[Used in how-to](docs/faq/index.md)
|
||||
![alt](docs/_static/logo-wide.svg)
|
|
@ -0,0 +1,10 @@
|
|||
"""An extended commonmark compliant parser, with bridges to docutils & sphinx."""
|
||||
__version__ = "0.18.1"
|
||||
|
||||
|
||||
def setup(app):
|
||||
"""Initialize the Sphinx extension."""
|
||||
from myst_parser.sphinx_ext.main import setup_sphinx
|
||||
|
||||
setup_sphinx(app, load_parser=True)
|
||||
return {"version": __version__, "parallel_read_safe": True}
|
|
@ -0,0 +1,11 @@
|
|||
"""Helpers for cross compatibility across dependency versions."""
|
||||
from typing import Callable, Iterable
|
||||
|
||||
from docutils.nodes import Element
|
||||
|
||||
|
||||
def findall(node: Element) -> Callable[..., Iterable[Element]]:
|
||||
"""Iterate through"""
|
||||
# findall replaces traverse in docutils v0.18
|
||||
# note a difference is that findall is an iterator
|
||||
return getattr(node, "findall", node.traverse)
|
|
@ -0,0 +1,198 @@
|
|||
"""Code to use internally, for documentation."""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from typing import Sequence, Union
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.frontend import OptionParser
|
||||
from docutils.parsers.rst import directives
|
||||
from sphinx.directives import other
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.docutils import SphinxDirective
|
||||
from typing_extensions import get_args, get_origin
|
||||
|
||||
from .config.main import MdParserConfig
|
||||
from .parsers.docutils_ import Parser as DocutilsParser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _ConfigBase(SphinxDirective):
|
||||
"""Directive to automate rendering of the configuration."""
|
||||
|
||||
@staticmethod
|
||||
def table_header():
|
||||
return [
|
||||
"```````{list-table}",
|
||||
":header-rows: 1",
|
||||
":widths: 15 10 20",
|
||||
"",
|
||||
"* - Name",
|
||||
" - Type",
|
||||
" - Description",
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def field_default(value):
|
||||
default = " ".join(f"{value!r}".splitlines())
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def field_type(field):
|
||||
ftypes: Sequence[str]
|
||||
if get_origin(field.type) is Union:
|
||||
ftypes = get_args(field.type)
|
||||
else:
|
||||
ftypes = [field.type]
|
||||
ctype = " | ".join(
|
||||
str("None" if ftype == type(None) else ftype) # type: ignore # noqa: E721
|
||||
for ftype in ftypes
|
||||
)
|
||||
ctype = " ".join(ctype.splitlines())
|
||||
ctype = ctype.replace("typing.", "")
|
||||
ctype = ctype.replace("typing_extensions.", "")
|
||||
for tname in ("str", "int", "float", "bool"):
|
||||
ctype = ctype.replace(f"<class '{tname}'>", tname)
|
||||
return ctype
|
||||
|
||||
|
||||
class MystConfigDirective(_ConfigBase):
|
||||
|
||||
option_spec = {
|
||||
"sphinx": directives.flag,
|
||||
"extensions": directives.flag,
|
||||
"scope": lambda x: directives.choice(x, ["global", "local"]),
|
||||
}
|
||||
|
||||
def run(self):
|
||||
"""Run the directive."""
|
||||
config = MdParserConfig()
|
||||
text = self.table_header()
|
||||
count = 0
|
||||
for name, value, field in config.as_triple():
|
||||
|
||||
# filter by sphinx options
|
||||
if "sphinx" in self.options and field.metadata.get("sphinx_exclude"):
|
||||
continue
|
||||
|
||||
if "extensions" in self.options:
|
||||
if not field.metadata.get("extension"):
|
||||
continue
|
||||
else:
|
||||
if field.metadata.get("extension"):
|
||||
continue
|
||||
|
||||
if self.options.get("scope") == "local":
|
||||
if field.metadata.get("global_only"):
|
||||
continue
|
||||
|
||||
if self.options.get("scope") == "global":
|
||||
name = f"myst_{name}"
|
||||
|
||||
description = " ".join(field.metadata.get("help", "").splitlines())
|
||||
if field.metadata.get("extension"):
|
||||
description = f"{field.metadata.get('extension')}: {description}"
|
||||
default = self.field_default(value)
|
||||
ctype = self.field_type(field)
|
||||
text.extend(
|
||||
[
|
||||
f"* - `{name}`",
|
||||
f" - `{ctype}`",
|
||||
f" - {description} (default: `{default}`)",
|
||||
]
|
||||
)
|
||||
|
||||
count += 1
|
||||
|
||||
if not count:
|
||||
return []
|
||||
|
||||
text.append("```````")
|
||||
node = nodes.Element()
|
||||
self.state.nested_parse(text, 0, node)
|
||||
return node.children
|
||||
|
||||
|
||||
class DocutilsCliHelpDirective(SphinxDirective):
|
||||
"""Directive to print the docutils CLI help."""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
|
||||
def run(self):
|
||||
"""Run the directive."""
|
||||
stream = io.StringIO()
|
||||
OptionParser(
|
||||
components=(DocutilsParser,),
|
||||
usage="myst-docutils-<writer> [options] [<source> [<destination>]]",
|
||||
).print_help(stream)
|
||||
return [nodes.literal_block("", stream.getvalue())]
|
||||
|
||||
|
||||
class DirectiveDoc(SphinxDirective):
|
||||
"""Load and document a directive."""
|
||||
|
||||
required_arguments = 1 # name of the directive
|
||||
has_content = True
|
||||
|
||||
def run(self):
|
||||
"""Run the directive."""
|
||||
name = self.arguments[0]
|
||||
# load the directive class
|
||||
klass, _ = directives.directive(
|
||||
name, self.state.memo.language, self.state.document
|
||||
)
|
||||
if klass is None:
|
||||
logger.warning(f"Directive {name} not found.", line=self.lineno)
|
||||
return []
|
||||
content = " ".join(self.content)
|
||||
text = f"""\
|
||||
:Name: `{name}`
|
||||
:Description: {content}
|
||||
:Arguments: {klass.required_arguments} required, {klass.optional_arguments} optional
|
||||
:Content: {'yes' if klass.has_content else 'no'}
|
||||
:Options:
|
||||
"""
|
||||
if klass.option_spec:
|
||||
text += " name | type\n -----|------\n"
|
||||
for key, func in klass.option_spec.items():
|
||||
text += f" {key} | {convert_opt(name, func)}\n"
|
||||
node = nodes.Element()
|
||||
self.state.nested_parse(text.splitlines(), 0, node)
|
||||
return node.children
|
||||
|
||||
|
||||
def convert_opt(name, func):
|
||||
"""Convert an option function to a string."""
|
||||
if func is directives.flag:
|
||||
return "flag"
|
||||
if func is directives.unchanged:
|
||||
return "text"
|
||||
if func is directives.unchanged_required:
|
||||
return "text"
|
||||
if func is directives.class_option:
|
||||
return "space-delimited list"
|
||||
if func is directives.uri:
|
||||
return "URI"
|
||||
if func is directives.path:
|
||||
return "path"
|
||||
if func is int:
|
||||
return "integer"
|
||||
if func is directives.positive_int:
|
||||
return "integer (positive)"
|
||||
if func is directives.nonnegative_int:
|
||||
return "integer (non-negative)"
|
||||
if func is directives.positive_int_list:
|
||||
return "space/comma-delimited list of integers (positive)"
|
||||
if func is directives.percentage:
|
||||
return "percentage"
|
||||
if func is directives.length_or_unitless:
|
||||
return "length or unitless"
|
||||
if func is directives.length_or_percentage_or_unitless:
|
||||
return "length, percentage or unitless"
|
||||
if func is other.int_or_nothing:
|
||||
return "integer"
|
||||
return ""
|
|
@ -0,0 +1,42 @@
|
|||
import argparse
|
||||
import sys
|
||||
|
||||
from markdown_it.renderer import RendererHTML
|
||||
|
||||
from myst_parser.config.main import MdParserConfig
|
||||
from myst_parser.parsers.mdit import create_md_parser
|
||||
|
||||
|
||||
def print_anchors(args=None):
|
||||
""" """
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument(
|
||||
"input",
|
||||
nargs="?",
|
||||
type=argparse.FileType("r", encoding="utf8"),
|
||||
default=sys.stdin,
|
||||
help="Input file (default stdin)",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
type=argparse.FileType("w", encoding="utf8"),
|
||||
default=sys.stdout,
|
||||
help="Output file (default stdout)",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"-l", "--level", type=int, default=2, help="Maximum heading level."
|
||||
)
|
||||
args = arg_parser.parse_args(args)
|
||||
parser = create_md_parser(MdParserConfig(heading_anchors=args.level), RendererHTML)
|
||||
|
||||
def _filter_plugin(state):
|
||||
state.tokens = [
|
||||
t
|
||||
for t in state.tokens
|
||||
if t.type.startswith("heading_") and int(t.tag[1]) <= args.level
|
||||
]
|
||||
|
||||
parser.use(lambda p: p.core.ruler.push("filter", _filter_plugin))
|
||||
text = parser.render(args.input.read())
|
||||
args.output.write(text)
|
|
@ -0,0 +1 @@
|
|||
"""This module holds the global configuration for the parser ``MdParserConfig``."""
|
|
@ -0,0 +1,161 @@
|
|||
"""Validators for dataclasses, mirroring those of https://github.com/python-attrs/attrs."""
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses as dc
|
||||
from typing import Any, Sequence
|
||||
|
||||
from typing_extensions import Protocol
|
||||
|
||||
|
||||
def validate_field(inst: Any, field: dc.Field, value: Any) -> None:
|
||||
"""Validate the field of a dataclass,
|
||||
according to a `validator` function set in the field.metadata.
|
||||
|
||||
The validator function should take as input (inst, field, value) and
|
||||
raise an exception if the value is invalid.
|
||||
"""
|
||||
if "validator" not in field.metadata:
|
||||
return
|
||||
if isinstance(field.metadata["validator"], list):
|
||||
for validator in field.metadata["validator"]:
|
||||
validator(inst, field, value)
|
||||
else:
|
||||
field.metadata["validator"](inst, field, value)
|
||||
|
||||
|
||||
def validate_fields(inst: Any) -> None:
|
||||
"""Validate the fields of a dataclass,
|
||||
according to `validator` functions set in the field metadata.
|
||||
|
||||
This function should be called in the `__post_init__` of the dataclass.
|
||||
|
||||
The validator function should take as input (inst, field, value) and
|
||||
raise an exception if the value is invalid.
|
||||
"""
|
||||
for field in dc.fields(inst):
|
||||
validate_field(inst, field, getattr(inst, field.name))
|
||||
|
||||
|
||||
class ValidatorType(Protocol):
|
||||
def __call__(
|
||||
self, inst: bytes, field: dc.Field, value: Any, suffix: str = ""
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
def instance_of(type: type[Any] | tuple[type[Any], ...]) -> ValidatorType:
|
||||
"""
|
||||
A validator that raises a `TypeError` if the initializer is called
|
||||
with a wrong type for this particular attribute (checks are performed using
|
||||
`isinstance` therefore it's also valid to pass a tuple of types).
|
||||
|
||||
:param type: The type to check for.
|
||||
"""
|
||||
|
||||
def _validator(inst, field, value, suffix=""):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if not isinstance(value, type):
|
||||
raise TypeError(
|
||||
f"'{field.name}{suffix}' must be of type {type!r} "
|
||||
f"(got {value!r} that is a {value.__class__!r})."
|
||||
)
|
||||
|
||||
return _validator
|
||||
|
||||
|
||||
def optional(validator: ValidatorType) -> ValidatorType:
|
||||
"""
|
||||
A validator that makes an attribute optional. An optional attribute is one
|
||||
which can be set to ``None`` in addition to satisfying the requirements of
|
||||
the sub-validator.
|
||||
"""
|
||||
|
||||
def _validator(inst, field, value, suffix=""):
|
||||
if value is None:
|
||||
return
|
||||
|
||||
validator(inst, field, value, suffix=suffix)
|
||||
|
||||
return _validator
|
||||
|
||||
|
||||
def is_callable(inst, field, value, suffix=""):
|
||||
"""
|
||||
A validator that raises a `TypeError` if the
|
||||
initializer is called with a value for this particular attribute
|
||||
that is not callable.
|
||||
"""
|
||||
if not callable(value):
|
||||
raise TypeError(
|
||||
f"'{field.name}{suffix}' must be callable "
|
||||
f"(got {value!r} that is a {value.__class__!r})."
|
||||
)
|
||||
|
||||
|
||||
def in_(options: Sequence) -> ValidatorType:
|
||||
"""
|
||||
A validator that raises a `ValueError` if the initializer is called
|
||||
with a value that does not belong in the options provided. The check is
|
||||
performed using ``value in options``.
|
||||
|
||||
:param options: Allowed options.
|
||||
"""
|
||||
|
||||
def _validator(inst, field, value, suffix=""):
|
||||
try:
|
||||
in_options = value in options
|
||||
except TypeError: # e.g. `1 in "abc"`
|
||||
in_options = False
|
||||
|
||||
if not in_options:
|
||||
raise ValueError(
|
||||
f"'{field.name}{suffix}' must be in {options!r} (got {value!r})"
|
||||
)
|
||||
|
||||
return _validator
|
||||
|
||||
|
||||
def deep_iterable(
|
||||
member_validator: ValidatorType, iterable_validator: ValidatorType | None = None
|
||||
) -> ValidatorType:
|
||||
"""
|
||||
A validator that performs deep validation of an iterable.
|
||||
|
||||
:param member_validator: Validator to apply to iterable members
|
||||
:param iterable_validator: Validator to apply to iterable itself
|
||||
"""
|
||||
|
||||
def _validator(inst, field, value, suffix=""):
|
||||
if iterable_validator is not None:
|
||||
iterable_validator(inst, field, value, suffix=suffix)
|
||||
|
||||
for idx, member in enumerate(value):
|
||||
member_validator(inst, field, member, suffix=f"{suffix}[{idx}]")
|
||||
|
||||
return _validator
|
||||
|
||||
|
||||
def deep_mapping(
|
||||
key_validator: ValidatorType,
|
||||
value_validator: ValidatorType,
|
||||
mapping_validator: ValidatorType | None = None,
|
||||
) -> ValidatorType:
|
||||
"""
|
||||
A validator that performs deep validation of a dictionary.
|
||||
|
||||
:param key_validator: Validator to apply to dictionary keys
|
||||
:param value_validator: Validator to apply to dictionary values
|
||||
:param mapping_validator: Validator to apply to top-level mapping attribute (optional)
|
||||
"""
|
||||
|
||||
def _validator(inst, field: dc.Field, value, suffix=""):
|
||||
if mapping_validator is not None:
|
||||
mapping_validator(inst, field, value)
|
||||
|
||||
for key in value:
|
||||
key_validator(inst, field, key, suffix=f"{suffix}[{key!r}]")
|
||||
value_validator(inst, field, value[key], suffix=f"{suffix}[{key!r}]")
|
||||
|
||||
return _validator
|
|
@ -0,0 +1,409 @@
|
|||
"""The configuration for the myst parser."""
|
||||
import dataclasses as dc
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from .dc_validators import (
|
||||
deep_iterable,
|
||||
deep_mapping,
|
||||
in_,
|
||||
instance_of,
|
||||
is_callable,
|
||||
optional,
|
||||
validate_field,
|
||||
validate_fields,
|
||||
)
|
||||
|
||||
|
||||
def check_extensions(_, __, value):
|
||||
if not isinstance(value, Iterable):
|
||||
raise TypeError(f"'enable_extensions' not iterable: {value}")
|
||||
diff = set(value).difference(
|
||||
[
|
||||
"amsmath",
|
||||
"attrs_image",
|
||||
"colon_fence",
|
||||
"deflist",
|
||||
"dollarmath",
|
||||
"fieldlist",
|
||||
"html_admonition",
|
||||
"html_image",
|
||||
"linkify",
|
||||
"replacements",
|
||||
"smartquotes",
|
||||
"strikethrough",
|
||||
"substitution",
|
||||
"tasklist",
|
||||
]
|
||||
)
|
||||
if diff:
|
||||
raise ValueError(f"'enable_extensions' items not recognised: {diff}")
|
||||
|
||||
|
||||
def check_sub_delimiters(_, __, value):
|
||||
if (not isinstance(value, (tuple, list))) or len(value) != 2:
|
||||
raise TypeError(f"myst_sub_delimiters is not a tuple of length 2: {value}")
|
||||
for delim in value:
|
||||
if (not isinstance(delim, str)) or len(delim) != 1:
|
||||
raise TypeError(
|
||||
f"myst_sub_delimiters does not contain strings of length 1: {value}"
|
||||
)
|
||||
|
||||
|
||||
@dc.dataclass()
|
||||
class MdParserConfig:
|
||||
"""Configuration options for the Markdown Parser.
|
||||
|
||||
Note in the sphinx configuration these option names are prepended with ``myst_``
|
||||
"""
|
||||
|
||||
# TODO replace commonmark_only, gfm_only with a single option
|
||||
|
||||
commonmark_only: bool = dc.field(
|
||||
default=False,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Use strict CommonMark parser",
|
||||
},
|
||||
)
|
||||
gfm_only: bool = dc.field(
|
||||
default=False,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Use strict Github Flavoured Markdown parser",
|
||||
},
|
||||
)
|
||||
|
||||
enable_extensions: Sequence[str] = dc.field(
|
||||
default_factory=list,
|
||||
metadata={"validator": check_extensions, "help": "Enable syntax extensions"},
|
||||
)
|
||||
|
||||
disable_syntax: Iterable[str] = dc.field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"validator": deep_iterable(instance_of(str), instance_of((list, tuple))),
|
||||
"help": "Disable Commonmark syntax elements",
|
||||
},
|
||||
)
|
||||
|
||||
all_links_external: bool = dc.field(
|
||||
default=False,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Parse all links as simple hyperlinks",
|
||||
},
|
||||
)
|
||||
|
||||
# see https://en.wikipedia.org/wiki/List_of_URI_schemes
|
||||
url_schemes: Optional[Iterable[str]] = dc.field(
|
||||
default=cast(Optional[Iterable[str]], ("http", "https", "mailto", "ftp")),
|
||||
metadata={
|
||||
"validator": optional(
|
||||
deep_iterable(instance_of(str), instance_of((list, tuple)))
|
||||
),
|
||||
"help": "URL scheme prefixes identified as external links",
|
||||
},
|
||||
)
|
||||
|
||||
ref_domains: Optional[Iterable[str]] = dc.field(
|
||||
default=None,
|
||||
metadata={
|
||||
"validator": optional(
|
||||
deep_iterable(instance_of(str), instance_of((list, tuple)))
|
||||
),
|
||||
"help": "Sphinx domain names to search in for link references",
|
||||
},
|
||||
)
|
||||
|
||||
highlight_code_blocks: bool = dc.field(
|
||||
default=True,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Syntax highlight code blocks with pygments",
|
||||
"docutils_only": True,
|
||||
},
|
||||
)
|
||||
|
||||
number_code_blocks: Sequence[str] = dc.field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"validator": deep_iterable(instance_of(str), instance_of((list, tuple))),
|
||||
"help": "Add line numbers to code blocks with these languages",
|
||||
},
|
||||
)
|
||||
|
||||
title_to_header: bool = dc.field(
|
||||
default=False,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Convert a `title` field in the top-matter to a H1 header",
|
||||
},
|
||||
)
|
||||
|
||||
heading_anchors: Optional[int] = dc.field(
|
||||
default=None,
|
||||
metadata={
|
||||
"validator": optional(in_([1, 2, 3, 4, 5, 6, 7])),
|
||||
"help": "Heading level depth to assign HTML anchors",
|
||||
},
|
||||
)
|
||||
|
||||
heading_slug_func: Optional[Callable[[str], str]] = dc.field(
|
||||
default=None,
|
||||
metadata={
|
||||
"validator": optional(is_callable),
|
||||
"help": "Function for creating heading anchors",
|
||||
"global_only": True,
|
||||
},
|
||||
)
|
||||
|
||||
html_meta: Dict[str, str] = dc.field(
|
||||
default_factory=dict,
|
||||
repr=False,
|
||||
metadata={
|
||||
"validator": deep_mapping(
|
||||
instance_of(str), instance_of(str), instance_of(dict)
|
||||
),
|
||||
"merge_topmatter": True,
|
||||
"help": "HTML meta tags",
|
||||
},
|
||||
)
|
||||
|
||||
footnote_transition: bool = dc.field(
|
||||
default=True,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Place a transition before any footnotes",
|
||||
},
|
||||
)
|
||||
|
||||
words_per_minute: int = dc.field(
|
||||
default=200,
|
||||
metadata={
|
||||
"validator": instance_of(int),
|
||||
"help": "For reading speed calculations",
|
||||
},
|
||||
)
|
||||
|
||||
# Extension specific
|
||||
|
||||
substitutions: Dict[str, Union[str, int, float]] = dc.field(
|
||||
default_factory=dict,
|
||||
repr=False,
|
||||
metadata={
|
||||
"validator": deep_mapping(
|
||||
instance_of(str), instance_of((str, int, float)), instance_of(dict)
|
||||
),
|
||||
"merge_topmatter": True,
|
||||
"help": "Substitutions mapping",
|
||||
"extension": "substitutions",
|
||||
},
|
||||
)
|
||||
|
||||
sub_delimiters: Tuple[str, str] = dc.field(
|
||||
default=("{", "}"),
|
||||
metadata={
|
||||
"validator": check_sub_delimiters,
|
||||
"help": "Substitution delimiters",
|
||||
"extension": "substitutions",
|
||||
},
|
||||
)
|
||||
|
||||
linkify_fuzzy_links: bool = dc.field(
|
||||
default=True,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Recognise URLs without schema prefixes",
|
||||
"extension": "linkify",
|
||||
},
|
||||
)
|
||||
|
||||
dmath_allow_labels: bool = dc.field(
|
||||
default=True,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Parse `$$...$$ (label)`",
|
||||
"extension": "dollarmath",
|
||||
},
|
||||
)
|
||||
dmath_allow_space: bool = dc.field(
|
||||
default=True,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Allow initial/final spaces in `$ ... $`",
|
||||
"extension": "dollarmath",
|
||||
},
|
||||
)
|
||||
dmath_allow_digits: bool = dc.field(
|
||||
default=True,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Allow initial/final digits `1$ ...$2`",
|
||||
"extension": "dollarmath",
|
||||
},
|
||||
)
|
||||
dmath_double_inline: bool = dc.field(
|
||||
default=False,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Parse inline `$$ ... $$`",
|
||||
"extension": "dollarmath",
|
||||
},
|
||||
)
|
||||
|
||||
update_mathjax: bool = dc.field(
|
||||
default=True,
|
||||
metadata={
|
||||
"validator": instance_of(bool),
|
||||
"help": "Update sphinx.ext.mathjax configuration to ignore `$` delimiters",
|
||||
"extension": "dollarmath",
|
||||
"global_only": True,
|
||||
},
|
||||
)
|
||||
|
||||
mathjax_classes: str = dc.field(
|
||||
default="tex2jax_process|mathjax_process|math|output_area",
|
||||
metadata={
|
||||
"validator": instance_of(str),
|
||||
"help": "MathJax classes to add to math HTML",
|
||||
"extension": "dollarmath",
|
||||
"global_only": True,
|
||||
},
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_fields(self)
|
||||
|
||||
def copy(self, **kwargs: Any) -> "MdParserConfig":
|
||||
"""Return a new object replacing specified fields with new values.
|
||||
|
||||
Note: initiating the copy will also validate the new fields.
|
||||
"""
|
||||
return dc.replace(self, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Tuple[dc.Field, ...]:
|
||||
"""Return all attribute fields in this class."""
|
||||
return dc.fields(cls)
|
||||
|
||||
def as_dict(self, dict_factory=dict) -> dict:
|
||||
"""Return a dictionary of field name -> value."""
|
||||
return dc.asdict(self, dict_factory=dict_factory)
|
||||
|
||||
def as_triple(self) -> Iterable[Tuple[str, Any, dc.Field]]:
|
||||
"""Yield triples of (name, value, field)."""
|
||||
fields = {f.name: f for f in dc.fields(self.__class__)}
|
||||
for name, value in dc.asdict(self).items():
|
||||
yield name, value, fields[name]
|
||||
|
||||
|
||||
def merge_file_level(
|
||||
config: MdParserConfig,
|
||||
topmatter: Dict[str, Any],
|
||||
warning: Callable[[str, str], None],
|
||||
) -> MdParserConfig:
|
||||
"""Merge the file-level topmatter with the global config.
|
||||
|
||||
:param config: Global config.
|
||||
:param topmatter: Topmatter from the file.
|
||||
:param warning: Function to call with a warning (type, message).
|
||||
:returns: A new config object
|
||||
"""
|
||||
# get updates
|
||||
updates: Dict[str, Any] = {}
|
||||
myst = topmatter.get("myst", {})
|
||||
if not isinstance(myst, dict):
|
||||
warning("topmatter", f"'myst' key not a dict: {type(myst)}")
|
||||
else:
|
||||
updates = myst
|
||||
|
||||
# allow html_meta and substitutions at top-level for back-compatibility
|
||||
if "html_meta" in topmatter:
|
||||
warning(
|
||||
"topmatter",
|
||||
"top-level 'html_meta' key is deprecated, "
|
||||
"place under 'myst' key instead",
|
||||
)
|
||||
updates["html_meta"] = topmatter["html_meta"]
|
||||
if "substitutions" in topmatter:
|
||||
warning(
|
||||
"topmatter",
|
||||
"top-level 'substitutions' key is deprecated, "
|
||||
"place under 'myst' key instead",
|
||||
)
|
||||
updates["substitutions"] = topmatter["substitutions"]
|
||||
|
||||
new = config.copy()
|
||||
|
||||
# validate each update
|
||||
fields = {name: (value, field) for name, value, field in config.as_triple()}
|
||||
for name, value in updates.items():
|
||||
|
||||
if name not in fields:
|
||||
warning("topmatter", f"Unknown field: {name}")
|
||||
continue
|
||||
|
||||
old_value, field = fields[name]
|
||||
|
||||
try:
|
||||
validate_field(new, field, value)
|
||||
except Exception as exc:
|
||||
warning("topmatter", str(exc))
|
||||
continue
|
||||
|
||||
if field.metadata.get("merge_topmatter"):
|
||||
value = {**old_value, **value}
|
||||
|
||||
setattr(new, name, value)
|
||||
|
||||
return new
|
||||
|
||||
|
||||
class TopmatterReadError(Exception):
|
||||
"""Topmatter parsing error."""
|
||||
|
||||
|
||||
def read_topmatter(text: Union[str, Iterator[str]]) -> Optional[Dict[str, Any]]:
|
||||
"""Read the (optional) YAML topmatter from a source string.
|
||||
|
||||
This is identified by the first line starting with `---`,
|
||||
then read up to a terminating line of `---`, or `...`.
|
||||
|
||||
:param source: The source string to read from
|
||||
:return: The topmatter
|
||||
"""
|
||||
import yaml
|
||||
|
||||
if isinstance(text, str):
|
||||
if not text.startswith("---"): # skip creating the line list in memory
|
||||
return None
|
||||
text = (line for line in text.splitlines())
|
||||
try:
|
||||
if not next(text).startswith("---"):
|
||||
return None
|
||||
except StopIteration:
|
||||
return None
|
||||
top_matter = []
|
||||
for line in text:
|
||||
if line.startswith("---") or line.startswith("..."):
|
||||
break
|
||||
top_matter.append(line.rstrip() + "\n")
|
||||
try:
|
||||
metadata = yaml.safe_load("".join(top_matter))
|
||||
assert isinstance(metadata, dict)
|
||||
except (yaml.parser.ParserError, yaml.scanner.ScannerError) as err:
|
||||
raise TopmatterReadError("Malformed YAML") from err
|
||||
if not isinstance(metadata, dict):
|
||||
raise TopmatterReadError(f"YAML is not a dict: {type(metadata)}")
|
||||
return metadata
|
|
@ -0,0 +1,6 @@
|
|||
"""A module for compatibility with the docutils>=0.17 `include` directive, in RST documents::
|
||||
|
||||
.. include:: path/to/file.md
|
||||
:parser: myst_parser.docutils_
|
||||
"""
|
||||
from myst_parser.parsers.docutils_ import Parser # noqa: F401
|
|
@ -0,0 +1 @@
|
|||
"""Conversion of Markdown-it tokens to docutils AST."""
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,139 @@
|
|||
"""Convert HTML to docutils nodes."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
from myst_parser.parsers.parse_html import Data, tokenize_html
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .base import DocutilsRenderer
|
||||
|
||||
|
||||
def make_error(
|
||||
document: nodes.document, error_msg: str, text: str, line_number: int
|
||||
) -> nodes.system_message:
|
||||
return document.reporter.error(
|
||||
error_msg,
|
||||
nodes.literal_block(text, text),
|
||||
line=line_number,
|
||||
)
|
||||
|
||||
|
||||
OPTION_KEYS_IMAGE = {"class", "alt", "height", "width", "align", "name"}
|
||||
# note: docutils also has scale and target
|
||||
|
||||
OPTION_KEYS_ADMONITION = {"class", "name"}
|
||||
|
||||
# See https://github.com/micromark/micromark-extension-gfm-tagfilter
|
||||
RE_FLOW = re.compile(
|
||||
r"<(\/?)(iframe|noembed|noframes|plaintext|script|style|title|textarea|xmp)(?=[\t\n\f\r />])",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def default_html(text: str, source: str, line_number: int) -> list[nodes.Element]:
|
||||
raw_html = nodes.raw("", text, format="html")
|
||||
raw_html.source = source
|
||||
raw_html.line = line_number
|
||||
return [raw_html]
|
||||
|
||||
|
||||
def html_to_nodes(
|
||||
text: str, line_number: int, renderer: DocutilsRenderer
|
||||
) -> list[nodes.Element]:
|
||||
"""Convert HTML to docutils nodes."""
|
||||
if renderer.md_config.gfm_only:
|
||||
text, _ = RE_FLOW.subn(lambda s: s.group(0).replace("<", "<"), text)
|
||||
|
||||
enable_html_img = "html_image" in renderer.md_config.enable_extensions
|
||||
enable_html_admonition = "html_admonition" in renderer.md_config.enable_extensions
|
||||
if not (enable_html_img or enable_html_admonition):
|
||||
return default_html(text, renderer.document["source"], line_number)
|
||||
|
||||
# parse the HTML to AST
|
||||
try:
|
||||
root = tokenize_html(text).strip(inplace=True, recurse=False)
|
||||
except Exception:
|
||||
msg_node = renderer.create_warning(
|
||||
"HTML could not be parsed", line=line_number, subtype="html"
|
||||
)
|
||||
return ([msg_node] if msg_node else []) + default_html(
|
||||
text, renderer.document["source"], line_number
|
||||
)
|
||||
|
||||
if len(root) < 1:
|
||||
# if empty
|
||||
return default_html(text, renderer.document["source"], line_number)
|
||||
|
||||
if not all(
|
||||
(enable_html_img and child.name == "img")
|
||||
or (
|
||||
enable_html_admonition
|
||||
and child.name == "div"
|
||||
and "admonition" in child.attrs.classes
|
||||
)
|
||||
for child in root
|
||||
):
|
||||
return default_html(text, renderer.document["source"], line_number)
|
||||
|
||||
nodes_list = []
|
||||
for child in root:
|
||||
|
||||
if child.name == "img":
|
||||
if "src" not in child.attrs:
|
||||
return [
|
||||
renderer.reporter.error(
|
||||
"<img> missing 'src' attribute", line=line_number
|
||||
)
|
||||
]
|
||||
content = "\n".join(
|
||||
f":{k}: {v}"
|
||||
for k, v in sorted(child.attrs.items())
|
||||
if k in OPTION_KEYS_IMAGE
|
||||
)
|
||||
nodes_list.extend(
|
||||
renderer.run_directive(
|
||||
"image", child.attrs["src"], content, line_number
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
children = child.strip().children
|
||||
if (
|
||||
children
|
||||
and children[0].name in ("div", "p")
|
||||
and (
|
||||
"title" in children[0].attrs.classes
|
||||
or "admonition-title" in children[0].attrs.classes
|
||||
)
|
||||
):
|
||||
title = "".join(child.render() for child in children.pop(0))
|
||||
else:
|
||||
title = "Note"
|
||||
|
||||
options = "\n".join(
|
||||
f":{k}: {v}"
|
||||
for k, v in sorted(child.attrs.items())
|
||||
if k in OPTION_KEYS_ADMONITION
|
||||
).rstrip()
|
||||
new_children = []
|
||||
for child in children:
|
||||
if child.name == "p":
|
||||
new_children.extend(child.children)
|
||||
new_children.append(Data("\n\n"))
|
||||
else:
|
||||
new_children.append(child)
|
||||
content = (
|
||||
options
|
||||
+ ("\n\n" if options else "")
|
||||
+ "".join(child.render() for child in new_children).lstrip()
|
||||
)
|
||||
|
||||
nodes_list.extend(
|
||||
renderer.run_directive("admonition", title, content, line_number)
|
||||
)
|
||||
|
||||
return nodes_list
|
|
@ -0,0 +1,245 @@
|
|||
"""Convert Markdown-it tokens to docutils nodes, including sphinx specific elements."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
from urllib.parse import unquote
|
||||
from uuid import uuid4
|
||||
|
||||
from docutils import nodes
|
||||
from markdown_it.tree import SyntaxTreeNode
|
||||
from sphinx import addnodes
|
||||
from sphinx.domains.math import MathDomain
|
||||
from sphinx.domains.std import StandardDomain
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.nodes import clean_astext
|
||||
|
||||
from myst_parser.mdit_to_docutils.base import DocutilsRenderer
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_warning(
|
||||
document: nodes.document,
|
||||
message: str,
|
||||
*,
|
||||
line: int | None = None,
|
||||
append_to: nodes.Element | None = None,
|
||||
wtype: str = "myst",
|
||||
subtype: str = "other",
|
||||
) -> nodes.system_message | None:
|
||||
"""Generate a warning, logging it if necessary.
|
||||
|
||||
If the warning type is listed in the ``suppress_warnings`` configuration,
|
||||
then ``None`` will be returned and no warning logged.
|
||||
"""
|
||||
message = f"{message} [{wtype}.{subtype}]"
|
||||
kwargs = {"line": line} if line is not None else {}
|
||||
|
||||
if logging.is_suppressed_warning(
|
||||
wtype, subtype, document.settings.env.app.config.suppress_warnings
|
||||
):
|
||||
return None
|
||||
|
||||
msg_node = document.reporter.warning(message, **kwargs)
|
||||
if append_to is not None:
|
||||
append_to.append(msg_node)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class SphinxRenderer(DocutilsRenderer):
|
||||
"""A markdown-it-py renderer to populate (in-place) a `docutils.document` AST.
|
||||
|
||||
This is sub-class of `DocutilsRenderer` that handles sphinx specific aspects,
|
||||
such as cross-referencing.
|
||||
"""
|
||||
|
||||
@property
|
||||
def doc_env(self) -> BuildEnvironment:
|
||||
return self.document.settings.env
|
||||
|
||||
def create_warning(
|
||||
self,
|
||||
message: str,
|
||||
*,
|
||||
line: int | None = None,
|
||||
append_to: nodes.Element | None = None,
|
||||
wtype: str = "myst",
|
||||
subtype: str = "other",
|
||||
) -> nodes.system_message | None:
|
||||
"""Generate a warning, logging it if necessary.
|
||||
|
||||
If the warning type is listed in the ``suppress_warnings`` configuration,
|
||||
then ``None`` will be returned and no warning logged.
|
||||
"""
|
||||
return create_warning(
|
||||
self.document,
|
||||
message,
|
||||
line=line,
|
||||
append_to=append_to,
|
||||
wtype=wtype,
|
||||
subtype=subtype,
|
||||
)
|
||||
|
||||
def render_internal_link(self, token: SyntaxTreeNode) -> None:
|
||||
"""Render link token `[text](link "title")`,
|
||||
where the link has not been identified as an external URL.
|
||||
"""
|
||||
destination = unquote(cast(str, token.attrGet("href") or ""))
|
||||
|
||||
# make the path relative to an "including" document
|
||||
# this is set when using the `relative-docs` option of the MyST `include` directive
|
||||
relative_include = self.md_env.get("relative-docs", None)
|
||||
if relative_include is not None and destination.startswith(relative_include[0]):
|
||||
source_dir, include_dir = relative_include[1:]
|
||||
destination = os.path.relpath(
|
||||
os.path.join(include_dir, os.path.normpath(destination)), source_dir
|
||||
)
|
||||
|
||||
potential_path = (
|
||||
Path(self.doc_env.doc2path(self.doc_env.docname)).parent / destination
|
||||
if self.doc_env.srcdir # not set in some test situations
|
||||
else None
|
||||
)
|
||||
if (
|
||||
potential_path
|
||||
and potential_path.is_file()
|
||||
and not any(
|
||||
destination.endswith(suffix)
|
||||
for suffix in self.doc_env.config.source_suffix
|
||||
)
|
||||
):
|
||||
wrap_node = addnodes.download_reference(
|
||||
refdoc=self.doc_env.docname,
|
||||
reftarget=destination,
|
||||
reftype="myst",
|
||||
refdomain=None, # Added to enable cross-linking
|
||||
refexplicit=len(token.children or []) > 0,
|
||||
refwarn=False,
|
||||
)
|
||||
classes = ["xref", "download", "myst"]
|
||||
text = destination if not token.children else ""
|
||||
else:
|
||||
wrap_node = addnodes.pending_xref(
|
||||
refdoc=self.doc_env.docname,
|
||||
reftarget=destination,
|
||||
reftype="myst",
|
||||
refdomain=None, # Added to enable cross-linking
|
||||
refexplicit=len(token.children or []) > 0,
|
||||
refwarn=True,
|
||||
)
|
||||
classes = ["xref", "myst"]
|
||||
text = ""
|
||||
|
||||
self.add_line_and_source_path(wrap_node, token)
|
||||
title = token.attrGet("title")
|
||||
if title:
|
||||
wrap_node["title"] = title
|
||||
self.current_node.append(wrap_node)
|
||||
|
||||
inner_node = nodes.inline("", text, classes=classes)
|
||||
wrap_node.append(inner_node)
|
||||
with self.current_node_context(inner_node):
|
||||
self.render_children(token)
|
||||
|
||||
def render_heading(self, token: SyntaxTreeNode) -> None:
|
||||
"""This extends the docutils method, to allow for the addition of heading ids.
|
||||
These ids are computed by the ``markdown-it-py`` ``anchors_plugin``
|
||||
as "slugs" which are unique to a document.
|
||||
|
||||
The approach is similar to ``sphinx.ext.autosectionlabel``
|
||||
"""
|
||||
super().render_heading(token)
|
||||
|
||||
if not isinstance(self.current_node, nodes.section):
|
||||
return
|
||||
|
||||
# create the slug string
|
||||
slug = cast(str, token.attrGet("id"))
|
||||
if slug is None:
|
||||
return
|
||||
|
||||
section = self.current_node
|
||||
doc_slug = self.doc_env.doc2path(self.doc_env.docname, base=False) + "#" + slug
|
||||
|
||||
# save the reference in the standard domain, so that it can be handled properly
|
||||
domain = cast(StandardDomain, self.doc_env.get_domain("std"))
|
||||
if doc_slug in domain.labels:
|
||||
other_doc = self.doc_env.doc2path(domain.labels[doc_slug][0])
|
||||
self.create_warning(
|
||||
f"duplicate label {doc_slug}, other instance in {other_doc}",
|
||||
line=section.line,
|
||||
subtype="anchor",
|
||||
)
|
||||
labelid = section["ids"][0]
|
||||
domain.anonlabels[doc_slug] = self.doc_env.docname, labelid
|
||||
domain.labels[doc_slug] = (
|
||||
self.doc_env.docname,
|
||||
labelid,
|
||||
clean_astext(section[0]),
|
||||
)
|
||||
|
||||
self.doc_env.metadata[self.doc_env.docname]["myst_anchors"] = True
|
||||
section["myst-anchor"] = doc_slug
|
||||
|
||||
def render_math_block_label(self, token: SyntaxTreeNode) -> None:
|
||||
"""Render math with referencable labels, e.g. ``$a=1$ (label)``."""
|
||||
label = token.info
|
||||
content = token.content
|
||||
node = nodes.math_block(
|
||||
content, content, nowrap=False, number=None, label=label
|
||||
)
|
||||
target = self.add_math_target(node)
|
||||
self.add_line_and_source_path(target, token)
|
||||
self.current_node.append(target)
|
||||
self.add_line_and_source_path(node, token)
|
||||
self.current_node.append(node)
|
||||
|
||||
def _random_label(self) -> str:
|
||||
return str(uuid4())
|
||||
|
||||
def render_amsmath(self, token: SyntaxTreeNode) -> None:
|
||||
"""Renderer for the amsmath extension."""
|
||||
# environment = token.meta["environment"]
|
||||
content = token.content
|
||||
|
||||
if token.meta["numbered"] != "*":
|
||||
# TODO how to parse and reference labels within environment?
|
||||
# for now we give create a unique hash, so the equation will be numbered
|
||||
# but there will be no reference clashes
|
||||
label = self._random_label()
|
||||
node = nodes.math_block(
|
||||
content,
|
||||
content,
|
||||
nowrap=True,
|
||||
number=None,
|
||||
classes=["amsmath"],
|
||||
label=label,
|
||||
)
|
||||
target = self.add_math_target(node)
|
||||
self.add_line_and_source_path(target, token)
|
||||
self.current_node.append(target)
|
||||
else:
|
||||
node = nodes.math_block(
|
||||
content, content, nowrap=True, number=None, classes=["amsmath"]
|
||||
)
|
||||
self.add_line_and_source_path(node, token)
|
||||
self.current_node.append(node)
|
||||
|
||||
def add_math_target(self, node: nodes.math_block) -> nodes.target:
|
||||
# Code mainly copied from sphinx.directives.patches.MathDirective
|
||||
|
||||
# register label to domain
|
||||
domain = cast(MathDomain, self.doc_env.get_domain("math"))
|
||||
domain.note_equation(self.doc_env.docname, node["label"], location=node)
|
||||
node["number"] = domain.get_equation_number_for(node["label"])
|
||||
node["docname"] = self.doc_env.docname
|
||||
|
||||
# create target node
|
||||
node_id = nodes.make_id("equation-%s" % node["label"])
|
||||
target = nodes.target("", "", ids=[node_id])
|
||||
self.document.note_explicit_target(target)
|
||||
return target
|
|
@ -0,0 +1,36 @@
|
|||
import html
|
||||
from typing import Iterable, Optional
|
||||
from urllib.parse import quote, urlparse
|
||||
|
||||
|
||||
def escape_url(raw: str) -> str:
|
||||
"""
|
||||
Escape urls to prevent code injection craziness. (Hopefully.)
|
||||
"""
|
||||
return html.escape(quote(html.unescape(raw), safe="/#:()*?=%@+,&"))
|
||||
|
||||
|
||||
def is_external_url(
|
||||
reference: str,
|
||||
known_url_schemes: Optional[Iterable[str]],
|
||||
match_fragment: bool = False,
|
||||
) -> bool:
|
||||
"""Return if a reference should be recognised as an external URL.
|
||||
|
||||
URLs are of the format: scheme://netloc/path;parameters?query#fragment
|
||||
|
||||
This checks if there is a url scheme (e.g. 'https') and, if so,
|
||||
if the scheme is is the list of known_url_schemes (if supplied).
|
||||
|
||||
:param known_url_schemes: e.g. ["http", "https", "mailto"]
|
||||
If None, match all schemes
|
||||
:param match_fragment: If True and a fragment found, then True will be returned,
|
||||
irrespective of a scheme match
|
||||
|
||||
"""
|
||||
url_check = urlparse(reference)
|
||||
if known_url_schemes is not None:
|
||||
scheme_known = url_check.scheme in known_url_schemes
|
||||
else:
|
||||
scheme_known = bool(url_check.scheme)
|
||||
return scheme_known or (match_fragment and url_check.fragment != "")
|
|
@ -0,0 +1,514 @@
|
|||
"""This module provides classes to Mock the core components of the docutils.RSTParser,
|
||||
the key difference being that nested parsing treats the text as Markdown not rST.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive, DirectiveError
|
||||
from docutils.parsers.rst import Parser as RSTParser
|
||||
from docutils.parsers.rst.directives.misc import Include
|
||||
from docutils.parsers.rst.states import Body, Inliner, RSTStateMachine
|
||||
from docutils.statemachine import StringList
|
||||
from docutils.utils import unescape
|
||||
|
||||
from .parsers.directives import parse_directive_text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .mdit_to_docutils.base import DocutilsRenderer
|
||||
|
||||
|
||||
class MockingError(Exception):
|
||||
"""An exception to signal an error during mocking of docutils components."""
|
||||
|
||||
|
||||
class MockInliner:
|
||||
"""A mock version of `docutils.parsers.rst.states.Inliner`.
|
||||
|
||||
This is parsed to role functions.
|
||||
"""
|
||||
|
||||
def __init__(self, renderer: DocutilsRenderer):
|
||||
"""Initialize the mock inliner."""
|
||||
self._renderer = renderer
|
||||
# here we mock that the `parse` method has already been called
|
||||
# which is where these attributes are set (via the RST state Memo)
|
||||
self.document = renderer.document
|
||||
self.reporter = renderer.document.reporter
|
||||
self.language = renderer.language_module_rst
|
||||
self.parent = renderer.current_node
|
||||
|
||||
if not hasattr(self.reporter, "get_source_and_line"):
|
||||
# In docutils this is set by `RSTState.runtime_init`
|
||||
self.reporter.get_source_and_line = lambda l: (self.document["source"], l)
|
||||
|
||||
self.rfc_url = "rfc%d.html"
|
||||
|
||||
def problematic(
|
||||
self, text: str, rawsource: str, message: nodes.system_message
|
||||
) -> nodes.problematic:
|
||||
"""Record a system message from parsing."""
|
||||
msgid = self.document.set_id(message, self.parent)
|
||||
problematic = nodes.problematic(rawsource, text, refid=msgid)
|
||||
prbid = self.document.set_id(problematic)
|
||||
message.add_backref(prbid)
|
||||
return problematic
|
||||
|
||||
def parse(
|
||||
self, text: str, lineno: int, memo: Any, parent: nodes.Node
|
||||
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
|
||||
"""Parse the text and return a list of nodes."""
|
||||
# note the only place this is normally called,
|
||||
# is by `RSTState.inline_text`, or in directives: `self.state.inline_text`,
|
||||
# and there the state parses its own parent
|
||||
# self.reporter = memo.reporter
|
||||
# self.document = memo.document
|
||||
# self.language = memo.language
|
||||
with self._renderer.current_node_context(parent):
|
||||
# the parent is never actually appended to though,
|
||||
# so we make a temporary parent to parse into
|
||||
container = nodes.Element()
|
||||
with self._renderer.current_node_context(container):
|
||||
self._renderer.nested_render_text(text, lineno, inline=True)
|
||||
|
||||
return container.children, []
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
"""This method is only be called if the attribute requested has not
|
||||
been defined. Defined attributes will not be overridden.
|
||||
"""
|
||||
# TODO use document.reporter mechanism?
|
||||
if hasattr(Inliner, name):
|
||||
msg = "{cls} has not yet implemented attribute '{name}'".format(
|
||||
cls=type(self).__name__, name=name
|
||||
)
|
||||
raise MockingError(msg).with_traceback(sys.exc_info()[2])
|
||||
msg = f"{type(self).__name__} has no attribute {name}"
|
||||
raise MockingError(msg).with_traceback(sys.exc_info()[2])
|
||||
|
||||
|
||||
class MockState:
|
||||
"""A mock version of `docutils.parsers.rst.states.RSTState`.
|
||||
|
||||
This is parsed to the `Directives.run()` method,
|
||||
so that they may run nested parses on their content that will be parsed as markdown,
|
||||
rather than RST.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderer: DocutilsRenderer,
|
||||
state_machine: MockStateMachine,
|
||||
lineno: int,
|
||||
):
|
||||
self._renderer = renderer
|
||||
self._lineno = lineno
|
||||
self.document = renderer.document
|
||||
self.reporter = renderer.document.reporter
|
||||
self.state_machine = state_machine
|
||||
self.inliner = MockInliner(renderer)
|
||||
|
||||
class Struct:
|
||||
document = self.document
|
||||
reporter = self.document.reporter
|
||||
language = renderer.language_module_rst
|
||||
title_styles: list[str] = []
|
||||
section_level = max(renderer._level_to_elem)
|
||||
section_bubble_up_kludge = False
|
||||
inliner = self.inliner
|
||||
|
||||
self.memo = Struct
|
||||
|
||||
def parse_directive_block(
|
||||
self,
|
||||
content: StringList,
|
||||
line_offset: int,
|
||||
directive: type[Directive],
|
||||
option_presets: dict,
|
||||
) -> tuple[list, dict, StringList, int]:
|
||||
"""Parse the full directive text
|
||||
|
||||
:returns: (arguments, options, content, content_offset)
|
||||
"""
|
||||
if option_presets:
|
||||
raise MockingError("parse_directive_block: option_presets not implemented")
|
||||
# TODO should argument_str always be ""?
|
||||
arguments, options, body_lines, content_offset = parse_directive_text(
|
||||
directive, "", "\n".join(content)
|
||||
)
|
||||
return (
|
||||
arguments,
|
||||
options,
|
||||
StringList(body_lines, source=content.source),
|
||||
line_offset + content_offset,
|
||||
)
|
||||
|
||||
def nested_parse(
|
||||
self,
|
||||
block: StringList,
|
||||
input_offset: int,
|
||||
node: nodes.Element,
|
||||
match_titles: bool = False,
|
||||
state_machine_class=None,
|
||||
state_machine_kwargs=None,
|
||||
) -> None:
|
||||
"""Perform a nested parse of the input block, with ``node`` as the parent.
|
||||
|
||||
:param block: The block of lines to parse.
|
||||
:param input_offset: The offset of the first line of block,
|
||||
to the starting line of the state (i.e. directive).
|
||||
:param node: The parent node to attach the parsed content to.
|
||||
:param match_titles: Whether to to allow the parsing of headings
|
||||
(normally this is false,
|
||||
since nested heading would break the document structure)
|
||||
"""
|
||||
sm_match_titles = self.state_machine.match_titles
|
||||
with self._renderer.current_node_context(node):
|
||||
self._renderer.nested_render_text(
|
||||
"\n".join(block),
|
||||
self._lineno + input_offset,
|
||||
allow_headings=match_titles,
|
||||
)
|
||||
self.state_machine.match_titles = sm_match_titles
|
||||
|
||||
def parse_target(self, block, block_text, lineno: int):
|
||||
"""
|
||||
Taken from https://github.com/docutils-mirror/docutils/blob/e88c5fb08d5cdfa8b4ac1020dd6f7177778d5990/docutils/parsers/rst/states.py#L1927 # noqa: E501
|
||||
"""
|
||||
# Commenting out this code because it only applies to rST
|
||||
# if block and block[-1].strip()[-1:] == "_": # possible indirect target
|
||||
# reference = " ".join([line.strip() for line in block])
|
||||
# refname = self.is_reference(reference)
|
||||
# if refname:
|
||||
# return "refname", refname
|
||||
reference = "".join(["".join(line.split()) for line in block])
|
||||
return "refuri", unescape(reference)
|
||||
|
||||
def inline_text(
|
||||
self, text: str, lineno: int
|
||||
) -> tuple[list[nodes.Element], list[nodes.Element]]:
|
||||
"""Parse text with only inline rules.
|
||||
|
||||
:returns: (list of nodes, list of messages)
|
||||
"""
|
||||
return self.inliner.parse(text, lineno, self.memo, self._renderer.current_node)
|
||||
|
||||
# U+2014 is an em-dash:
|
||||
attribution_pattern = re.compile("^((?:---?(?!-)|\u2014) *)(.+)")
|
||||
|
||||
def block_quote(self, lines: list[str], line_offset: int) -> list[nodes.Element]:
|
||||
"""Parse a block quote, which is a block of text,
|
||||
followed by an (optional) attribution.
|
||||
|
||||
::
|
||||
|
||||
No matter where you go, there you are.
|
||||
|
||||
-- Buckaroo Banzai
|
||||
"""
|
||||
elements = []
|
||||
# split attribution
|
||||
last_line_blank = False
|
||||
blockquote_lines = lines
|
||||
attribution_lines = []
|
||||
attribution_line_offset = None
|
||||
# First line after a blank line must begin with a dash
|
||||
for i, line in enumerate(lines):
|
||||
if not line.strip():
|
||||
last_line_blank = True
|
||||
continue
|
||||
if not last_line_blank:
|
||||
last_line_blank = False
|
||||
continue
|
||||
last_line_blank = False
|
||||
match = self.attribution_pattern.match(line)
|
||||
if not match:
|
||||
continue
|
||||
attribution_line_offset = i
|
||||
attribution_lines = [match.group(2)]
|
||||
for at_line in lines[i + 1 :]:
|
||||
indented_line = at_line[len(match.group(1)) :]
|
||||
if len(indented_line) != len(at_line.lstrip()):
|
||||
break
|
||||
attribution_lines.append(indented_line)
|
||||
blockquote_lines = lines[:i]
|
||||
break
|
||||
# parse block
|
||||
blockquote = nodes.block_quote()
|
||||
self.nested_parse(blockquote_lines, line_offset, blockquote)
|
||||
elements.append(blockquote)
|
||||
# parse attribution
|
||||
if attribution_lines:
|
||||
attribution_text = "\n".join(attribution_lines)
|
||||
lineno = self._lineno + line_offset + (attribution_line_offset or 0)
|
||||
textnodes, messages = self.inline_text(attribution_text, lineno)
|
||||
attribution = nodes.attribution(attribution_text, "", *textnodes)
|
||||
(
|
||||
attribution.source,
|
||||
attribution.line,
|
||||
) = self.state_machine.get_source_and_line(lineno)
|
||||
blockquote += attribution
|
||||
elements += messages
|
||||
return elements
|
||||
|
||||
def build_table(self, tabledata, tableline, stub_columns: int = 0, widths=None):
|
||||
return Body.build_table(self, tabledata, tableline, stub_columns, widths)
|
||||
|
||||
def build_table_row(self, rowdata, tableline):
|
||||
return Body.build_table_row(self, rowdata, tableline)
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
"""This method is only be called if the attribute requested has not
|
||||
been defined. Defined attributes will not be overridden.
|
||||
"""
|
||||
cls = type(self).__name__
|
||||
if hasattr(Body, name):
|
||||
msg = (
|
||||
f"{cls} has not yet implemented attribute '{name}'. "
|
||||
"You can parse RST directly via the `{eval-rst}` directive: "
|
||||
"https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#how-directives-parse-content" # noqa: E501
|
||||
)
|
||||
else:
|
||||
# The requested `name` is not a docutils Body element
|
||||
# (such as "footnote", "block_quote", "paragraph", …)
|
||||
msg = f"{cls} has no attribute '{name}'"
|
||||
raise MockingError(msg).with_traceback(sys.exc_info()[2])
|
||||
|
||||
|
||||
class MockStateMachine:
|
||||
"""A mock version of `docutils.parsers.rst.states.RSTStateMachine`.
|
||||
|
||||
This is parsed to the `Directives.run()` method.
|
||||
"""
|
||||
|
||||
def __init__(self, renderer: DocutilsRenderer, lineno: int):
|
||||
self._renderer = renderer
|
||||
self._lineno = lineno
|
||||
self.document = renderer.document
|
||||
self.language = renderer.language_module_rst
|
||||
self.reporter = self.document.reporter
|
||||
self.node: nodes.Element = renderer.current_node
|
||||
self.match_titles: bool = True
|
||||
|
||||
def get_source(self, lineno: int | None = None):
|
||||
"""Return document source path."""
|
||||
return self.document["source"]
|
||||
|
||||
def get_source_and_line(self, lineno: int | None = None):
|
||||
"""Return (source path, line) tuple for current or given line number."""
|
||||
return self.document["source"], lineno or self._lineno
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
"""This method is only be called if the attribute requested has not
|
||||
been defined. Defined attributes will not be overridden.
|
||||
"""
|
||||
if hasattr(RSTStateMachine, name):
|
||||
msg = "{cls} has not yet implemented attribute '{name}'".format(
|
||||
cls=type(self).__name__, name=name
|
||||
)
|
||||
raise MockingError(msg).with_traceback(sys.exc_info()[2])
|
||||
msg = f"{type(self).__name__} has no attribute {name}"
|
||||
raise MockingError(msg).with_traceback(sys.exc_info()[2])
|
||||
|
||||
|
||||
class MockIncludeDirective:
|
||||
"""This directive uses a lot of statemachine logic that is not yet mocked.
|
||||
Therefore, we treat it as a special case (at least for now).
|
||||
|
||||
See:
|
||||
https://docutils.sourceforge.io/docs/ref/rst/directives.html#including-an-external-document-fragment
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderer: DocutilsRenderer,
|
||||
name: str,
|
||||
klass: Include,
|
||||
arguments: list,
|
||||
options: dict,
|
||||
body: list[str],
|
||||
lineno: int,
|
||||
):
|
||||
self.renderer = renderer
|
||||
self.document = renderer.document
|
||||
self.name = name
|
||||
self.klass = klass
|
||||
self.arguments = arguments
|
||||
self.options = options
|
||||
self.body = body
|
||||
self.lineno = lineno
|
||||
|
||||
def run(self) -> list[nodes.Element]:
|
||||
|
||||
from docutils.parsers.rst.directives.body import CodeBlock, NumberLines
|
||||
|
||||
if not self.document.settings.file_insertion_enabled:
|
||||
raise DirectiveError(2, f'Directive "{self.name}" disabled.')
|
||||
|
||||
source_dir = Path(self.document["source"]).absolute().parent
|
||||
include_arg = "".join([s.strip() for s in self.arguments[0].splitlines()])
|
||||
|
||||
if include_arg.startswith("<") and include_arg.endswith(">"):
|
||||
# # docutils "standard" includes
|
||||
path = Path(self.klass.standard_include_path).joinpath(include_arg[1:-1])
|
||||
else:
|
||||
# if using sphinx interpret absolute paths "correctly",
|
||||
# i.e. relative to source directory
|
||||
try:
|
||||
sphinx_env = self.document.settings.env
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
_, include_arg = sphinx_env.relfn2path(self.arguments[0])
|
||||
sphinx_env.note_included(include_arg)
|
||||
path = Path(include_arg)
|
||||
path = source_dir.joinpath(path)
|
||||
# this ensures that the parent file is rebuilt if the included file changes
|
||||
self.document.settings.record_dependencies.add(str(path))
|
||||
|
||||
# read file
|
||||
encoding = self.options.get("encoding", self.document.settings.input_encoding)
|
||||
error_handler = self.document.settings.input_encoding_error_handler
|
||||
# tab_width = self.options.get("tab-width", self.document.settings.tab_width)
|
||||
try:
|
||||
file_content = path.read_text(encoding=encoding, errors=error_handler)
|
||||
except Exception as error:
|
||||
raise DirectiveError(
|
||||
4,
|
||||
'Directive "{}": error reading file: {}\n{}.'.format(
|
||||
self.name, path, error
|
||||
),
|
||||
)
|
||||
|
||||
# get required section of text
|
||||
startline = self.options.get("start-line", None)
|
||||
endline = self.options.get("end-line", None)
|
||||
file_content = "\n".join(file_content.splitlines()[startline:endline])
|
||||
startline = startline or 0
|
||||
for split_on_type in ["start-after", "end-before"]:
|
||||
split_on = self.options.get(split_on_type, None)
|
||||
if not split_on:
|
||||
continue
|
||||
split_index = file_content.find(split_on)
|
||||
if split_index < 0:
|
||||
raise DirectiveError(
|
||||
4,
|
||||
'Directive "{}"; option "{}": text not found "{}".'.format(
|
||||
self.name, split_on_type, split_on
|
||||
),
|
||||
)
|
||||
if split_on_type == "start-after":
|
||||
startline += split_index + len(split_on)
|
||||
file_content = file_content[split_index + len(split_on) :]
|
||||
else:
|
||||
file_content = file_content[:split_index]
|
||||
|
||||
if "literal" in self.options:
|
||||
literal_block = nodes.literal_block(
|
||||
file_content, source=str(path), classes=self.options.get("class", [])
|
||||
)
|
||||
literal_block.line = 1 # TODO don;t think this should be 1?
|
||||
self.add_name(literal_block)
|
||||
if "number-lines" in self.options:
|
||||
try:
|
||||
startline = int(self.options["number-lines"] or 1)
|
||||
except ValueError:
|
||||
raise DirectiveError(
|
||||
3, ":number-lines: with non-integer " "start value"
|
||||
)
|
||||
endline = startline + len(file_content.splitlines())
|
||||
if file_content.endswith("\n"):
|
||||
file_content = file_content[:-1]
|
||||
tokens = NumberLines([([], file_content)], startline, endline)
|
||||
for classes, value in tokens:
|
||||
if classes:
|
||||
literal_block += nodes.inline(value, value, classes=classes)
|
||||
else:
|
||||
literal_block += nodes.Text(value)
|
||||
else:
|
||||
literal_block += nodes.Text(file_content)
|
||||
return [literal_block]
|
||||
if "code" in self.options:
|
||||
self.options["source"] = str(path)
|
||||
state_machine = MockStateMachine(self.renderer, self.lineno)
|
||||
state = MockState(self.renderer, state_machine, self.lineno)
|
||||
codeblock = CodeBlock(
|
||||
name=self.name,
|
||||
arguments=[self.options.pop("code")],
|
||||
options=self.options,
|
||||
content=file_content.splitlines(),
|
||||
lineno=self.lineno,
|
||||
content_offset=0,
|
||||
block_text=file_content,
|
||||
state=state,
|
||||
state_machine=state_machine,
|
||||
)
|
||||
return codeblock.run()
|
||||
|
||||
# Here we perform a nested render, but temporarily setup the document/reporter
|
||||
# with the correct document path and lineno for the included file.
|
||||
source = self.renderer.document["source"]
|
||||
rsource = self.renderer.reporter.source
|
||||
line_func = getattr(self.renderer.reporter, "get_source_and_line", None)
|
||||
try:
|
||||
self.renderer.document["source"] = str(path)
|
||||
self.renderer.reporter.source = str(path)
|
||||
self.renderer.reporter.get_source_and_line = lambda l: (str(path), l)
|
||||
if "relative-images" in self.options:
|
||||
self.renderer.md_env["relative-images"] = os.path.relpath(
|
||||
path.parent, source_dir
|
||||
)
|
||||
if "relative-docs" in self.options:
|
||||
self.renderer.md_env["relative-docs"] = (
|
||||
self.options["relative-docs"],
|
||||
source_dir,
|
||||
path.parent,
|
||||
)
|
||||
self.renderer.nested_render_text(
|
||||
file_content, startline + 1, allow_headings=True
|
||||
)
|
||||
finally:
|
||||
self.renderer.document["source"] = source
|
||||
self.renderer.reporter.source = rsource
|
||||
self.renderer.md_env.pop("relative-images", None)
|
||||
self.renderer.md_env.pop("relative-docs", None)
|
||||
if line_func is not None:
|
||||
self.renderer.reporter.get_source_and_line = line_func
|
||||
else:
|
||||
del self.renderer.reporter.get_source_and_line
|
||||
return []
|
||||
|
||||
def add_name(self, node: nodes.Element):
|
||||
"""Append self.options['name'] to node['names'] if it exists.
|
||||
|
||||
Also normalize the name string and register it as explicit target.
|
||||
"""
|
||||
if "name" in self.options:
|
||||
name = nodes.fully_normalize_name(self.options.pop("name"))
|
||||
if "name" in node:
|
||||
del node["name"]
|
||||
node["names"].append(name)
|
||||
self.renderer.document.note_explicit_target(node, node)
|
||||
|
||||
|
||||
class MockRSTParser(RSTParser):
|
||||
"""RSTParser which avoids a negative side effect."""
|
||||
|
||||
def parse(self, inputstring: str, document: nodes.document):
|
||||
"""Parse the input to populate the document AST."""
|
||||
from docutils.parsers.rst import roles
|
||||
|
||||
should_restore = False
|
||||
if "" in roles._roles:
|
||||
should_restore = True
|
||||
blankrole = roles._roles[""]
|
||||
|
||||
super().parse(inputstring, document)
|
||||
|
||||
if should_restore:
|
||||
roles._roles[""] = blankrole
|
|
@ -0,0 +1 @@
|
|||
"""Parsers of MyST Markdown source text to docutils AST."""
|
|
@ -0,0 +1,190 @@
|
|||
"""Fenced code blocks are parsed as directives,
|
||||
if the block starts with ``{directive_name}``,
|
||||
followed by arguments on the same line.
|
||||
|
||||
Directive options are read from a YAML block,
|
||||
if the first content line starts with ``---``, e.g.
|
||||
|
||||
::
|
||||
|
||||
```{directive_name} arguments
|
||||
---
|
||||
option1: name
|
||||
option2: |
|
||||
Longer text block
|
||||
---
|
||||
content...
|
||||
```
|
||||
|
||||
Or the option block will be parsed if the first content line starts with ``:``,
|
||||
as a YAML block consisting of every line that starts with a ``:``, e.g.
|
||||
|
||||
::
|
||||
|
||||
```{directive_name} arguments
|
||||
:option1: name
|
||||
:option2: other
|
||||
|
||||
content...
|
||||
```
|
||||
|
||||
If the first line of a directive's content is blank, this will be stripped
|
||||
from the content.
|
||||
This is to allow for separation between the option block and content.
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import re
|
||||
from textwrap import dedent
|
||||
from typing import Any, Callable
|
||||
|
||||
import yaml
|
||||
from docutils.parsers.rst import Directive
|
||||
from docutils.parsers.rst.directives.misc import TestDirective
|
||||
|
||||
|
||||
class DirectiveParsingError(Exception):
|
||||
"""Raise on parsing/validation error."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def parse_directive_text(
|
||||
directive_class: type[Directive],
|
||||
first_line: str,
|
||||
content: str,
|
||||
validate_options: bool = True,
|
||||
) -> tuple[list[str], dict, list[str], int]:
|
||||
"""Parse (and validate) the full directive text.
|
||||
|
||||
:param first_line: The text on the same line as the directive name.
|
||||
May be an argument or body text, dependent on the directive
|
||||
:param content: All text after the first line. Can include options.
|
||||
:param validate_options: Whether to validate the values of options
|
||||
|
||||
:returns: (arguments, options, body_lines, content_offset)
|
||||
"""
|
||||
if directive_class.option_spec:
|
||||
body, options = parse_directive_options(
|
||||
content, directive_class, validate=validate_options
|
||||
)
|
||||
body_lines = body.splitlines()
|
||||
content_offset = len(content.splitlines()) - len(body_lines)
|
||||
else:
|
||||
# If there are no possible options, we do not look for a YAML block
|
||||
options = {}
|
||||
body_lines = content.splitlines()
|
||||
content_offset = 0
|
||||
|
||||
if not (directive_class.required_arguments or directive_class.optional_arguments):
|
||||
# If there are no possible arguments, then the body starts on the argument line
|
||||
if first_line:
|
||||
body_lines.insert(0, first_line)
|
||||
arguments = []
|
||||
else:
|
||||
arguments = parse_directive_arguments(directive_class, first_line)
|
||||
|
||||
# remove first line of body if blank
|
||||
# this is to allow space between the options and the content
|
||||
if body_lines and not body_lines[0].strip():
|
||||
body_lines = body_lines[1:]
|
||||
content_offset += 1
|
||||
|
||||
# check for body content
|
||||
if body_lines and not directive_class.has_content:
|
||||
raise DirectiveParsingError("No content permitted")
|
||||
|
||||
return arguments, options, body_lines, content_offset
|
||||
|
||||
|
||||
def parse_directive_options(
|
||||
content: str, directive_class: type[Directive], validate: bool = True
|
||||
):
|
||||
"""Parse (and validate) the directive option section."""
|
||||
options: dict[str, Any] = {}
|
||||
if content.startswith("---"):
|
||||
content = "\n".join(content.splitlines()[1:])
|
||||
match = re.search(r"^-{3,}", content, re.MULTILINE)
|
||||
if match:
|
||||
yaml_block = content[: match.start()]
|
||||
content = content[match.end() + 1 :] # TODO advance line number
|
||||
else:
|
||||
yaml_block = content
|
||||
content = ""
|
||||
yaml_block = dedent(yaml_block)
|
||||
try:
|
||||
options = yaml.safe_load(yaml_block) or {}
|
||||
except (yaml.parser.ParserError, yaml.scanner.ScannerError) as error:
|
||||
raise DirectiveParsingError("Invalid options YAML: " + str(error))
|
||||
elif content.lstrip().startswith(":"):
|
||||
content_lines = content.splitlines() # type: list
|
||||
yaml_lines = []
|
||||
while content_lines:
|
||||
if not content_lines[0].lstrip().startswith(":"):
|
||||
break
|
||||
yaml_lines.append(content_lines.pop(0).lstrip()[1:])
|
||||
yaml_block = "\n".join(yaml_lines)
|
||||
content = "\n".join(content_lines)
|
||||
try:
|
||||
options = yaml.safe_load(yaml_block) or {}
|
||||
except (yaml.parser.ParserError, yaml.scanner.ScannerError) as error:
|
||||
raise DirectiveParsingError("Invalid options YAML: " + str(error))
|
||||
if not isinstance(options, dict):
|
||||
raise DirectiveParsingError(f"Invalid options (not dict): {options}")
|
||||
|
||||
if (not validate) or issubclass(directive_class, TestDirective):
|
||||
# technically this directive spec only accepts one option ('option')
|
||||
# but since its for testing only we accept all options
|
||||
return content, options
|
||||
|
||||
# check options against spec
|
||||
options_spec: dict[str, Callable] = directive_class.option_spec
|
||||
for name, value in list(options.items()):
|
||||
try:
|
||||
convertor = options_spec[name]
|
||||
except KeyError:
|
||||
raise DirectiveParsingError(f"Unknown option: {name}")
|
||||
if not isinstance(value, str):
|
||||
if value is True or value is None:
|
||||
value = None # flag converter requires no argument
|
||||
elif isinstance(value, (int, float, datetime.date, datetime.datetime)):
|
||||
# convertor always requires string input
|
||||
value = str(value)
|
||||
else:
|
||||
raise DirectiveParsingError(
|
||||
f'option "{name}" value not string (enclose with ""): {value}'
|
||||
)
|
||||
try:
|
||||
converted_value = convertor(value)
|
||||
except (ValueError, TypeError) as error:
|
||||
raise DirectiveParsingError(
|
||||
"Invalid option value: (option: '{}'; value: {})\n{}".format(
|
||||
name, value, error
|
||||
)
|
||||
)
|
||||
options[name] = converted_value
|
||||
|
||||
return content, options
|
||||
|
||||
|
||||
def parse_directive_arguments(directive, arg_text):
|
||||
"""Parse (and validate) the directive argument section."""
|
||||
required = directive.required_arguments
|
||||
optional = directive.optional_arguments
|
||||
arguments = arg_text.split()
|
||||
if len(arguments) < required:
|
||||
raise DirectiveParsingError(
|
||||
f"{required} argument(s) required, {len(arguments)} supplied"
|
||||
)
|
||||
elif len(arguments) > required + optional:
|
||||
if directive.final_argument_whitespace:
|
||||
arguments = arg_text.split(None, required + optional - 1)
|
||||
else:
|
||||
raise DirectiveParsingError(
|
||||
"maximum {} argument(s) allowed, {} supplied".format(
|
||||
required + optional, len(arguments)
|
||||
)
|
||||
)
|
||||
return arguments
|
|
@ -0,0 +1,275 @@
|
|||
"""MyST Markdown parser for docutils."""
|
||||
from dataclasses import Field
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
from docutils import frontend, nodes
|
||||
from docutils.core import default_description, publish_cmdline
|
||||
from docutils.parsers.rst import Parser as RstParser
|
||||
from typing_extensions import Literal, get_args, get_origin
|
||||
|
||||
from myst_parser.config.main import (
|
||||
MdParserConfig,
|
||||
TopmatterReadError,
|
||||
merge_file_level,
|
||||
read_topmatter,
|
||||
)
|
||||
from myst_parser.mdit_to_docutils.base import DocutilsRenderer, create_warning
|
||||
from myst_parser.parsers.mdit import create_md_parser
|
||||
|
||||
|
||||
def _validate_int(
|
||||
setting, value, option_parser, config_parser=None, config_section=None
|
||||
) -> int:
|
||||
"""Validate an integer setting."""
|
||||
return int(value)
|
||||
|
||||
|
||||
def _create_validate_tuple(length: int) -> Callable[..., Tuple[str, ...]]:
|
||||
"""Create a validator for a tuple of length `length`."""
|
||||
|
||||
def _validate(
|
||||
setting, value, option_parser, config_parser=None, config_section=None
|
||||
):
|
||||
string_list = frontend.validate_comma_separated_list(
|
||||
setting, value, option_parser, config_parser, config_section
|
||||
)
|
||||
if len(string_list) != length:
|
||||
raise ValueError(
|
||||
f"Expecting {length} items in {setting}, got {len(string_list)}."
|
||||
)
|
||||
return tuple(string_list)
|
||||
|
||||
return _validate
|
||||
|
||||
|
||||
class Unset:
|
||||
"""A sentinel class for unset settings."""
|
||||
|
||||
def __repr__(self):
|
||||
return "UNSET"
|
||||
|
||||
|
||||
DOCUTILS_UNSET = Unset()
|
||||
"""Sentinel for arguments not set through docutils.conf."""
|
||||
|
||||
|
||||
DOCUTILS_EXCLUDED_ARGS = (
|
||||
# docutils.conf can't represent callables
|
||||
"heading_slug_func",
|
||||
# docutils.conf can't represent dicts
|
||||
"html_meta",
|
||||
"substitutions",
|
||||
# we can't add substitutions so not needed
|
||||
"sub_delimiters",
|
||||
# sphinx only options
|
||||
"heading_anchors",
|
||||
"ref_domains",
|
||||
"update_mathjax",
|
||||
"mathjax_classes",
|
||||
)
|
||||
"""Names of settings that cannot be set in docutils.conf."""
|
||||
|
||||
|
||||
def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]:
|
||||
"""Convert a field into a Docutils optparse options dict."""
|
||||
if at.type is int:
|
||||
return {"metavar": "<int>", "validator": _validate_int}, f"(default: {default})"
|
||||
if at.type is bool:
|
||||
return {
|
||||
"metavar": "<boolean>",
|
||||
"validator": frontend.validate_boolean,
|
||||
}, f"(default: {default})"
|
||||
if at.type is str:
|
||||
return {
|
||||
"metavar": "<str>",
|
||||
}, f"(default: '{default}')"
|
||||
if get_origin(at.type) is Literal and all(
|
||||
isinstance(a, str) for a in get_args(at.type)
|
||||
):
|
||||
args = get_args(at.type)
|
||||
return {
|
||||
"metavar": f"<{'|'.join(repr(a) for a in args)}>",
|
||||
"type": "choice",
|
||||
"choices": args,
|
||||
}, f"(default: {default!r})"
|
||||
if at.type in (Iterable[str], Sequence[str]):
|
||||
return {
|
||||
"metavar": "<comma-delimited>",
|
||||
"validator": frontend.validate_comma_separated_list,
|
||||
}, f"(default: '{','.join(default)}')"
|
||||
if at.type == Tuple[str, str]:
|
||||
return {
|
||||
"metavar": "<str,str>",
|
||||
"validator": _create_validate_tuple(2),
|
||||
}, f"(default: '{','.join(default)}')"
|
||||
if at.type == Union[int, type(None)]:
|
||||
return {
|
||||
"metavar": "<null|int>",
|
||||
"validator": _validate_int,
|
||||
}, f"(default: {default})"
|
||||
if at.type == Union[Iterable[str], type(None)]:
|
||||
default_str = ",".join(default) if default else ""
|
||||
return {
|
||||
"metavar": "<null|comma-delimited>",
|
||||
"validator": frontend.validate_comma_separated_list,
|
||||
}, f"(default: {default_str!r})"
|
||||
raise AssertionError(
|
||||
f"Configuration option {at.name} not set up for use in docutils.conf."
|
||||
)
|
||||
|
||||
|
||||
def attr_to_optparse_option(
|
||||
attribute: Field, default: Any, prefix: str = "myst_"
|
||||
) -> Tuple[str, List[str], Dict[str, Any]]:
|
||||
"""Convert an ``MdParserConfig`` attribute into a Docutils setting tuple.
|
||||
|
||||
:returns: A tuple of ``(help string, option flags, optparse kwargs)``.
|
||||
"""
|
||||
name = f"{prefix}{attribute.name}"
|
||||
flag = "--" + name.replace("_", "-")
|
||||
options = {"dest": name, "default": DOCUTILS_UNSET}
|
||||
at_options, type_str = _attr_to_optparse_option(attribute, default)
|
||||
options.update(at_options)
|
||||
help_str = attribute.metadata.get("help", "") if attribute.metadata else ""
|
||||
return (f"{help_str} {type_str}", [flag], options)
|
||||
|
||||
|
||||
def create_myst_settings_spec(
|
||||
excluded: Sequence[str], config_cls=MdParserConfig, prefix: str = "myst_"
|
||||
):
|
||||
"""Return a list of Docutils setting for the docutils MyST section."""
|
||||
defaults = config_cls()
|
||||
return tuple(
|
||||
attr_to_optparse_option(at, getattr(defaults, at.name), prefix)
|
||||
for at in config_cls.get_fields()
|
||||
if at.name not in excluded
|
||||
)
|
||||
|
||||
|
||||
def create_myst_config(
|
||||
settings: frontend.Values,
|
||||
excluded: Sequence[str],
|
||||
config_cls=MdParserConfig,
|
||||
prefix: str = "myst_",
|
||||
):
|
||||
"""Create a configuration instance from the given settings."""
|
||||
values = {}
|
||||
for attribute in config_cls.get_fields():
|
||||
if attribute.name in excluded:
|
||||
continue
|
||||
setting = f"{prefix}{attribute.name}"
|
||||
val = getattr(settings, setting, DOCUTILS_UNSET)
|
||||
if val is not DOCUTILS_UNSET:
|
||||
values[attribute.name] = val
|
||||
return config_cls(**values)
|
||||
|
||||
|
||||
class Parser(RstParser):
|
||||
"""Docutils parser for Markedly Structured Text (MyST)."""
|
||||
|
||||
supported: Tuple[str, ...] = ("md", "markdown", "myst")
|
||||
"""Aliases this parser supports."""
|
||||
|
||||
settings_spec = (
|
||||
"MyST options",
|
||||
None,
|
||||
create_myst_settings_spec(DOCUTILS_EXCLUDED_ARGS),
|
||||
*RstParser.settings_spec,
|
||||
)
|
||||
"""Runtime settings specification."""
|
||||
|
||||
config_section = "myst parser"
|
||||
config_section_dependencies = ("parsers",)
|
||||
translate_section_name = None
|
||||
|
||||
def parse(self, inputstring: str, document: nodes.document) -> None:
|
||||
"""Parse source text.
|
||||
|
||||
:param inputstring: The source string to parse
|
||||
:param document: The root docutils node to add AST elements to
|
||||
"""
|
||||
|
||||
self.setup_parse(inputstring, document)
|
||||
|
||||
# check for exorbitantly long lines
|
||||
if hasattr(document.settings, "line_length_limit"):
|
||||
for i, line in enumerate(inputstring.split("\n")):
|
||||
if len(line) > document.settings.line_length_limit:
|
||||
error = document.reporter.error(
|
||||
f"Line {i+1} exceeds the line-length-limit:"
|
||||
f" {document.settings.line_length_limit}."
|
||||
)
|
||||
document.append(error)
|
||||
return
|
||||
|
||||
# create parsing configuration from the global config
|
||||
try:
|
||||
config = create_myst_config(document.settings, DOCUTILS_EXCLUDED_ARGS)
|
||||
except Exception as exc:
|
||||
error = document.reporter.error(f"Global myst configuration invalid: {exc}")
|
||||
document.append(error)
|
||||
config = MdParserConfig()
|
||||
|
||||
# update the global config with the file-level config
|
||||
try:
|
||||
topmatter = read_topmatter(inputstring)
|
||||
except TopmatterReadError:
|
||||
pass # this will be reported during the render
|
||||
else:
|
||||
if topmatter:
|
||||
warning = lambda wtype, msg: create_warning( # noqa: E731
|
||||
document, msg, line=1, append_to=document, subtype=wtype
|
||||
)
|
||||
config = merge_file_level(config, topmatter, warning)
|
||||
|
||||
# parse content
|
||||
parser = create_md_parser(config, DocutilsRenderer)
|
||||
parser.options["document"] = document
|
||||
parser.render(inputstring)
|
||||
|
||||
# post-processing
|
||||
|
||||
# replace raw nodes if raw is not allowed
|
||||
if not getattr(document.settings, "raw_enabled", True):
|
||||
for node in document.traverse(nodes.raw):
|
||||
warning = document.reporter.warning("Raw content disabled.")
|
||||
node.parent.replace(node, warning)
|
||||
|
||||
self.finish_parse()
|
||||
|
||||
|
||||
def _run_cli(writer_name: str, writer_description: str, argv: Optional[List[str]]):
|
||||
"""Run the command line interface for a particular writer."""
|
||||
publish_cmdline(
|
||||
parser=Parser(),
|
||||
writer_name=writer_name,
|
||||
description=(
|
||||
f"Generates {writer_description} from standalone MyST sources.\n{default_description}"
|
||||
),
|
||||
argv=argv,
|
||||
)
|
||||
|
||||
|
||||
def cli_html(argv: Optional[List[str]] = None) -> None:
|
||||
"""Cmdline entrypoint for converting MyST to HTML."""
|
||||
_run_cli("html", "(X)HTML documents", argv)
|
||||
|
||||
|
||||
def cli_html5(argv: Optional[List[str]] = None):
|
||||
"""Cmdline entrypoint for converting MyST to HTML5."""
|
||||
_run_cli("html5", "HTML5 documents", argv)
|
||||
|
||||
|
||||
def cli_latex(argv: Optional[List[str]] = None):
|
||||
"""Cmdline entrypoint for converting MyST to LaTeX."""
|
||||
_run_cli("latex", "LaTeX documents", argv)
|
||||
|
||||
|
||||
def cli_xml(argv: Optional[List[str]] = None):
|
||||
"""Cmdline entrypoint for converting MyST to XML."""
|
||||
_run_cli("xml", "Docutils-native XML", argv)
|
||||
|
||||
|
||||
def cli_pseudoxml(argv: Optional[List[str]] = None):
|
||||
"""Cmdline entrypoint for converting MyST to pseudo-XML."""
|
||||
_run_cli("pseudoxml", "pseudo-XML", argv)
|
|
@ -0,0 +1,123 @@
|
|||
"""This module holds the ``create_md_parser`` function,
|
||||
which creates a parser from the config.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
|
||||
from markdown_it import MarkdownIt
|
||||
from markdown_it.renderer import RendererProtocol
|
||||
from mdit_py_plugins.amsmath import amsmath_plugin
|
||||
from mdit_py_plugins.anchors import anchors_plugin
|
||||
from mdit_py_plugins.attrs import attrs_plugin
|
||||
from mdit_py_plugins.colon_fence import colon_fence_plugin
|
||||
from mdit_py_plugins.deflist import deflist_plugin
|
||||
from mdit_py_plugins.dollarmath import dollarmath_plugin
|
||||
from mdit_py_plugins.field_list import fieldlist_plugin
|
||||
from mdit_py_plugins.footnote import footnote_plugin
|
||||
from mdit_py_plugins.front_matter import front_matter_plugin
|
||||
from mdit_py_plugins.myst_blocks import myst_block_plugin
|
||||
from mdit_py_plugins.myst_role import myst_role_plugin
|
||||
from mdit_py_plugins.substitution import substitution_plugin
|
||||
from mdit_py_plugins.tasklists import tasklists_plugin
|
||||
from mdit_py_plugins.wordcount import wordcount_plugin
|
||||
|
||||
from myst_parser.config.main import MdParserConfig
|
||||
|
||||
|
||||
def create_md_parser(
|
||||
config: MdParserConfig, renderer: Callable[[MarkdownIt], RendererProtocol]
|
||||
) -> MarkdownIt:
|
||||
"""Return a Markdown parser with the required MyST configuration."""
|
||||
|
||||
# TODO warn if linkify required and linkify-it-py not installed
|
||||
# (currently the parse will unceremoniously except)
|
||||
|
||||
if config.commonmark_only:
|
||||
# see https://spec.commonmark.org/
|
||||
md = MarkdownIt("commonmark", renderer_cls=renderer).use(
|
||||
wordcount_plugin, per_minute=config.words_per_minute
|
||||
)
|
||||
md.options.update({"myst_config": config})
|
||||
return md
|
||||
|
||||
if config.gfm_only:
|
||||
# see https://github.github.com/gfm/
|
||||
md = (
|
||||
MarkdownIt("commonmark", renderer_cls=renderer)
|
||||
# note, strikethrough currently only supported tentatively for HTML
|
||||
.enable("strikethrough")
|
||||
.enable("table")
|
||||
.use(tasklists_plugin)
|
||||
.enable("linkify")
|
||||
.use(wordcount_plugin, per_minute=config.words_per_minute)
|
||||
)
|
||||
md.options.update({"linkify": True, "myst_config": config})
|
||||
return md
|
||||
|
||||
md = (
|
||||
MarkdownIt("commonmark", renderer_cls=renderer)
|
||||
.enable("table")
|
||||
.use(front_matter_plugin)
|
||||
.use(myst_block_plugin)
|
||||
.use(myst_role_plugin)
|
||||
.use(footnote_plugin)
|
||||
.use(wordcount_plugin, per_minute=config.words_per_minute)
|
||||
.disable("footnote_inline")
|
||||
# disable this for now, because it need a new implementation in the renderer
|
||||
.disable("footnote_tail")
|
||||
)
|
||||
|
||||
typographer = False
|
||||
if "smartquotes" in config.enable_extensions:
|
||||
md.enable("smartquotes")
|
||||
typographer = True
|
||||
if "replacements" in config.enable_extensions:
|
||||
md.enable("replacements")
|
||||
typographer = True
|
||||
if "linkify" in config.enable_extensions:
|
||||
md.enable("linkify")
|
||||
if md.linkify is not None:
|
||||
md.linkify.set({"fuzzy_link": config.linkify_fuzzy_links})
|
||||
if "strikethrough" in config.enable_extensions:
|
||||
md.enable("strikethrough")
|
||||
if "dollarmath" in config.enable_extensions:
|
||||
md.use(
|
||||
dollarmath_plugin,
|
||||
allow_labels=config.dmath_allow_labels,
|
||||
allow_space=config.dmath_allow_space,
|
||||
allow_digits=config.dmath_allow_digits,
|
||||
double_inline=config.dmath_double_inline,
|
||||
)
|
||||
if "colon_fence" in config.enable_extensions:
|
||||
md.use(colon_fence_plugin)
|
||||
if "amsmath" in config.enable_extensions:
|
||||
md.use(amsmath_plugin)
|
||||
if "deflist" in config.enable_extensions:
|
||||
md.use(deflist_plugin)
|
||||
if "fieldlist" in config.enable_extensions:
|
||||
md.use(fieldlist_plugin)
|
||||
if "tasklist" in config.enable_extensions:
|
||||
md.use(tasklists_plugin)
|
||||
if "substitution" in config.enable_extensions:
|
||||
md.use(substitution_plugin, *config.sub_delimiters)
|
||||
if "attrs_image" in config.enable_extensions:
|
||||
md.use(attrs_plugin, after=("image",))
|
||||
if config.heading_anchors is not None:
|
||||
md.use(
|
||||
anchors_plugin,
|
||||
max_level=config.heading_anchors,
|
||||
slug_func=config.heading_slug_func,
|
||||
)
|
||||
for name in config.disable_syntax:
|
||||
md.disable(name, True)
|
||||
|
||||
md.options.update(
|
||||
{
|
||||
"typographer": typographer,
|
||||
"linkify": "linkify" in config.enable_extensions,
|
||||
"myst_config": config,
|
||||
}
|
||||
)
|
||||
|
||||
return md
|
|
@ -0,0 +1,440 @@
|
|||
"""A simple but complete HTML to Abstract Syntax Tree (AST) parser.
|
||||
|
||||
The AST can also reproduce the HTML text.
|
||||
|
||||
Example::
|
||||
|
||||
>> text = '<div class="note"><p>text</p></div>'
|
||||
>> ast = tokenize_html(text)
|
||||
>> list(ast.walk(include_self=True))
|
||||
[Root(''), Tag('div', {'class': 'note'}), Tag('p'), Data('text')]
|
||||
>> str(ast)
|
||||
'<div class="note"><p>text</p></div>'
|
||||
>> str(ast[0][0])
|
||||
'<p>text</p>'
|
||||
|
||||
Note: optional tags are not accounted for
|
||||
(see https://html.spec.whatwg.org/multipage/syntax.html#optional-tags)
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import itertools
|
||||
from collections import abc, deque
|
||||
from html.parser import HTMLParser
|
||||
from typing import Any, Callable, Iterable, Iterator
|
||||
|
||||
|
||||
class Attribute(dict):
|
||||
"""This class holds the tags's attributes."""
|
||||
|
||||
def __getitem__(self, key: str) -> str:
|
||||
"""If self doesn't have the key it returns ''."""
|
||||
return self.get(key, "")
|
||||
|
||||
@property
|
||||
def classes(self) -> list[str]:
|
||||
"""Return 'class' attribute as list."""
|
||||
return self["class"].split()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return a htmlized representation for attributes."""
|
||||
return " ".join(f'{key}="{value}"' for key, value in self.items())
|
||||
|
||||
|
||||
class Element(abc.MutableSequence):
|
||||
"""An Element of the xml/html document.
|
||||
|
||||
All xml/html entities inherit from this class.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str = "", attr: dict | None = None) -> None:
|
||||
"""Initialise the element."""
|
||||
self.name = name
|
||||
self.attrs: Attribute = Attribute(attr or {})
|
||||
self._parent: Element | None = None
|
||||
self._children: list[Element] = []
|
||||
|
||||
@property
|
||||
def parent(self) -> Element | None:
|
||||
"""Return parent."""
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
def children(self) -> list[Element]:
|
||||
"""Return copy of children."""
|
||||
return self._children[:]
|
||||
|
||||
def reset_children(self, children: list[Element], deepcopy: bool = False):
|
||||
new_children = []
|
||||
for i, item in enumerate(children):
|
||||
assert isinstance(item, Element)
|
||||
if deepcopy:
|
||||
item = item.deepcopy()
|
||||
if item._parent is None:
|
||||
item._parent = self
|
||||
elif item._parent != self:
|
||||
raise AssertionError(f"different parent already set for item {i}")
|
||||
new_children.append(item)
|
||||
self._children = new_children
|
||||
|
||||
def __getitem__(self, index: int) -> Element: # type: ignore[override]
|
||||
return self._children[index]
|
||||
|
||||
def __setitem__(self, index: int, item: Element): # type: ignore[override]
|
||||
assert isinstance(item, Element)
|
||||
if item._parent is not None and item._parent != self:
|
||||
raise AssertionError(f"different parent already set for: {item!r}")
|
||||
item._parent = self
|
||||
return self._children.__setitem__(index, item)
|
||||
|
||||
def __delitem__(self, index: int): # type: ignore[override]
|
||||
return self._children.__delitem__(index)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self._children.__len__()
|
||||
|
||||
def __iter__(self) -> Iterator[Element]:
|
||||
yield from self._children
|
||||
|
||||
def insert(self, index: int, item: Element):
|
||||
assert isinstance(item, Element)
|
||||
if item._parent is not None and item._parent != self:
|
||||
raise AssertionError(f"different parent already set for: {item!r}")
|
||||
item._parent = self
|
||||
return self._children.insert(index, item)
|
||||
|
||||
def deepcopy(self) -> Element:
|
||||
"""Recursively copy and remove parent."""
|
||||
_copy = self.__class__(self.name, self.attrs)
|
||||
for child in self:
|
||||
_copy_child = child.deepcopy()
|
||||
_copy.append(_copy_child)
|
||||
return _copy
|
||||
|
||||
def __repr__(self) -> str:
|
||||
text = f"{self.__class__.__name__}({self.name!r}"
|
||||
if self.attrs:
|
||||
text += f", {self.attrs!r}"
|
||||
text += ")"
|
||||
return text
|
||||
|
||||
def render(
|
||||
self,
|
||||
tag_overrides: dict[str, Callable[[Element, dict], str]] | None = None,
|
||||
**kwargs,
|
||||
) -> str:
|
||||
"""Returns a HTML string representation of the element.
|
||||
|
||||
:param tag_overrides: Provide a dictionary of render function
|
||||
for specific tag names, to override the normal render format
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.render()
|
||||
|
||||
def __eq__(self, item: Any) -> bool:
|
||||
return item is self
|
||||
|
||||
def walk(self, include_self: bool = False) -> Iterator[Element]:
|
||||
"""Walk through the xml/html AST."""
|
||||
if include_self:
|
||||
yield self
|
||||
for child in self:
|
||||
yield child
|
||||
yield from child.walk()
|
||||
|
||||
def strip(self, inplace: bool = False, recurse: bool = False) -> Element:
|
||||
"""Return copy with all `Data` tokens
|
||||
that only contain whitespace / newlines removed.
|
||||
"""
|
||||
element = self
|
||||
if not inplace:
|
||||
element = self.deepcopy()
|
||||
element.reset_children(
|
||||
[
|
||||
e
|
||||
for e in element.children
|
||||
if not (isinstance(e, Data) and e.data.strip() == "")
|
||||
]
|
||||
)
|
||||
if recurse:
|
||||
for child in element:
|
||||
child.strip(inplace=True, recurse=True)
|
||||
return element
|
||||
|
||||
def find(
|
||||
self,
|
||||
identifier: str | type[Element],
|
||||
attrs: dict | None = None,
|
||||
classes: Iterable[str] | None = None,
|
||||
include_self: bool = False,
|
||||
recurse: bool = True,
|
||||
) -> Iterator[Element]:
|
||||
"""Find all elements that match name and specific attributes."""
|
||||
iterator = self.walk() if recurse else self
|
||||
if include_self:
|
||||
iterator = itertools.chain([self], iterator)
|
||||
if inspect.isclass(identifier):
|
||||
test_func = lambda c: isinstance(c, identifier) # noqa: E731
|
||||
else:
|
||||
test_func = lambda c: c.name == identifier # noqa: E731
|
||||
classes = set(classes) if classes is not None else classes
|
||||
for child in iterator:
|
||||
if test_func(child):
|
||||
if classes is not None and not classes.issubset(child.attrs.classes):
|
||||
continue
|
||||
for key, value in (attrs or {}).items():
|
||||
if child.attrs[key] != value:
|
||||
break
|
||||
else:
|
||||
yield child
|
||||
|
||||
|
||||
class Root(Element):
|
||||
"""The root of the AST tree."""
|
||||
|
||||
def render(self, **kwargs) -> str: # type: ignore[override]
|
||||
"""Returns a string HTML representation of the structure."""
|
||||
return "".join(child.render(**kwargs) for child in self)
|
||||
|
||||
|
||||
class Tag(Element):
|
||||
"""Represent xml/html tags under the form: <name key="value" ...> ... </name>."""
|
||||
|
||||
def render(
|
||||
self,
|
||||
tag_overrides: dict[str, Callable[[Element, dict], str]] | None = None,
|
||||
**kwargs,
|
||||
) -> str:
|
||||
if tag_overrides and self.name in tag_overrides:
|
||||
return tag_overrides[self.name](self, tag_overrides)
|
||||
return (
|
||||
f"<{self.name}{' ' if self.attrs else ''}{self.attrs}>"
|
||||
+ "".join(
|
||||
child.render(tag_overrides=tag_overrides, **kwargs) for child in self
|
||||
)
|
||||
+ f"</{self.name}>"
|
||||
)
|
||||
|
||||
|
||||
class XTag(Element):
|
||||
"""Represent XHTML style tags with no children, like `<img src="t.gif" />`"""
|
||||
|
||||
def render(
|
||||
self,
|
||||
tag_overrides: dict[str, Callable[[Element, dict], str]] | None = None,
|
||||
**kwargs,
|
||||
) -> str:
|
||||
if tag_overrides is not None and self.name in tag_overrides:
|
||||
return tag_overrides[self.name](self, tag_overrides)
|
||||
return f"<{self.name}{' ' if self.attrs else ''}{self.attrs}/>"
|
||||
|
||||
|
||||
class VoidTag(Element):
|
||||
"""Represent tags with no children, only start tag, like `<img src="t.gif" >`"""
|
||||
|
||||
def render(self, **kwargs) -> str: # type: ignore[override]
|
||||
return f"<{self.name}{' ' if self.attrs else ''}{self.attrs}>"
|
||||
|
||||
|
||||
class TerminalElement(Element):
|
||||
def __init__(self, data: str):
|
||||
super().__init__("")
|
||||
self.data: str = data
|
||||
|
||||
def __repr__(self) -> str:
|
||||
text = self.data
|
||||
if len(text) > 20:
|
||||
text = text[:17] + "..."
|
||||
return f"{self.__class__.__name__}({text!r})"
|
||||
|
||||
def deepcopy(self) -> TerminalElement:
|
||||
"""Copy and remove parent."""
|
||||
_copy = self.__class__(self.data)
|
||||
return _copy
|
||||
|
||||
|
||||
class Data(TerminalElement):
|
||||
"""Represent data inside xml/html documents, like raw text."""
|
||||
|
||||
def render(self, **kwargs) -> str: # type: ignore[override]
|
||||
return self.data
|
||||
|
||||
|
||||
class Declaration(TerminalElement):
|
||||
"""Represent declarations, like `<!DOCTYPE html>`"""
|
||||
|
||||
def render(self, **kwargs) -> str: # type: ignore[override]
|
||||
return f"<!{self.data}>"
|
||||
|
||||
|
||||
class Comment(TerminalElement):
|
||||
"""Represent HTML comments"""
|
||||
|
||||
def render(self, **kwargs) -> str: # type: ignore[override]
|
||||
return f"<!--{self.data}-->"
|
||||
|
||||
|
||||
class Pi(TerminalElement):
|
||||
"""Represent processing instructions like `<?xml-stylesheet ?>`"""
|
||||
|
||||
def render(self, **kwargs) -> str: # type: ignore[override]
|
||||
return f"<?{self.data}>"
|
||||
|
||||
|
||||
class Char(TerminalElement):
|
||||
"""Represent character codes like: `�`"""
|
||||
|
||||
def render(self, **kwargs) -> str: # type: ignore[override]
|
||||
return f"&#{self.data};"
|
||||
|
||||
|
||||
class Entity(TerminalElement):
|
||||
"""Represent entities like `&`"""
|
||||
|
||||
def render(self, **kwargs) -> str: # type: ignore[override]
|
||||
return f"&{self.data};"
|
||||
|
||||
|
||||
class Tree:
|
||||
"""The engine class to generate the AST tree."""
|
||||
|
||||
def __init__(self, name: str = ""):
|
||||
"""Initialise Tree"""
|
||||
self.name = name
|
||||
self.outmost = Root(name)
|
||||
self.stack: deque = deque()
|
||||
self.stack.append(self.outmost)
|
||||
|
||||
def clear(self):
|
||||
"""Clear the outmost and stack for a new parsing."""
|
||||
self.outmost = Root(self.name)
|
||||
self.stack.clear()
|
||||
self.stack.append(self.outmost)
|
||||
|
||||
def last(self) -> Element:
|
||||
"""Return the last pointer which point to the actual tag scope."""
|
||||
return self.stack[-1]
|
||||
|
||||
def nest_tag(self, name: str, attrs: dict):
|
||||
"""Nest a given tag at the bottom of the tree using
|
||||
the last stack's pointer.
|
||||
"""
|
||||
pointer = self.stack.pop()
|
||||
item = Tag(name, attrs)
|
||||
pointer.append(item)
|
||||
self.stack.append(pointer)
|
||||
self.stack.append(item)
|
||||
|
||||
def nest_xtag(self, name: str, attrs: dict):
|
||||
"""Nest an XTag onto the tree."""
|
||||
top = self.last()
|
||||
item = XTag(name, attrs)
|
||||
top.append(item)
|
||||
|
||||
def nest_vtag(self, name: str, attrs: dict):
|
||||
"""Nest a VoidTag onto the tree."""
|
||||
top = self.last()
|
||||
item = VoidTag(name, attrs)
|
||||
top.append(item)
|
||||
|
||||
def nest_terminal(self, klass: type[TerminalElement], data: str):
|
||||
"""Nest the data onto the tree."""
|
||||
top = self.last()
|
||||
item = klass(data)
|
||||
top.append(item)
|
||||
|
||||
def enclose(self, name: str):
|
||||
"""When a closing tag is found, pop the pointer's scope from the stack,
|
||||
to then point to the earlier scope's tag.
|
||||
"""
|
||||
count = 0
|
||||
for ind in reversed(self.stack):
|
||||
count = count + 1
|
||||
if ind.name == name:
|
||||
break
|
||||
else:
|
||||
count = 0
|
||||
|
||||
# It pops all the items which do not match with the closing tag.
|
||||
for _ in range(0, count):
|
||||
self.stack.pop()
|
||||
|
||||
|
||||
class HtmlToAst(HTMLParser):
|
||||
"""The tokenizer class."""
|
||||
|
||||
# see https://html.spec.whatwg.org/multipage/syntax.html#void-elements
|
||||
void_elements = {
|
||||
"area",
|
||||
"base",
|
||||
"br",
|
||||
"col",
|
||||
"embed",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"track",
|
||||
"wbr",
|
||||
}
|
||||
|
||||
def __init__(self, name: str = "", convert_charrefs: bool = False):
|
||||
super().__init__(convert_charrefs=convert_charrefs)
|
||||
self.struct = Tree(name)
|
||||
|
||||
def feed(self, source: str) -> Root: # type: ignore[override]
|
||||
"""Parse the source string."""
|
||||
self.struct.clear()
|
||||
super().feed(source)
|
||||
return self.struct.outmost
|
||||
|
||||
def handle_starttag(self, name: str, attr):
|
||||
"""When found an opening tag then nest it onto the tree."""
|
||||
if name in self.void_elements:
|
||||
self.struct.nest_vtag(name, attr)
|
||||
else:
|
||||
self.struct.nest_tag(name, attr)
|
||||
|
||||
def handle_startendtag(self, name: str, attr):
|
||||
"""When found a XHTML tag style then nest it up to the tree."""
|
||||
self.struct.nest_xtag(name, attr)
|
||||
|
||||
def handle_endtag(self, name: str):
|
||||
"""When found a closing tag then makes it point to the right scope."""
|
||||
if name not in self.void_elements:
|
||||
self.struct.enclose(name)
|
||||
|
||||
def handle_data(self, data: str):
|
||||
"""Nest data onto the tree."""
|
||||
self.struct.nest_terminal(Data, data)
|
||||
|
||||
def handle_decl(self, decl: str):
|
||||
self.struct.nest_terminal(Declaration, decl)
|
||||
|
||||
def unknown_decl(self, decl: str):
|
||||
self.struct.nest_terminal(Declaration, decl)
|
||||
|
||||
def handle_charref(self, data: str):
|
||||
self.struct.nest_terminal(Char, data)
|
||||
|
||||
def handle_entityref(self, data: str):
|
||||
self.struct.nest_terminal(Entity, data)
|
||||
|
||||
def handle_pi(self, data: str):
|
||||
self.struct.nest_terminal(Pi, data)
|
||||
|
||||
def handle_comment(self, data: str):
|
||||
self.struct.nest_terminal(Comment, data)
|
||||
|
||||
|
||||
def tokenize_html(text: str, name: str = "", convert_charrefs: bool = False) -> Root:
|
||||
parser = HtmlToAst(name, convert_charrefs=convert_charrefs)
|
||||
return parser.feed(text)
|
|
@ -0,0 +1,69 @@
|
|||
"""MyST Markdown parser for sphinx."""
|
||||
from __future__ import annotations
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Parser as RstParser
|
||||
from sphinx.parsers import Parser as SphinxParser
|
||||
from sphinx.util import logging
|
||||
|
||||
from myst_parser.config.main import (
|
||||
MdParserConfig,
|
||||
TopmatterReadError,
|
||||
merge_file_level,
|
||||
read_topmatter,
|
||||
)
|
||||
from myst_parser.mdit_to_docutils.sphinx_ import SphinxRenderer, create_warning
|
||||
from myst_parser.parsers.mdit import create_md_parser
|
||||
|
||||
SPHINX_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MystParser(SphinxParser):
|
||||
"""Sphinx parser for Markedly Structured Text (MyST)."""
|
||||
|
||||
supported: tuple[str, ...] = ("md", "markdown", "myst")
|
||||
"""Aliases this parser supports."""
|
||||
|
||||
settings_spec = RstParser.settings_spec
|
||||
"""Runtime settings specification.
|
||||
|
||||
Defines runtime settings and associated command-line options, as used by
|
||||
`docutils.frontend.OptionParser`. This is a concatenation of tuples of:
|
||||
|
||||
- Option group title (string or `None` which implies no group, just a list
|
||||
of single options).
|
||||
|
||||
- Description (string or `None`).
|
||||
|
||||
- A sequence of option tuples
|
||||
"""
|
||||
|
||||
config_section = "myst parser"
|
||||
config_section_dependencies = ("parsers",)
|
||||
translate_section_name = None
|
||||
|
||||
def parse(self, inputstring: str, document: nodes.document) -> None:
|
||||
"""Parse source text.
|
||||
|
||||
:param inputstring: The source string to parse
|
||||
:param document: The root docutils node to add AST elements to
|
||||
|
||||
"""
|
||||
# get the global config
|
||||
config: MdParserConfig = document.settings.env.myst_config
|
||||
|
||||
# update the global config with the file-level config
|
||||
try:
|
||||
topmatter = read_topmatter(inputstring)
|
||||
except TopmatterReadError:
|
||||
pass # this will be reported during the render
|
||||
else:
|
||||
if topmatter:
|
||||
warning = lambda wtype, msg: create_warning( # noqa: E731
|
||||
document, msg, line=1, append_to=document, subtype=wtype
|
||||
)
|
||||
config = merge_file_level(config, topmatter, warning)
|
||||
|
||||
parser = create_md_parser(config, SphinxRenderer)
|
||||
parser.options["document"] = document
|
||||
parser.render(inputstring)
|
|
@ -0,0 +1 @@
|
|||
# Marker file for PEP 561
|
|
@ -0,0 +1,6 @@
|
|||
"""A module for compatibility with the docutils>=0.17 `include` directive, in RST documents::
|
||||
|
||||
.. include:: path/to/file.md
|
||||
:parser: myst_parser.sphinx_
|
||||
"""
|
||||
from myst_parser.parsers.sphinx_ import MystParser as Parser # noqa: F401
|
|
@ -0,0 +1 @@
|
|||
"""Sphinx extension for myst_parser."""
|
|
@ -0,0 +1,136 @@
|
|||
"""MyST specific directives"""
|
||||
from copy import copy
|
||||
from typing import List, Tuple, cast
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives
|
||||
from sphinx.directives import SphinxDirective
|
||||
from sphinx.util.docutils import SphinxRole
|
||||
|
||||
from myst_parser.mocking import MockState
|
||||
|
||||
|
||||
def align(argument):
|
||||
return directives.choice(argument, ("left", "center", "right"))
|
||||
|
||||
|
||||
def figwidth_value(argument):
|
||||
if argument.lower() == "image":
|
||||
return "image"
|
||||
else:
|
||||
return directives.length_or_percentage_or_unitless(argument, "px")
|
||||
|
||||
|
||||
class SubstitutionReferenceRole(SphinxRole):
|
||||
"""Implement substitution references as a role.
|
||||
|
||||
Note, in ``docutils/parsers/rst/roles.py`` this is left unimplemented.
|
||||
"""
|
||||
|
||||
def run(self) -> Tuple[List[nodes.Node], List[nodes.system_message]]:
|
||||
subref_node = nodes.substitution_reference(self.rawtext, self.text)
|
||||
self.set_source_info(subref_node, self.lineno)
|
||||
subref_node["refname"] = nodes.fully_normalize_name(self.text)
|
||||
return [subref_node], []
|
||||
|
||||
|
||||
class FigureMarkdown(SphinxDirective):
|
||||
"""Directive for creating a figure with Markdown compatible syntax.
|
||||
|
||||
Example::
|
||||
|
||||
:::{figure-md} target
|
||||
<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
|
||||
|
||||
This is a caption in **Markdown**
|
||||
:::
|
||||
|
||||
"""
|
||||
|
||||
required_arguments = 0
|
||||
optional_arguments = 1 # image target
|
||||
final_argument_whitespace = True
|
||||
has_content = True
|
||||
|
||||
option_spec = {
|
||||
"width": figwidth_value,
|
||||
"class": directives.class_option,
|
||||
"align": align,
|
||||
"name": directives.unchanged,
|
||||
}
|
||||
|
||||
def run(self) -> List[nodes.Node]:
|
||||
figwidth = self.options.pop("width", None)
|
||||
figclasses = self.options.pop("class", None)
|
||||
align = self.options.pop("align", None)
|
||||
|
||||
if not isinstance(self.state, MockState):
|
||||
return [self.figure_error("Directive is only supported in myst parser")]
|
||||
state = cast(MockState, self.state)
|
||||
|
||||
# ensure html image enabled
|
||||
myst_extensions = copy(state._renderer.md_config.enable_extensions)
|
||||
node = nodes.Element()
|
||||
try:
|
||||
state._renderer.md_config.enable_extensions = list(
|
||||
state._renderer.md_config.enable_extensions
|
||||
) + ["html_image"]
|
||||
state.nested_parse(self.content, self.content_offset, node)
|
||||
finally:
|
||||
state._renderer.md_config.enable_extensions = myst_extensions
|
||||
|
||||
if not len(node.children) == 2:
|
||||
return [
|
||||
self.figure_error(
|
||||
"content should be one image, "
|
||||
"followed by a single paragraph caption"
|
||||
)
|
||||
]
|
||||
|
||||
image_node, caption_para = node.children
|
||||
if isinstance(image_node, nodes.paragraph):
|
||||
image_node = image_node[0]
|
||||
|
||||
if not isinstance(image_node, nodes.image):
|
||||
return [
|
||||
self.figure_error(
|
||||
"content should be one image (not found), "
|
||||
"followed by single paragraph caption"
|
||||
)
|
||||
]
|
||||
|
||||
if not isinstance(caption_para, nodes.paragraph):
|
||||
return [
|
||||
self.figure_error(
|
||||
"content should be one image, "
|
||||
"followed by single paragraph caption (not found)"
|
||||
)
|
||||
]
|
||||
|
||||
caption_node = nodes.caption(caption_para.rawsource, "", *caption_para.children)
|
||||
caption_node.source = caption_para.source
|
||||
caption_node.line = caption_para.line
|
||||
|
||||
figure_node = nodes.figure("", image_node, caption_node)
|
||||
self.set_source_info(figure_node)
|
||||
|
||||
if figwidth is not None:
|
||||
figure_node["width"] = figwidth
|
||||
if figclasses:
|
||||
figure_node["classes"] += figclasses
|
||||
if align:
|
||||
figure_node["align"] = align
|
||||
if self.arguments:
|
||||
self.options["name"] = self.arguments[0]
|
||||
self.add_name(figure_node)
|
||||
|
||||
return [figure_node]
|
||||
|
||||
def figure_error(self, message):
|
||||
"""A warning for reporting an invalid figure."""
|
||||
error = self.state_machine.reporter.error(
|
||||
message,
|
||||
nodes.literal_block(self.block_text, self.block_text),
|
||||
line=self.lineno,
|
||||
)
|
||||
return error
|
|
@ -0,0 +1,60 @@
|
|||
"""The setup for the sphinx extension."""
|
||||
from typing import Any
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
|
||||
def setup_sphinx(app: Sphinx, load_parser=False):
|
||||
"""Initialize all settings and transforms in Sphinx."""
|
||||
# we do this separately to setup,
|
||||
# so that it can be called by external packages like myst_nb
|
||||
from myst_parser.config.main import MdParserConfig
|
||||
from myst_parser.parsers.sphinx_ import MystParser
|
||||
from myst_parser.sphinx_ext.directives import (
|
||||
FigureMarkdown,
|
||||
SubstitutionReferenceRole,
|
||||
)
|
||||
from myst_parser.sphinx_ext.mathjax import override_mathjax
|
||||
from myst_parser.sphinx_ext.myst_refs import MystReferenceResolver
|
||||
|
||||
if load_parser:
|
||||
app.add_source_suffix(".md", "markdown")
|
||||
app.add_source_parser(MystParser)
|
||||
|
||||
app.add_role("sub-ref", SubstitutionReferenceRole())
|
||||
app.add_directive("figure-md", FigureMarkdown)
|
||||
|
||||
app.add_post_transform(MystReferenceResolver)
|
||||
|
||||
for name, default, field in MdParserConfig().as_triple():
|
||||
if not field.metadata.get("docutils_only", False):
|
||||
# TODO add types?
|
||||
app.add_config_value(f"myst_{name}", default, "env", types=Any)
|
||||
|
||||
app.connect("builder-inited", create_myst_config)
|
||||
app.connect("builder-inited", override_mathjax)
|
||||
|
||||
|
||||
def create_myst_config(app):
|
||||
from sphinx.util import logging
|
||||
|
||||
# Ignore type checkers because the attribute is dynamically assigned
|
||||
from sphinx.util.console import bold # type: ignore[attr-defined]
|
||||
|
||||
from myst_parser import __version__
|
||||
from myst_parser.config.main import MdParserConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
values = {
|
||||
name: app.config[f"myst_{name}"]
|
||||
for name, _, field in MdParserConfig().as_triple()
|
||||
if not field.metadata.get("docutils_only", False)
|
||||
}
|
||||
|
||||
try:
|
||||
app.env.myst_config = MdParserConfig(**values)
|
||||
logger.info(bold("myst v%s:") + " %s", __version__, app.env.myst_config)
|
||||
except (TypeError, ValueError) as error:
|
||||
logger.error("myst configuration invalid: %s", error.args[0])
|
||||
app.env.myst_config = MdParserConfig()
|
|
@ -0,0 +1,118 @@
|
|||
"""Overrides to ``sphinx.ext.mathjax``
|
||||
|
||||
This fixes two issues:
|
||||
|
||||
1. Mathjax should not search for ``$`` delimiters, nor LaTeX amsmath environments,
|
||||
since we already achieve this with the dollarmath and amsmath mrakdown-it-py plugins
|
||||
2. amsmath math blocks should be wrapped in mathjax delimiters (default ``\\[...\\]``),
|
||||
and assigned an equation number
|
||||
|
||||
"""
|
||||
from docutils import nodes
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.ext import mathjax
|
||||
from sphinx.locale import _
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.math import get_node_equation_number
|
||||
from sphinx.writers.html import HTMLTranslator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def log_override_warning(app: Sphinx, version: int, current: str, new: str) -> None:
|
||||
"""Log a warning if MathJax configuration being overridden."""
|
||||
if logging.is_suppressed_warning("myst", "mathjax", app.config.suppress_warnings):
|
||||
return
|
||||
config_name = (
|
||||
"mathjax3_config['options']['processHtmlClass']"
|
||||
if version == 3
|
||||
else "mathjax_config['tex2jax']['processClass']"
|
||||
)
|
||||
logger.warning(
|
||||
f"`{config_name}` is being overridden by myst-parser: '{current}' -> '{new}'. "
|
||||
"Set `suppress_warnings=['myst.mathjax']` to ignore this warning, or "
|
||||
"`myst_update_mathjax=False` if this is undesirable."
|
||||
)
|
||||
|
||||
|
||||
def override_mathjax(app: Sphinx):
|
||||
"""Override aspects of the mathjax extension.
|
||||
|
||||
MyST-Parser parses dollar and latex math, via markdown-it plugins.
|
||||
Therefore, we tell Mathjax to only render these HTML elements.
|
||||
This is accompanied by setting the `ignoreClass` on the top-level section of each MyST document.
|
||||
"""
|
||||
if (
|
||||
"amsmath" in app.config["myst_enable_extensions"]
|
||||
and "mathjax" in app.registry.html_block_math_renderers
|
||||
):
|
||||
app.registry.html_block_math_renderers["mathjax"] = (
|
||||
html_visit_displaymath, # type: ignore[assignment]
|
||||
None,
|
||||
)
|
||||
|
||||
if "dollarmath" not in app.config["myst_enable_extensions"]:
|
||||
return
|
||||
if not app.env.myst_config.update_mathjax: # type: ignore
|
||||
return
|
||||
|
||||
mjax_classes = app.env.myst_config.mathjax_classes # type: ignore
|
||||
|
||||
if "mathjax3_config" in app.config:
|
||||
# sphinx 4 + mathjax 3
|
||||
app.config.mathjax3_config = app.config.mathjax3_config or {} # type: ignore
|
||||
app.config.mathjax3_config.setdefault("options", {})
|
||||
if (
|
||||
"processHtmlClass" in app.config.mathjax3_config["options"]
|
||||
and app.config.mathjax3_config["options"]["processHtmlClass"]
|
||||
!= mjax_classes
|
||||
):
|
||||
log_override_warning(
|
||||
app,
|
||||
3,
|
||||
app.config.mathjax3_config["options"]["processHtmlClass"],
|
||||
mjax_classes,
|
||||
)
|
||||
app.config.mathjax3_config["options"]["processHtmlClass"] = mjax_classes
|
||||
elif "mathjax_config" in app.config:
|
||||
# sphinx 3 + mathjax 2
|
||||
app.config.mathjax_config = app.config.mathjax_config or {} # type: ignore[attr-defined]
|
||||
app.config.mathjax_config.setdefault("tex2jax", {})
|
||||
if (
|
||||
"processClass" in app.config.mathjax_config["tex2jax"]
|
||||
and app.config.mathjax_config["tex2jax"]["processClass"] != mjax_classes
|
||||
):
|
||||
log_override_warning(
|
||||
app,
|
||||
2,
|
||||
app.config.mathjax_config["tex2jax"]["processClass"],
|
||||
mjax_classes,
|
||||
)
|
||||
app.config.mathjax_config["tex2jax"]["processClass"] = mjax_classes
|
||||
|
||||
|
||||
def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None:
|
||||
"""Override for sphinx.ext.mathjax.html_visit_displaymath to handle amsmath.
|
||||
|
||||
By default displaymath, are normally wrapped in a prefix/suffix,
|
||||
defined by mathjax_display, and labelled nodes are numbered.
|
||||
However, this is not the case if the math_block is set as 'nowrap', as for amsmath.
|
||||
Therefore, we need to override this behaviour.
|
||||
"""
|
||||
if "amsmath" in node.get("classes", []):
|
||||
self.body.append(
|
||||
self.starttag(node, "div", CLASS="math notranslate nohighlight amsmath")
|
||||
)
|
||||
if node["number"]:
|
||||
number = get_node_equation_number(self, node)
|
||||
self.body.append('<span class="eqno">(%s)' % number)
|
||||
self.add_permalink_ref(node, _("Permalink to this equation"))
|
||||
self.body.append("</span>")
|
||||
prefix, suffix = self.builder.config.mathjax_display
|
||||
self.body.append(prefix)
|
||||
self.body.append(self.encode(node.astext()))
|
||||
self.body.append(suffix)
|
||||
self.body.append("</div>\n")
|
||||
raise nodes.SkipNode
|
||||
|
||||
return mathjax.html_visit_displaymath(self, node)
|
|
@ -0,0 +1,282 @@
|
|||
"""A post-transform for overriding the behaviour of sphinx reference resolution.
|
||||
|
||||
This is applied to MyST type references only, such as ``[text](target)``,
|
||||
and allows for nested syntax
|
||||
"""
|
||||
import os
|
||||
from typing import Any, List, Optional, Tuple, cast
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Element, document
|
||||
from sphinx import addnodes, version_info
|
||||
from sphinx.addnodes import pending_xref
|
||||
from sphinx.domains.std import StandardDomain
|
||||
from sphinx.locale import __
|
||||
from sphinx.transforms.post_transforms import ReferencesResolver
|
||||
from sphinx.util import docname_join, logging
|
||||
from sphinx.util.nodes import clean_astext, make_refnode
|
||||
|
||||
from myst_parser._compat import findall
|
||||
|
||||
try:
|
||||
from sphinx.errors import NoUri
|
||||
except ImportError:
|
||||
# sphinx < 2.1
|
||||
from sphinx.environment import NoUri # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MystReferenceResolver(ReferencesResolver):
|
||||
"""Resolves cross-references on doctrees.
|
||||
|
||||
Overrides default sphinx implementation, to allow for nested syntax
|
||||
"""
|
||||
|
||||
default_priority = 9 # higher priority than ReferencesResolver (10)
|
||||
|
||||
def run(self, **kwargs: Any) -> None:
|
||||
self.document: document
|
||||
for node in findall(self.document)(addnodes.pending_xref):
|
||||
if node["reftype"] != "myst":
|
||||
continue
|
||||
|
||||
contnode = cast(nodes.TextElement, node[0].deepcopy())
|
||||
newnode = None
|
||||
|
||||
target = node["reftarget"]
|
||||
refdoc = node.get("refdoc", self.env.docname)
|
||||
domain = None
|
||||
|
||||
try:
|
||||
newnode = self.resolve_myst_ref(refdoc, node, contnode)
|
||||
if newnode is None:
|
||||
# no new node found? try the missing-reference event
|
||||
# but first we change the the reftype to 'any'
|
||||
# this means it is picked up by extensions like intersphinx
|
||||
node["reftype"] = "any"
|
||||
try:
|
||||
newnode = self.app.emit_firstresult(
|
||||
"missing-reference",
|
||||
self.env,
|
||||
node,
|
||||
contnode,
|
||||
**(
|
||||
{"allowed_exceptions": (NoUri,)}
|
||||
if version_info[0] > 2
|
||||
else {}
|
||||
),
|
||||
)
|
||||
finally:
|
||||
node["reftype"] = "myst"
|
||||
# still not found? warn if node wishes to be warned about or
|
||||
# we are in nit-picky mode
|
||||
if newnode is None:
|
||||
node["refdomain"] = ""
|
||||
# TODO ideally we would override the warning message here,
|
||||
# to show the [ref.myst] for suppressing warning
|
||||
self.warn_missing_reference(
|
||||
refdoc, node["reftype"], target, node, domain
|
||||
)
|
||||
except NoUri:
|
||||
newnode = contnode
|
||||
|
||||
node.replace_self(newnode or contnode)
|
||||
|
||||
def resolve_myst_ref(
|
||||
self, refdoc: str, node: pending_xref, contnode: Element
|
||||
) -> Element:
|
||||
"""Resolve reference generated by the "myst" role; ``[text](reference)``.
|
||||
|
||||
This builds on the sphinx ``any`` role to also resolve:
|
||||
|
||||
- Document references with extensions; ``[text](./doc.md)``
|
||||
- Document references with anchors with anchors; ``[text](./doc.md#target)``
|
||||
- Nested syntax for explicit text with std:doc and std:ref;
|
||||
``[**nested**](reference)``
|
||||
|
||||
"""
|
||||
target = node["reftarget"] # type: str
|
||||
results = [] # type: List[Tuple[str, Element]]
|
||||
|
||||
res_anchor = self._resolve_anchor(node, refdoc)
|
||||
if res_anchor:
|
||||
results.append(("std:doc", res_anchor))
|
||||
else:
|
||||
# if we've already found an anchored doc,
|
||||
# don't search in the std:ref/std:doc (leads to duplication)
|
||||
|
||||
# resolve standard references
|
||||
res = self._resolve_ref_nested(node, refdoc)
|
||||
if res:
|
||||
results.append(("std:ref", res))
|
||||
|
||||
# resolve doc names
|
||||
res = self._resolve_doc_nested(node, refdoc)
|
||||
if res:
|
||||
results.append(("std:doc", res))
|
||||
|
||||
# get allowed domains for referencing
|
||||
ref_domains = self.env.config.myst_ref_domains
|
||||
|
||||
assert self.app.builder
|
||||
|
||||
# next resolve for any other standard reference objects
|
||||
if ref_domains is None or "std" in ref_domains:
|
||||
stddomain = cast(StandardDomain, self.env.get_domain("std"))
|
||||
for objtype in stddomain.object_types:
|
||||
key = (objtype, target)
|
||||
if objtype == "term":
|
||||
key = (objtype, target.lower())
|
||||
if key in stddomain.objects:
|
||||
docname, labelid = stddomain.objects[key]
|
||||
domain_role = "std:" + stddomain.role_for_objtype(objtype)
|
||||
ref_node = make_refnode(
|
||||
self.app.builder, refdoc, docname, labelid, contnode
|
||||
)
|
||||
results.append((domain_role, ref_node))
|
||||
|
||||
# finally resolve for any other type of allowed reference domain
|
||||
for domain in self.env.domains.values():
|
||||
if domain.name == "std":
|
||||
continue # we did this one already
|
||||
if ref_domains is not None and domain.name not in ref_domains:
|
||||
continue
|
||||
try:
|
||||
results.extend(
|
||||
domain.resolve_any_xref(
|
||||
self.env, refdoc, self.app.builder, target, node, contnode
|
||||
)
|
||||
)
|
||||
except NotImplementedError:
|
||||
# the domain doesn't yet support the new interface
|
||||
# we have to manually collect possible references (SLOW)
|
||||
if not (getattr(domain, "__module__", "").startswith("sphinx.")):
|
||||
logger.warning(
|
||||
f"Domain '{domain.__module__}::{domain.name}' has not "
|
||||
"implemented a `resolve_any_xref` method [myst.domains]",
|
||||
type="myst",
|
||||
subtype="domains",
|
||||
once=True,
|
||||
)
|
||||
for role in domain.roles:
|
||||
res = domain.resolve_xref(
|
||||
self.env, refdoc, self.app.builder, role, target, node, contnode
|
||||
)
|
||||
if res and len(res) and isinstance(res[0], nodes.Element):
|
||||
results.append((f"{domain.name}:{role}", res))
|
||||
|
||||
# now, see how many matches we got...
|
||||
if not results:
|
||||
return None
|
||||
if len(results) > 1:
|
||||
|
||||
def stringify(name, node):
|
||||
reftitle = node.get("reftitle", node.astext())
|
||||
return f":{name}:`{reftitle}`"
|
||||
|
||||
candidates = " or ".join(stringify(name, role) for name, role in results)
|
||||
logger.warning(
|
||||
__(
|
||||
f"more than one target found for 'myst' cross-reference {target}: "
|
||||
f"could be {candidates} [myst.ref]"
|
||||
),
|
||||
location=node,
|
||||
type="myst",
|
||||
subtype="ref",
|
||||
)
|
||||
|
||||
res_role, newnode = results[0]
|
||||
# Override "myst" class with the actual role type to get the styling
|
||||
# approximately correct.
|
||||
res_domain = res_role.split(":")[0]
|
||||
if len(newnode) > 0 and isinstance(newnode[0], nodes.Element):
|
||||
newnode[0]["classes"] = newnode[0].get("classes", []) + [
|
||||
res_domain,
|
||||
res_role.replace(":", "-"),
|
||||
]
|
||||
|
||||
return newnode
|
||||
|
||||
def _resolve_anchor(
|
||||
self, node: pending_xref, fromdocname: str
|
||||
) -> Optional[Element]:
|
||||
"""Resolve doc with anchor."""
|
||||
if self.env.config.myst_heading_anchors is None:
|
||||
# no target anchors will have been created, so we don't look for them
|
||||
return None
|
||||
target = node["reftarget"] # type: str
|
||||
if "#" not in target:
|
||||
return None
|
||||
# the link may be a heading anchor; we need to first get the relative path
|
||||
rel_path, anchor = target.rsplit("#", 1)
|
||||
rel_path = os.path.normpath(rel_path)
|
||||
if rel_path == ".":
|
||||
# anchor in the same doc as the node
|
||||
doc_path = self.env.doc2path(node.get("refdoc", fromdocname), base=False)
|
||||
else:
|
||||
# anchor in a different doc from the node
|
||||
doc_path = os.path.normpath(
|
||||
os.path.join(node.get("refdoc", fromdocname), "..", rel_path)
|
||||
)
|
||||
return self._resolve_ref_nested(node, fromdocname, doc_path + "#" + anchor)
|
||||
|
||||
def _resolve_ref_nested(
|
||||
self, node: pending_xref, fromdocname: str, target=None
|
||||
) -> Optional[Element]:
|
||||
"""This is the same as ``sphinx.domains.std._resolve_ref_xref``,
|
||||
but allows for nested syntax, rather than converting the inner node to raw text.
|
||||
"""
|
||||
stddomain = cast(StandardDomain, self.env.get_domain("std"))
|
||||
target = target or node["reftarget"].lower()
|
||||
|
||||
if node["refexplicit"]:
|
||||
# reference to anonymous label; the reference uses
|
||||
# the supplied link caption
|
||||
docname, labelid = stddomain.anonlabels.get(target, ("", ""))
|
||||
sectname = node.astext()
|
||||
innernode = nodes.inline(sectname, "")
|
||||
innernode.extend(node[0].children)
|
||||
else:
|
||||
# reference to named label; the final node will
|
||||
# contain the section name after the label
|
||||
docname, labelid, sectname = stddomain.labels.get(target, ("", "", ""))
|
||||
innernode = nodes.inline(sectname, sectname)
|
||||
|
||||
if not docname:
|
||||
return None
|
||||
|
||||
assert self.app.builder
|
||||
return make_refnode(self.app.builder, fromdocname, docname, labelid, innernode)
|
||||
|
||||
def _resolve_doc_nested(
|
||||
self, node: pending_xref, fromdocname: str
|
||||
) -> Optional[Element]:
|
||||
"""This is the same as ``sphinx.domains.std._resolve_doc_xref``,
|
||||
but allows for nested syntax, rather than converting the inner node to raw text.
|
||||
|
||||
It also allows for extensions on document names.
|
||||
"""
|
||||
# directly reference to document by source name; can be absolute or relative
|
||||
refdoc = node.get("refdoc", fromdocname)
|
||||
docname = docname_join(refdoc, node["reftarget"])
|
||||
|
||||
if docname not in self.env.all_docs:
|
||||
# try stripping known extensions from doc name
|
||||
if os.path.splitext(docname)[1] in self.env.config.source_suffix:
|
||||
docname = os.path.splitext(docname)[0]
|
||||
if docname not in self.env.all_docs:
|
||||
return None
|
||||
|
||||
if node["refexplicit"]:
|
||||
# reference with explicit title
|
||||
caption = node.astext()
|
||||
innernode = nodes.inline(caption, "", classes=["doc"])
|
||||
innernode.extend(node[0].children)
|
||||
else:
|
||||
# TODO do we want nested syntax for titles?
|
||||
caption = clean_astext(self.env.titles[docname])
|
||||
innernode = nodes.inline(caption, caption, classes=["doc"])
|
||||
|
||||
assert self.app.builder
|
||||
return make_refnode(self.app.builder, fromdocname, docname, "", innernode)
|
|
@ -0,0 +1,110 @@
|
|||
[build-system]
|
||||
requires = ["flit_core >=3.4,<4"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
|
||||
[project]
|
||||
name = "myst-parser"
|
||||
dynamic = ["version", "description"]
|
||||
authors = [{name = "Chris Sewell", email = "chrisj_sewell@hotmail.com"}]
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE"}
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Framework :: Sphinx :: Extension",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"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",
|
||||
"lexer",
|
||||
"parser",
|
||||
"development",
|
||||
"docutils",
|
||||
"sphinx",
|
||||
]
|
||||
requires-python = ">=3.7"
|
||||
dependencies = [
|
||||
"docutils>=0.15,<0.20",
|
||||
"jinja2", # required for substitutions, but let sphinx choose version
|
||||
"markdown-it-py>=1.0.0,<3.0.0",
|
||||
"mdit-py-plugins~=0.3.1",
|
||||
"pyyaml",
|
||||
"sphinx>=4,<6",
|
||||
"typing-extensions",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/executablebooks/MyST-Parser"
|
||||
Documentation = "https://myst-parser.readthedocs.io"
|
||||
|
||||
[project.optional-dependencies]
|
||||
code_style = ["pre-commit~=2.12"]
|
||||
# for use with "linkify" extension
|
||||
linkify = ["linkify-it-py~=1.0"]
|
||||
# Note: This is only required for internal use
|
||||
rtd = [
|
||||
"ipython",
|
||||
"sphinx-book-theme",
|
||||
"sphinx-design",
|
||||
"sphinxext-rediraffe~=0.2.7",
|
||||
"sphinxcontrib.mermaid~=0.7.1",
|
||||
"sphinxext-opengraph~=0.6.3",
|
||||
]
|
||||
testing = [
|
||||
"beautifulsoup4",
|
||||
"coverage[toml]",
|
||||
"pytest>=6,<7",
|
||||
"pytest-cov",
|
||||
"pytest-regressions",
|
||||
"pytest-param-files~=0.3.4",
|
||||
"sphinx-pytest",
|
||||
"sphinx<5.2", # TODO 5.2 changes the attributes of desc/desc_signature nodes
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
myst-anchors = "myst_parser.cli:print_anchors"
|
||||
myst-docutils-html = "myst_parser.parsers.docutils_:cli_html"
|
||||
myst-docutils-html5 = "myst_parser.parsers.docutils_:cli_html5"
|
||||
myst-docutils-latex = "myst_parser.parsers.docutils_:cli_latex"
|
||||
myst-docutils-xml = "myst_parser.parsers.docutils_:cli_xml"
|
||||
myst-docutils-pseudoxml = "myst_parser.parsers.docutils_:cli_pseudoxml"
|
||||
|
||||
[tool.flit.module]
|
||||
name = "myst_parser"
|
||||
|
||||
[tool.flit.sdist]
|
||||
exclude = [
|
||||
"docs/",
|
||||
"tests/",
|
||||
]
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
known_first_party = ["myst_parser", "tests"]
|
||||
known_third_party = ["docutils", "markdown_it", "sphinx"]
|
||||
# Group first party and local folder imports together
|
||||
no_lines_before = "LOCALFOLDER"
|
||||
|
||||
[tool.mypy]
|
||||
show_error_codes = true
|
||||
check_untyped_defs = true
|
||||
strict_equality = true
|
||||
no_implicit_optional = true
|
||||
warn_unused_ignores = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = ["docutils.*", "yaml.*"]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.coverage.run]
|
||||
omit = ["*/_docs.py"]
|
|
@ -0,0 +1,15 @@
|
|||
from unittest import mock
|
||||
|
||||
from myst_parser.cli import print_anchors
|
||||
|
||||
|
||||
def test_print_anchors():
|
||||
from io import StringIO
|
||||
|
||||
in_stream = StringIO("# a\n\n## b\n\ntext")
|
||||
out_stream = StringIO()
|
||||
with mock.patch("sys.stdin", in_stream):
|
||||
with mock.patch("sys.stdout", out_stream):
|
||||
print_anchors(["-l", "1"])
|
||||
out_stream.seek(0)
|
||||
assert out_stream.read().strip() == '<h1 id="a"></h1>'
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
REPO="https://github.com/commonmark/CommonMark.git"
|
||||
VERSION="0.29"
|
||||
|
||||
function main {
|
||||
echo "Cloning from repo: $REPO..."
|
||||
git clone --quiet $REPO
|
||||
|
||||
echo "Using version $VERSION..."
|
||||
cd "CommonMark"
|
||||
git checkout --quiet $VERSION
|
||||
|
||||
echo "Dumping tests file..."
|
||||
python3 "test/spec_tests.py" --dump-tests > "../commonmark.json"
|
||||
|
||||
echo "Cleaning up..."
|
||||
cd ..
|
||||
rm -rf CommonMark
|
||||
|
||||
echo "Done."
|
||||
}
|
||||
|
||||
main
|
|
@ -0,0 +1,44 @@
|
|||
"""In this module tests are run against the full test set,
|
||||
provided by https://github.com/commonmark/CommonMark.git.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from markdown_it.renderer import RendererHTML
|
||||
|
||||
from myst_parser.config.main import MdParserConfig
|
||||
from myst_parser.parsers.mdit import create_md_parser
|
||||
|
||||
with open(
|
||||
os.path.join(os.path.dirname(__file__), "commonmark.json"), encoding="utf8"
|
||||
) as fin:
|
||||
tests = json.load(fin)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("entry", tests)
|
||||
def test_commonmark(entry):
|
||||
if entry["example"] == 14:
|
||||
# This is just a test that +++ are not parsed as thematic breaks
|
||||
pytest.skip("Expects '+++' to be unconverted (not block break).")
|
||||
if entry["example"] in [66, 68]:
|
||||
# Front matter is supported by numerous Markdown flavours,
|
||||
# but not strictly CommonMark,
|
||||
# see: https://talk.commonmark.org/t/metadata-in-documents/721/86
|
||||
pytest.skip(
|
||||
"Thematic breaks on the first line conflict with front matter syntax"
|
||||
)
|
||||
test_case = entry["markdown"]
|
||||
md = create_md_parser(MdParserConfig(), RendererHTML)
|
||||
output = md.render(test_case)
|
||||
|
||||
if entry["example"] == 593:
|
||||
# this doesn't have any bearing on the output
|
||||
output = output.replace("mailto", "MAILTO")
|
||||
if entry["example"] in [187, 209, 210]:
|
||||
# this doesn't have any bearing on the output
|
||||
output = output.replace(
|
||||
"<blockquote></blockquote>", "<blockquote>\n</blockquote>"
|
||||
)
|
||||
|
||||
assert output == entry["html"]
|
|
@ -0,0 +1,116 @@
|
|||
import io
|
||||
from dataclasses import dataclass, field, fields
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
from docutils import VersionInfo, __version_info__
|
||||
from typing_extensions import Literal
|
||||
|
||||
from myst_parser.mdit_to_docutils.base import make_document
|
||||
from myst_parser.parsers.docutils_ import (
|
||||
Parser,
|
||||
attr_to_optparse_option,
|
||||
cli_html,
|
||||
cli_html5,
|
||||
cli_latex,
|
||||
cli_pseudoxml,
|
||||
cli_xml,
|
||||
)
|
||||
|
||||
|
||||
def test_attr_to_optparse_option():
|
||||
@dataclass
|
||||
class Config:
|
||||
name: Literal["a"] = field(default="default")
|
||||
|
||||
output = attr_to_optparse_option(fields(Config)[0], "default")
|
||||
assert len(output) == 3
|
||||
|
||||
|
||||
def test_parser():
|
||||
"""Test calling `Parser.parse` directly."""
|
||||
parser = Parser()
|
||||
document = make_document(parser_cls=Parser)
|
||||
parser.parse("something", document)
|
||||
assert (
|
||||
document.pformat().strip()
|
||||
== '<document source="notset">\n <paragraph>\n something'
|
||||
)
|
||||
|
||||
|
||||
def test_cli_html(monkeypatch, capsys):
|
||||
monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
|
||||
cli_html([])
|
||||
captured = capsys.readouterr()
|
||||
assert not captured.err
|
||||
assert "text" in captured.out
|
||||
|
||||
|
||||
def test_cli_html5(monkeypatch, capsys):
|
||||
monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
|
||||
cli_html5([])
|
||||
captured = capsys.readouterr()
|
||||
assert not captured.err
|
||||
assert "text" in captured.out
|
||||
|
||||
|
||||
def test_cli_latex(monkeypatch, capsys):
|
||||
monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
|
||||
cli_latex([])
|
||||
captured = capsys.readouterr()
|
||||
assert not captured.err
|
||||
assert "text" in captured.out
|
||||
|
||||
|
||||
def test_cli_xml(monkeypatch, capsys):
|
||||
monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
|
||||
cli_xml([])
|
||||
captured = capsys.readouterr()
|
||||
assert not captured.err
|
||||
assert "text" in captured.out
|
||||
|
||||
|
||||
def test_cli_pseudoxml(monkeypatch, capsys):
|
||||
monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
|
||||
cli_pseudoxml([])
|
||||
captured = capsys.readouterr()
|
||||
assert not captured.err
|
||||
assert "text" in captured.out
|
||||
|
||||
|
||||
def test_help_text():
|
||||
"""Test retrieving settings help text."""
|
||||
from docutils.frontend import OptionParser
|
||||
|
||||
stream = io.StringIO()
|
||||
OptionParser(components=(Parser,)).print_help(stream)
|
||||
assert "MyST options" in stream.getvalue()
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
__version_info__ < VersionInfo(0, 17, 0, "final", 0, True),
|
||||
reason="parser option added in docutils 0.17",
|
||||
)
|
||||
def test_include_from_rst(tmp_path):
|
||||
"""Test including a MyST file from within an RST file."""
|
||||
from docutils.parsers.rst import Parser as RSTParser
|
||||
|
||||
include_path = tmp_path.joinpath("include.md")
|
||||
include_path.write_text("# Title")
|
||||
|
||||
parser = RSTParser()
|
||||
document = make_document(parser_cls=RSTParser)
|
||||
parser.parse(
|
||||
f".. include:: {include_path}\n :parser: myst_parser.docutils_", document
|
||||
)
|
||||
assert (
|
||||
document.pformat().strip()
|
||||
== dedent(
|
||||
"""\
|
||||
<document source="notset">
|
||||
<section ids="title" names="title">
|
||||
<title>
|
||||
Title
|
||||
"""
|
||||
).strip()
|
||||
)
|
|
@ -0,0 +1,124 @@
|
|||
tags
|
||||
.
|
||||
<html>
|
||||
<head>
|
||||
<title class="a b" other="x">Title of the document</title>
|
||||
</head>
|
||||
<body>
|
||||
The content of the document......
|
||||
</body>
|
||||
</html>
|
||||
.
|
||||
Root('')
|
||||
Tag('html')
|
||||
Data('\n')
|
||||
Tag('head')
|
||||
Data('\n')
|
||||
Tag('title', {'class': 'a b', 'other': 'x'})
|
||||
Data('Title of the docu...')
|
||||
Data('\n')
|
||||
Data('\n')
|
||||
Tag('body')
|
||||
Data('\nThe content of t...')
|
||||
Data('\n')
|
||||
Data('\n')
|
||||
.
|
||||
|
||||
un-closed tags
|
||||
.
|
||||
<div class="a">
|
||||
<div class="b">
|
||||
.
|
||||
Root('')
|
||||
Tag('div', {'class': 'a'})
|
||||
Data('\n')
|
||||
Tag('div', {'class': 'b'})
|
||||
Data('\n')
|
||||
.
|
||||
|
||||
xtag
|
||||
.
|
||||
<img src="img_girl.jpg" alt="Girl in a jacket" width="500" height="600"/>
|
||||
.
|
||||
Root('')
|
||||
XTag('img', {'src': 'img_girl.jpg', 'alt': 'Girl in a jacket', 'width': '500', 'height': '600'})
|
||||
Data('\n')
|
||||
.
|
||||
|
||||
data
|
||||
.
|
||||
a
|
||||
.
|
||||
Root('')
|
||||
Data('a\n')
|
||||
.
|
||||
|
||||
declaration
|
||||
.
|
||||
<!DOCTYPE html>
|
||||
.
|
||||
Root('')
|
||||
Declaration('DOCTYPE html')
|
||||
Data('\n')
|
||||
.
|
||||
|
||||
process information
|
||||
.
|
||||
<?xml-stylesheet ?>
|
||||
.
|
||||
Root('')
|
||||
Pi('xml-stylesheet ?')
|
||||
Data('\n')
|
||||
.
|
||||
|
||||
entities
|
||||
.
|
||||
&
|
||||
|
||||
{
|
||||
.
|
||||
Root('')
|
||||
Entity('amp')
|
||||
Data('\n\n')
|
||||
Char('123')
|
||||
Data('\n')
|
||||
.
|
||||
|
||||
comments
|
||||
.
|
||||
<!--This is a comment. Comments are not displayed in the browser
|
||||
-->
|
||||
.
|
||||
Root('')
|
||||
Comment('This is a comment...')
|
||||
Data('\n')
|
||||
.
|
||||
|
||||
admonition
|
||||
.
|
||||
<div class="admonition tip alert alert-warning">
|
||||
<div class="admonition-title" style="font-weight: bold;">Tip</div>
|
||||
parameter allows to get a deterministic results even if we
|
||||
use some random process (i.e. data shuffling).
|
||||
</div>
|
||||
.
|
||||
Root('')
|
||||
Tag('div', {'class': 'admonition tip alert alert-warning'})
|
||||
Data('\n')
|
||||
Tag('div', {'class': 'admonition-title', 'style': 'font-weight: bold;'})
|
||||
Data('Tip')
|
||||
Data('\nparameter allows...')
|
||||
Data('\n')
|
||||
.
|
||||
|
||||
image
|
||||
.
|
||||
<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
|
||||
<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="300px">
|
||||
.
|
||||
Root('')
|
||||
VoidTag('img', {'src': 'img/fun-fish.png', 'alt': 'fishy', 'class': 'bg-primary mb-1', 'width': '200px'})
|
||||
Data('\n')
|
||||
VoidTag('img', {'src': 'img/fun-fish.png', 'alt': 'fishy', 'class': 'bg-primary mb-1', 'width': '300px'})
|
||||
Data('\n')
|
||||
.
|
|
@ -0,0 +1,87 @@
|
|||
tags
|
||||
.
|
||||
<html>
|
||||
<head>
|
||||
<title class="a b" other="x">Title of the document</title>
|
||||
</head>
|
||||
<body>
|
||||
The content of the document......
|
||||
</body>
|
||||
</html>
|
||||
.
|
||||
<html>
|
||||
<head>
|
||||
<title class="a b" other="x">Title of the document</title>
|
||||
</head>
|
||||
<body>
|
||||
The content of the document......
|
||||
</body>
|
||||
</html>
|
||||
.
|
||||
|
||||
un-closed tags
|
||||
.
|
||||
<div class="a">
|
||||
<div class="b">
|
||||
.
|
||||
<div class="a">
|
||||
<div class="b">
|
||||
</div></div>
|
||||
.
|
||||
|
||||
xtag
|
||||
.
|
||||
<img src="img_girl.jpg" alt="Girl in a jacket" width="500" height="600"/>
|
||||
.
|
||||
<img src="img_girl.jpg" alt="Girl in a jacket" width="500" height="600"/>
|
||||
.
|
||||
|
||||
data
|
||||
.
|
||||
a
|
||||
.
|
||||
a
|
||||
.
|
||||
|
||||
declaration
|
||||
.
|
||||
<!DOCTYPE html>
|
||||
.
|
||||
<!DOCTYPE html>
|
||||
.
|
||||
|
||||
process information
|
||||
.
|
||||
<?xml-stylesheet ?>
|
||||
.
|
||||
<?xml-stylesheet ?>
|
||||
.
|
||||
|
||||
entities
|
||||
.
|
||||
&
|
||||
|
||||
{
|
||||
.
|
||||
&
|
||||
|
||||
{
|
||||
.
|
||||
|
||||
comments
|
||||
.
|
||||
<!--This is a comment. Comments are not displayed in the browser
|
||||
-->
|
||||
.
|
||||
<!--This is a comment. Comments are not displayed in the browser
|
||||
-->
|
||||
.
|
||||
|
||||
image
|
||||
.
|
||||
<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
|
||||
<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="300px">
|
||||
.
|
||||
<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
|
||||
<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="300px">
|
||||
.
|
|
@ -0,0 +1,199 @@
|
|||
empty
|
||||
.
|
||||
|
||||
.
|
||||
<container>
|
||||
<raw format="html" xml:space="preserve">
|
||||
.
|
||||
|
||||
text
|
||||
.
|
||||
abc
|
||||
.
|
||||
<container>
|
||||
<raw format="html" xml:space="preserve">
|
||||
abc
|
||||
.
|
||||
|
||||
normal HTML
|
||||
.
|
||||
<div></div>
|
||||
.
|
||||
<container>
|
||||
<raw format="html" xml:space="preserve">
|
||||
<div></div>
|
||||
.
|
||||
|
||||
image no src
|
||||
.
|
||||
<img>
|
||||
.
|
||||
<container>
|
||||
<system_message>
|
||||
<paragraph>
|
||||
error
|
||||
.
|
||||
|
||||
image
|
||||
.
|
||||
<img src="a">
|
||||
.
|
||||
<container>
|
||||
<Element first="a" name="image" position="0">
|
||||
.
|
||||
|
||||
image unknown attribute
|
||||
.
|
||||
<img src="a" other="b">
|
||||
.
|
||||
<container>
|
||||
<Element first="a" name="image" position="0">
|
||||
.
|
||||
|
||||
image known attributes
|
||||
.
|
||||
<img src="a" height="200px" class="a b" name="b" align="left">
|
||||
.
|
||||
<container>
|
||||
<Element first="a" name="image" position="0">
|
||||
:align: left
|
||||
:class: a b
|
||||
:height: 200px
|
||||
:name: b
|
||||
.
|
||||
|
||||
multiple images
|
||||
.
|
||||
<img src="a">
|
||||
<img src="b">
|
||||
.
|
||||
<container>
|
||||
<Element first="a" name="image" position="0">
|
||||
<Element first="b" name="image" position="0">
|
||||
.
|
||||
|
||||
admonition no close
|
||||
.
|
||||
<div class="admonition">
|
||||
.
|
||||
<container>
|
||||
<Element first="Note" name="admonition" position="0">
|
||||
:class: admonition
|
||||
.
|
||||
|
||||
admonition
|
||||
.
|
||||
<div class="admonition">
|
||||
</div>
|
||||
.
|
||||
<container>
|
||||
<Element first="Note" name="admonition" position="0">
|
||||
:class: admonition
|
||||
.
|
||||
|
||||
admonition attributes
|
||||
.
|
||||
<div class="admonition tip" name="aname">
|
||||
</div>
|
||||
.
|
||||
<container>
|
||||
<Element first="Note" name="admonition" position="0">
|
||||
:class: admonition tip
|
||||
:name: aname
|
||||
.
|
||||
|
||||
admonition div-title
|
||||
.
|
||||
<div class="admonition tip">
|
||||
<div class="title">*Hallo*</div>
|
||||
.
|
||||
<container>
|
||||
<Element first="*Hallo*" name="admonition" position="0">
|
||||
:class: admonition tip
|
||||
.
|
||||
|
||||
admonition p-title
|
||||
.
|
||||
<div class="admonition tip">
|
||||
<p class="title">*Hallo*</p>
|
||||
.
|
||||
<container>
|
||||
<Element first="*Hallo*" name="admonition" position="0">
|
||||
:class: admonition tip
|
||||
.
|
||||
|
||||
admonition title+content
|
||||
.
|
||||
<div class="admonition">
|
||||
<div class="title">*Hallo*</div>
|
||||
content
|
||||
</div>
|
||||
.
|
||||
<container>
|
||||
<Element first="*Hallo*" name="admonition" position="0">
|
||||
:class: admonition
|
||||
|
||||
content
|
||||
.
|
||||
|
||||
admonition multiple
|
||||
.
|
||||
<div class="admonition">
|
||||
<div class="title">first</div>
|
||||
content 1
|
||||
</div>
|
||||
<div class="admonition">
|
||||
<div class="title">second</div>
|
||||
content 2
|
||||
</div>
|
||||
.
|
||||
<container>
|
||||
<Element first="first" name="admonition" position="0">
|
||||
:class: admonition
|
||||
|
||||
content 1
|
||||
<Element first="second" name="admonition" position="0">
|
||||
:class: admonition
|
||||
|
||||
content 2
|
||||
.
|
||||
|
||||
admonition with paragraphs
|
||||
.
|
||||
<div class="admonition">
|
||||
<p>paragraph 1</p>
|
||||
<p>paragraph 2</p>
|
||||
</div>
|
||||
.
|
||||
<container>
|
||||
<Element first="Note" name="admonition" position="0">
|
||||
:class: admonition
|
||||
|
||||
paragraph 1
|
||||
|
||||
paragraph 2
|
||||
.
|
||||
|
||||
nested
|
||||
.
|
||||
<div class="admonition">
|
||||
<p>Some **content**</p>
|
||||
<div class="admonition tip">
|
||||
<div class="title">A *title*</div>
|
||||
<p>Paragraph 1</p>
|
||||
<p>Paragraph 2</p>
|
||||
</div>
|
||||
</div>
|
||||
.
|
||||
<container>
|
||||
<Element first="Note" name="admonition" position="0">
|
||||
:class: admonition
|
||||
|
||||
Some **content**
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="title">A *title*</div>
|
||||
<p>Paragraph 1</p>
|
||||
<p>Paragraph 2</p>
|
||||
</div>
|
||||
.
|
|
@ -0,0 +1,35 @@
|
|||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from docutils import nodes
|
||||
|
||||
from myst_parser.config.main import MdParserConfig
|
||||
from myst_parser.mdit_to_docutils.html_to_nodes import html_to_nodes
|
||||
|
||||
FIXTURE_PATH = Path(__file__).parent
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_renderer():
|
||||
def _run_directive(name: str, first_line: str, content: str, position: int):
|
||||
node = nodes.Element(name=name, first=first_line, position=position)
|
||||
node += nodes.Text(content)
|
||||
return [node]
|
||||
|
||||
return Mock(
|
||||
md_config=MdParserConfig(enable_extensions=["html_image", "html_admonition"]),
|
||||
document={"source": "source"},
|
||||
reporter=Mock(
|
||||
warning=Mock(return_value=nodes.system_message("warning")),
|
||||
error=Mock(return_value=nodes.system_message("error")),
|
||||
),
|
||||
run_directive=_run_directive,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "html_to_nodes.md")
|
||||
def test_html_to_nodes(file_params, mock_renderer):
|
||||
output = nodes.container()
|
||||
output += html_to_nodes(file_params.content, line_number=0, renderer=mock_renderer)
|
||||
file_params.assert_expected(output.pformat(), rstrip=True)
|
|
@ -0,0 +1,41 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from myst_parser.parsers.parse_html import tokenize_html
|
||||
|
||||
FIXTURE_PATH = Path(__file__).parent
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "html_ast.md")
|
||||
def test_html_ast(file_params):
|
||||
tokens = "\n".join(
|
||||
repr(t) for t in tokenize_html(file_params.content).walk(include_self=True)
|
||||
)
|
||||
file_params.assert_expected(tokens, rstrip=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "html_round_trip.md")
|
||||
def test_html_round_trip(file_params):
|
||||
ast = tokenize_html(file_params.content)
|
||||
file_params.assert_expected(str(ast), rstrip=True)
|
||||
|
||||
|
||||
def test_render_overrides():
|
||||
text = "<div><abc></abc></div>"
|
||||
ast = tokenize_html(text)
|
||||
|
||||
def _render_abc(element, *args, **kwargs):
|
||||
return "hallo"
|
||||
|
||||
output = ast.render(tag_overrides={"abc": _render_abc})
|
||||
assert output == "<div>hallo</div>"
|
||||
|
||||
|
||||
def test_ast_find():
|
||||
text = (
|
||||
'<div class="a"><div class="c"><x/><y>z</y><div class="a b"></div></div></div>'
|
||||
)
|
||||
ast = tokenize_html(text)
|
||||
found = list(ast.find("div", classes=["a"]))
|
||||
assert [e.attrs.classes for e in found] == [["a"], ["a", "b"]]
|
|
@ -0,0 +1,52 @@
|
|||
Single Line:
|
||||
.
|
||||
\begin{equation} a \end{equation}
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<target ids="equation-mock-uuid">
|
||||
<math_block classes="amsmath" docname="index" label="mock-uuid" nowrap="True" number="1" xml:space="preserve">
|
||||
\begin{equation} a \end{equation}
|
||||
.
|
||||
|
||||
Multi Line:
|
||||
.
|
||||
\begin{equation}
|
||||
a
|
||||
\end{equation}
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<target ids="equation-mock-uuid">
|
||||
<math_block classes="amsmath" docname="index" label="mock-uuid" nowrap="True" number="1" xml:space="preserve">
|
||||
\begin{equation}
|
||||
a
|
||||
\end{equation}
|
||||
.
|
||||
|
||||
Multi Line no number:
|
||||
.
|
||||
\begin{equation*}
|
||||
a
|
||||
\end{equation*}
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<math_block classes="amsmath" nowrap="True" number="True" xml:space="preserve">
|
||||
\begin{equation*}
|
||||
a
|
||||
\end{equation*}
|
||||
.
|
||||
|
||||
In list:
|
||||
.
|
||||
- \begin{equation}
|
||||
a = 1
|
||||
\end{equation}
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<target ids="equation-mock-uuid">
|
||||
<math_block classes="amsmath" docname="index" label="mock-uuid" nowrap="True" number="1" xml:space="preserve">
|
||||
\begin{equation}
|
||||
a = 1
|
||||
\end{equation}
|
||||
.
|
|
@ -0,0 +1,31 @@
|
|||
Basic note:
|
||||
.
|
||||
::: {note}
|
||||
*hallo*
|
||||
:::
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<note>
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
hallo
|
||||
.
|
||||
|
||||
Admonition with options:
|
||||
.
|
||||
::: {admonition} A **title**
|
||||
:class: other
|
||||
|
||||
*hallo*
|
||||
:::
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<admonition classes="other">
|
||||
<title>
|
||||
A
|
||||
<strong>
|
||||
title
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
hallo
|
||||
.
|
|
@ -0,0 +1,56 @@
|
|||
Simple:
|
||||
.
|
||||
Term **1**
|
||||
|
||||
: Definition *1*
|
||||
|
||||
second paragraph
|
||||
|
||||
Term 2
|
||||
~ Definition 2a
|
||||
~ Definition 2b
|
||||
|
||||
Term 3
|
||||
: code block
|
||||
|
||||
: > quote
|
||||
|
||||
: other
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<definition_list classes="simple myst">
|
||||
<definition_list_item>
|
||||
<term>
|
||||
Term
|
||||
<strong>
|
||||
1
|
||||
<definition>
|
||||
<paragraph>
|
||||
Definition
|
||||
<emphasis>
|
||||
1
|
||||
<paragraph>
|
||||
second paragraph
|
||||
<definition_list_item>
|
||||
<term>
|
||||
Term 2
|
||||
<definition>
|
||||
<paragraph>
|
||||
Definition 2a
|
||||
<definition>
|
||||
<paragraph>
|
||||
Definition 2b
|
||||
<definition_list_item>
|
||||
<term>
|
||||
Term 3
|
||||
<definition>
|
||||
<literal_block language="none" xml:space="preserve">
|
||||
code block
|
||||
<definition>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
quote
|
||||
<definition>
|
||||
<paragraph>
|
||||
other
|
||||
.
|
|
@ -0,0 +1,159 @@
|
|||
Test Directive 1:
|
||||
.
|
||||
```{restructuredtext-test-directive}
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=[], options={}, content: None
|
||||
.
|
||||
|
||||
Test Directive 2:
|
||||
.
|
||||
```{restructuredtext-test-directive}
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=[], options={}, content:
|
||||
<literal_block xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Test Directive 3:
|
||||
.
|
||||
```{restructuredtext-test-directive} foo
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=['foo'], options={}, content: None
|
||||
.
|
||||
|
||||
Test Directive 4:
|
||||
.
|
||||
```{restructuredtext-test-directive} foo
|
||||
bar
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=['foo'], options={}, content:
|
||||
<literal_block xml:space="preserve">
|
||||
bar
|
||||
.
|
||||
|
||||
Test Directive 5:
|
||||
.
|
||||
```{restructuredtext-test-directive} foo bar
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=['foo bar'], options={}, content: None
|
||||
.
|
||||
|
||||
Test Directive 6:
|
||||
.
|
||||
```{restructuredtext-test-directive} foo bar
|
||||
baz
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=['foo bar'], options={}, content:
|
||||
<literal_block xml:space="preserve">
|
||||
baz
|
||||
.
|
||||
|
||||
Test Directive 7:
|
||||
.
|
||||
```{restructuredtext-test-directive}
|
||||
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=[], options={}, content:
|
||||
<literal_block xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Test Directive Options 1:
|
||||
.
|
||||
```{restructuredtext-test-directive}
|
||||
---
|
||||
option1: a
|
||||
option2: b
|
||||
---
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=[], options={'option1': 'a', 'option2': 'b'}, content:
|
||||
<literal_block xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Test Directive Options 2:
|
||||
.
|
||||
```{restructuredtext-test-directive}
|
||||
:option1: a
|
||||
:option2: b
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=[], options={'option1': 'a', 'option2': 'b'}, content:
|
||||
<literal_block xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Test Directive Options Error:
|
||||
.
|
||||
```{restructuredtext-test-directive}
|
||||
:option1
|
||||
:option2: b
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="3" line="1" source="<src>/index.md" type="ERROR">
|
||||
<paragraph>
|
||||
Directive 'restructuredtext-test-directive': Invalid options YAML: mapping values are not allowed here
|
||||
in "<unicode string>", line 2, column 8:
|
||||
option2: b
|
||||
^
|
||||
<literal_block xml:space="preserve">
|
||||
:option1
|
||||
:option2: b
|
||||
foo
|
||||
.
|
||||
|
||||
Unknown Directive:
|
||||
.
|
||||
```{unknown}
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="3" line="1" source="<src>/index.md" type="ERROR">
|
||||
<paragraph>
|
||||
Unknown directive type "unknown".
|
||||
<system_message level="1" line="1" source="<src>/index.md" type="INFO">
|
||||
<paragraph>
|
||||
No directive entry for "unknown" in module "docutils.parsers.rst.languages.en".
|
||||
Trying "unknown" as canonical directive name.
|
||||
.
|
|
@ -0,0 +1,141 @@
|
|||
note: content in first line only
|
||||
.
|
||||
```{note} a
|
||||
```
|
||||
.
|
||||
arguments: []
|
||||
body:
|
||||
- a
|
||||
content_offset: 0
|
||||
options: {}
|
||||
.
|
||||
|
||||
note: content in body only
|
||||
.
|
||||
```{note}
|
||||
a
|
||||
```
|
||||
.
|
||||
arguments: []
|
||||
body:
|
||||
- a
|
||||
content_offset: 0
|
||||
options: {}
|
||||
.
|
||||
|
||||
note: content after option
|
||||
.
|
||||
```{note}
|
||||
:class: name
|
||||
a
|
||||
```
|
||||
.
|
||||
arguments: []
|
||||
body:
|
||||
- a
|
||||
content_offset: 1
|
||||
options:
|
||||
class:
|
||||
- name
|
||||
.
|
||||
|
||||
note: content after option with new line
|
||||
.
|
||||
```{note}
|
||||
:class: name
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
arguments: []
|
||||
body:
|
||||
- a
|
||||
content_offset: 2
|
||||
options:
|
||||
class:
|
||||
- name
|
||||
.
|
||||
|
||||
note: content after yaml option
|
||||
.
|
||||
```{note}
|
||||
---
|
||||
class: name
|
||||
---
|
||||
a
|
||||
```
|
||||
.
|
||||
arguments: []
|
||||
body:
|
||||
- a
|
||||
content_offset: 3
|
||||
options:
|
||||
class:
|
||||
- name
|
||||
.
|
||||
|
||||
note: content in first line and body
|
||||
.
|
||||
```{note} first line
|
||||
:class: tip
|
||||
|
||||
body line
|
||||
```
|
||||
.
|
||||
arguments: []
|
||||
body:
|
||||
- first line
|
||||
- ''
|
||||
- body line
|
||||
content_offset: 1
|
||||
options:
|
||||
class:
|
||||
- tip
|
||||
.
|
||||
|
||||
admonition: no options, no new line
|
||||
.
|
||||
```{admonition} first line
|
||||
body line
|
||||
```
|
||||
.
|
||||
arguments:
|
||||
- first line
|
||||
body:
|
||||
- body line
|
||||
content_offset: 0
|
||||
options: {}
|
||||
.
|
||||
|
||||
admonition: no options, new line
|
||||
.
|
||||
```{admonition} first line
|
||||
|
||||
body line
|
||||
```
|
||||
.
|
||||
arguments:
|
||||
- first line
|
||||
body:
|
||||
- body line
|
||||
content_offset: 1
|
||||
options: {}
|
||||
.
|
||||
|
||||
admonition: with options
|
||||
.
|
||||
```{admonition} first line
|
||||
:class: tip
|
||||
|
||||
body line
|
||||
```
|
||||
.
|
||||
arguments:
|
||||
- first line
|
||||
body:
|
||||
- body line
|
||||
content_offset: 2
|
||||
options:
|
||||
class:
|
||||
- tip
|
||||
.
|
|
@ -0,0 +1,436 @@
|
|||
--------------------------------
|
||||
[attention] (`docutils.parsers.rst.directives.admonitions.Attention`):
|
||||
.
|
||||
```{attention}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<attention>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[caution] (`docutils.parsers.rst.directives.admonitions.Caution`):
|
||||
.
|
||||
```{caution}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<caution>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[danger] (`docutils.parsers.rst.directives.admonitions.Danger`):
|
||||
.
|
||||
```{danger}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<danger>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[error] (`docutils.parsers.rst.directives.admonitions.Error`):
|
||||
.
|
||||
```{error}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<error>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[important] (`docutils.parsers.rst.directives.admonitions.Important`):
|
||||
.
|
||||
```{important}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<important>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[note] (`docutils.parsers.rst.directives.admonitions.Note`):
|
||||
.
|
||||
```{note}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<note>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[tip] (`docutils.parsers.rst.directives.admonitions.Tip`):
|
||||
.
|
||||
```{tip}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<tip>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[hint] (`docutils.parsers.rst.directives.admonitions.Hint`):
|
||||
.
|
||||
```{hint}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<hint>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[warning] (`docutils.parsers.rst.directives.admonitions.Warning`):
|
||||
.
|
||||
```{warning}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<warning>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[admonition] (`docutils.parsers.rst.directives.admonitions.Admonition`):
|
||||
.
|
||||
```{admonition} myclass
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<admonition classes="admonition-myclass">
|
||||
<title>
|
||||
myclass
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[sidebar] (`docutils.parsers.rst.directives.body.Sidebar`):
|
||||
.
|
||||
```{sidebar} sidebar title
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<sidebar>
|
||||
<title>
|
||||
sidebar title
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[topic] (`docutils.parsers.rst.directives.body.Topic`):
|
||||
.
|
||||
```{topic} Topic Title
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<topic>
|
||||
<title>
|
||||
Topic Title
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[line-block] (`docutils.parsers.rst.directives.body.LineBlock`) SKIP: MockingError: MockState has not yet implemented attribute 'nest_line_block_lines'
|
||||
.
|
||||
```{line-block}
|
||||
|
||||
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[parsed-literal] (`docutils.parsers.rst.directives.body.ParsedLiteral`):
|
||||
.
|
||||
```{parsed-literal}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<literal_block xml:space="preserve">
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[rubric] (`docutils.parsers.rst.directives.body.Rubric`):
|
||||
.
|
||||
```{rubric} Rubric Title
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<rubric>
|
||||
Rubric Title
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[epigraph] (`docutils.parsers.rst.directives.body.Epigraph`):
|
||||
.
|
||||
```{epigraph}
|
||||
|
||||
a
|
||||
|
||||
-- attribution
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<block_quote classes="epigraph">
|
||||
<paragraph>
|
||||
a
|
||||
<attribution>
|
||||
attribution
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[highlights] (`docutils.parsers.rst.directives.body.Highlights`):
|
||||
.
|
||||
```{highlights}
|
||||
|
||||
a
|
||||
|
||||
-- attribution
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<block_quote classes="highlights">
|
||||
<paragraph>
|
||||
a
|
||||
<attribution>
|
||||
attribution
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[pull-quote] (`docutils.parsers.rst.directives.body.PullQuote`):
|
||||
.
|
||||
```{pull-quote}
|
||||
|
||||
a
|
||||
|
||||
-- attribution
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<block_quote classes="pull-quote">
|
||||
<paragraph>
|
||||
a
|
||||
<attribution>
|
||||
attribution
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[compound] (`docutils.parsers.rst.directives.body.Compound`):
|
||||
.
|
||||
```{compound}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<compound>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[container] (`docutils.parsers.rst.directives.body.Container`):
|
||||
.
|
||||
```{container}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<container>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[image] (`docutils.parsers.rst.directives.images.Image`):
|
||||
.
|
||||
```{image} path/to/image
|
||||
:alt: abc
|
||||
:name: name
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<image alt="abc" ids="name" names="name" uri="path/to/image">
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[raw] (`docutils.parsers.rst.directives.misc.Raw`):
|
||||
.
|
||||
```{raw} raw
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<raw format="raw" xml:space="preserve">
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[class] (`docutils.parsers.rst.directives.misc.Class`):
|
||||
.
|
||||
```{class} myclass
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph classes="myclass">
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[role] (`docutils.parsers.rst.directives.misc.Role`) + raw (`docutils.parsers.rst.roles.raw_role`):
|
||||
.
|
||||
```{role} raw-latex(raw)
|
||||
:format: latex
|
||||
```
|
||||
|
||||
{raw-latex}`\tag{content}`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<raw classes="raw-latex" format="latex" xml:space="preserve">
|
||||
\tag{content}
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[title] (`docutils.parsers.rst.directives.misc.Title`):
|
||||
.
|
||||
```{title} title
|
||||
```
|
||||
.
|
||||
<document source="notset" title="title">
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[restructuredtext-test-directive] (`docutils.parsers.rst.directives.misc.TestDirective`):
|
||||
.
|
||||
```{restructuredtext-test-directive}
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<system_message level="1" line="1" source="notset" type="INFO">
|
||||
<paragraph>
|
||||
Directive processed. Type="restructuredtext-test-directive", arguments=[], options={}, content: None
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[contents] (`docutils.parsers.rst.directives.parts.Contents`):
|
||||
.
|
||||
```{contents} Contents
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<topic classes="contents" ids="contents" names="contents">
|
||||
<title>
|
||||
Contents
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.parts.Contents
|
||||
.details:
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[sectnum] (`docutils.parsers.rst.directives.parts.Sectnum`):
|
||||
.
|
||||
```{sectnum}
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.parts.SectNum
|
||||
.details:
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[header] (`docutils.parsers.rst.directives.parts.Header`):
|
||||
.
|
||||
```{header}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<decoration>
|
||||
<header>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[footer] (`docutils.parsers.rst.directives.parts.Footer`):
|
||||
.
|
||||
```{footer}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<decoration>
|
||||
<footer>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[target-notes] (`docutils.parsers.rst.directives.references.TargetNotes`):
|
||||
.
|
||||
```{target-notes}
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.references.TargetNotes
|
||||
.details:
|
||||
.
|
|
@ -0,0 +1,131 @@
|
|||
--------------------------------
|
||||
[abbreviation] (`docutils.parsers.rst.roles.GenericRole`):
|
||||
.
|
||||
{abbreviation}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<abbreviation>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[acronym] (`docutils.parsers.rst.roles.GenericRole`):
|
||||
.
|
||||
{acronym}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<acronym>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[emphasis] (`docutils.parsers.rst.roles.GenericRole`):
|
||||
.
|
||||
{emphasis}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[literal] (`docutils.parsers.rst.roles.GenericRole`):
|
||||
.
|
||||
{literal}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<literal>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[strong] (`docutils.parsers.rst.roles.GenericRole`):
|
||||
.
|
||||
{strong}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<strong>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[subscript] (`docutils.parsers.rst.roles.GenericRole`):
|
||||
.
|
||||
{subscript}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<subscript>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[superscript] (`docutils.parsers.rst.roles.GenericRole`):
|
||||
.
|
||||
{superscript}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<superscript>
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[title-reference] (`docutils.parsers.rst.roles.GenericRole`):
|
||||
.
|
||||
{title-reference}`t`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<title_reference>
|
||||
t
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[pep-reference] (`docutils.parsers.rst.roles.pep_reference_role`):
|
||||
.
|
||||
{pep-reference}`0`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<reference refuri="https://peps.python.org/pep-0000">
|
||||
PEP 0
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[rfc-reference] (`docutils.parsers.rst.roles.rfc_reference_role`):
|
||||
.
|
||||
{rfc-reference}`1`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<reference refuri="https://tools.ietf.org/html/rfc1.html">
|
||||
RFC 1
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[code] (`docutils.parsers.rst.roles.code_role`):
|
||||
.
|
||||
{code}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<literal classes="code">
|
||||
a
|
||||
.
|
||||
|
||||
--------------------------------
|
||||
[math] (`docutils.parsers.rst.roles.math_role`):
|
||||
.
|
||||
{math}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<math>
|
||||
a
|
||||
.
|
|
@ -0,0 +1,794 @@
|
|||
Raw
|
||||
.
|
||||
foo
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
foo
|
||||
.
|
||||
|
||||
Hard-break
|
||||
.
|
||||
foo\
|
||||
bar
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
foo
|
||||
<raw format="html" xml:space="preserve">
|
||||
<br />
|
||||
<raw format="latex" xml:space="preserve">
|
||||
\\
|
||||
bar
|
||||
.
|
||||
|
||||
Strong:
|
||||
.
|
||||
**foo**
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<strong>
|
||||
foo
|
||||
.
|
||||
|
||||
Emphasis
|
||||
.
|
||||
*foo*
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
foo
|
||||
.
|
||||
|
||||
Escaped Emphasis:
|
||||
.
|
||||
\*foo*
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
*foo*
|
||||
.
|
||||
|
||||
Mixed Inline
|
||||
.
|
||||
a *b* **c** `abc` \\*
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
a
|
||||
<emphasis>
|
||||
b
|
||||
|
||||
<strong>
|
||||
c
|
||||
|
||||
<literal>
|
||||
abc
|
||||
\*
|
||||
.
|
||||
|
||||
Inline Code:
|
||||
.
|
||||
`foo`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<literal>
|
||||
foo
|
||||
.
|
||||
|
||||
Heading:
|
||||
.
|
||||
# foo
|
||||
.
|
||||
<document source="notset">
|
||||
<section ids="foo" names="foo">
|
||||
<title>
|
||||
foo
|
||||
.
|
||||
|
||||
Heading Levels:
|
||||
.
|
||||
# a
|
||||
## b
|
||||
### c
|
||||
# d
|
||||
.
|
||||
<document source="notset">
|
||||
<section ids="a" names="a">
|
||||
<title>
|
||||
a
|
||||
<section ids="b" names="b">
|
||||
<title>
|
||||
b
|
||||
<section ids="c" names="c">
|
||||
<title>
|
||||
c
|
||||
<section ids="d" names="d">
|
||||
<title>
|
||||
d
|
||||
.
|
||||
|
||||
Block Code:
|
||||
.
|
||||
foo
|
||||
.
|
||||
<document source="notset">
|
||||
<literal_block classes="code" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Fenced Code:
|
||||
.
|
||||
```sh
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<literal_block classes="code sh" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Fenced Code no language:
|
||||
.
|
||||
```
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<literal_block classes="code" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Fenced Code no language with trailing whitespace:
|
||||
.
|
||||
```
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<literal_block classes="code" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Image empty:
|
||||
.
|
||||
![]()
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<image alt="" uri="">
|
||||
.
|
||||
|
||||
Image with alt and title:
|
||||
.
|
||||
![alt](src "title")
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<image alt="alt" title="title" uri="src">
|
||||
.
|
||||
|
||||
Image with escapable html:
|
||||
.
|
||||
![alt](http://www.google<>.com)
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<image alt="alt" uri="http://www.google%3C%3E.com">
|
||||
.
|
||||
|
||||
Block Quote:
|
||||
.
|
||||
> *foo*
|
||||
.
|
||||
<document source="notset">
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
foo
|
||||
.
|
||||
|
||||
Bullet List:
|
||||
.
|
||||
- *foo*
|
||||
* bar
|
||||
.
|
||||
<document source="notset">
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
foo
|
||||
<bullet_list bullet="*">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
bar
|
||||
.
|
||||
|
||||
Nested Bullets
|
||||
.
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
- d
|
||||
.
|
||||
<document source="notset">
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
a
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
b
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
c
|
||||
<list_item>
|
||||
<paragraph>
|
||||
d
|
||||
.
|
||||
|
||||
Enumerated List:
|
||||
.
|
||||
1. *foo*
|
||||
|
||||
1) bar
|
||||
|
||||
para
|
||||
|
||||
10. starting
|
||||
11. enumerator
|
||||
.
|
||||
<document source="notset">
|
||||
<enumerated_list enumtype="arabic" prefix="" suffix=".">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
foo
|
||||
<enumerated_list enumtype="arabic" prefix="" suffix=")">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
bar
|
||||
<paragraph>
|
||||
para
|
||||
<enumerated_list enumtype="arabic" prefix="" start="10" suffix=".">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
starting
|
||||
<list_item>
|
||||
<paragraph>
|
||||
enumerator
|
||||
.
|
||||
|
||||
Nested Enumrated List:
|
||||
.
|
||||
1. a
|
||||
2. b
|
||||
1. c
|
||||
.
|
||||
<document source="notset">
|
||||
<enumerated_list enumtype="arabic" prefix="" suffix=".">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
a
|
||||
<list_item>
|
||||
<paragraph>
|
||||
b
|
||||
<enumerated_list enumtype="arabic" prefix="" suffix=".">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
c
|
||||
.
|
||||
|
||||
Sphinx Role containing backtick:
|
||||
.
|
||||
{code}``a=1{`}``
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<literal classes="code">
|
||||
a=1{`}
|
||||
.
|
||||
|
||||
Target:
|
||||
.
|
||||
(target)=
|
||||
.
|
||||
<document source="notset">
|
||||
<target ids="target" names="target">
|
||||
.
|
||||
|
||||
Target with whitespace:
|
||||
.
|
||||
(target with space)=
|
||||
.
|
||||
<document source="notset">
|
||||
<target ids="target-with-space" names="target\ with\ space">
|
||||
.
|
||||
|
||||
Referencing:
|
||||
.
|
||||
(target)=
|
||||
|
||||
Title
|
||||
=====
|
||||
|
||||
[alt1](target)
|
||||
|
||||
[](target2)
|
||||
|
||||
[alt2](https://www.google.com)
|
||||
|
||||
[alt3](#target3)
|
||||
.
|
||||
<document source="notset">
|
||||
<target ids="target" names="target">
|
||||
<section ids="title" names="title">
|
||||
<title>
|
||||
Title
|
||||
<paragraph>
|
||||
<reference refname="target">
|
||||
alt1
|
||||
<paragraph>
|
||||
<reference refname="target2">
|
||||
<paragraph>
|
||||
<reference refuri="https://www.google.com">
|
||||
alt2
|
||||
<paragraph>
|
||||
<reference refname="#target3">
|
||||
alt3
|
||||
.
|
||||
|
||||
Comments:
|
||||
.
|
||||
line 1
|
||||
% a comment
|
||||
line 2
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
line 1
|
||||
<comment xml:space="preserve">
|
||||
a comment
|
||||
<paragraph>
|
||||
line 2
|
||||
.
|
||||
|
||||
Block Break:
|
||||
.
|
||||
+++ string
|
||||
.
|
||||
<document source="notset">
|
||||
<comment classes="block_break" xml:space="preserve">
|
||||
string
|
||||
.
|
||||
|
||||
Link Reference:
|
||||
.
|
||||
[name][key]
|
||||
|
||||
[key]: https://www.google.com "a title"
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<reference refuri="https://www.google.com" title="a title">
|
||||
name
|
||||
.
|
||||
|
||||
Link Reference short version:
|
||||
.
|
||||
[name]
|
||||
|
||||
[name]: https://www.google.com "a title"
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<reference refuri="https://www.google.com" title="a title">
|
||||
name
|
||||
.
|
||||
|
||||
Block Quotes:
|
||||
.
|
||||
```{epigraph}
|
||||
a b*c*
|
||||
|
||||
-- a**b**
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<block_quote classes="epigraph">
|
||||
<paragraph>
|
||||
a b
|
||||
<emphasis>
|
||||
c
|
||||
<attribution>
|
||||
a
|
||||
<strong>
|
||||
b
|
||||
.
|
||||
|
||||
Link Definition in directive:
|
||||
.
|
||||
```{note}
|
||||
[a]
|
||||
```
|
||||
|
||||
[a]: link
|
||||
.
|
||||
<document source="notset">
|
||||
<note>
|
||||
<paragraph>
|
||||
<reference refname="link">
|
||||
a
|
||||
.
|
||||
|
||||
Link Definition in nested directives:
|
||||
.
|
||||
```{note}
|
||||
[ref1]: link
|
||||
```
|
||||
|
||||
```{note}
|
||||
[ref1]
|
||||
[ref2]
|
||||
```
|
||||
|
||||
```{note}
|
||||
[ref2]: link
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<note>
|
||||
<note>
|
||||
<paragraph>
|
||||
<reference refname="link">
|
||||
ref1
|
||||
|
||||
[ref2]
|
||||
<note>
|
||||
.
|
||||
|
||||
Footnotes:
|
||||
.
|
||||
[^a]
|
||||
|
||||
[^a]: footnote*text*
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<footnote_reference auto="1" ids="id1" refname="a">
|
||||
<transition classes="footnotes">
|
||||
<footnote auto="1" ids="a" names="a">
|
||||
<paragraph>
|
||||
footnote
|
||||
<emphasis>
|
||||
text
|
||||
.
|
||||
|
||||
Footnotes nested blocks:
|
||||
.
|
||||
[^a]
|
||||
|
||||
[^a]: footnote*text*
|
||||
|
||||
abc
|
||||
xyz
|
||||
|
||||
> a
|
||||
|
||||
- b
|
||||
|
||||
c
|
||||
|
||||
finish
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<footnote_reference auto="1" ids="id1" refname="a">
|
||||
<paragraph>
|
||||
finish
|
||||
<transition classes="footnotes">
|
||||
<footnote auto="1" ids="a" names="a">
|
||||
<paragraph>
|
||||
footnote
|
||||
<emphasis>
|
||||
text
|
||||
<paragraph>
|
||||
abc
|
||||
|
||||
xyz
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
a
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
b
|
||||
<paragraph>
|
||||
c
|
||||
.
|
||||
|
||||
Front Matter:
|
||||
.
|
||||
---
|
||||
a: 1
|
||||
b: foo
|
||||
c:
|
||||
d: 2
|
||||
---
|
||||
.
|
||||
<document source="notset">
|
||||
<field_list>
|
||||
<field>
|
||||
<field_name>
|
||||
a
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
1
|
||||
<field>
|
||||
<field_name>
|
||||
b
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
foo
|
||||
<field>
|
||||
<field_name>
|
||||
c
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
{"d": 2}
|
||||
.
|
||||
|
||||
Front Matter Biblio:
|
||||
.
|
||||
---
|
||||
author: Chris Sewell
|
||||
authors: Chris Sewell, Chris Hodgraf
|
||||
organization: EPFL
|
||||
address: |
|
||||
1 Cedar Park Close
|
||||
Thundersley
|
||||
Essex
|
||||
contact: <https://example.com>
|
||||
version: 1.0
|
||||
revision: 1.1
|
||||
status: good
|
||||
date: 2/12/1985
|
||||
copyright: MIT
|
||||
dedication: |
|
||||
To my *homies*
|
||||
abstract:
|
||||
Something something **dark** side
|
||||
other: Something else
|
||||
---
|
||||
.
|
||||
<document source="notset">
|
||||
<field_list>
|
||||
<field>
|
||||
<field_name>
|
||||
author
|
||||
<field_body>
|
||||
<paragraph>
|
||||
Chris Sewell
|
||||
<field>
|
||||
<field_name>
|
||||
authors
|
||||
<field_body>
|
||||
<paragraph>
|
||||
Chris Sewell, Chris Hodgraf
|
||||
<field>
|
||||
<field_name>
|
||||
organization
|
||||
<field_body>
|
||||
<paragraph>
|
||||
EPFL
|
||||
<field>
|
||||
<field_name>
|
||||
address
|
||||
<field_body>
|
||||
<paragraph>
|
||||
1 Cedar Park Close
|
||||
|
||||
Thundersley
|
||||
|
||||
Essex
|
||||
|
||||
<field>
|
||||
<field_name>
|
||||
contact
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<reference refuri="https://example.com">
|
||||
https://example.com
|
||||
<field>
|
||||
<field_name>
|
||||
version
|
||||
<field_body>
|
||||
<paragraph>
|
||||
1.0
|
||||
<field>
|
||||
<field_name>
|
||||
revision
|
||||
<field_body>
|
||||
<paragraph>
|
||||
1.1
|
||||
<field>
|
||||
<field_name>
|
||||
status
|
||||
<field_body>
|
||||
<paragraph>
|
||||
good
|
||||
<field>
|
||||
<field_name>
|
||||
date
|
||||
<field_body>
|
||||
<paragraph>
|
||||
2/12/1985
|
||||
<field>
|
||||
<field_name>
|
||||
copyright
|
||||
<field_body>
|
||||
<paragraph>
|
||||
MIT
|
||||
<field>
|
||||
<field_name>
|
||||
dedication
|
||||
<field_body>
|
||||
<paragraph>
|
||||
To my
|
||||
<emphasis>
|
||||
homies
|
||||
|
||||
<field>
|
||||
<field_name>
|
||||
abstract
|
||||
<field_body>
|
||||
<paragraph>
|
||||
Something something
|
||||
<strong>
|
||||
dark
|
||||
side
|
||||
<field>
|
||||
<field_name>
|
||||
other
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
Something else
|
||||
.
|
||||
|
||||
Front Matter Bad Yaml:
|
||||
.
|
||||
---
|
||||
a: {
|
||||
---
|
||||
.
|
||||
<document source="notset">
|
||||
<system_message level="2" line="1" source="notset" type="WARNING">
|
||||
<paragraph>
|
||||
Malformed YAML [myst.topmatter]
|
||||
.
|
||||
|
||||
Front Matter HTML Meta
|
||||
.
|
||||
---
|
||||
myst:
|
||||
html_meta:
|
||||
keywords: Sphinx, documentation, builder
|
||||
description lang=en: An amusing story
|
||||
description lang=fr: Un histoire amusant
|
||||
http-equiv=Content-Type: text/html; charset=ISO-8859-1
|
||||
---
|
||||
.
|
||||
<document source="notset">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.components.Filter
|
||||
.details:
|
||||
component: 'writer'
|
||||
format: 'html'
|
||||
nodes:
|
||||
<meta content="Sphinx, documentation, builder" name="keywords">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.components.Filter
|
||||
.details:
|
||||
component: 'writer'
|
||||
format: 'html'
|
||||
nodes:
|
||||
<meta content="An amusing story" lang="en" name="description">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.components.Filter
|
||||
.details:
|
||||
component: 'writer'
|
||||
format: 'html'
|
||||
nodes:
|
||||
<meta content="Un histoire amusant" lang="fr" name="description">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.components.Filter
|
||||
.details:
|
||||
component: 'writer'
|
||||
format: 'html'
|
||||
nodes:
|
||||
<meta content="text/html; charset=ISO-8859-1" http-equiv="Content-Type">
|
||||
.
|
||||
|
||||
Full Test:
|
||||
.
|
||||
---
|
||||
a: 1
|
||||
---
|
||||
|
||||
(target)=
|
||||
# header 1
|
||||
## sub header 1
|
||||
|
||||
a *b* **c** `abc`
|
||||
|
||||
## sub header 2
|
||||
|
||||
x y [a](http://www.xyz.com) z
|
||||
|
||||
---
|
||||
|
||||
# header 2
|
||||
|
||||
```::python {a=1}
|
||||
a = 1
|
||||
```
|
||||
|
||||
[](target)
|
||||
.
|
||||
<document source="notset">
|
||||
<field_list>
|
||||
<field>
|
||||
<field_name>
|
||||
a
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
1
|
||||
<target ids="target" names="target">
|
||||
<section ids="header-1" names="header\ 1">
|
||||
<title>
|
||||
header 1
|
||||
<section ids="sub-header-1" names="sub\ header\ 1">
|
||||
<title>
|
||||
sub header 1
|
||||
<paragraph>
|
||||
a
|
||||
<emphasis>
|
||||
b
|
||||
|
||||
<strong>
|
||||
c
|
||||
|
||||
<literal>
|
||||
abc
|
||||
<section ids="sub-header-2" names="sub\ header\ 2">
|
||||
<title>
|
||||
sub header 2
|
||||
<paragraph>
|
||||
x y
|
||||
<reference refuri="http://www.xyz.com">
|
||||
a
|
||||
z
|
||||
<transition>
|
||||
<section ids="header-2" names="header\ 2">
|
||||
<title>
|
||||
header 2
|
||||
<literal_block classes="code ::python" xml:space="preserve">
|
||||
a = 1
|
||||
<paragraph>
|
||||
<reference refname="target">
|
||||
.
|
|
@ -0,0 +1,139 @@
|
|||
[dollarmath] --myst-enable-extensions=dollarmath
|
||||
.
|
||||
$foo$
|
||||
|
||||
a $foo
|
||||
bar$ b
|
||||
|
||||
$$foo$$
|
||||
|
||||
$$
|
||||
a = 1
|
||||
$$
|
||||
.
|
||||
<document source="<string>">
|
||||
<paragraph>
|
||||
<math>
|
||||
foo
|
||||
<paragraph>
|
||||
a
|
||||
<math>
|
||||
foo
|
||||
bar
|
||||
b
|
||||
<math_block nowrap="False" number="True" xml:space="preserve">
|
||||
foo
|
||||
<math_block nowrap="False" number="True" xml:space="preserve">
|
||||
|
||||
a = 1
|
||||
.
|
||||
|
||||
[amsmath] --myst-enable-extensions=amsmath
|
||||
.
|
||||
\begin{equation} a \end{equation}
|
||||
|
||||
\begin{equation}
|
||||
a
|
||||
\end{equation}
|
||||
|
||||
\begin{equation*}
|
||||
a
|
||||
\end{equation*}
|
||||
.
|
||||
<document source="<string>">
|
||||
<math_block classes="amsmath" nowrap="True" numbered="True" xml:space="preserve">
|
||||
\begin{equation} a \end{equation}
|
||||
<math_block classes="amsmath" nowrap="True" numbered="True" xml:space="preserve">
|
||||
\begin{equation}
|
||||
a
|
||||
\end{equation}
|
||||
<math_block classes="amsmath" nowrap="True" xml:space="preserve">
|
||||
\begin{equation*}
|
||||
a
|
||||
\end{equation*}
|
||||
.
|
||||
|
||||
[deflist] --myst-enable-extensions=deflist
|
||||
.
|
||||
term
|
||||
: definition
|
||||
.
|
||||
<document source="<string>">
|
||||
<definition_list classes="simple myst">
|
||||
<definition_list_item>
|
||||
<term>
|
||||
term
|
||||
<definition>
|
||||
<paragraph>
|
||||
definition
|
||||
.
|
||||
|
||||
[fieldlist] --myst-enable-extensions=fieldlist
|
||||
.
|
||||
:name: value
|
||||
.
|
||||
<document source="<string>">
|
||||
<docinfo>
|
||||
<field classes="name">
|
||||
<field_name>
|
||||
name
|
||||
<field_body>
|
||||
<paragraph>
|
||||
value
|
||||
.
|
||||
|
||||
[colon_fence] --myst-enable-extensions=colon_fence
|
||||
.
|
||||
:::{note}
|
||||
content
|
||||
:::
|
||||
.
|
||||
<document source="<string>">
|
||||
<note>
|
||||
<paragraph>
|
||||
content
|
||||
.
|
||||
|
||||
[replacements] --myst-enable-extensions=replacements
|
||||
.
|
||||
(c) (C) (r) (R) (tm) (TM) (p) (P) +- ...
|
||||
.
|
||||
<document source="<string>">
|
||||
<paragraph>
|
||||
© © ® ® ™ ™ § § ± …
|
||||
.
|
||||
|
||||
[strikethrough] --myst-enable-extensions=strikethrough
|
||||
.
|
||||
~~foo~~
|
||||
.
|
||||
<document source="<string>">
|
||||
<paragraph>
|
||||
<system_message level="2" line="1" source="<string>" type="WARNING">
|
||||
<paragraph>
|
||||
Strikethrough is currently only supported in HTML output [myst.strikethrough]
|
||||
<raw format="html" xml:space="preserve">
|
||||
<s>
|
||||
foo
|
||||
<raw format="html" xml:space="preserve">
|
||||
</s>
|
||||
.
|
||||
|
||||
[tasklist] --myst-enable-extensions=tasklist
|
||||
.
|
||||
- [ ] foo
|
||||
- [x] bar
|
||||
.
|
||||
<document source="<string>">
|
||||
<bullet_list bullet="-" classes="contains-task-list">
|
||||
<list_item classes="task-list-item">
|
||||
<paragraph>
|
||||
<raw format="html" xml:space="preserve">
|
||||
<input class="task-list-item-checkbox" disabled="disabled" type="checkbox">
|
||||
foo
|
||||
<list_item classes="task-list-item">
|
||||
<paragraph>
|
||||
<raw format="html" xml:space="preserve">
|
||||
<input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox">
|
||||
bar
|
||||
.
|
|
@ -0,0 +1,75 @@
|
|||
Inline Math:
|
||||
.
|
||||
$foo$
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<math>
|
||||
foo
|
||||
.
|
||||
|
||||
Inline Math, multi-line:
|
||||
.
|
||||
a $foo
|
||||
bar$ b
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
a
|
||||
<math>
|
||||
foo
|
||||
bar
|
||||
b
|
||||
.
|
||||
|
||||
Inline Math, multi-line with line break (invalid):
|
||||
.
|
||||
a $foo
|
||||
|
||||
bar$ b
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
a $foo
|
||||
<paragraph>
|
||||
bar$ b
|
||||
.
|
||||
|
||||
Math Block:
|
||||
.
|
||||
$$foo$$
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<math_block nowrap="False" number="True" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Math Block With Equation Label:
|
||||
.
|
||||
$$foo$$ (abc)
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<target ids="equation-abc">
|
||||
<math_block docname="index" label="abc" nowrap="False" number="1" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Math Block multiple:
|
||||
.
|
||||
$$
|
||||
a = 1
|
||||
$$
|
||||
|
||||
$$
|
||||
b = 2
|
||||
$$ (a)
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<math_block nowrap="False" number="True" xml:space="preserve">
|
||||
|
||||
a = 1
|
||||
<target ids="equation-a">
|
||||
<math_block docname="index" label="a" nowrap="False" number="1" xml:space="preserve">
|
||||
|
||||
b = 2
|
||||
.
|
|
@ -0,0 +1,23 @@
|
|||
eval-rst link
|
||||
.
|
||||
```{eval-rst}
|
||||
`MyST Parser <https://myst-parser.readthedocs.io/>`_
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<reference name="MyST Parser" refuri="https://myst-parser.readthedocs.io/">
|
||||
MyST Parser
|
||||
<target ids="myst-parser" names="myst\ parser" refuri="https://myst-parser.readthedocs.io/">
|
||||
.
|
||||
|
||||
eval-rst bold
|
||||
.
|
||||
```{eval-rst}
|
||||
**bold**
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<strong>
|
||||
bold
|
||||
.
|
|
@ -0,0 +1,62 @@
|
|||
Basic Include:
|
||||
.
|
||||
```{include} other.md
|
||||
```
|
||||
.
|
||||
<document source="tmpdir/test.md">
|
||||
<paragraph>
|
||||
a
|
||||
|
||||
b
|
||||
|
||||
c
|
||||
.
|
||||
|
||||
Include with Front Matter (should be ignored):
|
||||
.
|
||||
```{include} fmatter.md
|
||||
```
|
||||
.
|
||||
<document source="tmpdir/test.md">
|
||||
<paragraph>
|
||||
b
|
||||
.
|
||||
|
||||
Include Literal:
|
||||
.
|
||||
```{include} other.md
|
||||
:literal: True
|
||||
```
|
||||
.
|
||||
<document source="tmpdir/test.md">
|
||||
<literal_block source="tmpdir/other.md" xml:space="preserve">
|
||||
a
|
||||
b
|
||||
c
|
||||
.
|
||||
|
||||
Include Literal, line range:
|
||||
.
|
||||
```{include} other.md
|
||||
:literal: True
|
||||
:start-line: 1
|
||||
:end-line: 2
|
||||
```
|
||||
.
|
||||
<document source="tmpdir/test.md">
|
||||
<literal_block source="tmpdir/other.md" xml:space="preserve">
|
||||
b
|
||||
.
|
||||
|
||||
Include code:
|
||||
.
|
||||
```{include} other.md
|
||||
:code: md
|
||||
```
|
||||
.
|
||||
<document source="tmpdir/test.md">
|
||||
<literal_block classes="code md" source="tmpdir/other.md" xml:space="preserve">
|
||||
a
|
||||
b
|
||||
c
|
||||
.
|
|
@ -0,0 +1,24 @@
|
|||
Missing path:
|
||||
.
|
||||
```{include}
|
||||
```
|
||||
.
|
||||
tmpdir/test.md:1: (ERROR/3) Directive 'include': 1 argument(s) required, 0 supplied
|
||||
.
|
||||
|
||||
Non-existent path:
|
||||
.
|
||||
```{include} other.md
|
||||
```
|
||||
.
|
||||
tmpdir/test.md:1: (SEVERE/4) Directive "include": error reading file: tmpdir/other.md
|
||||
[Errno 2] No such file or directory: 'tmpdir/other.md'.
|
||||
.
|
||||
|
||||
Error in include file:
|
||||
.
|
||||
```{include} bad.md
|
||||
```
|
||||
.
|
||||
tmpdir/bad.md:2: (ERROR/3) Unknown interpreted text role "a".
|
||||
.
|
|
@ -0,0 +1,176 @@
|
|||
[title-to-header] --myst-title-to-header="yes"
|
||||
.
|
||||
---
|
||||
title: "The title *nested syntax*"
|
||||
---
|
||||
|
||||
# Other header
|
||||
.
|
||||
<document source="<string>">
|
||||
<docinfo>
|
||||
<field classes="title">
|
||||
<field_name>
|
||||
title
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
The title *nested syntax*
|
||||
<section ids="the-title-nested-syntax" names="the\ title\ nested\ syntax">
|
||||
<title>
|
||||
The title
|
||||
<emphasis>
|
||||
nested syntax
|
||||
<section ids="other-header" names="other\ header">
|
||||
<title>
|
||||
Other header
|
||||
.
|
||||
|
||||
[linkify] --myst-enable-extensions=linkify
|
||||
.
|
||||
www.example.com
|
||||
.
|
||||
<document source="<string>">
|
||||
<paragraph>
|
||||
<reference refuri="http://www.example.com">
|
||||
www.example.com
|
||||
.
|
||||
|
||||
[gfm-strikethrough] --myst-gfm-only="yes"
|
||||
.
|
||||
~~strike~~
|
||||
.
|
||||
<document source="<string>">
|
||||
<paragraph>
|
||||
<system_message level="2" line="1" source="<string>" type="WARNING">
|
||||
<paragraph>
|
||||
Strikethrough is currently only supported in HTML output [myst.strikethrough]
|
||||
<raw format="html" xml:space="preserve">
|
||||
<s>
|
||||
strike
|
||||
<raw format="html" xml:space="preserve">
|
||||
</s>
|
||||
|
||||
<string>:1: (WARNING/2) Strikethrough is currently only supported in HTML output [myst.strikethrough]
|
||||
.
|
||||
|
||||
[gfm-disallowed-html] --myst-gfm-only="yes"
|
||||
.
|
||||
<strong> <title> <style> <em>
|
||||
|
||||
<blockquote>
|
||||
<xmp> is disallowed. <XMP> is also disallowed.
|
||||
</blockquote>
|
||||
.
|
||||
<document source="<string>">
|
||||
<paragraph>
|
||||
<raw format="html" xml:space="preserve">
|
||||
<strong>
|
||||
|
||||
<raw format="html" xml:space="preserve">
|
||||
<title>
|
||||
|
||||
<raw format="html" xml:space="preserve">
|
||||
<style>
|
||||
|
||||
<raw format="html" xml:space="preserve">
|
||||
<em>
|
||||
<raw format="html" xml:space="preserve">
|
||||
<blockquote>
|
||||
<xmp> is disallowed. <XMP> is also disallowed.
|
||||
</blockquote>
|
||||
.
|
||||
|
||||
[gfm-autolink] --myst-gfm-only="yes"
|
||||
.
|
||||
www.commonmark.org
|
||||
|
||||
Visit www.commonmark.org/help for more information.
|
||||
|
||||
www.google.com/search?q=Markup+(business)
|
||||
|
||||
www.google.com/search?q=Markup+(business)))
|
||||
|
||||
(www.google.com/search?q=Markup+(business))
|
||||
|
||||
(www.google.com/search?q=Markup+(business)
|
||||
|
||||
www.google.com/search?q=(business))+ok
|
||||
|
||||
www.google.com/search?q=commonmark&hl=en
|
||||
|
||||
www.google.com/search?q=commonmark&hl;
|
||||
|
||||
www.commonmark.org/he<lp
|
||||
.
|
||||
<document source="<string>">
|
||||
<paragraph>
|
||||
<reference refuri="http://www.commonmark.org">
|
||||
www.commonmark.org
|
||||
<paragraph>
|
||||
Visit
|
||||
<reference refuri="http://www.commonmark.org/help">
|
||||
www.commonmark.org/help
|
||||
for more information.
|
||||
<paragraph>
|
||||
<reference refuri="http://www.google.com/search?q=Markup+(business)">
|
||||
www.google.com/search?q=Markup+(business)
|
||||
<paragraph>
|
||||
<reference refuri="http://www.google.com/search?q=Markup+(business)">
|
||||
www.google.com/search?q=Markup+(business)
|
||||
))
|
||||
<paragraph>
|
||||
(
|
||||
<reference refuri="http://www.google.com/search?q=Markup+(business)">
|
||||
www.google.com/search?q=Markup+(business)
|
||||
)
|
||||
<paragraph>
|
||||
(
|
||||
<reference refuri="http://www.google.com/search?q=Markup+(business)">
|
||||
www.google.com/search?q=Markup+(business)
|
||||
<paragraph>
|
||||
<reference refuri="http://www.google.com/search?q=(business)">
|
||||
www.google.com/search?q=(business)
|
||||
)+ok
|
||||
<paragraph>
|
||||
<reference refuri="http://www.google.com/search?q=commonmark&hl=en">
|
||||
www.google.com/search?q=commonmark&hl=en
|
||||
<paragraph>
|
||||
<reference refuri="http://www.google.com/search?q=commonmark&hl">
|
||||
www.google.com/search?q=commonmark&hl
|
||||
;
|
||||
<paragraph>
|
||||
<reference refuri="http://www.commonmark.org/he">
|
||||
www.commonmark.org/he
|
||||
<lp
|
||||
.
|
||||
|
||||
[attrs_image] --myst-enable-extensions=attrs_image
|
||||
.
|
||||
![a](b){#id .a width="100%" align=center height=20px}{.b}
|
||||
.
|
||||
<document source="<string>">
|
||||
<paragraph>
|
||||
<image align="center" alt="a" classes="a b" height="20px" ids="id" names="id" uri="b" width="100%">
|
||||
.
|
||||
|
||||
[attrs_image_warnings] --myst-enable-extensions=attrs_image
|
||||
.
|
||||
![a](b){width=1x height=2x align=other }
|
||||
.
|
||||
<document source="<string>">
|
||||
<paragraph>
|
||||
<system_message level="2" line="1" source="<string>" type="WARNING">
|
||||
<paragraph>
|
||||
Invalid width value for image: '1x' [myst.image]
|
||||
<system_message level="2" line="1" source="<string>" type="WARNING">
|
||||
<paragraph>
|
||||
Invalid height value for image: '2x' [myst.image]
|
||||
<system_message level="2" line="1" source="<string>" type="WARNING">
|
||||
<paragraph>
|
||||
Invalid align value for image: 'other' [myst.image]
|
||||
<image alt="a" uri="b">
|
||||
|
||||
<string>:1: (WARNING/2) Invalid width value for image: '1x' [myst.image]
|
||||
<string>:1: (WARNING/2) Invalid height value for image: '2x' [myst.image]
|
||||
<string>:1: (WARNING/2) Invalid align value for image: 'other' [myst.image]
|
||||
.
|
|
@ -0,0 +1,180 @@
|
|||
Duplicate Reference definitions:
|
||||
.
|
||||
[a]: b
|
||||
[a]: c
|
||||
.
|
||||
<string>:2: (WARNING/2) Duplicate reference definition: A [myst.ref]
|
||||
.
|
||||
|
||||
Missing Reference:
|
||||
.
|
||||
[a](b)
|
||||
.
|
||||
<string>:1: (ERROR/3) Unknown target name: "b".
|
||||
.
|
||||
|
||||
Unknown role:
|
||||
.
|
||||
abc
|
||||
|
||||
{xyz}`a`
|
||||
.
|
||||
<string>:3: (ERROR/3) Unknown interpreted text role "xyz".
|
||||
.
|
||||
|
||||
Unknown directive:
|
||||
.
|
||||
|
||||
```{xyz}
|
||||
```
|
||||
.
|
||||
<string>:2: (ERROR/3) Unknown directive type "xyz".
|
||||
.
|
||||
|
||||
Bad Front Matter:
|
||||
.
|
||||
---
|
||||
a: {
|
||||
---
|
||||
.
|
||||
<string>:1: (WARNING/2) Malformed YAML [myst.topmatter]
|
||||
.
|
||||
|
||||
Unknown Front Matter myst key:
|
||||
.
|
||||
---
|
||||
myst:
|
||||
unknown: true
|
||||
---
|
||||
.
|
||||
<string>:1: (WARNING/2) Unknown field: unknown [myst.topmatter]
|
||||
.
|
||||
|
||||
Invalid Front Matter myst key:
|
||||
.
|
||||
---
|
||||
myst:
|
||||
title_to_header: 1
|
||||
url_schemes: [1]
|
||||
substitutions:
|
||||
key: []
|
||||
---
|
||||
.
|
||||
<string>:1: (WARNING/2) 'title_to_header' must be of type <class 'bool'> (got 1 that is a <class 'int'>). [myst.topmatter]
|
||||
<string>:1: (WARNING/2) 'url_schemes[0]' must be of type <class 'str'> (got 1 that is a <class 'int'>). [myst.topmatter]
|
||||
<string>:1: (WARNING/2) 'substitutions['key']' must be of type (<class 'str'>, <class 'int'>, <class 'float'>) (got [] that is a <class 'list'>). [myst.topmatter]
|
||||
.
|
||||
|
||||
Bad HTML Meta
|
||||
.
|
||||
---
|
||||
myst:
|
||||
html_meta:
|
||||
name noequals: value
|
||||
|
||||
---
|
||||
.
|
||||
<string>:: (ERROR/3) Error parsing meta tag attribute "name noequals": no '=' in noequals.
|
||||
.
|
||||
|
||||
Directive parsing error:
|
||||
.
|
||||
|
||||
```{class}
|
||||
```
|
||||
.
|
||||
<string>:2: (ERROR/3) Directive 'class': 1 argument(s) required, 0 supplied
|
||||
.
|
||||
|
||||
Directive run error:
|
||||
.
|
||||
|
||||
```{date}
|
||||
x
|
||||
```
|
||||
.
|
||||
<string>:2: (ERROR/3) Invalid context: the "date" directive can only be used within a substitution definition.
|
||||
.
|
||||
|
||||
Do not start headings at H1:
|
||||
.
|
||||
## title 1
|
||||
.
|
||||
<string>:1: (WARNING/2) Document headings start at H2, not H1 [myst.header]
|
||||
.
|
||||
|
||||
Non-consecutive headings:
|
||||
.
|
||||
# title 1
|
||||
### title 3
|
||||
.
|
||||
<string>:2: (WARNING/2) Non-consecutive header level increase; H1 to H3 [myst.header]
|
||||
.
|
||||
|
||||
multiple footnote definitions
|
||||
.
|
||||
[^a]
|
||||
|
||||
[^a]: definition 1
|
||||
[^a]: definition 2
|
||||
.
|
||||
<string>:: (WARNING/2) Multiple footnote definitions found for label: 'a' [myst.footnote]
|
||||
.
|
||||
|
||||
Warnings in eval-rst
|
||||
.
|
||||
some external
|
||||
|
||||
lines
|
||||
|
||||
```{eval-rst}
|
||||
some internal
|
||||
|
||||
lines
|
||||
|
||||
.. unknown:: some text
|
||||
|
||||
:unknown:`a`
|
||||
```
|
||||
.
|
||||
<string>:10: (ERROR/3) Unknown directive type "unknown".
|
||||
|
||||
.. unknown:: some text
|
||||
|
||||
<string>:12: (ERROR/3) Unknown interpreted text role "unknown".
|
||||
.
|
||||
|
||||
bad-option-value
|
||||
.
|
||||
```{note}
|
||||
:class: [1]
|
||||
```
|
||||
.
|
||||
<string>:1: (ERROR/3) Directive 'note': option "class" value not string (enclose with ""): [1]
|
||||
|
||||
:class: [1]
|
||||
|
||||
.
|
||||
|
||||
header nested in admonition
|
||||
.
|
||||
```{note}
|
||||
# Header
|
||||
```
|
||||
.
|
||||
<string>:2: (WARNING/2) Disallowed nested header found, converting to rubric [myst.nested_header]
|
||||
.
|
||||
|
||||
nested parse warning
|
||||
.
|
||||
Paragraph
|
||||
|
||||
```{note}
|
||||
:class: abc
|
||||
:name: xyz
|
||||
|
||||
{unknown}`a`
|
||||
```
|
||||
.
|
||||
<string>:7: (ERROR/3) Unknown interpreted text role "unknown".
|
||||
.
|
|
@ -0,0 +1,441 @@
|
|||
default-role (`sphinx.directives.DefaultRole`):
|
||||
.
|
||||
```{default-role}
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
.
|
||||
|
||||
default-domain (`sphinx.directives.DefaultDomain`):
|
||||
.
|
||||
```{default-domain} mydomain
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
.
|
||||
|
||||
object (`sphinx.directives.ObjectDescription`):
|
||||
.
|
||||
```{object} something
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<index entries="">
|
||||
<desc classes="object" desctype="object" domain="" noindex="False" objtype="object">
|
||||
<desc_signature classes="sig sig-object">
|
||||
<desc_name classes="sig-name descname" xml:space="preserve">
|
||||
something
|
||||
<desc_content>
|
||||
.
|
||||
|
||||
highlight (`sphinx.directives.code.Highlight`):
|
||||
.
|
||||
```{highlight} something
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<highlightlang force="False" lang="something" linenothreshold="9223372036854775807">
|
||||
.
|
||||
|
||||
code-block (`sphinx.directives.code.CodeBlock`):
|
||||
.
|
||||
```{code-block}
|
||||
:dedent:
|
||||
|
||||
a=1
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<literal_block force="False" highlight_args="{}" language="default" xml:space="preserve">
|
||||
a=1
|
||||
.
|
||||
|
||||
sourcecode (`sphinx.directives.code.CodeBlock`):
|
||||
.
|
||||
```{sourcecode}
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<literal_block force="False" highlight_args="{}" language="default" xml:space="preserve">
|
||||
.
|
||||
|
||||
SKIP: Tested in sphinx builds
|
||||
.
|
||||
```{literalinclude} /path/to/file
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
<system_message level="2" line="1" source="notset" type="WARNING">
|
||||
<paragraph>
|
||||
Include file '/srcdir/path/to/file' not found or reading it failed
|
||||
.
|
||||
|
||||
toctree (`sphinx.directives.other.TocTree`):
|
||||
.
|
||||
```{toctree}
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<compound classes="toctree-wrapper">
|
||||
<toctree caption="True" entries="" glob="False" hidden="False" includefiles="" includehidden="False" maxdepth="-1" numbered="0" parent="index" titlesonly="False">
|
||||
.
|
||||
|
||||
sectionauthor (`sphinx.directives.other.Author`):
|
||||
.
|
||||
```{sectionauthor} bob geldof
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
.
|
||||
|
||||
moduleauthor (`sphinx.directives.other.Author`):
|
||||
.
|
||||
```{moduleauthor} ringo starr
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
.
|
||||
|
||||
codeauthor (`sphinx.directives.other.Author`):
|
||||
.
|
||||
```{codeauthor} paul mcartney
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
.
|
||||
|
||||
index (`sphinx.directives.other.Index`):
|
||||
.
|
||||
```{index} something
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<index entries="('single',\ 'something',\ 'index-0',\ '',\ None)" inline="False">
|
||||
<target ids="index-0">
|
||||
.
|
||||
|
||||
seealso (`sphinx.directives.other.SeeAlso`):
|
||||
.
|
||||
```{seealso}
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<seealso>
|
||||
<paragraph>
|
||||
a
|
||||
.
|
||||
|
||||
tabularcolumns (`sphinx.directives.other.TabularColumns`):
|
||||
.
|
||||
```{tabularcolumns} spec
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<tabular_col_spec spec="spec">
|
||||
.
|
||||
|
||||
centered (`sphinx.directives.other.Centered`):
|
||||
.
|
||||
```{centered} text
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<centered>
|
||||
text
|
||||
.
|
||||
|
||||
acks (`sphinx.directives.other.Acks`):
|
||||
.
|
||||
```{acks}
|
||||
|
||||
- name
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<acks>
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
name
|
||||
.
|
||||
|
||||
hlist (`sphinx.directives.other.HList`):
|
||||
.
|
||||
```{hlist}
|
||||
|
||||
- item
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<hlist ncolumns="2">
|
||||
<hlistcol>
|
||||
<bullet_list>
|
||||
<list_item>
|
||||
<paragraph>
|
||||
item
|
||||
<hlistcol>
|
||||
<bullet_list>
|
||||
.
|
||||
|
||||
only (`sphinx.directives.other.Only`):
|
||||
.
|
||||
```{only} expr
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<only expr="expr">
|
||||
.
|
||||
|
||||
SKIP: Tested in sphinx builds
|
||||
.
|
||||
```{include} path/to/include
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
.
|
||||
|
||||
figure (`sphinx.directives.patches.Figure`):
|
||||
.
|
||||
```{figure} path/to/figure
|
||||
|
||||
*caption*
|
||||
|
||||
legend
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<figure>
|
||||
<image uri="path/to/figure">
|
||||
<caption>
|
||||
<emphasis>
|
||||
caption
|
||||
<legend>
|
||||
<paragraph>
|
||||
legend
|
||||
.
|
||||
|
||||
SKIP: MockingError: MockState has not yet implemented attribute 'nested_list_parse'
|
||||
.
|
||||
```{meta}
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="notset">
|
||||
.
|
||||
|
||||
table (`sphinx.directives.patches.RSTTable`):
|
||||
.
|
||||
```{table} *title*
|
||||
:name: name
|
||||
|
||||
| a | b |
|
||||
|---|---|
|
||||
| 1 | 2 |
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<table classes="colwidths-auto" ids="name" names="name">
|
||||
<title>
|
||||
<emphasis>
|
||||
title
|
||||
<tgroup cols="2">
|
||||
<colspec colwidth="50">
|
||||
<colspec colwidth="50">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
a
|
||||
<entry>
|
||||
<paragraph>
|
||||
b
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
1
|
||||
<entry>
|
||||
<paragraph>
|
||||
2
|
||||
.
|
||||
|
||||
csv-table (`sphinx.directives.patches.CSVTable`):
|
||||
.
|
||||
```{csv-table}
|
||||
|
||||
"Albatross", 2.99, "On a stick!"
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<table>
|
||||
<tgroup cols="3">
|
||||
<colspec colwidth="33">
|
||||
<colspec colwidth="33">
|
||||
<colspec colwidth="33">
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
Albatross
|
||||
<entry>
|
||||
<paragraph>
|
||||
2.99
|
||||
<entry>
|
||||
<paragraph>
|
||||
On a stick!
|
||||
.
|
||||
|
||||
list-table (`sphinx.directives.patches.ListTable`):
|
||||
.
|
||||
```{list-table}
|
||||
|
||||
* - item
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<table>
|
||||
<tgroup cols="1">
|
||||
<colspec colwidth="100">
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
item
|
||||
.
|
||||
|
||||
code (`sphinx.directives.patches.Code`):
|
||||
.
|
||||
```{code} python
|
||||
|
||||
a
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<literal_block force="False" highlight_args="{}" language="python" xml:space="preserve">
|
||||
a
|
||||
.
|
||||
|
||||
math (`sphinx.directives.patches.MathDirective`):
|
||||
.
|
||||
```{math}
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<math_block docname="index" label="True" nowrap="False" number="True" xml:space="preserve">
|
||||
.
|
||||
|
||||
deprecated (`sphinx.domains.changeset.VersionChange`):
|
||||
.
|
||||
```{deprecated} 0.3
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<versionmodified type="deprecated" version="0.3">
|
||||
<paragraph translatable="False">
|
||||
<inline classes="versionmodified deprecated">
|
||||
Deprecated since version 0.3.
|
||||
.
|
||||
|
||||
versionadded (`sphinx.domains.changeset.VersionChange`):
|
||||
.
|
||||
```{versionadded} 0.2
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<versionmodified type="versionadded" version="0.2">
|
||||
<paragraph translatable="False">
|
||||
<inline classes="versionmodified added">
|
||||
New in version 0.2.
|
||||
.
|
||||
|
||||
versionchanged (`sphinx.domains.changeset.VersionChange`):
|
||||
.
|
||||
```{versionchanged} 0.1
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<versionmodified type="versionchanged" version="0.1">
|
||||
<paragraph translatable="False">
|
||||
<inline classes="versionmodified changed">
|
||||
Changed in version 0.1.
|
||||
.
|
||||
|
||||
glossary (`sphinx.domains.std.Glossary`):
|
||||
.
|
||||
```{glossary}
|
||||
|
||||
term 1 : A
|
||||
term 2 : B
|
||||
Definition of both terms.
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<glossary>
|
||||
<definition_list classes="glossary">
|
||||
<definition_list_item>
|
||||
<term ids="term-term-1">
|
||||
term 1
|
||||
<index entries="('single',\ 'term\ 1',\ 'term-term-1',\ 'main',\ 'A')">
|
||||
<term ids="term-term-2">
|
||||
term 2
|
||||
<index entries="('single',\ 'term\ 2',\ 'term-term-2',\ 'main',\ 'B')">
|
||||
<definition>
|
||||
<paragraph>
|
||||
Definition of both terms.
|
||||
.
|
||||
|
||||
SPHINX4-SKIP productionlist (`sphinx.domains.std.ProductionList`):
|
||||
.
|
||||
```{productionlist} try_stmt: try1_stmt | try2_stmt
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<productionlist>
|
||||
<production ids="grammar-token-try_stmt" tokenname="try_stmt" xml:space="preserve">
|
||||
try1_stmt | try2_stmt
|
||||
.
|
||||
|
||||
cmdoption (`sphinx.domains.std.Cmdoption`):
|
||||
.
|
||||
```{cmdoption} a
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<index entries="('pair',\ 'command\ line\ option;\ a',\ 'cmdoption-arg-a',\ '',\ None)">
|
||||
<desc classes="std cmdoption" desctype="cmdoption" domain="std" noindex="False" objtype="cmdoption">
|
||||
<desc_signature allnames="a" classes="sig sig-object" ids="cmdoption-arg-a">
|
||||
<desc_name classes="sig-name descname" xml:space="preserve">
|
||||
a
|
||||
<desc_addname classes="sig-prename descclassname" xml:space="preserve">
|
||||
<desc_content>
|
||||
.
|
||||
|
||||
rst:directive (`sphinx.domains.rst.ReSTDirective`):
|
||||
.
|
||||
```{rst:directive} a
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<index entries="('single',\ 'a\ (directive)',\ 'directive-a',\ '',\ None)">
|
||||
<desc classes="rst directive" desctype="directive" domain="rst" noindex="False" objtype="directive">
|
||||
<desc_signature classes="sig sig-object" ids="directive-a">
|
||||
<desc_name classes="sig-name descname" xml:space="preserve">
|
||||
.. a::
|
||||
<desc_content>
|
||||
.
|
||||
|
||||
SPHINX4-SKIP rst:directive:option (`sphinx.domains.rst.ReSTDirectiveOption`):
|
||||
.
|
||||
```{rst:directive:option} a
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<index entries="('single',\ ':a:\ (directive\ option)',\ 'directive-option-a',\ '',\ 'A')">
|
||||
<desc classes="rst directive:option" desctype="directive:option" domain="rst" noindex="False" objtype="directive:option">
|
||||
<desc_signature classes="sig sig-object" ids="directive-option-a">
|
||||
<desc_name classes="sig-name descname" xml:space="preserve">
|
||||
:a:
|
||||
<desc_content>
|
||||
.
|
|
@ -0,0 +1,632 @@
|
|||
c:func (`sphinx.domains.c.CXRefRole`):
|
||||
.
|
||||
{c:func}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="func" refwarn="False">
|
||||
<literal classes="xref c c-func">
|
||||
a()
|
||||
.
|
||||
|
||||
c:member (`sphinx.domains.c.CObject`):
|
||||
.
|
||||
{c:member}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="member" refwarn="False">
|
||||
<literal classes="xref c c-member">
|
||||
a
|
||||
.
|
||||
|
||||
c:macro (`sphinx.domains.c.CObject`):
|
||||
.
|
||||
{c:macro}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="macro" refwarn="False">
|
||||
<literal classes="xref c c-macro">
|
||||
a
|
||||
.
|
||||
|
||||
c:data (`sphinx.domains.c.CXRefRole`):
|
||||
.
|
||||
{c:data}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="data" refwarn="False">
|
||||
<literal classes="xref c c-data">
|
||||
a
|
||||
.
|
||||
|
||||
c:type (`sphinx.domains.c.CObject`):
|
||||
.
|
||||
{c:type}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="type" refwarn="False">
|
||||
<literal classes="xref c c-type">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:any (`sphinx.domains.cpp.CPPXRefRole`):
|
||||
.
|
||||
{cpp:any}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="any" refwarn="False">
|
||||
<literal classes="xref cpp cpp-any">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:class (`sphinx.domains.cpp.CPPClassObject`):
|
||||
.
|
||||
{cpp:class}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="class" refwarn="False">
|
||||
<literal classes="xref cpp cpp-class">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:struct (`sphinx.domains.cpp.CPPClassObject`):
|
||||
.
|
||||
{cpp:struct}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="struct" refwarn="False">
|
||||
<literal classes="xref cpp cpp-struct">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:union (`sphinx.domains.cpp.CPPUnionObject`):
|
||||
.
|
||||
{cpp:union}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="union" refwarn="False">
|
||||
<literal classes="xref cpp cpp-union">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:func (`sphinx.domains.cpp.CPPXRefRole`):
|
||||
.
|
||||
{cpp:func}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="func" refwarn="False">
|
||||
<literal classes="xref cpp cpp-func">
|
||||
a()
|
||||
.
|
||||
|
||||
cpp:member (`sphinx.domains.cpp.CPPMemberObject`):
|
||||
.
|
||||
{cpp:member}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="member" refwarn="False">
|
||||
<literal classes="xref cpp cpp-member">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:var (`sphinx.domains.cpp.CPPMemberObject`):
|
||||
.
|
||||
{cpp:var}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="var" refwarn="False">
|
||||
<literal classes="xref cpp cpp-var">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:type (`sphinx.domains.cpp.CPPTypeObject`):
|
||||
.
|
||||
{cpp:type}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="type" refwarn="False">
|
||||
<literal classes="xref cpp cpp-type">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:concept (`sphinx.domains.cpp.CPPConceptObject`):
|
||||
.
|
||||
{cpp:concept}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="concept" refwarn="False">
|
||||
<literal classes="xref cpp cpp-concept">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:enum (`sphinx.domains.cpp.CPPEnumObject`):
|
||||
.
|
||||
{cpp:enum}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="enum" refwarn="False">
|
||||
<literal classes="xref cpp cpp-enum">
|
||||
a
|
||||
.
|
||||
|
||||
cpp:enumerator (`sphinx.domains.cpp.CPPEnumeratorObject`):
|
||||
.
|
||||
{cpp:enumerator}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="enumerator" refwarn="False">
|
||||
<literal classes="xref cpp cpp-enumerator">
|
||||
a
|
||||
.
|
||||
|
||||
SKIP cpp:expr (`sphinx.domains.cpp.CPPExprRole`):
|
||||
.
|
||||
{cpp:expr}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<desc_inline classes="cpp-expr sig sig-inline cpp">
|
||||
<pending_xref classname="True" cpp:parent_key="<sphinx.domains.cpp.LookupKey object at 0x7f948a6a73d0>" modname="True" refdomain="cpp" reftarget="a" reftype="identifier">
|
||||
<desc_sig_name classes="n">
|
||||
a
|
||||
.
|
||||
|
||||
SKIP cpp:texpr (`sphinx.domains.cpp.CPPExprRole`):
|
||||
.
|
||||
{cpp:texpr}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<desc_inline classes="cpp-texpr sig sig-inline cpp">
|
||||
<pending_xref classname="True" cpp:parent_key="<sphinx.domains.cpp.LookupKey object at 0x7fac40b5f950>" modname="True" refdomain="cpp" reftarget="a" reftype="identifier">
|
||||
<desc_sig_name classes="n">
|
||||
a
|
||||
.
|
||||
|
||||
js:func (`sphinx.domains.javascript.JSXRefRole`):
|
||||
.
|
||||
{js:func}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="func" refwarn="False">
|
||||
<literal classes="xref js js-func">
|
||||
a()
|
||||
.
|
||||
|
||||
js:meth (`sphinx.domains.javascript.JSXRefRole`):
|
||||
.
|
||||
{js:meth}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="meth" refwarn="False">
|
||||
<literal classes="xref js js-meth">
|
||||
a()
|
||||
.
|
||||
|
||||
js:class (`sphinx.domains.javascript.JSConstructor`):
|
||||
.
|
||||
{js:class}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="class" refwarn="False">
|
||||
<literal classes="xref js js-class">
|
||||
a()
|
||||
.
|
||||
|
||||
js:data (`sphinx.domains.javascript.JSObject`):
|
||||
.
|
||||
{js:data}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="data" refwarn="False">
|
||||
<literal classes="xref js js-data">
|
||||
a
|
||||
.
|
||||
|
||||
js:attr (`sphinx.domains.javascript.JSXRefRole`):
|
||||
.
|
||||
{js:attr}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="attr" refwarn="False">
|
||||
<literal classes="xref js js-attr">
|
||||
a
|
||||
.
|
||||
|
||||
js:mod (`sphinx.domains.javascript.JSXRefRole`):
|
||||
.
|
||||
{js:mod}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="mod" refwarn="False">
|
||||
<literal classes="xref js js-mod">
|
||||
a
|
||||
.
|
||||
|
||||
eq (`sphinx.domains.math.MathReferenceRole`):
|
||||
.
|
||||
{eq}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="math" refexplicit="False" reftarget="a" reftype="eq" refwarn="True">
|
||||
<literal classes="xref eq">
|
||||
a
|
||||
.
|
||||
|
||||
math:numref (`sphinx.domains.math.MathReferenceRole`):
|
||||
.
|
||||
{math:numref}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="math" refexplicit="False" reftarget="a" reftype="numref" refwarn="False">
|
||||
<literal classes="xref math math-numref">
|
||||
a
|
||||
.
|
||||
|
||||
py:data (`sphinx.domains.python.PyVariable`):
|
||||
.
|
||||
{py:data}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="data" refwarn="False">
|
||||
<literal classes="xref py py-data">
|
||||
a
|
||||
.
|
||||
|
||||
py:exc (`sphinx.domains.python.PyXRefRole`):
|
||||
.
|
||||
{py:exc}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="exc" refwarn="False">
|
||||
<literal classes="xref py py-exc">
|
||||
a
|
||||
.
|
||||
|
||||
py:func (`sphinx.domains.python.PyXRefRole`):
|
||||
.
|
||||
{py:func}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="func" refwarn="False">
|
||||
<literal classes="xref py py-func">
|
||||
a()
|
||||
.
|
||||
|
||||
py:class (`sphinx.domains.python.PyClasslike`):
|
||||
.
|
||||
{py:class}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="class" refwarn="False">
|
||||
<literal classes="xref py py-class">
|
||||
a
|
||||
.
|
||||
|
||||
py:const (`sphinx.domains.python.PyXRefRole`):
|
||||
.
|
||||
{py:const}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="const" refwarn="False">
|
||||
<literal classes="xref py py-const">
|
||||
a
|
||||
.
|
||||
|
||||
py:attr (`sphinx.domains.python.PyXRefRole`):
|
||||
.
|
||||
{py:attr}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="attr" refwarn="False">
|
||||
<literal classes="xref py py-attr">
|
||||
a
|
||||
.
|
||||
|
||||
py:meth (`sphinx.domains.python.PyXRefRole`):
|
||||
.
|
||||
{py:meth}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="meth" refwarn="False">
|
||||
<literal classes="xref py py-meth">
|
||||
a()
|
||||
.
|
||||
|
||||
py:mod (`sphinx.domains.python.PyXRefRole`):
|
||||
.
|
||||
{py:mod}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="mod" refwarn="False">
|
||||
<literal classes="xref py py-mod">
|
||||
a
|
||||
.
|
||||
|
||||
py:obj (`sphinx.domains.python.PyXRefRole`):
|
||||
.
|
||||
{py:obj}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="obj" refwarn="False">
|
||||
<literal classes="xref py py-obj">
|
||||
a
|
||||
.
|
||||
|
||||
rst:role (`sphinx.domains.rst.ReSTRole`):
|
||||
.
|
||||
{rst:role}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="rst" refexplicit="False" reftarget="a" reftype="role" refwarn="False">
|
||||
<literal classes="xref rst rst-role">
|
||||
a
|
||||
.
|
||||
|
||||
program (`sphinx.domains.std.Program`):
|
||||
.
|
||||
{program}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<literal_strong classes="program">
|
||||
a
|
||||
.
|
||||
|
||||
option (`sphinx.domains.std.Cmdoption`):
|
||||
.
|
||||
{option}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="option" refwarn="True" std:program="True">
|
||||
<literal classes="xref std std-option">
|
||||
a
|
||||
.
|
||||
|
||||
envvar (`sphinx.domains.std.EnvVarXRefRole`):
|
||||
.
|
||||
{envvar}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<index entries="('single',\ 'a',\ 'index-0',\ '',\ None) ('single',\ 'environment\ variable;\ a',\ 'index-0',\ '',\ None)">
|
||||
<target ids="index-0">
|
||||
<pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="envvar" refwarn="False">
|
||||
<literal classes="xref std std-envvar">
|
||||
a
|
||||
.
|
||||
|
||||
index (`sphinx.roles.Index`):
|
||||
.
|
||||
{index}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<index entries="('single',\ 'a',\ 'index-0',\ '',\ None)">
|
||||
<target ids="index-0">
|
||||
a
|
||||
.
|
||||
|
||||
download (`sphinx.roles.XRefRole`):
|
||||
.
|
||||
{download}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<download_reference refdoc="index" refdomain="" refexplicit="False" reftarget="a" reftype="download" refwarn="False">
|
||||
<literal classes="xref download">
|
||||
a
|
||||
.
|
||||
|
||||
any (`sphinx.roles.AnyXRefRole`):
|
||||
.
|
||||
{any}`a <alt text>`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="" refexplicit="True" reftarget="alt text" reftype="any" refwarn="True">
|
||||
<literal classes="xref any">
|
||||
a
|
||||
.
|
||||
|
||||
pep (`sphinx.roles.PEP`):
|
||||
.
|
||||
{pep}`1`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<index entries="('single',\ 'Python\ Enhancement\ Proposals;\ PEP\ 1',\ 'index-0',\ '',\ None)">
|
||||
<target ids="index-0">
|
||||
<reference classes="pep" internal="False" refuri="https://peps.python.org/pep-0001/">
|
||||
<strong>
|
||||
PEP 1
|
||||
.
|
||||
|
||||
rfc (`sphinx.roles.RFC`):
|
||||
.
|
||||
{rfc}`1`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<index entries="('single',\ 'RFC;\ RFC\ 1',\ 'index-0',\ '',\ None)">
|
||||
<target ids="index-0">
|
||||
<reference classes="rfc" internal="False" refuri="https://datatracker.ietf.org/doc/html/rfc1.html">
|
||||
<strong>
|
||||
RFC 1
|
||||
.
|
||||
|
||||
guilabel (`sphinx.roles.GUILabel`):
|
||||
.
|
||||
{guilabel}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<inline classes="guilabel" rawtext=":guilabel:`a`">
|
||||
a
|
||||
.
|
||||
|
||||
menuselection (`sphinx.roles.MenuSelection`):
|
||||
.
|
||||
{menuselection}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<inline classes="menuselection" rawtext=":menuselection:`a`">
|
||||
a
|
||||
.
|
||||
|
||||
file (`sphinx.roles.EmphasizedLiteral`):
|
||||
.
|
||||
{file}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<literal classes="file" role="file">
|
||||
a
|
||||
.
|
||||
|
||||
samp (`sphinx.roles.EmphasizedLiteral`):
|
||||
.
|
||||
{samp}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<literal classes="samp" role="samp">
|
||||
a
|
||||
.
|
||||
|
||||
SKIP: Non-deterministic output
|
||||
.
|
||||
{abbr}`a`
|
||||
.
|
||||
<document source="notset">
|
||||
<paragraph>
|
||||
<abbreviation class="<function class_option at 0x1079fb830>>
|
||||
a
|
||||
.
|
||||
|
||||
rst:dir (`sphinx.roles.XRefRole`):
|
||||
.
|
||||
{rst:dir}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="rst" refexplicit="False" reftarget="a" reftype="dir" refwarn="False">
|
||||
<literal classes="xref rst rst-dir">
|
||||
a
|
||||
.
|
||||
|
||||
token (`sphinx.roles.XRefRole`):
|
||||
.
|
||||
{token}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="token" refwarn="False">
|
||||
<literal classes="xref std std-token">
|
||||
a
|
||||
.
|
||||
|
||||
term (`sphinx.roles.XRefRole`):
|
||||
.
|
||||
{term}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="term" refwarn="True">
|
||||
<inline classes="xref std std-term">
|
||||
a
|
||||
.
|
||||
|
||||
ref (`sphinx.roles.XRefRole`):
|
||||
.
|
||||
{ref}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="ref" refwarn="True">
|
||||
<inline classes="xref std std-ref">
|
||||
a
|
||||
.
|
||||
|
||||
ref with line breaks (`sphinx.roles.XRefRole`):
|
||||
.
|
||||
{ref}`some
|
||||
text
|
||||
<and
|
||||
a
|
||||
custom reference>`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="std" refexplicit="True" reftarget="and a custom reference" reftype="ref" refwarn="True">
|
||||
<inline classes="xref std std-ref">
|
||||
some text
|
||||
.
|
||||
|
||||
numref (`sphinx.roles.XRefRole`):
|
||||
.
|
||||
{numref}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="numref" refwarn="True">
|
||||
<literal classes="xref std std-numref">
|
||||
a
|
||||
.
|
||||
|
||||
keyword (`sphinx.roles.XRefRole`):
|
||||
.
|
||||
{keyword}`a`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="keyword" refwarn="True">
|
||||
<literal classes="xref std std-keyword">
|
||||
a
|
||||
.
|
||||
|
||||
doc (`sphinx.roles.XRefRole`):
|
||||
.
|
||||
{doc}`this lecture <heavy_tails>`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="std" refexplicit="True" reftarget="heavy_tails" reftype="doc" refwarn="True">
|
||||
<inline classes="xref std std-doc">
|
||||
this lecture
|
||||
.
|
|
@ -0,0 +1,800 @@
|
|||
Raw
|
||||
.
|
||||
foo
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
foo
|
||||
.
|
||||
|
||||
Hard-break
|
||||
.
|
||||
foo\
|
||||
bar
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
foo
|
||||
<raw format="html" xml:space="preserve">
|
||||
<br />
|
||||
<raw format="latex" xml:space="preserve">
|
||||
\\
|
||||
bar
|
||||
.
|
||||
|
||||
Strong:
|
||||
.
|
||||
**foo**
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<strong>
|
||||
foo
|
||||
.
|
||||
|
||||
Emphasis
|
||||
.
|
||||
*foo*
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
foo
|
||||
.
|
||||
|
||||
Escaped Emphasis:
|
||||
.
|
||||
\*foo*
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
*foo*
|
||||
.
|
||||
|
||||
Mixed Inline
|
||||
.
|
||||
a *b* **c** `abc` \\*
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
a
|
||||
<emphasis>
|
||||
b
|
||||
|
||||
<strong>
|
||||
c
|
||||
|
||||
<literal>
|
||||
abc
|
||||
\*
|
||||
.
|
||||
|
||||
Inline Code:
|
||||
.
|
||||
`foo`
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<literal>
|
||||
foo
|
||||
.
|
||||
|
||||
Heading:
|
||||
.
|
||||
# foo
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<section ids="foo" names="foo">
|
||||
<title>
|
||||
foo
|
||||
.
|
||||
|
||||
Heading Levels:
|
||||
.
|
||||
# a
|
||||
## b
|
||||
### c
|
||||
# d
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<section ids="a" names="a">
|
||||
<title>
|
||||
a
|
||||
<section ids="b" names="b">
|
||||
<title>
|
||||
b
|
||||
<section ids="c" names="c">
|
||||
<title>
|
||||
c
|
||||
<section ids="d" names="d">
|
||||
<title>
|
||||
d
|
||||
.
|
||||
|
||||
Block Code:
|
||||
.
|
||||
foo
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<literal_block language="none" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Fenced Code:
|
||||
.
|
||||
```sh
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<literal_block language="sh" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Fenced Code no language:
|
||||
.
|
||||
```
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<literal_block language="default" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Fenced Code no language with trailing whitespace:
|
||||
.
|
||||
```
|
||||
foo
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<literal_block language="default" xml:space="preserve">
|
||||
foo
|
||||
.
|
||||
|
||||
Image empty:
|
||||
.
|
||||
![]()
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<image alt="" uri="">
|
||||
.
|
||||
|
||||
Image with alt and title:
|
||||
.
|
||||
![alt](src "title")
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<image alt="alt" title="title" uri="src">
|
||||
.
|
||||
|
||||
Image with escapable html:
|
||||
.
|
||||
![alt](http://www.google<>.com)
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<image alt="alt" uri="http://www.google%3C%3E.com">
|
||||
.
|
||||
|
||||
Block Quote:
|
||||
.
|
||||
> *foo*
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
foo
|
||||
.
|
||||
|
||||
Bullet List:
|
||||
.
|
||||
- *foo*
|
||||
* bar
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
foo
|
||||
<bullet_list bullet="*">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
bar
|
||||
.
|
||||
|
||||
Nested Bullets
|
||||
.
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
- d
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
a
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
b
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
c
|
||||
<list_item>
|
||||
<paragraph>
|
||||
d
|
||||
.
|
||||
|
||||
Enumerated List:
|
||||
.
|
||||
1. *foo*
|
||||
|
||||
1) bar
|
||||
|
||||
para
|
||||
|
||||
10. starting
|
||||
11. enumerator
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<enumerated_list enumtype="arabic" prefix="" suffix=".">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
foo
|
||||
<enumerated_list enumtype="arabic" prefix="" suffix=")">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
bar
|
||||
<paragraph>
|
||||
para
|
||||
<enumerated_list enumtype="arabic" prefix="" start="10" suffix=".">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
starting
|
||||
<list_item>
|
||||
<paragraph>
|
||||
enumerator
|
||||
.
|
||||
|
||||
Nested Enumrated List:
|
||||
.
|
||||
1. a
|
||||
2. b
|
||||
1. c
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<enumerated_list enumtype="arabic" prefix="" suffix=".">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
a
|
||||
<list_item>
|
||||
<paragraph>
|
||||
b
|
||||
<enumerated_list enumtype="arabic" prefix="" suffix=".">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
c
|
||||
.
|
||||
|
||||
Sphinx Role containing backtick:
|
||||
.
|
||||
{code}``a=1{`}``
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<literal classes="code">
|
||||
a=1{`}
|
||||
.
|
||||
|
||||
Target:
|
||||
.
|
||||
(target)=
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<target ids="target" names="target">
|
||||
.
|
||||
|
||||
Target with whitespace:
|
||||
.
|
||||
(target with space)=
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<target ids="target-with-space" names="target\ with\ space">
|
||||
.
|
||||
|
||||
Referencing:
|
||||
.
|
||||
(target)=
|
||||
|
||||
Title
|
||||
=====
|
||||
|
||||
[alt1](target)
|
||||
|
||||
[](target2)
|
||||
|
||||
[alt2](https://www.google.com)
|
||||
|
||||
[alt3](#target3)
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<target ids="target" names="target">
|
||||
<section ids="title" names="title">
|
||||
<title>
|
||||
Title
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="target" reftype="myst" refwarn="True">
|
||||
<inline classes="xref myst">
|
||||
alt1
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="target2" reftype="myst" refwarn="True">
|
||||
<inline classes="xref myst">
|
||||
<paragraph>
|
||||
<reference refuri="https://www.google.com">
|
||||
alt2
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="#target3" reftype="myst" refwarn="True">
|
||||
<inline classes="xref myst">
|
||||
alt3
|
||||
.
|
||||
|
||||
Comments:
|
||||
.
|
||||
line 1
|
||||
% a comment
|
||||
line 2
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
line 1
|
||||
<comment xml:space="preserve">
|
||||
a comment
|
||||
<paragraph>
|
||||
line 2
|
||||
.
|
||||
|
||||
Block Break:
|
||||
.
|
||||
+++ string
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<comment classes="block_break" xml:space="preserve">
|
||||
string
|
||||
.
|
||||
|
||||
Link Reference:
|
||||
.
|
||||
[name][key]
|
||||
|
||||
[key]: https://www.google.com "a title"
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<reference refuri="https://www.google.com" title="a title">
|
||||
name
|
||||
.
|
||||
|
||||
Link Reference short version:
|
||||
.
|
||||
[name]
|
||||
|
||||
[name]: https://www.google.com "a title"
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<reference refuri="https://www.google.com" title="a title">
|
||||
name
|
||||
.
|
||||
|
||||
Block Quotes:
|
||||
.
|
||||
```{epigraph}
|
||||
a b*c*
|
||||
|
||||
-- a**b**
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<block_quote classes="epigraph">
|
||||
<paragraph>
|
||||
a b
|
||||
<emphasis>
|
||||
c
|
||||
<attribution>
|
||||
a
|
||||
<strong>
|
||||
b
|
||||
.
|
||||
|
||||
Link Definition in directive:
|
||||
.
|
||||
```{note}
|
||||
[a]
|
||||
```
|
||||
|
||||
[a]: link
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<note>
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="link" reftype="myst" refwarn="True">
|
||||
<inline classes="xref myst">
|
||||
a
|
||||
.
|
||||
|
||||
Link Definition in nested directives:
|
||||
.
|
||||
```{note}
|
||||
[ref1]: link
|
||||
```
|
||||
|
||||
```{note}
|
||||
[ref1]
|
||||
[ref2]
|
||||
```
|
||||
|
||||
```{note}
|
||||
[ref2]: link
|
||||
```
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<note>
|
||||
<note>
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="link" reftype="myst" refwarn="True">
|
||||
<inline classes="xref myst">
|
||||
ref1
|
||||
|
||||
[ref2]
|
||||
<note>
|
||||
.
|
||||
|
||||
Footnotes:
|
||||
.
|
||||
[^a]
|
||||
|
||||
[^a]: footnote*text*
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<footnote_reference auto="1" ids="id1" refname="a">
|
||||
<transition classes="footnotes">
|
||||
<footnote auto="1" ids="a" names="a">
|
||||
<paragraph>
|
||||
footnote
|
||||
<emphasis>
|
||||
text
|
||||
.
|
||||
|
||||
Footnotes nested blocks:
|
||||
.
|
||||
[^a]
|
||||
|
||||
[^a]: footnote*text*
|
||||
|
||||
abc
|
||||
xyz
|
||||
|
||||
> a
|
||||
|
||||
- b
|
||||
|
||||
c
|
||||
|
||||
finish
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<paragraph>
|
||||
<footnote_reference auto="1" ids="id1" refname="a">
|
||||
<paragraph>
|
||||
finish
|
||||
<transition classes="footnotes">
|
||||
<footnote auto="1" ids="a" names="a">
|
||||
<paragraph>
|
||||
footnote
|
||||
<emphasis>
|
||||
text
|
||||
<paragraph>
|
||||
abc
|
||||
|
||||
xyz
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
a
|
||||
<bullet_list bullet="-">
|
||||
<list_item>
|
||||
<paragraph>
|
||||
b
|
||||
<paragraph>
|
||||
c
|
||||
.
|
||||
|
||||
Front Matter:
|
||||
.
|
||||
---
|
||||
a: 1
|
||||
b: foo
|
||||
c:
|
||||
d: 2
|
||||
---
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<field_list>
|
||||
<field>
|
||||
<field_name>
|
||||
a
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
1
|
||||
<field>
|
||||
<field_name>
|
||||
b
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
foo
|
||||
<field>
|
||||
<field_name>
|
||||
c
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
{"d": 2}
|
||||
.
|
||||
|
||||
Front Matter Biblio:
|
||||
.
|
||||
---
|
||||
author: Chris Sewell
|
||||
authors: Chris Sewell, Chris Hodgraf
|
||||
organization: EPFL
|
||||
address: |
|
||||
1 Cedar Park Close
|
||||
Thundersley
|
||||
Essex
|
||||
contact: <https://example.com>
|
||||
version: 1.0
|
||||
revision: 1.1
|
||||
status: good
|
||||
date: 2/12/1985
|
||||
copyright: MIT
|
||||
dedication: |
|
||||
To my *homies*
|
||||
abstract:
|
||||
Something something **dark** side
|
||||
other: Something else
|
||||
---
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<field_list>
|
||||
<field>
|
||||
<field_name>
|
||||
author
|
||||
<field_body>
|
||||
<paragraph>
|
||||
Chris Sewell
|
||||
<field>
|
||||
<field_name>
|
||||
authors
|
||||
<field_body>
|
||||
<paragraph>
|
||||
Chris Sewell, Chris Hodgraf
|
||||
<field>
|
||||
<field_name>
|
||||
organization
|
||||
<field_body>
|
||||
<paragraph>
|
||||
EPFL
|
||||
<field>
|
||||
<field_name>
|
||||
address
|
||||
<field_body>
|
||||
<paragraph>
|
||||
1 Cedar Park Close
|
||||
|
||||
Thundersley
|
||||
|
||||
Essex
|
||||
|
||||
<field>
|
||||
<field_name>
|
||||
contact
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<reference refuri="https://example.com">
|
||||
https://example.com
|
||||
<field>
|
||||
<field_name>
|
||||
version
|
||||
<field_body>
|
||||
<paragraph>
|
||||
1.0
|
||||
<field>
|
||||
<field_name>
|
||||
revision
|
||||
<field_body>
|
||||
<paragraph>
|
||||
1.1
|
||||
<field>
|
||||
<field_name>
|
||||
status
|
||||
<field_body>
|
||||
<paragraph>
|
||||
good
|
||||
<field>
|
||||
<field_name>
|
||||
date
|
||||
<field_body>
|
||||
<paragraph>
|
||||
2/12/1985
|
||||
<field>
|
||||
<field_name>
|
||||
copyright
|
||||
<field_body>
|
||||
<paragraph>
|
||||
MIT
|
||||
<field>
|
||||
<field_name>
|
||||
dedication
|
||||
<field_body>
|
||||
<paragraph>
|
||||
To my
|
||||
<emphasis>
|
||||
homies
|
||||
|
||||
<field>
|
||||
<field_name>
|
||||
abstract
|
||||
<field_body>
|
||||
<paragraph>
|
||||
Something something
|
||||
<strong>
|
||||
dark
|
||||
side
|
||||
<field>
|
||||
<field_name>
|
||||
other
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
Something else
|
||||
.
|
||||
|
||||
Front Matter Bad Yaml:
|
||||
.
|
||||
---
|
||||
a: {
|
||||
---
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<system_message level="2" line="1" source="<src>/index.md" type="WARNING">
|
||||
<paragraph>
|
||||
Malformed YAML [myst.topmatter]
|
||||
.
|
||||
|
||||
Front Matter HTML Meta
|
||||
.
|
||||
---
|
||||
myst:
|
||||
html_meta:
|
||||
keywords: Sphinx, documentation, builder
|
||||
description lang=en: An amusing story
|
||||
description lang=fr: Un histoire amusant
|
||||
http-equiv=Content-Type: text/html; charset=ISO-8859-1
|
||||
---
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.components.Filter
|
||||
.details:
|
||||
component: 'writer'
|
||||
format: 'html'
|
||||
nodes:
|
||||
<meta content="Sphinx, documentation, builder" name="keywords">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.components.Filter
|
||||
.details:
|
||||
component: 'writer'
|
||||
format: 'html'
|
||||
nodes:
|
||||
<meta content="An amusing story" lang="en" name="description">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.components.Filter
|
||||
.details:
|
||||
component: 'writer'
|
||||
format: 'html'
|
||||
nodes:
|
||||
<meta content="Un histoire amusant" lang="fr" name="description">
|
||||
<pending>
|
||||
.. internal attributes:
|
||||
.transform: docutils.transforms.components.Filter
|
||||
.details:
|
||||
component: 'writer'
|
||||
format: 'html'
|
||||
nodes:
|
||||
<meta content="text/html; charset=ISO-8859-1" http-equiv="Content-Type">
|
||||
.
|
||||
|
||||
Full Test:
|
||||
.
|
||||
---
|
||||
a: 1
|
||||
---
|
||||
|
||||
(target)=
|
||||
# header 1
|
||||
## sub header 1
|
||||
|
||||
a *b* **c** `abc`
|
||||
|
||||
## sub header 2
|
||||
|
||||
x y [a](http://www.xyz.com) z
|
||||
|
||||
---
|
||||
|
||||
# header 2
|
||||
|
||||
```::python {a=1}
|
||||
a = 1
|
||||
```
|
||||
|
||||
[](target)
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<field_list>
|
||||
<field>
|
||||
<field_name>
|
||||
a
|
||||
<field_body>
|
||||
<paragraph>
|
||||
<literal>
|
||||
1
|
||||
<target ids="target" names="target">
|
||||
<section ids="header-1" names="header\ 1">
|
||||
<title>
|
||||
header 1
|
||||
<section ids="sub-header-1" names="sub\ header\ 1">
|
||||
<title>
|
||||
sub header 1
|
||||
<paragraph>
|
||||
a
|
||||
<emphasis>
|
||||
b
|
||||
|
||||
<strong>
|
||||
c
|
||||
|
||||
<literal>
|
||||
abc
|
||||
<section ids="sub-header-2" names="sub\ header\ 2">
|
||||
<title>
|
||||
sub header 2
|
||||
<paragraph>
|
||||
x y
|
||||
<reference refuri="http://www.xyz.com">
|
||||
a
|
||||
z
|
||||
<transition>
|
||||
<section ids="header-2" names="header\ 2">
|
||||
<title>
|
||||
header 2
|
||||
<literal_block language="::python" xml:space="preserve">
|
||||
a = 1
|
||||
<paragraph>
|
||||
<pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="target" reftype="myst" refwarn="True">
|
||||
<inline classes="xref myst">
|
||||
.
|
|
@ -0,0 +1,148 @@
|
|||
Simple:
|
||||
.
|
||||
a|b
|
||||
-|-
|
||||
1|2
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<table classes="colwidths-auto">
|
||||
<tgroup cols="2">
|
||||
<colspec colwidth="50">
|
||||
<colspec colwidth="50">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
a
|
||||
<entry>
|
||||
<paragraph>
|
||||
b
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
1
|
||||
<entry>
|
||||
<paragraph>
|
||||
2
|
||||
.
|
||||
|
||||
Header only:
|
||||
.
|
||||
| abc | def |
|
||||
| --- | --- |
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<table classes="colwidths-auto">
|
||||
<tgroup cols="2">
|
||||
<colspec colwidth="50">
|
||||
<colspec colwidth="50">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
abc
|
||||
<entry>
|
||||
<paragraph>
|
||||
def
|
||||
.
|
||||
|
||||
Aligned:
|
||||
.
|
||||
a | b | c
|
||||
:-|:-:| -:
|
||||
1 | 2 | 3
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<table classes="colwidths-auto">
|
||||
<tgroup cols="3">
|
||||
<colspec colwidth="33">
|
||||
<colspec colwidth="33">
|
||||
<colspec colwidth="33">
|
||||
<thead>
|
||||
<row>
|
||||
<entry classes="text-left">
|
||||
<paragraph>
|
||||
a
|
||||
<entry classes="text-center">
|
||||
<paragraph>
|
||||
b
|
||||
<entry classes="text-right">
|
||||
<paragraph>
|
||||
c
|
||||
<tbody>
|
||||
<row>
|
||||
<entry classes="text-left">
|
||||
<paragraph>
|
||||
1
|
||||
<entry classes="text-center">
|
||||
<paragraph>
|
||||
2
|
||||
<entry classes="text-right">
|
||||
<paragraph>
|
||||
3
|
||||
.
|
||||
|
||||
Nested syntax:
|
||||
.
|
||||
| *a* | __*b*__ |
|
||||
| --- | -------- |
|
||||
|c | {sub}`x` |
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<table classes="colwidths-auto">
|
||||
<tgroup cols="2">
|
||||
<colspec colwidth="50">
|
||||
<colspec colwidth="50">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
<emphasis>
|
||||
a
|
||||
<entry>
|
||||
<paragraph>
|
||||
<strong>
|
||||
<emphasis>
|
||||
b
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
c
|
||||
<entry>
|
||||
<paragraph>
|
||||
<subscript>
|
||||
x
|
||||
.
|
||||
|
||||
External links:
|
||||
.
|
||||
a|b
|
||||
|-|-|
|
||||
[link-a](https://www.google.com/)|[link-b](https://www.python.org/)
|
||||
.
|
||||
<document source="<src>/index.md">
|
||||
<table classes="colwidths-auto">
|
||||
<tgroup cols="2">
|
||||
<colspec colwidth="50">
|
||||
<colspec colwidth="50">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
a
|
||||
<entry>
|
||||
<paragraph>
|
||||
b
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<paragraph>
|
||||
<reference refuri="https://www.google.com/">
|
||||
link-a
|
||||
<entry>
|
||||
<paragraph>
|
||||
<reference refuri="https://www.python.org/">
|
||||
link-b
|
||||
.
|
|
@ -0,0 +1,22 @@
|
|||
"""Tests of the warning reporting for different MyST Markdown inputs."""
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from docutils.core import publish_doctree
|
||||
|
||||
from myst_parser.parsers.docutils_ import Parser
|
||||
|
||||
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "reporter_warnings.md")
|
||||
def test_basic(file_params):
|
||||
"""Test basic functionality."""
|
||||
report_stream = StringIO()
|
||||
publish_doctree(
|
||||
file_params.content,
|
||||
parser=Parser(),
|
||||
settings_overrides={"warning_stream": report_stream},
|
||||
)
|
||||
file_params.assert_expected(report_stream.getvalue(), rstrip=True)
|
|
@ -0,0 +1,107 @@
|
|||
"""Test fixture files, using the ``DocutilsRenderer``.
|
||||
|
||||
Note, the output AST is before any transforms are applied.
|
||||
"""
|
||||
import shlex
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from docutils.core import Publisher, publish_doctree
|
||||
|
||||
from myst_parser.parsers.docutils_ import Parser
|
||||
|
||||
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "docutil_syntax_elements.md")
|
||||
def test_syntax_elements(file_params, monkeypatch):
|
||||
"""Test conversion of Markdown to docutils AST (before transforms are applied)."""
|
||||
|
||||
def _apply_transforms(self):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(Publisher, "apply_transforms", _apply_transforms)
|
||||
|
||||
doctree = publish_doctree(
|
||||
file_params.content,
|
||||
source_path="notset",
|
||||
parser=Parser(),
|
||||
settings_overrides={"myst_highlight_code_blocks": False},
|
||||
)
|
||||
|
||||
# in docutils 0.18 footnote ids have changed
|
||||
outcome = doctree.pformat().replace('"footnote-reference-1"', '"id1"')
|
||||
file_params.assert_expected(outcome, rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "docutil_roles.md")
|
||||
def test_docutils_roles(file_params, monkeypatch):
|
||||
"""Test conversion of Markdown to docutils AST (before transforms are applied)."""
|
||||
|
||||
def _apply_transforms(self):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(Publisher, "apply_transforms", _apply_transforms)
|
||||
|
||||
doctree = publish_doctree(
|
||||
file_params.content,
|
||||
source_path="notset",
|
||||
parser=Parser(),
|
||||
)
|
||||
|
||||
ptree = doctree.pformat()
|
||||
# docutils >=0.19 changes:
|
||||
ptree = ptree.replace(
|
||||
'refuri="http://tools.ietf.org/html/rfc1.html"',
|
||||
'refuri="https://tools.ietf.org/html/rfc1.html"',
|
||||
)
|
||||
ptree = ptree.replace(
|
||||
'refuri="http://www.python.org/dev/peps/pep-0000"',
|
||||
'refuri="https://peps.python.org/pep-0000"',
|
||||
)
|
||||
|
||||
file_params.assert_expected(ptree, rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "docutil_directives.md")
|
||||
def test_docutils_directives(file_params, monkeypatch):
|
||||
"""Test output of docutils directives."""
|
||||
if "SKIP" in file_params.description: # line-block directive not yet supported
|
||||
pytest.skip(file_params.description)
|
||||
|
||||
def _apply_transforms(self):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(Publisher, "apply_transforms", _apply_transforms)
|
||||
|
||||
doctree = publish_doctree(
|
||||
file_params.content,
|
||||
source_path="notset",
|
||||
parser=Parser(),
|
||||
)
|
||||
|
||||
file_params.assert_expected(doctree.pformat(), rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "docutil_syntax_extensions.txt")
|
||||
def test_syntax_extensions(file_params):
|
||||
"""The description is parsed as a docutils commandline"""
|
||||
pub = Publisher(parser=Parser())
|
||||
option_parser = pub.setup_option_parser()
|
||||
try:
|
||||
settings = option_parser.parse_args(
|
||||
shlex.split(file_params.description)
|
||||
).__dict__
|
||||
except Exception as err:
|
||||
raise AssertionError(
|
||||
f"Failed to parse commandline: {file_params.description}\n{err}"
|
||||
)
|
||||
report_stream = StringIO()
|
||||
settings["warning_stream"] = report_stream
|
||||
doctree = publish_doctree(
|
||||
file_params.content,
|
||||
parser=Parser(),
|
||||
settings_overrides=settings,
|
||||
)
|
||||
file_params.assert_expected(doctree.pformat(), rstrip_lines=True)
|
|
@ -0,0 +1,119 @@
|
|||
"""Test fixture files, using the ``SphinxRenderer``.
|
||||
|
||||
Note, the output AST is before any transforms are applied.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from sphinx_pytest.plugin import CreateDoctree
|
||||
|
||||
from myst_parser.mdit_to_docutils.sphinx_ import SphinxRenderer
|
||||
|
||||
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "sphinx_syntax_elements.md")
|
||||
def test_syntax_elements(file_params, sphinx_doctree_no_tr: CreateDoctree):
|
||||
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
|
||||
result = sphinx_doctree_no_tr(file_params.content, "index.md")
|
||||
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "tables.md")
|
||||
def test_tables(file_params, sphinx_doctree_no_tr: CreateDoctree):
|
||||
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
|
||||
result = sphinx_doctree_no_tr(file_params.content, "index.md")
|
||||
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "directive_options.md")
|
||||
def test_directive_options(file_params, sphinx_doctree_no_tr: CreateDoctree):
|
||||
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
|
||||
result = sphinx_doctree_no_tr(file_params.content, "index.md")
|
||||
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "sphinx_directives.md")
|
||||
def test_sphinx_directives(file_params, sphinx_doctree_no_tr: CreateDoctree):
|
||||
# TODO fix skipped directives
|
||||
# TODO test domain directives
|
||||
if file_params.title.startswith("SKIP") or file_params.title.startswith(
|
||||
"SPHINX4-SKIP"
|
||||
):
|
||||
pytest.skip(file_params.title)
|
||||
|
||||
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
|
||||
pformat = sphinx_doctree_no_tr(file_params.content, "index.md").pformat("index")
|
||||
# see https://github.com/sphinx-doc/sphinx/issues/9827
|
||||
pformat = pformat.replace('<glossary sorted="False">', "<glossary>")
|
||||
# see https://github.com/executablebooks/MyST-Parser/issues/522
|
||||
if sys.maxsize == 2147483647:
|
||||
pformat = pformat.replace('"2147483647"', '"9223372036854775807"')
|
||||
file_params.assert_expected(pformat, rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "sphinx_roles.md")
|
||||
def test_sphinx_roles(file_params, sphinx_doctree_no_tr: CreateDoctree):
|
||||
if file_params.title.startswith("SKIP"):
|
||||
pytest.skip(file_params.title)
|
||||
|
||||
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
|
||||
pformat = sphinx_doctree_no_tr(file_params.content, "index.md").pformat("index")
|
||||
# sphinx 3 adds a parent key
|
||||
pformat = re.sub('cpp:parent_key="[^"]*"', 'cpp:parent_key=""', pformat)
|
||||
# sphinx >= 4.5.0 adds a trailing slash to PEP URLs,
|
||||
# see https://github.com/sphinx-doc/sphinx/commit/658689433eacc9eb
|
||||
pformat = pformat.replace(
|
||||
' refuri="http://www.python.org/dev/peps/pep-0001">',
|
||||
' refuri="http://www.python.org/dev/peps/pep-0001/">',
|
||||
)
|
||||
file_params.assert_expected(pformat, rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "dollarmath.md")
|
||||
def test_dollarmath(file_params, sphinx_doctree_no_tr: CreateDoctree):
|
||||
sphinx_doctree_no_tr.set_conf(
|
||||
{"extensions": ["myst_parser"], "myst_enable_extensions": ["dollarmath"]}
|
||||
)
|
||||
result = sphinx_doctree_no_tr(file_params.content, "index.md")
|
||||
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "amsmath.md")
|
||||
def test_amsmath(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch):
|
||||
monkeypatch.setattr(SphinxRenderer, "_random_label", lambda self: "mock-uuid")
|
||||
sphinx_doctree_no_tr.set_conf(
|
||||
{"extensions": ["myst_parser"], "myst_enable_extensions": ["amsmath"]}
|
||||
)
|
||||
result = sphinx_doctree_no_tr(file_params.content, "index.md")
|
||||
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "containers.md")
|
||||
def test_containers(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch):
|
||||
monkeypatch.setattr(SphinxRenderer, "_random_label", lambda self: "mock-uuid")
|
||||
sphinx_doctree_no_tr.set_conf(
|
||||
{"extensions": ["myst_parser"], "myst_enable_extensions": ["colon_fence"]}
|
||||
)
|
||||
result = sphinx_doctree_no_tr(file_params.content, "index.md")
|
||||
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "eval_rst.md")
|
||||
def test_evalrst_elements(file_params, sphinx_doctree_no_tr: CreateDoctree):
|
||||
sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
|
||||
result = sphinx_doctree_no_tr(file_params.content, "index.md")
|
||||
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "definition_lists.md")
|
||||
def test_definition_lists(file_params, sphinx_doctree_no_tr: CreateDoctree):
|
||||
sphinx_doctree_no_tr.set_conf(
|
||||
{"extensions": ["myst_parser"], "myst_enable_extensions": ["deflist"]}
|
||||
)
|
||||
result = sphinx_doctree_no_tr(file_params.content, "index.md")
|
||||
file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
|
|
@ -0,0 +1,52 @@
|
|||
import os
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from docutils.core import publish_doctree
|
||||
|
||||
from myst_parser.parsers.docutils_ import Parser
|
||||
|
||||
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "mock_include.md")
|
||||
def test_render(file_params, tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
tmp_path.joinpath("other.md").write_text("a\nb\nc")
|
||||
tmp_path.joinpath("fmatter.md").write_text("---\na: 1\n---\nb")
|
||||
|
||||
doctree = publish_doctree(
|
||||
file_params.content,
|
||||
parser=Parser(),
|
||||
settings_overrides={"myst_highlight_code_blocks": False},
|
||||
)
|
||||
|
||||
doctree["source"] = "tmpdir/test.md"
|
||||
output = doctree.pformat().replace(str(tmp_path) + os.sep, "tmpdir" + "/").rstrip()
|
||||
|
||||
file_params.assert_expected(output, rstrip=True)
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "mock_include_errors.md")
|
||||
def test_errors(file_params, tmp_path, monkeypatch):
|
||||
if file_params.title.startswith("Non-existent path") and os.name == "nt":
|
||||
pytest.skip("tmp_path not converted correctly on Windows")
|
||||
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
tmp_path.joinpath("bad.md").write_text("{a}`b`")
|
||||
|
||||
report_stream = StringIO()
|
||||
publish_doctree(
|
||||
file_params.content,
|
||||
source_path=str(tmp_path / "test.md"),
|
||||
parser=Parser(),
|
||||
settings_overrides={"halt_level": 6, "warning_stream": report_stream},
|
||||
)
|
||||
|
||||
file_params.assert_expected(
|
||||
report_stream.getvalue().replace(str(tmp_path) + os.sep, "tmpdir" + "/"),
|
||||
rstrip=True,
|
||||
)
|
|
@ -0,0 +1,38 @@
|
|||
"""Test (docutils) parsing with different ``MdParserConfig`` options set."""
|
||||
import shlex
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from docutils.core import Publisher, publish_doctree
|
||||
|
||||
from myst_parser.parsers.docutils_ import Parser
|
||||
|
||||
FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
|
||||
|
||||
|
||||
@pytest.mark.param_file(FIXTURE_PATH / "myst-config.txt")
|
||||
def test_cmdline(file_params):
|
||||
"""The description is parsed as a docutils commandline"""
|
||||
pub = Publisher(parser=Parser())
|
||||
option_parser = pub.setup_option_parser()
|
||||
try:
|
||||
settings = option_parser.parse_args(
|
||||
shlex.split(file_params.description)
|
||||
).__dict__
|
||||
except Exception as err:
|
||||
raise AssertionError(
|
||||
f"Failed to parse commandline: {file_params.description}\n{err}"
|
||||
)
|
||||
report_stream = StringIO()
|
||||
settings["warning_stream"] = report_stream
|
||||
doctree = publish_doctree(
|
||||
file_params.content,
|
||||
parser=Parser(),
|
||||
settings_overrides=settings,
|
||||
)
|
||||
output = doctree.pformat()
|
||||
warnings = report_stream.getvalue()
|
||||
if warnings:
|
||||
output += "\n" + warnings
|
||||
file_params.assert_expected(output, rstrip_lines=True)
|
|
@ -0,0 +1,38 @@
|
|||
import pytest
|
||||
from sphinx_pytest.plugin import CreateDoctree
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_name,text,should_warn",
|
||||
[
|
||||
("null", "", False),
|
||||
("missing", "[](ref)", True),
|
||||
("doc", "[](index)", False),
|
||||
("doc_with_extension", "[](index.md)", False),
|
||||
("doc_nested", "[*text*](index)", False),
|
||||
("ref", "(ref)=\n# Title\n[](ref)", False),
|
||||
("ref_nested", "(ref)=\n# Title\n[*text*](ref)", False),
|
||||
("duplicate", "(index)=\n# Title\n[](index)", True),
|
||||
("ref_colon", "(ref:colon)=\n# Title\n[](ref:colon)", False),
|
||||
],
|
||||
)
|
||||
def test_parse(
|
||||
test_name: str,
|
||||
text: str,
|
||||
should_warn: bool,
|
||||
sphinx_doctree: CreateDoctree,
|
||||
file_regression,
|
||||
):
|
||||
sphinx_doctree.set_conf({"extensions": ["myst_parser"]})
|
||||
result = sphinx_doctree(text, "index.md")
|
||||
assert not result.warnings
|
||||
|
||||
doctree = result.get_resolved_doctree("index")
|
||||
|
||||
if should_warn:
|
||||
assert result.warnings
|
||||
else:
|
||||
assert not result.warnings
|
||||
|
||||
doctree["source"] = "root/index.md"
|
||||
file_regression.check(doctree.pformat(), basename=test_name, extension=".xml")
|
|
@ -0,0 +1,5 @@
|
|||
<document source="root/index.md">
|
||||
<paragraph>
|
||||
<reference internal="True" refuri="">
|
||||
<inline classes="doc std std-doc">
|
||||
<no title>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue