├── .coveragerc ├── .github ├── dependabot.yml └── workflows │ └── tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── codecov.yml ├── docs ├── conf.py └── index.md ├── mdit_py_plugins ├── __init__.py ├── admon │ ├── LICENSE │ ├── __init__.py │ ├── index.py │ └── port.yaml ├── amsmath │ └── __init__.py ├── anchors │ ├── __init__.py │ └── index.py ├── attrs │ ├── __init__.py │ ├── index.py │ └── parse.py ├── colon_fence.py ├── container │ ├── LICENSE │ ├── README.md │ ├── __init__.py │ ├── index.py │ └── port.yaml ├── deflist │ ├── LICENSE │ ├── README.md │ ├── __init__.py │ ├── index.py │ └── port.yaml ├── dollarmath │ ├── __init__.py │ └── index.py ├── field_list │ └── __init__.py ├── footnote │ ├── LICENSE │ ├── __init__.py │ ├── index.py │ └── port.yaml ├── front_matter │ ├── LICENSE │ ├── __init__.py │ ├── index.py │ └── port.yaml ├── myst_blocks │ ├── __init__.py │ └── index.py ├── myst_role │ ├── __init__.py │ └── index.py ├── py.typed ├── substitution.py ├── tasklists │ ├── __init__.py │ └── port.yaml ├── texmath │ ├── LICENSE │ ├── README.md │ ├── __init__.py │ ├── index.py │ └── port.yaml ├── utils.py └── wordcount │ └── __init__.py ├── pyproject.toml ├── tests ├── fixtures │ ├── admon.md │ ├── amsmath.md │ ├── anchors.md │ ├── attrs.md │ ├── colon_fence.md │ ├── container.md │ ├── deflist.md │ ├── dollar_math.md │ ├── field_list.md │ ├── footnote.md │ ├── front_matter.md │ ├── myst_block.md │ ├── myst_role.md │ ├── span.md │ ├── substitution.md │ ├── tasklists.md │ ├── texmath_bracket.md │ ├── texmath_dollar.md │ └── wordcount.md ├── test_admon.py ├── test_admon │ ├── test_plugin_parse_0_.yml │ ├── test_plugin_parse_1_.yml │ └── test_plugin_parse_2_.yml ├── test_amsmath.py ├── test_amsmath │ └── test_plugin_parse.yml ├── test_anchors.py ├── test_attrs.py ├── test_attrs │ └── test_attrs_allowed.yml ├── test_colon_fence.py ├── test_colon_fence │ └── test_plugin_parse.yml ├── test_container.py ├── test_container │ ├── test_no_new_line_issue.yml │ └── test_plugin_parse.yml ├── test_deflist.py ├── test_deflist │ └── test_plugin_parse.yml ├── test_dollarmath.py ├── test_dollarmath │ └── test_plugin_parse.yml ├── test_field_list.py ├── test_field_list │ └── test_plugin_parse.yml ├── test_footnote.py ├── test_front_matter.py ├── test_myst_block.py ├── test_myst_role.py ├── test_substitution.py ├── test_substitution │ └── test_tokens.yml ├── test_tasklists.py ├── test_tasklists │ └── test_plugin_parse.yml ├── test_texmath.py ├── test_texmath │ └── test_plugin_parse.yml └── test_wordcount.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_lines = 3 | pragma: no cover 4 | if TYPE_CHECKING: 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: github-actions 9 | directory: / 10 | commit-message: 11 | prefix: ⬆️ 12 | schedule: 13 | interval: weekly 14 | - package-ecosystem: pip 15 | directory: / 16 | commit-message: 17 | prefix: ⬆️ 18 | schedule: 19 | interval: weekly 20 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: continuous-integration 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | tags: 10 | - 'v*' 11 | pull_request: 12 | 13 | jobs: 14 | 15 | pre-commit: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Set up Python 3.8 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: 3.8 25 | - uses: pre-commit/action@v3.0.1 26 | 27 | tests: 28 | 29 | runs-on: ubuntu-latest 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | python-version: ['pypy-3.8', '3.8', '3.9', '3.10', '3.11', '3.12'] 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | - name: Set up Python ${{ matrix.python-version }} 38 | uses: actions/setup-python@v5 39 | with: 40 | python-version: ${{ matrix.python-version }} 41 | - name: Install dependencies 42 | run: | 43 | python -m pip install --upgrade pip 44 | pip install -e .[testing] 45 | - name: Run pytest 46 | run: | 47 | pytest --cov=mdit_py_plugins --cov-report=xml --cov-report=term-missing 48 | - name: Upload to Codecov 49 | uses: codecov/codecov-action@v3 50 | if: github.event.pull_request.head.repo.full_name == github.repository 51 | with: 52 | token: ${{ secrets.CODECOV_TOKEN }} 53 | name: mdit-py-plugins-pytests 54 | flags: pytests 55 | file: ./coverage.xml 56 | fail_ci_if_error: true 57 | 58 | allgood: 59 | runs-on: ubuntu-latest 60 | needs: 61 | - pre-commit 62 | - tests 63 | steps: 64 | - run: echo "Great success!" 65 | 66 | publish: 67 | 68 | name: Publish to PyPi 69 | needs: [pre-commit, tests] 70 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') 71 | runs-on: ubuntu-latest 72 | steps: 73 | - name: Checkout source 74 | uses: actions/checkout@v4 75 | - name: Set up Python 76 | uses: actions/setup-python@v5 77 | with: 78 | python-version: "3.8" 79 | - name: install flit 80 | run: | 81 | pip install flit~=3.4 82 | - name: Build and publish 83 | run: | 84 | flit publish 85 | env: 86 | FLIT_USERNAME: __token__ 87 | FLIT_PASSWORD: ${{ secrets.PYPI_KEY }} 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | benchmark/extra/ 132 | node_modules/ 133 | coverage/ 134 | demo/ 135 | apidoc/ 136 | *.log 137 | __pycache__/ 138 | .ropeproject/ 139 | *.egg-info/ 140 | .vscode/ 141 | .DS_Store 142 | 143 | docs/api/ 144 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Install pre-commit hooks via 2 | # pre-commit install 3 | 4 | exclude: > 5 | (?x)^( 6 | \.vscode/settings\.json| 7 | test.*\.md| 8 | test.*\.txt| 9 | test.*\.html| 10 | )$ 11 | 12 | repos: 13 | 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v4.6.0 16 | hooks: 17 | - id: check-json 18 | - id: check-yaml 19 | - id: end-of-file-fixer 20 | - id: trailing-whitespace 21 | 22 | - repo: https://github.com/astral-sh/ruff-pre-commit 23 | rev: v0.4.4 24 | hooks: 25 | - id: ruff 26 | args: [--fix] 27 | - id: ruff-format 28 | 29 | - repo: https://github.com/pre-commit/mirrors-mypy 30 | rev: v1.10.0 31 | hooks: 32 | - id: mypy 33 | additional_dependencies: [markdown-it-py~=3.0] 34 | exclude: ^tests/ 35 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.8" 7 | 8 | python: 9 | install: 10 | - method: pip 11 | path: . 12 | extra_requirements: [rtd] 13 | 14 | sphinx: 15 | builder: html 16 | fail_on_warning: true 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.4.2 - 2024-09-09 4 | 5 | - 👌 Improve parsing of nested amsmath 6 | 7 | The previous logic was problematic for amsmath blocks nested in other blocs (such as blockquotes) 8 | 9 | The new parsing code now principally follows the logic in `markdown_it/rules_block/fence.py` 10 | (see also ), 11 | except that: 12 | 13 | 1. it allows for a closing tag on the same line as the opening tag, and 14 | 2. it does not allow for an opening tag without closing tag (i.e. no auto-closing) 15 | 16 | - ✨ Add `allowed` option for inline/block attributes 17 | 18 | The `allowed` option accepts a list of allowed attribute names. 19 | If not ``None``, any attributes not in this list will be removed 20 | and placed in the token's meta under the key `"insecure_attrs"`. 21 | 22 | ## 0.4.1 - 2024-05-12 23 | 24 | * 👌 Add option for footnotes references to always be matched 25 | 26 | Usually footnote references are only matched when a footnote definition of the same label has already been found. If `always_match_refs=True`, any `[^...]` syntax will be treated as a footnote. 27 | 28 | ## 0.4.0 - 2023-06-05 29 | 30 | * ⬆️ UPGRADE: Drop python 3.7 and support 3.11 ([#77](https://github.com/executablebooks/mdit-py-plugins/pull/77)) 31 | 32 | * ⬆️ UPGRADE: Allow markdown-it-py v3 ([#85](https://github.com/executablebooks/mdit-py-plugins/pull/85)) 33 | * 👌 Make field_list compatible with latest upstream ([#75](https://github.com/executablebooks/mdit-py-plugins/pull/75)) 34 | * 🔧 Convert `state.srcCharCode` -> `state.src` ([#84](https://github.com/executablebooks/mdit-py-plugins/pull/84)) 35 | * 🔧 Remove unnecessary method arg by @chrisjsewell in( [#76](https://github.com/executablebooks/mdit-py-plugins/pull/76)) 36 | * 👌 Centralise code block test ([#83](https://github.com/executablebooks/mdit-py-plugins/pull/83) and [#87](https://github.com/executablebooks/mdit-py-plugins/pull/87)) 37 | * This means that disabling the `code` block rule in markdown-it-py v3+ will now allow all syntax blocks to be indented by any amount of whitespace. 38 | 39 | * 👌 Improve `dollarmath` plugin: Add `allow_blank_lines` option, thanks to [@eric-wieser](https://github.com/eric-wieser) ([#46](https://github.com/executablebooks/mdit-py-plugins/pull/46)) 40 | 41 | * 👌 Improve `admon` plugin: Add `???` support, thanks to [@KyleKing](https://github.com/KyleKing) ([#58](https://github.com/executablebooks/mdit-py-plugins/pull/58)) 42 | 43 | * 🔧 MAINTAIN: Make type checking strict ([#86](https://github.com/executablebooks/mdit-py-plugins/pull/86)) 44 | 45 | **Full Changelog**: 46 | 47 | ## 0.3.5 - 2023-03-02 48 | 49 | - 🐛 FIX: Regression in dollarmath by @chrisjsewell in [#69](https://github.com/executablebooks/mdit-py-plugins/pull/69) 50 | - 🐛 Fix regression in amsmath by @chrisjsewell in [#70](https://github.com/executablebooks/mdit-py-plugins/pull/70) 51 | - 🔧 Correct project documentation link by @andersk in [#73](https://github.com/executablebooks/mdit-py-plugins/pull/73) 52 | 53 | ## 0.3.4 - 2023-02-18 54 | 55 | - ✨ NEW: Add attrs_block_plugin by @chrisjsewell in [#66](https://github.com/executablebooks/mdit-py-plugins/pull/66) 56 | - 👌 Improve field lists by @chrisjsewell in [#65](https://github.com/executablebooks/mdit-py-plugins/pull/65) 57 | - 🔧 Update pre-commit by @chrisjsewell in [#64](https://github.com/executablebooks/mdit-py-plugins/pull/64) (moving from flake8 to ruff) 58 | 59 | **Full Changelog**: [v0.3.3...v0.3.](https://github.com/executablebooks/mdit-py-plugins/compare/v0.3.3...v0.3.4) 60 | 61 | ## 0.3.3 - 2022-12-06 62 | 63 | 🐛 FIX: span with end of inline before attrs 64 | 65 | ## 0.3.2 - 2022-12-05 66 | 67 | - ✨ NEW: Port `admon` plugin by @KyleKing ([#53](https://github.com/executablebooks/mdit-py-plugins/pull/53)) 68 | - ✨ NEW: Add span parsing to inline attributes plugin by @chrisjsewell ([#55](https://github.com/executablebooks/mdit-py-plugins/pull/55)) 69 | - 🐛 FIX: Task list item marker can be followed by any GFM whitespace by @hukkin in ([#42](https://github.com/executablebooks/mdit-py-plugins/pull/42)) 70 | 71 | **Full Changelog**: [v0.3.1...v0.4.0](https://github.com/executablebooks/mdit-py-plugins/compare/v0.3.1...v0.4.0) 72 | 73 | ## 0.3.1 - 2022-09-27 74 | 75 | - ⬆️ UPGRADE: Drop Python 3.6, support Python 3.10 76 | - 🐛 FIX: Parsing when newline is between footnote ID and first paragraph 77 | - 🐛 FIX: Anchor ids in separate renders no longer affect each other. 78 | - ✨ NEW: Add `attrs_plugin` 79 | - 🔧 MAINTAIN: Use flit PEP 621 package build 80 | 81 | ## 0.3.0 - 2021-12-03 82 | 83 | - ⬆️ UPGRADE: Compatible with markdown-it-py `v2`. 84 | - ✨ NEW: Add field list plugin, Based on the [restructuredtext syntax](https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#field-lists) 85 | - ♻️ REFACTOR: dollarmath plugin, `math_block_eqno` -> `math_block_label` token 86 | - ♻️ REFACTOR: Remove AttrDict usage from texmath 87 | - 👌 IMPROVE: Default HTML rendering for dollarmath and amsmath plugins 88 | - 👌 IMPROVE: Add render options for dollarmath and amsmath plugins 89 | - 👌 IMPROVE: MyST parsing of target blocks (allow whitespace) and roles (allow for new lines) 90 | 91 | ## 0.2.8 - 2021-05-03 92 | 93 | 🐛 FIX: `wordcount` update of minutes 94 | 95 | ## 0.2.7 - 2021-05-03 96 | 97 | - ⬆️ UPDATE: markdown-it-py~=1.0 98 | - ✨ NEW: Add `wordcount_plugin` 99 | - 👌 IMPROVE: `dollarmath`: Allow inline double-dollar 100 | - 👌 IMPROVE: `myst_blocks`: Parse multiline comments 101 | - 👌 IMPROVE: Replace use of `env` as an `AttrDict` 102 | - 🐛 FIX: `front_matter`: don't duplicate content storage in `Token.meta` 103 | 104 | ## 0.2.6 - 2021-03-17 105 | 106 | 👌 IMPROVE: Remove direct use of `Token.attrs` 107 | 108 | ## 0.2.5 - 2021-02-06 109 | 110 | 🐛 FIX: front-matter: `IndexError` if first line is single dash 111 | 112 | ## 0.2.2 - 2020-12-16 113 | 114 | ✨ NEW: Add substitution_plugin 115 | (improvements in 0.2.3 and 0.2.4) 116 | 117 | ## 0.2.0 - 2020-12-14 118 | 119 | Add mypy type-checking, code taken from: https://github.com/executablebooks/markdown-it-py/commit/2eb1fe6b47cc0ad4ebe954cabd91fb8e52a2f03d 120 | 121 | ## 0.1.0 - 2020-12-14 122 | 123 | First release, code taken from: https://github.com/executablebooks/markdown-it-py/commit/3a5bdcc98e67de9df26ebb8bc7cd0221a0d6b51b 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ExecutableBookProject 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mdit-py-plugins 2 | 3 | [![Github-CI][github-ci]][github-link] 4 | [![Coverage Status][codecov-badge]][codecov-link] 5 | [![PyPI][pypi-badge]][pypi-link] 6 | [![Conda][conda-badge]][conda-link] 7 | [![Code style: black][black-badge]][black-link] 8 | 9 | Collection of core plugins for [markdown-it-py](https://github.com/executablebooks/markdown-it-py). 10 | 11 | [github-ci]: https://github.com/executablebooks/mdit-py-plugins/workflows/continuous-integration/badge.svg 12 | [github-link]: https://github.com/executablebooks/mdit-py-plugins 13 | [pypi-badge]: https://img.shields.io/pypi/v/mdit-py-plugins.svg 14 | [pypi-link]: https://pypi.org/project/mdit-py-plugins 15 | [conda-badge]: https://anaconda.org/conda-forge/mdit-py-plugins/badges/version.svg 16 | [conda-link]: https://anaconda.org/conda-forge/mdit-py-plugins 17 | [codecov-badge]: https://codecov.io/gh/executablebooks/mdit-py-plugins/branch/master/graph/badge.svg 18 | [codecov-link]: https://codecov.io/gh/executablebooks/mdit-py-plugins 19 | [black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg 20 | [black-link]: https://github.com/ambv/black 21 | [install-badge]: https://img.shields.io/pypi/dw/mdit-py-plugins?label=pypi%20installs 22 | [install-link]: https://pypistats.org/packages/mdit-py-plugins 23 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 92% 6 | threshold: 0.2% 7 | patch: 8 | default: 9 | target: 80% 10 | threshold: 0.2% 11 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | project = "mdit-py-plugins" 2 | copyright = "2020, Executable Book Project" 3 | author = "Executable Book Project" 4 | 5 | master_doc = "index" 6 | 7 | extensions = [ 8 | "sphinx.ext.autodoc", 9 | "sphinx.ext.viewcode", 10 | "sphinx.ext.intersphinx", 11 | "myst_parser", 12 | ] 13 | 14 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 15 | 16 | intersphinx_mapping = { 17 | "python": ("https://docs.python.org/3.8", None), 18 | "markdown_it": ("https://markdown-it-py.readthedocs.io/en/latest", None), 19 | } 20 | 21 | html_title = "mdit-py-plugins" 22 | html_theme = "sphinx_book_theme" 23 | html_theme_options = { 24 | "use_edit_page_button": True, 25 | "repository_url": "https://github.com/executablebooks/mdit-py-plugins", 26 | "repository_branch": "master", 27 | "path_to_docs": "docs", 28 | } 29 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Markdown-It-Py Plugin Extensions 2 | 3 | ## Built-in 4 | 5 | The following plugins are embedded within the core package: 6 | 7 | - [tables](https://help.github.com/articles/organizing-information-with-tables/) (GFM) 8 | - [strikethrough](https://help.github.com/articles/basic-writing-and-formatting-syntax/#styling-text) (GFM) 9 | 10 | These can be enabled individually: 11 | 12 | ```python 13 | from markdown_it import MarkdownIt 14 | md = MarkdownIt("commonmark").enable('table') 15 | ``` 16 | 17 | or as part of a configuration: 18 | 19 | ```python 20 | from markdown_it import MarkdownIt 21 | md = MarkdownIt("gfm-like") 22 | ``` 23 | 24 | ```{seealso} 25 | See 26 | ``` 27 | 28 | ## mdit-py-plugins package 29 | 30 | The [`mdit_py_plugins`](https://github.com/executablebooks/mdit-py-plugins), contains a number of common plugins. 31 | They can be chained and loaded *via*: 32 | 33 | ```python 34 | from markdown_it import MarkdownIt 35 | from mdit_py_plugins import plugin1, plugin2 36 | md = MarkdownIt().use(plugin1, keyword=value).use(plugin2, keyword=value) 37 | html_string = md.render("some *Markdown*") 38 | ``` 39 | 40 | ## Front-Matter 41 | 42 | ```{eval-rst} 43 | .. autofunction:: mdit_py_plugins.front_matter.front_matter_plugin 44 | ``` 45 | 46 | ## Footnotes 47 | 48 | ```{eval-rst} 49 | .. autofunction:: mdit_py_plugins.footnote.footnote_plugin 50 | ``` 51 | 52 | ## Definition Lists 53 | 54 | ```{eval-rst} 55 | .. autofunction:: mdit_py_plugins.deflist.deflist_plugin 56 | ``` 57 | 58 | ## Task lists 59 | 60 | ```{eval-rst} 61 | .. autofunction:: mdit_py_plugins.tasklists.tasklists_plugin 62 | ``` 63 | 64 | ## Field Lists 65 | 66 | ```{eval-rst} 67 | .. autofunction:: mdit_py_plugins.field_list.fieldlist_plugin 68 | ``` 69 | 70 | ## Heading Anchors 71 | 72 | ```{eval-rst} 73 | .. autofunction:: mdit_py_plugins.anchors.anchors_plugin 74 | ``` 75 | 76 | ## Word Count 77 | 78 | ```{eval-rst} 79 | .. autofunction:: mdit_py_plugins.wordcount.wordcount_plugin 80 | ``` 81 | 82 | ## Containers 83 | 84 | ```{eval-rst} 85 | .. autofunction:: mdit_py_plugins.container.container_plugin 86 | ``` 87 | 88 | ```{eval-rst} 89 | .. autofunction:: mdit_py_plugins.admon.admon_plugin 90 | ``` 91 | 92 | ## Attributes 93 | 94 | ```{eval-rst} 95 | .. autofunction:: mdit_py_plugins.attrs.attrs_plugin 96 | ``` 97 | 98 | ```{eval-rst} 99 | .. autofunction:: mdit_py_plugins.attrs.attrs_block_plugin 100 | ``` 101 | 102 | ## Math 103 | 104 | ```{eval-rst} 105 | .. autofunction:: mdit_py_plugins.texmath.texmath_plugin 106 | ``` 107 | 108 | ```{eval-rst} 109 | .. autofunction:: mdit_py_plugins.dollarmath.dollarmath_plugin 110 | ``` 111 | 112 | ```{eval-rst} 113 | .. autofunction:: mdit_py_plugins.amsmath.amsmath_plugin 114 | ``` 115 | 116 | ## MyST plugins 117 | 118 | `myst_blocks` and `myst_role` plugins are also available, for utilisation by the [MyST renderer](https://myst-parser.readthedocs.io/en/latest/using/syntax.html) 119 | 120 | ```{eval-rst} 121 | .. autofunction:: mdit_py_plugins.myst_role.myst_role_plugin 122 | .. autofunction:: mdit_py_plugins.myst_blocks.myst_block_plugin 123 | ``` 124 | 125 | ## Write your own 126 | 127 | Use the `mdit_py_plugins` as a guide to write your own, following the [markdown-it design principles](inv:markdown_it#architecture). 128 | 129 | There are many other plugins which could easily be ported from the JS versions (and hopefully will): 130 | 131 | - [subscript](https://github.com/markdown-it/markdown-it-sub) 132 | - [superscript](https://github.com/markdown-it/markdown-it-sup) 133 | - [abbreviation](https://github.com/markdown-it/markdown-it-abbr) 134 | - [emoji](https://github.com/markdown-it/markdown-it-emoji) 135 | - [insert](https://github.com/markdown-it/markdown-it-ins) 136 | - [mark](https://github.com/markdown-it/markdown-it-mark) 137 | - ... and [others](https://www.npmjs.org/browse/keyword/markdown-it-plugin) 138 | -------------------------------------------------------------------------------- /mdit_py_plugins/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.4.2" 2 | -------------------------------------------------------------------------------- /mdit_py_plugins/admon/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Vitaly Puzrin, Alex Kocharin. 2 | Copyright (c) 2018 jebbs 3 | Copyright (c) 2021- commenthol 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /mdit_py_plugins/admon/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import admon_plugin 2 | 3 | __all__ = ("admon_plugin",) 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/admon/port.yaml: -------------------------------------------------------------------------------- 1 | - package: markdown-it-admon 2 | commit: 9820ba89415c464a3cc18a780f222a0ceb3e18bd 3 | date: Jul 3, 2021 4 | version: 1.0.0 5 | -------------------------------------------------------------------------------- /mdit_py_plugins/amsmath/__init__.py: -------------------------------------------------------------------------------- 1 | """An extension to capture amsmath latex environments.""" 2 | 3 | from __future__ import annotations 4 | 5 | import re 6 | from typing import TYPE_CHECKING, Callable, Sequence 7 | 8 | from markdown_it import MarkdownIt 9 | from markdown_it.common.utils import escapeHtml 10 | from markdown_it.rules_block import StateBlock 11 | 12 | from mdit_py_plugins.utils import is_code_block 13 | 14 | if TYPE_CHECKING: 15 | from markdown_it.renderer import RendererProtocol 16 | from markdown_it.token import Token 17 | from markdown_it.utils import EnvType, OptionsDict 18 | 19 | # Taken from amsmath version 2.1 20 | # http://anorien.csc.warwick.ac.uk/mirrors/CTAN/macros/latex/required/amsmath/amsldoc.pdf 21 | ENVIRONMENTS = [ 22 | # 3.2 single equation with an automatically gen-erated number 23 | "equation", 24 | # 3.3 variation equation, used for equations that dont fit on a single line 25 | "multline", 26 | # 3.5 a group of consecutive equations when there is no alignment desired among them 27 | "gather", 28 | # 3.6 Used for two or more equations when vertical alignment is desired 29 | "align", 30 | # allows the horizontal space between equationsto be explicitly specified. 31 | "alignat", 32 | # stretches the space betweenthe equation columns to the maximum possible width 33 | "flalign", 34 | # 4.1 The pmatrix, bmatrix, Bmatrix, vmatrix and Vmatrix have (respectively) 35 | # (),[],{},||,and ‖‖ delimiters built in. 36 | "matrix", 37 | "pmatrix", 38 | "bmatrix", 39 | "Bmatrix", 40 | "vmatrix", 41 | "Vmatrix", 42 | # eqnarray is another math environment, it is not part of amsmath, 43 | # and note that it is better to use align or equation+split instead 44 | "eqnarray", 45 | ] 46 | # other "non-top-level" environments: 47 | 48 | # 3.4 the split environment is for single equations that are too long to fit on one line 49 | # and hence must be split into multiple lines, 50 | # it is intended for use only inside some other displayed equation structure, 51 | # usually an equation, align, or gather environment 52 | 53 | # 3.7 variants gathered, aligned,and alignedat are provided 54 | # whose total width is the actual width of the contents; 55 | # thus they can be used as a component in a containing expression 56 | 57 | RE_OPEN = r"\\begin\{(" + "|".join(ENVIRONMENTS) + r")([\*]?)\}" 58 | 59 | 60 | def amsmath_plugin( 61 | md: MarkdownIt, *, renderer: Callable[[str], str] | None = None 62 | ) -> None: 63 | """Parses TeX math equations, without any surrounding delimiters, 64 | only for top-level `amsmath `__ environments: 65 | 66 | .. code-block:: latex 67 | 68 | \\begin{gather*} 69 | a_1=b_1+c_1\\\\ 70 | a_2=b_2+c_2-d_2+e_2 71 | \\end{gather*} 72 | 73 | :param renderer: Function to render content, by default escapes HTML 74 | 75 | """ 76 | md.block.ruler.before( 77 | "blockquote", 78 | "amsmath", 79 | amsmath_block, 80 | {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]}, 81 | ) 82 | 83 | _renderer = (lambda content: escapeHtml(content)) if renderer is None else renderer 84 | 85 | def render_amsmath_block( 86 | self: RendererProtocol, 87 | tokens: Sequence[Token], 88 | idx: int, 89 | options: OptionsDict, 90 | env: EnvType, 91 | ) -> str: 92 | content = _renderer(str(tokens[idx].content)) 93 | return f'
\n{content}\n
\n' 94 | 95 | md.add_render_rule("amsmath", render_amsmath_block) 96 | 97 | 98 | def amsmath_block( 99 | state: StateBlock, startLine: int, endLine: int, silent: bool 100 | ) -> bool: 101 | # note the code principally follows the logic in markdown_it/rules_block/fence.py, 102 | # except that: 103 | # (a) it allows for closing tag on same line as opening tag 104 | # (b) it does not allow for opening tag without closing tag (i.e. no auto-closing) 105 | 106 | if is_code_block(state, startLine): 107 | return False 108 | 109 | # does the first line contain the beginning of an amsmath environment 110 | first_start = state.bMarks[startLine] + state.tShift[startLine] 111 | first_end = state.eMarks[startLine] 112 | first_text = state.src[first_start:first_end] 113 | 114 | if not (match_open := re.match(RE_OPEN, first_text)): 115 | return False 116 | 117 | # construct the closing tag 118 | environment = match_open.group(1) 119 | numbered = match_open.group(2) 120 | closing = rf"\end{{{match_open.group(1)}{match_open.group(2)}}}" 121 | 122 | # start looking for the closing tag, including the current line 123 | nextLine = startLine - 1 124 | 125 | while True: 126 | nextLine += 1 127 | if nextLine >= endLine: 128 | # reached the end of the block without finding the closing tag 129 | return False 130 | 131 | next_start = state.bMarks[nextLine] + state.tShift[nextLine] 132 | next_end = state.eMarks[nextLine] 133 | if next_start < first_end and state.sCount[nextLine] < state.blkIndent: 134 | # non-empty line with negative indent should stop the list: 135 | # - \begin{align} 136 | # test 137 | return False 138 | 139 | if state.src[next_start:next_end].rstrip().endswith(closing): 140 | # found the closing tag 141 | break 142 | 143 | state.line = nextLine + 1 144 | 145 | if not silent: 146 | token = state.push("amsmath", "math", 0) 147 | token.block = True 148 | token.content = state.getLines( 149 | startLine, state.line, state.sCount[startLine], False 150 | ) 151 | token.meta = {"environment": environment, "numbered": numbered} 152 | token.map = [startLine, nextLine] 153 | 154 | return True 155 | -------------------------------------------------------------------------------- /mdit_py_plugins/anchors/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import anchors_plugin 2 | 3 | __all__ = ("anchors_plugin",) 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/anchors/index.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Callable, List, Optional, Set 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.rules_core import StateCore 6 | from markdown_it.token import Token 7 | 8 | 9 | def anchors_plugin( 10 | md: MarkdownIt, 11 | min_level: int = 1, 12 | max_level: int = 2, 13 | slug_func: Optional[Callable[[str], str]] = None, 14 | permalink: bool = False, 15 | permalinkSymbol: str = "¶", 16 | permalinkBefore: bool = False, 17 | permalinkSpace: bool = True, 18 | ) -> None: 19 | """Plugin for adding header anchors, based on 20 | `markdown-it-anchor `__ 21 | 22 | .. code-block:: md 23 | 24 | # Title String 25 | 26 | renders as: 27 | 28 | .. code-block:: html 29 | 30 |

Title String

31 | 32 | :param min_level: minimum header level to apply anchors 33 | :param max_level: maximum header level to apply anchors 34 | :param slug_func: function to convert title text to id slug. 35 | :param permalink: Add a permalink next to the title 36 | :param permalinkSymbol: the symbol to show 37 | :param permalinkBefore: Add the permalink before the title, otherwise after 38 | :param permalinkSpace: Add a space between the permalink and the title 39 | 40 | Note, the default slug function aims to mimic the GitHub Markdown format, see: 41 | 42 | - https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb 43 | - https://gist.github.com/asabaylus/3071099 44 | 45 | """ 46 | selected_levels = list(range(min_level, max_level + 1)) 47 | md.core.ruler.push( 48 | "anchor", 49 | _make_anchors_func( 50 | selected_levels, 51 | slug_func or slugify, 52 | permalink, 53 | permalinkSymbol, 54 | permalinkBefore, 55 | permalinkSpace, 56 | ), 57 | ) 58 | 59 | 60 | def _make_anchors_func( 61 | selected_levels: List[int], 62 | slug_func: Callable[[str], str], 63 | permalink: bool, 64 | permalinkSymbol: str, 65 | permalinkBefore: bool, 66 | permalinkSpace: bool, 67 | ) -> Callable[[StateCore], None]: 68 | def _anchor_func(state: StateCore) -> None: 69 | slugs: Set[str] = set() 70 | for idx, token in enumerate(state.tokens): 71 | if token.type != "heading_open": 72 | continue 73 | level = int(token.tag[1]) 74 | if level not in selected_levels: 75 | continue 76 | inline_token = state.tokens[idx + 1] 77 | assert inline_token.children is not None 78 | title = "".join( 79 | child.content 80 | for child in inline_token.children 81 | if child.type in ["text", "code_inline"] 82 | ) 83 | slug = unique_slug(slug_func(title), slugs) 84 | token.attrSet("id", slug) 85 | 86 | if permalink: 87 | link_open = Token( 88 | "link_open", 89 | "a", 90 | 1, 91 | ) 92 | link_open.attrSet("class", "header-anchor") 93 | link_open.attrSet("href", f"#{slug}") 94 | link_tokens = [ 95 | link_open, 96 | Token("html_block", "", 0, content=permalinkSymbol), 97 | Token("link_close", "a", -1), 98 | ] 99 | if permalinkBefore: 100 | inline_token.children = ( 101 | link_tokens 102 | + ( 103 | [Token("text", "", 0, content=" ")] 104 | if permalinkSpace 105 | else [] 106 | ) 107 | + inline_token.children 108 | ) 109 | else: 110 | inline_token.children.extend( 111 | ([Token("text", "", 0, content=" ")] if permalinkSpace else []) 112 | + link_tokens 113 | ) 114 | 115 | return _anchor_func 116 | 117 | 118 | def slugify(title: str) -> str: 119 | return re.sub(r"[^\w\u4e00-\u9fff\- ]", "", title.strip().lower().replace(" ", "-")) 120 | 121 | 122 | def unique_slug(slug: str, slugs: Set[str]) -> str: 123 | uniq = slug 124 | i = 1 125 | while uniq in slugs: 126 | uniq = f"{slug}-{i}" 127 | i += 1 128 | slugs.add(uniq) 129 | return uniq 130 | -------------------------------------------------------------------------------- /mdit_py_plugins/attrs/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import attrs_block_plugin, attrs_plugin 2 | 3 | __all__ = ("attrs_block_plugin", "attrs_plugin") 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/colon_fence.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Sequence 4 | 5 | from markdown_it import MarkdownIt 6 | from markdown_it.common.utils import escapeHtml, unescapeAll 7 | from markdown_it.rules_block import StateBlock 8 | 9 | from mdit_py_plugins.utils import is_code_block 10 | 11 | if TYPE_CHECKING: 12 | from markdown_it.renderer import RendererProtocol 13 | from markdown_it.token import Token 14 | from markdown_it.utils import EnvType, OptionsDict 15 | 16 | 17 | def colon_fence_plugin(md: MarkdownIt) -> None: 18 | """This plugin directly mimics regular fences, but with `:` colons. 19 | 20 | Example:: 21 | 22 | :::name 23 | contained text 24 | ::: 25 | 26 | """ 27 | 28 | md.block.ruler.before( 29 | "fence", 30 | "colon_fence", 31 | _rule, 32 | {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]}, 33 | ) 34 | md.add_render_rule("colon_fence", _render) 35 | 36 | 37 | def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 38 | if is_code_block(state, startLine): 39 | return False 40 | 41 | haveEndMarker = False 42 | pos = state.bMarks[startLine] + state.tShift[startLine] 43 | maximum = state.eMarks[startLine] 44 | 45 | if pos + 3 > maximum: 46 | return False 47 | 48 | marker = state.src[pos] 49 | 50 | if marker != ":": 51 | return False 52 | 53 | # scan marker length 54 | mem = pos 55 | pos = _skipCharsStr(state, pos, marker) 56 | 57 | length = pos - mem 58 | 59 | if length < 3: 60 | return False 61 | 62 | markup = state.src[mem:pos] 63 | params = state.src[pos:maximum] 64 | 65 | # Since start is found, we can report success here in validation mode 66 | if silent: 67 | return True 68 | 69 | # search end of block 70 | nextLine = startLine 71 | 72 | while True: 73 | nextLine += 1 74 | if nextLine >= endLine: 75 | # unclosed block should be autoclosed by end of document. 76 | # also block seems to be autoclosed by end of parent 77 | break 78 | 79 | pos = mem = state.bMarks[nextLine] + state.tShift[nextLine] 80 | maximum = state.eMarks[nextLine] 81 | 82 | if pos < maximum and state.sCount[nextLine] < state.blkIndent: 83 | # non-empty line with negative indent should stop the list: 84 | # - ``` 85 | # test 86 | break 87 | 88 | if state.src[pos] != marker: 89 | continue 90 | 91 | if is_code_block(state, nextLine): 92 | continue 93 | 94 | pos = _skipCharsStr(state, pos, marker) 95 | 96 | # closing code fence must be at least as long as the opening one 97 | if pos - mem < length: 98 | continue 99 | 100 | # make sure tail has spaces only 101 | pos = state.skipSpaces(pos) 102 | 103 | if pos < maximum: 104 | continue 105 | 106 | haveEndMarker = True 107 | # found! 108 | break 109 | 110 | # If a fence has heading spaces, they should be removed from its inner block 111 | length = state.sCount[startLine] 112 | 113 | state.line = nextLine + (1 if haveEndMarker else 0) 114 | 115 | token = state.push("colon_fence", "code", 0) 116 | token.info = params 117 | token.content = state.getLines(startLine + 1, nextLine, length, True) 118 | token.markup = markup 119 | token.map = [startLine, state.line] 120 | 121 | return True 122 | 123 | 124 | def _skipCharsStr(state: StateBlock, pos: int, ch: str) -> int: 125 | """Skip character string from given position.""" 126 | # TODO this can be replaced with StateBlock.skipCharsStr in markdown-it-py 3.0.0 127 | while True: 128 | try: 129 | current = state.src[pos] 130 | except IndexError: 131 | break 132 | if current != ch: 133 | break 134 | pos += 1 135 | return pos 136 | 137 | 138 | def _render( 139 | self: RendererProtocol, 140 | tokens: Sequence[Token], 141 | idx: int, 142 | options: OptionsDict, 143 | env: EnvType, 144 | ) -> str: 145 | token = tokens[idx] 146 | info = unescapeAll(token.info).strip() if token.info else "" 147 | content = escapeHtml(token.content) 148 | block_name = "" 149 | 150 | if info: 151 | block_name = info.split()[0] 152 | 153 | return ( 154 | "
"
157 |         + content
158 |         + "
\n" 159 | ) 160 | -------------------------------------------------------------------------------- /mdit_py_plugins/container/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Vitaly Puzrin, Alex Kocharin. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /mdit_py_plugins/container/README.md: -------------------------------------------------------------------------------- 1 | # markdown-it-container 2 | 3 | [![Build Status](https://img.shields.io/travis/markdown-it/markdown-it-container/master.svg?style=flat)](https://travis-ci.org/markdown-it/markdown-it-container) 4 | [![NPM version](https://img.shields.io/npm/v/markdown-it-container.svg?style=flat)](https://www.npmjs.org/package/markdown-it-container) 5 | [![Coverage Status](https://img.shields.io/coveralls/markdown-it/markdown-it-container/master.svg?style=flat)](https://coveralls.io/r/markdown-it/markdown-it-container?branch=master) 6 | 7 | > Plugin for creating block-level custom containers for [markdown-it](https://github.com/markdown-it/markdown-it) markdown parser. 8 | 9 | __v2.+ requires `markdown-it` v5.+, see changelog.__ 10 | 11 | With this plugin you can create block containers like: 12 | 13 | ``` 14 | ::: warning 15 | *here be dragons* 16 | ::: 17 | ``` 18 | 19 | .... and specify how they should be rendered. If no renderer defined, `
` with 20 | container name class will be created: 21 | 22 | ```html 23 |
24 | here be dragons 25 |
26 | ``` 27 | 28 | Markup is the same as for [fenced code blocks](http://spec.commonmark.org/0.18/#fenced-code-blocks). 29 | Difference is, that marker use another character and content is rendered as markdown markup. 30 | 31 | 32 | ## Installation 33 | 34 | node.js, browser: 35 | 36 | ```bash 37 | $ npm install markdown-it-container --save 38 | $ bower install markdown-it-container --save 39 | ``` 40 | 41 | 42 | ## API 43 | 44 | ```js 45 | var md = require('markdown-it')() 46 | .use(require('markdown-it-container'), name [, options]); 47 | ``` 48 | 49 | Params: 50 | 51 | - __name__ - container name (mandatory) 52 | - __options:__ 53 | - __validate__ - optional, function to validate tail after opening marker, should 54 | return `true` on success. 55 | - __render__ - optional, renderer function for opening/closing tokens. 56 | - __marker__ - optional (`:`), character to use in delimiter. 57 | 58 | 59 | ## Example 60 | 61 | ```js 62 | var md = require('markdown-it')(); 63 | 64 | md.use(require('markdown-it-container'), 'spoiler', { 65 | 66 | validate: function(params) { 67 | return params.trim().match(/^spoiler\s+(.*)$/); 68 | }, 69 | 70 | render: function (tokens, idx) { 71 | var m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/); 72 | 73 | if (tokens[idx].nesting === 1) { 74 | // opening tag 75 | return '
' + md.utils.escapeHtml(m[1]) + '\n'; 76 | 77 | } else { 78 | // closing tag 79 | return '
\n'; 80 | } 81 | } 82 | }); 83 | 84 | console.log(md.render('::: spoiler click me\n*content*\n:::\n')); 85 | 86 | // Output: 87 | // 88 | //
click me 89 | //

content

90 | //
91 | ``` 92 | 93 | ## License 94 | 95 | [MIT](https://github.com/markdown-it/markdown-it-container/blob/master/LICENSE) 96 | -------------------------------------------------------------------------------- /mdit_py_plugins/container/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import container_plugin 2 | 3 | __all__ = ("container_plugin",) 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/container/index.py: -------------------------------------------------------------------------------- 1 | """Process block-level custom containers.""" 2 | 3 | from __future__ import annotations 4 | 5 | from math import floor 6 | from typing import TYPE_CHECKING, Any, Callable, Sequence 7 | 8 | from markdown_it import MarkdownIt 9 | from markdown_it.rules_block import StateBlock 10 | 11 | from mdit_py_plugins.utils import is_code_block 12 | 13 | if TYPE_CHECKING: 14 | from markdown_it.renderer import RendererProtocol 15 | from markdown_it.token import Token 16 | from markdown_it.utils import EnvType, OptionsDict 17 | 18 | 19 | def container_plugin( 20 | md: MarkdownIt, 21 | name: str, 22 | marker: str = ":", 23 | validate: None | Callable[[str, str], bool] = None, 24 | render: None | Callable[..., str] = None, 25 | ) -> None: 26 | """Plugin ported from 27 | `markdown-it-container `__. 28 | 29 | It is a plugin for creating block-level custom containers: 30 | 31 | .. code-block:: md 32 | 33 | :::: name 34 | ::: name 35 | *markdown* 36 | ::: 37 | :::: 38 | 39 | :param name: the name of the container to parse 40 | :param marker: the marker character to use 41 | :param validate: func(marker, param) -> bool, default matches against the name 42 | :param render: render func 43 | 44 | """ 45 | 46 | def validateDefault(params: str, *args: Any) -> bool: 47 | return params.strip().split(" ", 2)[0] == name 48 | 49 | def renderDefault( 50 | self: RendererProtocol, 51 | tokens: Sequence[Token], 52 | idx: int, 53 | _options: OptionsDict, 54 | env: EnvType, 55 | ) -> str: 56 | # add a class to the opening tag 57 | if tokens[idx].nesting == 1: 58 | tokens[idx].attrJoin("class", name) 59 | 60 | return self.renderToken(tokens, idx, _options, env) # type: ignore[attr-defined,no-any-return] 61 | 62 | min_markers = 3 63 | marker_str = marker 64 | marker_char = marker_str[0] 65 | marker_len = len(marker_str) 66 | validate = validate or validateDefault 67 | render = render or renderDefault 68 | 69 | def container_func( 70 | state: StateBlock, startLine: int, endLine: int, silent: bool 71 | ) -> bool: 72 | if is_code_block(state, startLine): 73 | return False 74 | 75 | auto_closed = False 76 | start = state.bMarks[startLine] + state.tShift[startLine] 77 | maximum = state.eMarks[startLine] 78 | 79 | # Check out the first character quickly, 80 | # this should filter out most of non-containers 81 | if marker_char != state.src[start]: 82 | return False 83 | 84 | # Check out the rest of the marker string 85 | pos = start + 1 86 | while pos <= maximum: 87 | try: 88 | character = state.src[pos] 89 | except IndexError: 90 | break 91 | if marker_str[(pos - start) % marker_len] != character: 92 | break 93 | pos += 1 94 | 95 | marker_count = floor((pos - start) / marker_len) 96 | if marker_count < min_markers: 97 | return False 98 | pos -= (pos - start) % marker_len 99 | 100 | markup = state.src[start:pos] 101 | params = state.src[pos:maximum] 102 | assert validate is not None 103 | if not validate(params, markup): 104 | return False 105 | 106 | # Since start is found, we can report success here in validation mode 107 | if silent: 108 | return True 109 | 110 | # Search for the end of the block 111 | nextLine = startLine 112 | 113 | while True: 114 | nextLine += 1 115 | if nextLine >= endLine: 116 | # unclosed block should be autoclosed by end of document. 117 | # also block seems to be autoclosed by end of parent 118 | break 119 | 120 | start = state.bMarks[nextLine] + state.tShift[nextLine] 121 | maximum = state.eMarks[nextLine] 122 | 123 | if start < maximum and state.sCount[nextLine] < state.blkIndent: 124 | # non-empty line with negative indent should stop the list: 125 | # - ``` 126 | # test 127 | break 128 | 129 | if marker_char != state.src[start]: 130 | continue 131 | 132 | if is_code_block(state, nextLine): 133 | continue 134 | 135 | pos = start + 1 136 | while pos <= maximum: 137 | try: 138 | character = state.src[pos] 139 | except IndexError: 140 | break 141 | if marker_str[(pos - start) % marker_len] != character: 142 | break 143 | pos += 1 144 | 145 | # closing code fence must be at least as long as the opening one 146 | if floor((pos - start) / marker_len) < marker_count: 147 | continue 148 | 149 | # make sure tail has spaces only 150 | pos -= (pos - start) % marker_len 151 | pos = state.skipSpaces(pos) 152 | 153 | if pos < maximum: 154 | continue 155 | 156 | # found! 157 | auto_closed = True 158 | break 159 | 160 | old_parent = state.parentType 161 | old_line_max = state.lineMax 162 | state.parentType = "container" 163 | 164 | # this will prevent lazy continuations from ever going past our end marker 165 | state.lineMax = nextLine 166 | 167 | token = state.push(f"container_{name}_open", "div", 1) 168 | token.markup = markup 169 | token.block = True 170 | token.info = params 171 | token.map = [startLine, nextLine] 172 | 173 | state.md.block.tokenize(state, startLine + 1, nextLine) 174 | 175 | token = state.push(f"container_{name}_close", "div", -1) 176 | token.markup = state.src[start:pos] 177 | token.block = True 178 | 179 | state.parentType = old_parent 180 | state.lineMax = old_line_max 181 | state.line = nextLine + (1 if auto_closed else 0) 182 | 183 | return True 184 | 185 | md.block.ruler.before( 186 | "fence", 187 | "container_" + name, 188 | container_func, 189 | {"alt": ["paragraph", "reference", "blockquote", "list"]}, 190 | ) 191 | md.add_render_rule(f"container_{name}_open", render) 192 | md.add_render_rule(f"container_{name}_close", render) 193 | -------------------------------------------------------------------------------- /mdit_py_plugins/container/port.yaml: -------------------------------------------------------------------------------- 1 | - package: markdown-it-container 2 | commit: adb3defde3a1c56015895b47ce4c6591b8b1e3a2 3 | date: Jun 2, 2020 4 | version: 3.0.0 5 | changes: 6 | -------------------------------------------------------------------------------- /mdit_py_plugins/deflist/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015 Vitaly Puzrin, Alex Kocharin. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /mdit_py_plugins/deflist/README.md: -------------------------------------------------------------------------------- 1 | # markdown-it-deflist 2 | 3 | [![Build Status](https://img.shields.io/travis/markdown-it/markdown-it-deflist/master.svg?style=flat)](https://travis-ci.org/markdown-it/markdown-it-deflist) 4 | [![NPM version](https://img.shields.io/npm/v/markdown-it-deflist.svg?style=flat)](https://www.npmjs.org/package/markdown-it-deflist) 5 | [![Coverage Status](https://img.shields.io/coveralls/markdown-it/markdown-it-deflist/master.svg?style=flat)](https://coveralls.io/r/markdown-it/markdown-it-deflist?branch=master) 6 | 7 | > Definition list (`
`) tag plugin for [markdown-it](https://github.com/markdown-it/markdown-it) markdown parser. 8 | 9 | __v2.+ requires `markdown-it` v5.+, see changelog.__ 10 | 11 | Syntax is based on [pandoc definition lists](http://johnmacfarlane.net/pandoc/README.html#definition-lists). 12 | 13 | 14 | ## Install 15 | 16 | node.js, browser: 17 | 18 | ```bash 19 | npm install markdown-it-deflist --save 20 | bower install markdown-it-deflist --save 21 | ``` 22 | 23 | ## Use 24 | 25 | ```js 26 | var md = require('markdown-it')() 27 | .use(require('markdown-it-deflist')); 28 | 29 | md.render(/*...*/); 30 | ``` 31 | 32 | _Differences in browser._ If you load script directly into the page, without 33 | package system, module will add itself globally as `window.markdownitDeflist`. 34 | 35 | 36 | ## License 37 | 38 | [MIT](https://github.com/markdown-it/markdown-it-deflist/blob/master/LICENSE) 39 | -------------------------------------------------------------------------------- /mdit_py_plugins/deflist/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import deflist_plugin 2 | 3 | __all__ = ("deflist_plugin",) 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/deflist/port.yaml: -------------------------------------------------------------------------------- 1 | - package: markdown-it-deflist 2 | commit: 20db400948520308291da029a23b0751cb30f3a0 3 | date: July 12, 2017 4 | version: 2.0.3 5 | changes: 6 | -------------------------------------------------------------------------------- /mdit_py_plugins/dollarmath/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import dollarmath_plugin 2 | 3 | __all__ = ("dollarmath_plugin",) 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/footnote/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015 Vitaly Puzrin, Alex Kocharin. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /mdit_py_plugins/footnote/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import footnote_plugin 2 | 3 | __all__ = ("footnote_plugin",) 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/footnote/port.yaml: -------------------------------------------------------------------------------- 1 | - package: markdown-it-footnote 2 | commit: cab6665ba39c6eb517cbbae3baeb549004bf740c 3 | date: Jul 9, 2019 4 | version: 3.0.2 5 | -------------------------------------------------------------------------------- /mdit_py_plugins/front_matter/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2020 ParkSB. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /mdit_py_plugins/front_matter/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import front_matter_plugin 2 | 3 | __all__ = ("front_matter_plugin",) 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/front_matter/index.py: -------------------------------------------------------------------------------- 1 | """Process front matter.""" 2 | 3 | from markdown_it import MarkdownIt 4 | from markdown_it.rules_block import StateBlock 5 | 6 | from mdit_py_plugins.utils import is_code_block 7 | 8 | 9 | def front_matter_plugin(md: MarkdownIt) -> None: 10 | """Plugin ported from 11 | `markdown-it-front-matter `__. 12 | 13 | It parses initial metadata, stored between opening/closing dashes: 14 | 15 | .. code-block:: md 16 | 17 | --- 18 | valid-front-matter: true 19 | --- 20 | 21 | """ 22 | md.block.ruler.before( 23 | "table", 24 | "front_matter", 25 | _front_matter_rule, 26 | {"alt": ["paragraph", "reference", "blockquote", "list"]}, 27 | ) 28 | 29 | 30 | def _front_matter_rule( 31 | state: StateBlock, startLine: int, endLine: int, silent: bool 32 | ) -> bool: 33 | marker_chr = "-" 34 | min_markers = 3 35 | 36 | auto_closed = False 37 | start = state.bMarks[startLine] + state.tShift[startLine] 38 | maximum = state.eMarks[startLine] 39 | src_len = len(state.src) 40 | 41 | # Check out the first character of the first line quickly, 42 | # this should filter out non-front matter 43 | if startLine != 0 or state.src[0] != marker_chr: 44 | return False 45 | 46 | # Check out the rest of the marker string 47 | # while pos <= 3 48 | pos = start + 1 49 | while pos <= maximum and pos < src_len: 50 | if state.src[pos] != marker_chr: 51 | break 52 | pos += 1 53 | 54 | marker_count = pos - start 55 | 56 | if marker_count < min_markers: 57 | return False 58 | 59 | # Since start is found, we can report success here in validation mode 60 | if silent: 61 | return True 62 | 63 | # Search for the end of the block 64 | nextLine = startLine 65 | 66 | while True: 67 | nextLine += 1 68 | if nextLine >= endLine: 69 | # unclosed block should be autoclosed by end of document. 70 | return False 71 | 72 | if state.src[start:maximum] == "...": 73 | break 74 | 75 | start = state.bMarks[nextLine] + state.tShift[nextLine] 76 | maximum = state.eMarks[nextLine] 77 | 78 | if start < maximum and state.sCount[nextLine] < state.blkIndent: 79 | # non-empty line with negative indent should stop the list: 80 | # - ``` 81 | # test 82 | break 83 | 84 | if state.src[start] != marker_chr: 85 | continue 86 | 87 | if is_code_block(state, nextLine): 88 | continue 89 | 90 | pos = start + 1 91 | while pos < maximum: 92 | if state.src[pos] != marker_chr: 93 | break 94 | pos += 1 95 | 96 | # closing code fence must be at least as long as the opening one 97 | if (pos - start) < marker_count: 98 | continue 99 | 100 | # make sure tail has spaces only 101 | pos = state.skipSpaces(pos) 102 | 103 | if pos < maximum: 104 | continue 105 | 106 | # found! 107 | auto_closed = True 108 | break 109 | 110 | old_parent = state.parentType 111 | old_line_max = state.lineMax 112 | state.parentType = "container" 113 | 114 | # this will prevent lazy continuations from ever going past our end marker 115 | state.lineMax = nextLine 116 | 117 | token = state.push("front_matter", "", 0) 118 | token.hidden = True 119 | token.markup = marker_chr * min_markers 120 | token.content = state.src[state.bMarks[startLine + 1] : state.eMarks[nextLine - 1]] 121 | token.block = True 122 | 123 | state.parentType = old_parent 124 | state.lineMax = old_line_max 125 | state.line = nextLine + (1 if auto_closed else 0) 126 | token.map = [startLine, state.line] 127 | 128 | return True 129 | -------------------------------------------------------------------------------- /mdit_py_plugins/front_matter/port.yaml: -------------------------------------------------------------------------------- 1 | - package: markdown-it-front-matter 2 | commit: b404f5d8fd536e7e9ddb276267ae0b6f76e9cf9d 3 | date: Feb 7, 2020 4 | version: 0.2.1 5 | -------------------------------------------------------------------------------- /mdit_py_plugins/myst_blocks/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import myst_block_plugin 2 | 3 | __all__ = ("myst_block_plugin",) 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/myst_blocks/index.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import itertools 4 | from typing import TYPE_CHECKING, Sequence 5 | 6 | from markdown_it import MarkdownIt 7 | from markdown_it.common.utils import escapeHtml 8 | from markdown_it.rules_block import StateBlock 9 | 10 | from mdit_py_plugins.utils import is_code_block 11 | 12 | if TYPE_CHECKING: 13 | from markdown_it.renderer import RendererProtocol 14 | from markdown_it.token import Token 15 | from markdown_it.utils import EnvType, OptionsDict 16 | 17 | 18 | def myst_block_plugin(md: MarkdownIt) -> None: 19 | """Parse MyST targets (``(name)=``), blockquotes (``% comment``) and block breaks (``+++``).""" 20 | md.block.ruler.before( 21 | "blockquote", 22 | "myst_line_comment", 23 | line_comment, 24 | {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]}, 25 | ) 26 | md.block.ruler.before( 27 | "hr", 28 | "myst_block_break", 29 | block_break, 30 | {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]}, 31 | ) 32 | md.block.ruler.before( 33 | "hr", 34 | "myst_target", 35 | target, 36 | {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]}, 37 | ) 38 | md.add_render_rule("myst_target", render_myst_target) 39 | md.add_render_rule("myst_line_comment", render_myst_line_comment) 40 | 41 | 42 | def line_comment(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 43 | if is_code_block(state, startLine): 44 | return False 45 | 46 | pos = state.bMarks[startLine] + state.tShift[startLine] 47 | maximum = state.eMarks[startLine] 48 | 49 | if state.src[pos] != "%": 50 | return False 51 | 52 | if silent: 53 | return True 54 | 55 | token = state.push("myst_line_comment", "", 0) 56 | token.attrSet("class", "myst-line-comment") 57 | token.content = state.src[pos + 1 : maximum].rstrip() 58 | token.markup = "%" 59 | 60 | # search end of block while appending lines to `token.content` 61 | for nextLine in itertools.count(startLine + 1): 62 | if nextLine >= endLine: 63 | break 64 | pos = state.bMarks[nextLine] + state.tShift[nextLine] 65 | maximum = state.eMarks[nextLine] 66 | 67 | if state.src[pos] != "%": 68 | break 69 | token.content += "\n" + state.src[pos + 1 : maximum].rstrip() 70 | 71 | state.line = nextLine 72 | token.map = [startLine, nextLine] 73 | 74 | return True 75 | 76 | 77 | def block_break(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 78 | if is_code_block(state, startLine): 79 | return False 80 | 81 | pos = state.bMarks[startLine] + state.tShift[startLine] 82 | maximum = state.eMarks[startLine] 83 | 84 | marker = state.src[pos] 85 | pos += 1 86 | 87 | # Check block marker 88 | if marker != "+": 89 | return False 90 | 91 | # markers can be mixed with spaces, but there should be at least 3 of them 92 | 93 | cnt = 1 94 | while pos < maximum: 95 | ch = state.src[pos] 96 | if ch != marker and ch not in ("\t", " "): 97 | break 98 | if ch == marker: 99 | cnt += 1 100 | pos += 1 101 | 102 | if cnt < 3: 103 | return False 104 | 105 | if silent: 106 | return True 107 | 108 | state.line = startLine + 1 109 | 110 | token = state.push("myst_block_break", "hr", 0) 111 | token.attrSet("class", "myst-block") 112 | token.content = state.src[pos:maximum].strip() 113 | token.map = [startLine, state.line] 114 | token.markup = marker * cnt 115 | 116 | return True 117 | 118 | 119 | def target(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 120 | if is_code_block(state, startLine): 121 | return False 122 | 123 | pos = state.bMarks[startLine] + state.tShift[startLine] 124 | maximum = state.eMarks[startLine] 125 | 126 | text = state.src[pos:maximum].strip() 127 | if not text.startswith("("): 128 | return False 129 | if not text.endswith(")="): 130 | return False 131 | if not text[1:-2]: 132 | return False 133 | 134 | if silent: 135 | return True 136 | 137 | state.line = startLine + 1 138 | 139 | token = state.push("myst_target", "", 0) 140 | token.attrSet("class", "myst-target") 141 | token.content = text[1:-2] 142 | token.map = [startLine, state.line] 143 | 144 | return True 145 | 146 | 147 | def render_myst_target( 148 | self: RendererProtocol, 149 | tokens: Sequence[Token], 150 | idx: int, 151 | options: OptionsDict, 152 | env: EnvType, 153 | ) -> str: 154 | label = tokens[idx].content 155 | class_name = "myst-target" 156 | target = f'({label})=' 157 | return f'
{target}
' 158 | 159 | 160 | def render_myst_line_comment( 161 | self: RendererProtocol, 162 | tokens: Sequence[Token], 163 | idx: int, 164 | options: OptionsDict, 165 | env: EnvType, 166 | ) -> str: 167 | # Strip leading whitespace from all lines 168 | content = "\n".join(line.lstrip() for line in tokens[idx].content.split("\n")) 169 | return f"" 170 | -------------------------------------------------------------------------------- /mdit_py_plugins/myst_role/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import myst_role_plugin 2 | 3 | __all__ = ("myst_role_plugin",) 4 | -------------------------------------------------------------------------------- /mdit_py_plugins/myst_role/index.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import TYPE_CHECKING, Sequence 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.common.utils import escapeHtml 6 | from markdown_it.rules_inline import StateInline 7 | 8 | if TYPE_CHECKING: 9 | from markdown_it.renderer import RendererProtocol 10 | from markdown_it.token import Token 11 | from markdown_it.utils import EnvType, OptionsDict 12 | 13 | VALID_NAME_PATTERN = re.compile(r"^\{([a-zA-Z0-9\_\-\+\:]+)\}") 14 | 15 | 16 | def myst_role_plugin(md: MarkdownIt) -> None: 17 | """Parse ``{role-name}`content```""" 18 | md.inline.ruler.before("backticks", "myst_role", myst_role) 19 | md.add_render_rule("myst_role", render_myst_role) 20 | 21 | 22 | def myst_role(state: StateInline, silent: bool) -> bool: 23 | # check name 24 | match = VALID_NAME_PATTERN.match(state.src[state.pos :]) 25 | if not match: 26 | return False 27 | name = match.group(1) 28 | 29 | # check for starting backslash escape 30 | try: 31 | if state.src[state.pos - 1] == "\\": 32 | # escaped (this could be improved in the case of edge case '\\{') 33 | return False 34 | except IndexError: 35 | pass 36 | 37 | # scan opening tick length 38 | start = pos = state.pos + match.end() 39 | try: 40 | while state.src[pos] == "`": 41 | pos += 1 42 | except IndexError: 43 | return False 44 | 45 | tick_length = pos - start 46 | if not tick_length: 47 | return False 48 | 49 | # search for closing ticks 50 | match = re.search("`" * tick_length, state.src[pos + 1 :]) 51 | if not match: 52 | return False 53 | content = state.src[pos : pos + match.start() + 1].replace("\n", " ") 54 | 55 | if not silent: 56 | token = state.push("myst_role", "", 0) 57 | token.meta = {"name": name} 58 | token.content = content 59 | 60 | state.pos = pos + match.end() + 1 61 | 62 | return True 63 | 64 | 65 | def render_myst_role( 66 | self: "RendererProtocol", 67 | tokens: Sequence["Token"], 68 | idx: int, 69 | options: "OptionsDict", 70 | env: "EnvType", 71 | ) -> str: 72 | token = tokens[idx] 73 | name = token.meta.get("name", "unknown") 74 | return f'{{{name}}}[{escapeHtml(token.content)}]' 75 | -------------------------------------------------------------------------------- /mdit_py_plugins/py.typed: -------------------------------------------------------------------------------- 1 | # Marker file for PEP 561 2 | -------------------------------------------------------------------------------- /mdit_py_plugins/substitution.py: -------------------------------------------------------------------------------- 1 | from markdown_it import MarkdownIt 2 | from markdown_it.rules_block import StateBlock 3 | from markdown_it.rules_inline import StateInline 4 | 5 | from mdit_py_plugins.utils import is_code_block 6 | 7 | 8 | def substitution_plugin( 9 | md: MarkdownIt, start_delimiter: str = "{", end_delimiter: str = "}" 10 | ) -> None: 11 | """A plugin to create substitution tokens. 12 | 13 | These, token should be handled by the renderer. 14 | 15 | Example:: 16 | 17 | {{ block }} 18 | 19 | a {{ inline }} b 20 | 21 | """ 22 | 23 | def _substitution_inline(state: StateInline, silent: bool) -> bool: 24 | try: 25 | if ( 26 | state.src[state.pos] != start_delimiter 27 | or state.src[state.pos + 1] != start_delimiter 28 | ): 29 | return False 30 | except IndexError: 31 | return False 32 | 33 | pos = state.pos + 2 34 | found_closing = False 35 | while True: 36 | try: 37 | end = state.src.index(end_delimiter, pos) 38 | except ValueError: 39 | return False 40 | try: 41 | if state.src[end + 1] == end_delimiter: 42 | found_closing = True 43 | break 44 | except IndexError: 45 | return False 46 | pos = end + 2 47 | 48 | if not found_closing: 49 | return False 50 | 51 | text = state.src[state.pos + 2 : end].strip() 52 | state.pos = end + 2 53 | 54 | if silent: 55 | return True 56 | 57 | token = state.push("substitution_inline", "span", 0) 58 | token.block = False 59 | token.content = text 60 | token.attrSet("class", "substitution") 61 | token.attrSet("text", text) 62 | token.markup = f"{start_delimiter}{end_delimiter}" 63 | 64 | return True 65 | 66 | def _substitution_block( 67 | state: StateBlock, startLine: int, endLine: int, silent: bool 68 | ) -> bool: 69 | if is_code_block(state, startLine): 70 | return False 71 | 72 | startPos = state.bMarks[startLine] + state.tShift[startLine] 73 | end = state.eMarks[startLine] 74 | 75 | lineText = state.src[startPos:end].strip() 76 | 77 | try: 78 | if ( 79 | lineText[0] != start_delimiter 80 | or lineText[1] != start_delimiter 81 | or lineText[-1] != end_delimiter 82 | or lineText[-2] != end_delimiter 83 | or len(lineText) < 5 84 | ): 85 | return False 86 | except IndexError: 87 | return False 88 | 89 | text = lineText[2:-2].strip() 90 | 91 | # special case if multiple on same line, e.g. {{a}}{{b}} 92 | if (end_delimiter * 2) in text: 93 | return False 94 | 95 | state.line = startLine + 1 96 | 97 | if silent: 98 | return True 99 | 100 | token = state.push("substitution_block", "div", 0) 101 | token.block = True 102 | token.content = text 103 | token.attrSet("class", "substitution") 104 | token.attrSet("text", text) 105 | token.markup = f"{start_delimiter}{end_delimiter}" 106 | token.map = [startLine, state.line] 107 | 108 | return True 109 | 110 | md.block.ruler.before("fence", "substitution_block", _substitution_block) 111 | md.inline.ruler.before("escape", "substitution_inline", _substitution_inline) 112 | -------------------------------------------------------------------------------- /mdit_py_plugins/tasklists/__init__.py: -------------------------------------------------------------------------------- 1 | """Builds task/todo lists out of markdown lists with items starting with [ ] or [x]""" 2 | 3 | # Ported by Wolmar Nyberg Åkerström from https://github.com/revin/markdown-it-task-lists 4 | # ISC License 5 | # Copyright (c) 2016, Revin Guillen 6 | # 7 | # Permission to use, copy, modify, and/or distribute this software for any 8 | # purpose with or without fee is hereby granted, provided that the above 9 | # copyright notice and this permission notice appear in all copies. 10 | # 11 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | from __future__ import annotations 19 | 20 | import re 21 | from uuid import uuid4 22 | 23 | from markdown_it import MarkdownIt 24 | from markdown_it.rules_core import StateCore 25 | from markdown_it.token import Token 26 | 27 | # Regex string to match a whitespace character, as specified in 28 | # https://github.github.com/gfm/#whitespace-character 29 | # (spec version 0.29-gfm (2019-04-06)) 30 | _GFM_WHITESPACE_RE = r"[ \t\n\v\f\r]" 31 | 32 | 33 | def tasklists_plugin( 34 | md: MarkdownIt, 35 | enabled: bool = False, 36 | label: bool = False, 37 | label_after: bool = False, 38 | ) -> None: 39 | """Plugin for building task/todo lists out of markdown lists with items starting with [ ] or [x] 40 | .. Nothing else 41 | 42 | For example:: 43 | - [ ] An item that needs doing 44 | - [x] An item that is complete 45 | 46 | The rendered HTML checkboxes are disabled; to change this, pass a truthy value into the enabled 47 | property of the plugin options. 48 | 49 | :param enabled: True enables the rendered checkboxes 50 | :param label: True wraps the rendered list items in a
262 | . 263 | 264 | Indented by 4 spaces 265 | . 266 | \begin{equation} 267 | a = 1 268 | \end{equation} 269 | . 270 |
\begin{equation}
271 | a = 1
272 | \end{equation}
273 | 
274 | . 275 | 276 | Indented by 4 spaces, DISABLE-CODEBLOCKS 277 | . 278 | \begin{equation} 279 | a = 1 280 | \end{equation} 281 | . 282 |
283 | \begin{equation} 284 | a = 1 285 | \end{equation} 286 |
287 | . 288 | -------------------------------------------------------------------------------- /tests/fixtures/anchors.md: -------------------------------------------------------------------------------- 1 | basic (max level 2): 2 | . 3 | # H1 4 | 5 | ## H2 6 | 7 | ### H3 8 | . 9 |

H1

10 |

H2

11 |

H3

12 | . 13 | 14 | space: 15 | . 16 | # a b c 17 | . 18 |

a b c

19 | . 20 | 21 | characters: 22 | . 23 | # a,b\cβÊ 24 | . 25 |

a,b\cβÊ

26 | . 27 | 28 | emoji: 29 | . 30 | # 🚀a 31 | . 32 |

🚀a

33 | . 34 | 35 | html entity: 36 | . 37 | # foo&bar 38 | . 39 |

foo&bar

40 | . 41 | 42 | uniqueness: 43 | . 44 | # a 45 | # a 46 | ## a 47 | . 48 |

a

49 |

a

50 |

a

51 | . 52 | 53 | standard (permalink after): 54 | . 55 | # a 56 | . 57 |

a

58 | . 59 | 60 | standard (permalink before): 61 | . 62 | # a 63 | . 64 |

a

65 | . 66 | -------------------------------------------------------------------------------- /tests/fixtures/attrs.md: -------------------------------------------------------------------------------- 1 | block with preceding text is not a block 2 | . 3 | {#a .a b=c} a 4 | . 5 |

{#a .a b=c} a

6 | . 7 | 8 | block no preceding 9 | . 10 | {#a .a c=1} 11 | . 12 | 13 | . 14 | 15 | block basic 16 | . 17 | {#a .a c=1} 18 | a 19 | . 20 |

a

21 | . 22 | 23 | multiple blocks 24 | . 25 | {#a .a c=1} 26 | 27 | {#b .b c=2} 28 | a 29 | . 30 |

a

31 | . 32 | 33 | block list 34 | . 35 | {#a .a c=1} 36 | - a 37 | . 38 |
    39 |
  • a
  • 40 |
41 | . 42 | 43 | block quote 44 | . 45 | {#a .a c=1} 46 | > a 47 | . 48 |
49 |

a

50 |
51 | . 52 | 53 | block fence 54 | . 55 | {#a .b c=1} 56 | ```python 57 | a = 1 58 | ``` 59 | . 60 |
a = 1
 61 | 
62 | . 63 | 64 | block after paragraph 65 | . 66 | a 67 | {#a .a c=1} 68 | . 69 |

a 70 | {#a .a c=1}

71 | . 72 | 73 | 74 | simple reference link 75 | . 76 | [text *emphasis*](a){#id .a} 77 | . 78 |

text emphasis

79 | . 80 | 81 | simple definition link 82 | . 83 | [a][]{#id .b} 84 | 85 | [a]: /url 86 | . 87 |

a

88 | . 89 | 90 | simple image 91 | . 92 | ![a](b){#id .a b=c} 93 | . 94 |

a

95 | . 96 | 97 | simple inline code 98 | . 99 | `a`{#id .a b=c} 100 | . 101 |

a

102 | . 103 | 104 | ignore if space 105 | . 106 | ![a](b) {#id key="*"} 107 | . 108 |

a {#id key="*"}

109 | . 110 | 111 | ignore if text 112 | . 113 | ![a](b)b{#id key="*"} 114 | . 115 |

ab{#id key="*"}

116 | . 117 | 118 | multi-line 119 | . 120 | ![a](b){ 121 | #id .a 122 | b=c 123 | } 124 | more 125 | . 126 |

a 127 | more

128 | . 129 | 130 | merging attributes 131 | . 132 | ![a](b){#a .a}{.b class=x other=h}{#x class="x g" other=a} 133 | . 134 |

a

135 | . 136 | 137 | spans: simple 138 | . 139 | [a]{#id .b}c 140 | . 141 |

ac

142 | . 143 | 144 | spans: end of inline before attrs 145 | . 146 | [a] 147 | . 148 |

[a]

149 | . 150 | 151 | spans: space between brace and attrs 152 | . 153 | [a] {.b} 154 | . 155 |

[a] {.b}

156 | . 157 | 158 | spans: escaped span start 159 | . 160 | \[a]{.b} 161 | . 162 |

[a]{.b}

163 | . 164 | 165 | spans: escaped span end 166 | . 167 | [a\]{.b} 168 | . 169 |

[a]{.b}

170 | . 171 | 172 | spans: escaped span attribute 173 | . 174 | [a]\{.b} 175 | . 176 |

[a]{.b}

177 | . 178 | 179 | spans: nested text syntax 180 | . 181 | [*a*]{.b}c 182 | . 183 |

ac

184 | . 185 | 186 | spans: nested span 187 | . 188 | *[a]{.b}c* 189 | . 190 |

ac

191 | . 192 | 193 | spans: multi-line 194 | . 195 | x [a 196 | b]{#id 197 | b=c} y 198 | . 199 |

x a 200 | b y

201 | . 202 | 203 | spans: nested spans 204 | . 205 | [[a]{.b}]{.c} 206 | . 207 |

a

208 | . 209 | 210 | spans: short link takes precedence over span 211 | . 212 | [a]{#id .b} 213 | 214 | [a]: /url 215 | . 216 |

a

217 | . 218 | 219 | spans: long link takes precedence over span 220 | . 221 | [a][a]{#id .b} 222 | 223 | [a]: /url 224 | . 225 |

a

226 | . 227 | 228 | spans: link inside span 229 | . 230 | [[a]]{#id .b} 231 | 232 | [a]: /url 233 | . 234 |

a

235 | . 236 | 237 | spans: merge attributes 238 | . 239 | [a]{#a .a}{#b .a .b other=c}{other=d} 240 | . 241 |

a

242 | . 243 | 244 | Indented by 4 spaces 245 | . 246 | {#a .a b=c} 247 | # head 248 | . 249 |
{#a .a b=c}
250 | # head
251 | 
252 | . 253 | 254 | Indented by 4 spaces, DISABLE-CODEBLOCKS 255 | . 256 | {#a .a b=c} 257 | # head 258 | . 259 |

head

260 | . 261 | -------------------------------------------------------------------------------- /tests/fixtures/colon_fence.md: -------------------------------------------------------------------------------- 1 | # The initial tests are adapted from the test for normal code fences in tests/test_port/fixtures/commonmark_spec.md 2 | 3 | src line: 1638 4 | 5 | . 6 | ::: 7 | < 8 | > 9 | ::: 10 | . 11 |
<
 12 |  >
 13 | 
14 | . 15 | 16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 17 | src line: 1665 18 | 19 | . 20 | :: 21 | foo 22 | :: 23 | . 24 |

:: 25 | foo 26 | ::

27 | . 28 | 29 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 30 | src line: 1676 31 | 32 | . 33 | ::: 34 | aaa 35 | ~~~ 36 | ::: 37 | . 38 |
aaa
 39 | ~~~
 40 | 
41 | . 42 | 43 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 44 | src line: 1688 45 | 46 | . 47 | ::: 48 | aaa 49 | ``` 50 | ::: 51 | . 52 |
aaa
 53 | ```
 54 | 
55 | . 56 | 57 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 | src line: 1702 59 | 60 | . 61 | :::: 62 | aaa 63 | ::: 64 | :::::: 65 | . 66 |
aaa
 67 | :::
 68 | 
69 | . 70 | 71 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 72 | src line: 1729 73 | 74 | . 75 | ::: 76 | . 77 |
78 | . 79 | 80 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 81 | src line: 1736 82 | 83 | . 84 | ::::: 85 | 86 | ::: 87 | aaa 88 | . 89 |

 90 | :::
 91 | aaa
 92 | 
93 | . 94 | 95 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 96 | src line: 1749 97 | 98 | . 99 | > ::: 100 | > aaa 101 | 102 | bbb 103 | . 104 |
105 |
aaa
106 | 
107 |
108 |

bbb

109 | . 110 | 111 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 112 | src line: 1765 113 | 114 | . 115 | ::: 116 | 117 | 118 | ::: 119 | . 120 |

121 |   
122 | 
123 | . 124 | 125 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 126 | src line: 1779 127 | 128 | . 129 | ::: 130 | ::: 131 | . 132 |
133 | . 134 | 135 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 136 | src line: 1791 137 | 138 | . 139 | ::: 140 | aaa 141 | aaa 142 | ::: 143 | . 144 |
aaa
145 | aaa
146 | 
147 | . 148 | 149 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 150 | src line: 1803 151 | 152 | . 153 | ::: 154 | aaa 155 | aaa 156 | aaa 157 | ::: 158 | . 159 |
aaa
160 | aaa
161 | aaa
162 | 
163 | . 164 | 165 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 166 | src line: 1817 167 | 168 | . 169 | ::: 170 | aaa 171 | aaa 172 | aaa 173 | ::: 174 | . 175 |
aaa
176 |  aaa
177 | aaa
178 | 
179 | . 180 | 181 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 182 | src line: 1833 183 | 184 | . 185 | ::: 186 | aaa 187 | ::: 188 | . 189 |
:::
190 | aaa
191 | :::
192 | 
193 | . 194 | 195 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 196 | src line: 1848 197 | 198 | . 199 | ::: 200 | aaa 201 | ::: 202 | . 203 |
aaa
204 | 
205 | . 206 | 207 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 208 | src line: 1858 209 | 210 | . 211 | ::: 212 | aaa 213 | ::: 214 | . 215 |
aaa
216 | 
217 | . 218 | 219 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 220 | src line: 1870 221 | 222 | . 223 | ::: 224 | aaa 225 | ::: 226 | . 227 |
aaa
228 |     :::
229 | 
230 | . 231 | 232 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 233 | src line: 1884 234 | 235 | . 236 | ::: ::: 237 | aaa 238 | . 239 |
aaa
240 | 
241 | . 242 | 243 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 244 | src line: 1907 245 | 246 | . 247 | foo 248 | ::: 249 | bar 250 | ::: 251 | baz 252 | . 253 |

foo

254 |
bar
255 | 
256 |

baz

257 | . 258 | 259 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 260 | src line: 1946 261 | 262 | . 263 | :::ruby 264 | def foo(x) 265 | return 3 266 | end 267 | ::: 268 | . 269 |
def foo(x)
270 |   return 3
271 | end
272 | 
273 | . 274 | 275 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 276 | src line: 1974 277 | 278 | . 279 | ::::; 280 | :::: 281 | . 282 |
283 | . 284 | 285 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 286 | src line: 1984 287 | 288 | . 289 | ::: aa ::: 290 | foo 291 | . 292 |
foo
293 | 
294 | . 295 | 296 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 297 | src line: 2007 298 | 299 | . 300 | ::: 301 | ::: aaa 302 | ::: 303 | . 304 |
::: aaa
305 | 
306 | . 307 | 308 | 309 | 310 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 311 | src line: 2007 312 | 313 | . 314 | ::: 315 | ::: aaa 316 | ::: 317 | . 318 |
::: aaa
319 | 
320 | . 321 | 322 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 323 | Ending marker could be longer 324 | . 325 | ::::: name ::::: 326 | hello world 327 | :::::::::::::::: 328 | . 329 |
  hello world
330 | 
331 | . 332 | 333 | Nested blocks 334 | . 335 | ::::: name 336 | :::: name 337 | xxx 338 | :::: 339 | ::::: 340 | . 341 |
:::: name
342 | xxx
343 | ::::
344 | 
345 | . 346 | 347 | Name could be adjacent to marker 348 | . 349 | :::name 350 | xxx 351 | ::: 352 | . 353 |
xxx
354 | 
355 | . 356 | 357 | They should terminate paragraphs 358 | . 359 | blah blah 360 | ::: name 361 | content 362 | ::: 363 | . 364 |

blah blah

365 |
content
366 | 
367 | . 368 | 369 | They could be nested in lists 370 | . 371 | - ::: name 372 | - xxx 373 | ::: 374 | . 375 |
    376 |
  • 377 |
    - xxx
    378 | 
    379 |
  • 380 |
381 | . 382 | 383 | Or in blockquotes 384 | . 385 | > ::: name 386 | > xxx 387 | >> yyy 388 | > zzz 389 | > ::: 390 | . 391 |
392 |
xxx
393 | > yyy
394 | zzz
395 | 
396 |
397 | . 398 | 399 | List indentation quirks 400 | . 401 | - ::: name 402 | xxx 403 | yyy 404 | ::: 405 | 406 | - ::: name 407 | xxx 408 | yyy 409 | ::: 410 | . 411 |
    412 |
  • 413 |
    xxx
    414 | yyy
    415 | 
    416 |
  • 417 |
418 |

419 | -  ::: name
420 |  xxx
421 | yyy
422 | 
423 | . 424 | 425 | 426 | Indented by 4 spaces 427 | . 428 | :::name 429 | foo 430 | ::: 431 | . 432 |
:::name
433 | foo
434 | :::
435 | 
436 | . 437 | 438 | Indented by 4 spaces, DISABLE-CODEBLOCKS 439 | . 440 | :::name 441 | foo 442 | ::: 443 | . 444 |
foo
445 | 
446 | . 447 | -------------------------------------------------------------------------------- /tests/fixtures/container.md: -------------------------------------------------------------------------------- 1 | 2 | Simple container 3 | . 4 | ::: name 5 | *content* 6 | ::: 7 | . 8 |
9 |

content

10 |
11 | . 12 | 13 | Delimiters too short 14 | . 15 | :: name 16 | *content* 17 | :: 18 | . 19 |

:: name 20 | content 21 | ::

22 | . 23 | 24 | 25 | Could contain block elements too 26 | . 27 | ::: name 28 | ### heading 29 | 30 | ----------- 31 | ::: 32 | . 33 |
34 |

heading

35 |
36 |
37 | . 38 | 39 | 40 | Ending marker could be longer 41 | . 42 | ::::: name ::::: 43 | hello world 44 | :::::::::::::::: 45 | . 46 |
47 |

hello world

48 |
49 | . 50 | 51 | 52 | Nested blocks 53 | . 54 | ::::: name 55 | :::: name 56 | xxx 57 | :::: 58 | ::::: 59 | . 60 |
61 |
62 |

xxx

63 |
64 |
65 | . 66 | 67 | 68 | Incorrectly nested blocks 69 | . 70 | :::: name 71 | this block is closed with 5 markers below 72 | 73 | ::::: name 74 | auto-closed block 75 | 76 | ::::: 77 | :::: 78 | . 79 |
80 |

this block is closed with 5 markers below

81 |
82 |

auto-closed block

83 |
84 |
85 |

::::

86 | . 87 | 88 | 89 | Marker could be indented up to 3 spaces 90 | . 91 | ::: name 92 | content 93 | ::: 94 | ::: 95 | . 96 |
97 |

content 98 | :::

99 |
100 | . 101 | 102 | 103 | But that's a code block 104 | . 105 | ::: name 106 | content 107 | ::: 108 | . 109 |
::: name
110 | content
111 | :::
112 | 
113 | . 114 | 115 | 116 | Some more indent checks 117 | . 118 | ::: name 119 | not a code block 120 | 121 | code block 122 | ::: 123 | . 124 |
125 |

not a code block

126 |
code block
127 | 
128 |
129 | . 130 | 131 | 132 | Name could be adjacent to marker 133 | . 134 | :::name 135 | xxx 136 | ::: 137 | . 138 |
139 |

xxx

140 |
141 | . 142 | 143 | 144 | Or several spaces apart 145 | . 146 | ::: name 147 | xxx 148 | ::: 149 | . 150 |
151 |

xxx

152 |
153 | . 154 | 155 | 156 | It could contain params 157 | . 158 | ::: name arg=123 foo=456 159 | xxx 160 | ::: 161 | . 162 |
163 |

xxx

164 |
165 | . 166 | 167 | 168 | But closing marker can't 169 | . 170 | ::: name 171 | xxx 172 | ::: arg=123 173 | . 174 |
175 |

xxx 176 | ::: arg=123

177 |
178 | . 179 | 180 | 181 | This however isn't a valid one 182 | . 183 | ::: namename 184 | xxx 185 | ::: 186 | . 187 |

::: namename 188 | xxx 189 | :::

190 | . 191 | 192 | 193 | Containers self-close at the end of the document 194 | . 195 | ::: name 196 | xxx 197 | . 198 |
199 |

xxx

200 |
201 | . 202 | 203 | 204 | They should terminate paragraphs 205 | . 206 | blah blah 207 | ::: name 208 | content 209 | ::: 210 | . 211 |

blah blah

212 |
213 |

content

214 |
215 | . 216 | 217 | 218 | They could be nested in lists 219 | . 220 | - ::: name 221 | - xxx 222 | ::: 223 | . 224 |
    225 |
  • 226 |
    227 |
      228 |
    • xxx
    • 229 |
    230 |
    231 |
  • 232 |
233 | . 234 | 235 | 236 | Or in blockquotes 237 | . 238 | > ::: name 239 | > xxx 240 | >> yyy 241 | > zzz 242 | > ::: 243 | . 244 |
245 |
246 |

xxx

247 |
248 |

yyy 249 | zzz

250 |
251 |
252 |
253 | . 254 | 255 | 256 | List indentation quirks 257 | . 258 | - ::: name 259 | xxx 260 | yyy 261 | ::: 262 | 263 | - ::: name 264 | xxx 265 | yyy 266 | ::: 267 | . 268 |
    269 |
  • 270 |
    271 |

    xxx 272 | yyy

    273 |
    274 |
  • 275 |
276 |

:::

277 |
    278 |
  • 279 |
    280 |

    xxx

    281 |
    282 |
  • 283 |
284 |

yyy 285 | :::

286 | . 287 | 288 | 289 | Indented by 4 spaces 290 | . 291 | ::: name 292 | content 293 | ::: 294 | . 295 |
::: name
296 | content
297 | :::
298 | 
299 | . 300 | 301 | Indented by 4 spaces, DISABLE-CODEBLOCKS 302 | . 303 | ::: name 304 | content 305 | ::: 306 | . 307 |
308 |

content

309 |
310 | . 311 | -------------------------------------------------------------------------------- /tests/fixtures/deflist.md: -------------------------------------------------------------------------------- 1 | 2 | Pandoc (with slightly changed indents): 3 | 4 | . 5 | Term 1 6 | 7 | : Definition 1 8 | 9 | Term 2 with *inline markup* 10 | 11 | : Definition 2 12 | 13 | { some code, part of Definition 2 } 14 | 15 | Third paragraph of definition 2. 16 | . 17 |
18 |
Term 1
19 |
20 |

Definition 1

21 |
22 |
Term 2 with inline markup
23 |
24 |

Definition 2

25 |
{ some code, part of Definition 2 }
 26 | 
27 |

Third paragraph of definition 2.

28 |
29 |
30 | . 31 | 32 | Pandoc again: 33 | 34 | . 35 | Term 1 36 | 37 | : Definition 38 | with lazy continuation. 39 | 40 | Second paragraph of the definition. 41 | . 42 |
43 |
Term 1
44 |
45 |

Definition 46 | with lazy continuation.

47 |

Second paragraph of the definition.

48 |
49 |
50 | . 51 | 52 | Well, I might just copy-paste the third one while I'm at it: 53 | 54 | . 55 | Term 1 56 | ~ Definition 1 57 | 58 | Term 2 59 | ~ Definition 2a 60 | ~ Definition 2b 61 | . 62 |
63 |
Term 1
64 |
Definition 1
65 |
Term 2
66 |
Definition 2a
67 |
Definition 2b
68 |
69 | . 70 | 71 | Now, with our custom ones. Spaces after a colon: 72 | 73 | . 74 | Term 1 75 | : paragraph 76 | 77 | Term 2 78 | : code block 79 | . 80 |
81 |
Term 1
82 |
paragraph
83 |
Term 2
84 |
85 |
code block
 86 | 
87 |
88 |
89 | . 90 | 91 | There should be something after a colon by the way: 92 | 93 | . 94 | Non-term 1 95 | : 96 | 97 | Non-term 2 98 | : 99 | . 100 |

Non-term 1 101 | :

102 |

Non-term 2 103 | :

104 | . 105 | 106 | 107 | List is tight iff all dts are tight: 108 | . 109 | Term 1 110 | : foo 111 | : bar 112 | 113 | Term 2 114 | : foo 115 | 116 | : bar 117 | . 118 |
119 |
Term 1
120 |
121 |

foo

122 |
123 |
124 |

bar

125 |
126 |
Term 2
127 |
128 |

foo

129 |
130 |
131 |

bar

132 |
133 |
134 | . 135 | 136 | 137 | Regression test (first paragraphs shouldn't be tight): 138 | . 139 | Term 1 140 | : foo 141 | 142 | bar 143 | Term 2 144 | : foo 145 | . 146 |
147 |
Term 1
148 |
149 |

foo

150 |

bar 151 | Term 2

152 |
153 |
154 |

foo

155 |
156 |
157 | . 158 | 159 | Definition lists should be second last in the queue. Exemplī grātiā, this isn't a valid one: 160 | 161 | . 162 | # test 163 | : just a paragraph with a colon 164 | . 165 |

test

166 |

: just a paragraph with a colon

167 | . 168 | 169 | Nested definition lists: 170 | 171 | . 172 | test 173 | : foo 174 | : bar 175 | : baz 176 | : bar 177 | : foo 178 | . 179 |
180 |
test
181 |
182 |
183 |
foo
184 |
185 |
186 |
bar
187 |
baz
188 |
189 |
190 |
bar
191 |
192 |
193 |
foo
194 |
195 | . 196 | 197 | 198 | Regression test, tabs 199 | . 200 | Term 1 201 | : code block 202 | . 203 |
204 |
Term 1
205 |
206 |
code block
207 | 
208 |
209 |
210 | . 211 | 212 | 213 | Regression test (blockquote inside deflist) 214 | . 215 | foo 216 | : > bar 217 | : baz 218 | . 219 |
220 |
foo
221 |
222 |
223 |

bar

224 |
225 |
226 |
baz
227 |
228 | . 229 | 230 | 231 | Coverage, 1 blank line 232 | . 233 | test 234 | 235 | . 236 |

test

237 | . 238 | 239 | 240 | Coverage, 2 blank lines 241 | . 242 | test 243 | 244 | 245 | . 246 |

test

247 | . 248 | 249 | Indented by 4 spaces 250 | . 251 | Term 1 252 | 253 | : Definition 1 254 | . 255 |
Term 1
256 | 
257 | : Definition 1
258 | 
259 | . 260 | 261 | Indented by 4 spaces, DISABLE-CODEBLOCKS 262 | . 263 | Term 1 264 | 265 | : Definition 1 266 | . 267 |
268 |
Term 1
269 |
Definition 1
270 |
271 | . 272 | -------------------------------------------------------------------------------- /tests/fixtures/field_list.md: -------------------------------------------------------------------------------- 1 | Docutils example 2 | . 3 | :Date: 2001-08-16 4 | :Version: 1 5 | :Authors: - Me 6 | - Myself 7 | - I 8 | :Indentation: Since the field marker may be quite long, the second 9 | and subsequent lines of the field body do not have to line up 10 | with the first line, but they must be indented relative to the 11 | field name marker, and they must line up with each other. 12 | :Parameter i: integer 13 | . 14 |
15 |
Date
16 |
17 |

2001-08-16

18 |
19 |
Version
20 |
21 |

1

22 |
23 |
Authors
24 |
25 |
    26 |
  • Me
  • 27 |
  • Myself
  • 28 |
  • I
  • 29 |
30 |
31 |
Indentation
32 |
33 |

Since the field marker may be quite long, the second 34 | and subsequent lines of the field body do not have to line up 35 | with the first line, but they must be indented relative to the 36 | field name marker, and they must line up with each other.

37 |
38 |
Parameter i
39 |
40 |

integer

41 |
42 |
43 | . 44 | 45 | Body alignment: 46 | . 47 | :no body: 48 | 49 | :single line: content 50 | :paragraph: content 51 | running onto new line 52 | :body inline: paragraph 1 53 | 54 | paragraph 2 55 | 56 | paragraph 3 57 | 58 | :body less: paragraph 1 59 | 60 | paragraph 2 61 | 62 | paragraph 3 63 | 64 | :body on 2nd line: 65 | paragraph 1 66 | 67 | paragraph 2 68 | 69 | :body on 3rd line: 70 | 71 | paragraph 1 72 | 73 | paragraph 2 74 | . 75 |
76 |
no body
77 |
78 |
single line
79 |
80 |

content

81 |
82 |
paragraph
83 |
84 |

content 85 | running onto new line

86 |
87 |
body inline
88 |
89 |

paragraph 1

90 |

paragraph 2

91 |

paragraph 3

92 |
93 |
body less
94 |
95 |

paragraph 1

96 |

paragraph 2

97 |

paragraph 3

98 |
99 |
body on 2nd line
100 |
101 |

paragraph 1

102 |

paragraph 2

103 |
104 |
body on 3rd line
105 |
106 |

paragraph 1

107 |

paragraph 2

108 |
109 |
110 | . 111 | 112 | choose smallest indent 113 | . 114 | :name: a 115 | 116 | b 117 | 118 | c 119 | . 120 |
121 |
name
122 |
123 |

a

124 |

b

125 |

c

126 |
127 |
128 | . 129 | 130 | Empty name: 131 | . 132 | :: 133 | . 134 |

::

135 | . 136 | 137 | Whitespace only name: 138 | . 139 | : : 140 | . 141 |

: :

142 | . 143 | 144 | Name markup: 145 | . 146 | :inline *markup*: 147 | . 148 |
149 |
inline markup
150 |
151 |
152 | . 153 | 154 | Content paragraph markup: 155 | . 156 | :name: body *markup* 157 | . 158 |
159 |
name
160 |
161 |

body markup

162 |
163 |
164 | . 165 | 166 | Body list: 167 | . 168 | :name: 169 | - item 1 170 | - item 2 171 | :name: - item 1 172 | - item 2 173 | . 174 |
175 |
name
176 |
177 |
    178 |
  • item 1
  • 179 |
  • item 2
  • 180 |
181 |
182 |
name
183 |
184 |
    185 |
  • item 1
  • 186 |
  • item 2
  • 187 |
188 |
189 |
190 | . 191 | 192 | Body code block 193 | . 194 | :name: 195 | not code 196 | :name: body 197 | 198 | code 199 | . 200 |
201 |
name
202 |
203 |

not code

204 |
205 |
name
206 |
207 |

body

208 |
code
209 | 
210 |
211 |
212 | . 213 | 214 | Body blockquote: 215 | . 216 | :name: 217 | > item 1 218 | > item 2 219 | :name: > item 1 220 | > item 2 221 | . 222 |
223 |
name
224 |
225 |
226 |

item 1 227 | item 2

228 |
229 |
230 |
name
231 |
232 |
233 |

item 1 234 | item 2

235 |
236 |
237 |
238 | . 239 | 240 | Body fence: 241 | . 242 | :name: 243 | ```python 244 | code 245 | ``` 246 | . 247 |
248 |
name
249 |
250 |
code
251 | 
252 |
253 |
254 | . 255 | 256 | Following blocks: 257 | . 258 | :name: body 259 | - item 1 260 | :name: body 261 | > block quote 262 | :name: body 263 | ```python 264 | code 265 | ``` 266 | :name: body 267 | more 268 | 269 | more 270 | trailing 271 | 272 | other 273 | . 274 |
275 |
name
276 |
277 |

body

278 |
279 |
280 |
    281 |
  • item 1
  • 282 |
283 |
284 |
name
285 |
286 |

body

287 |
288 |
289 |
290 |

block quote

291 |
292 |
293 |
name
294 |
295 |

body

296 |
297 |
298 |
code
299 | 
300 |
301 |
name
302 |
303 |

body 304 | more

305 |

more 306 | trailing

307 |
308 |
309 |

other

310 | . 311 | 312 | In list: 313 | . 314 | - :name: body 315 | - item 2 316 | . 317 |
    318 |
  • 319 |
    320 |
    name
    321 |
    322 |

    body

    323 |
    324 |
    325 |
  • 326 |
  • item 2
  • 327 |
328 | . 329 | 330 | In blockquote: 331 | . 332 | > :name: body 333 | > :name: body 334 | > other 335 | > :name: body 336 | > 337 | > other 338 | > :name: body 339 | > 340 | > other 341 | . 342 |
343 |
344 |
name
345 |
346 |

body

347 |
348 |
name
349 |
350 |

body 351 | other

352 |
353 |
name
354 |
355 |

body

356 |

other

357 |
358 |
name
359 |
360 |

body

361 |

other

362 |
363 |
364 |
365 | . 366 | 367 | 368 | Indented by 4 spaces 369 | . 370 | :name: text 371 | indented 372 | . 373 |
:name: text
374 |     indented
375 | 
376 | . 377 | 378 | Indented by 4 spaces, DISABLE-CODEBLOCKS 379 | . 380 | :name: text 381 | indented 382 | . 383 |
384 |
name
385 |
386 |

text 387 | indented

388 |
389 |
390 | . 391 | -------------------------------------------------------------------------------- /tests/fixtures/front_matter.md: -------------------------------------------------------------------------------- 1 | 2 | should parse empty front matter: 3 | . 4 | --- 5 | --- 6 | # Head 7 | . 8 | 9 |

Head

10 | . 11 | 12 | 13 | should parse basic front matter: 14 | . 15 | --- 16 | x: 1 17 | --- 18 | # Head 19 | . 20 | 21 |

Head

22 | . 23 | 24 | should parse until triple dots: 25 | . 26 | --- 27 | x: 1 28 | ... 29 | # Head 30 | . 31 | 32 |

Head

33 | . 34 | 35 | should parse front matter with indentation: 36 | . 37 | --- 38 | title: Associative arrays 39 | people: 40 | name: John Smith 41 | age: 33 42 | morePeople: { name: Grace Jones, age: 21 } 43 | --- 44 | # Head 45 | . 46 | 47 |

Head

48 | . 49 | 50 | should ignore spaces after front matter delimiters: 51 | . 52 | --- 53 | title: Associative arrays 54 | people: 55 | name: John Smith 56 | age: 33 57 | morePeople: { name: Grace Jones, age: 21 } 58 | --- 59 | # Head 60 | . 61 | 62 |

Head

63 | . 64 | 65 | should ignore front matter with less than 3 opening dashes: 66 | . 67 | -- 68 | x: 1 69 | -- 70 | # Head 71 | . 72 |

-- 73 | x: 1

74 |

Head

75 | . 76 | 77 | should require front matter have matching number of opening and closing dashes: 78 | . 79 | ---- 80 | x: 1 81 | --- 82 | # Head 83 | . 84 |
85 |

x: 1

86 |

Head

87 | . 88 | -------------------------------------------------------------------------------- /tests/fixtures/myst_block.md: -------------------------------------------------------------------------------- 1 | 2 | Block Break: 3 | . 4 | +++ 5 | . 6 |
7 | . 8 | 9 | Block Break Split Markers: 10 | . 11 | + + + + xfsdfsdf 12 | . 13 |
14 | . 15 | 16 | Block Break Too Few Markers: 17 | . 18 | ++ 19 | . 20 |

++

21 | . 22 | 23 | Block Break terminates other blocks: 24 | . 25 | a 26 | +++ 27 | - b 28 | +++ 29 | > c 30 | +++ 31 | . 32 |

a

33 |
34 |
    35 |
  • b
  • 36 |
37 |
38 |
39 |

c

40 |
41 |
42 | . 43 | 44 | Target: 45 | . 46 | (a)= 47 | . 48 | 49 | . 50 | 51 | Target characters: 52 | . 53 | (a bc |@<>*./_-+:)= 54 | . 55 | 56 | . 57 | 58 | Empty target: 59 | . 60 | ()= 61 | . 62 |

()=

63 | . 64 | 65 | Escaped target: 66 | . 67 | \(a)= 68 | . 69 |

(a)=

70 | . 71 | 72 | Indented target: 73 | . 74 | (a)= 75 | . 76 | 77 | . 78 | 79 | Target terminates other blocks: 80 | . 81 | a 82 | (a-b)= 83 | - b 84 | (a b)= 85 | > c 86 | (a)= 87 | . 88 |

a

89 |
    90 |
  • b
  • 91 |
92 |
93 |

c

94 |
95 | 96 | . 97 | 98 | Comment: 99 | . 100 | % abc 101 | . 102 | 103 | . 104 | 105 | Comment terminates other blocks: 106 | . 107 | a 108 | % abc 109 | - b 110 | % abc 111 | > c 112 | % abc 113 | . 114 |

a

115 |
    116 |
  • b
  • 117 |
118 |
119 |

c

120 |
121 | 122 | . 123 | 124 | Multiline comment: 125 | . 126 | a 127 | % abc 128 | % def 129 | - b 130 | % abc 131 | %def 132 | > c 133 | %abc 134 | % 135 | %def 136 | . 137 |

a

138 |
    140 |
  • b
  • 141 |
142 |
144 |

c

145 |
146 | 149 | . 150 | 151 | 152 | Indented by 4 spaces 153 | . 154 | +++ 155 | 156 | % abc 157 | 158 | (a)= 159 | . 160 |
+++
161 | 
162 | % abc
163 | 
164 | (a)=
165 | 
166 | . 167 | 168 | Indented by 4 spaces, DISABLE-CODEBLOCKS 169 | . 170 | +++ 171 | 172 | % abc 173 | 174 | (a)= 175 | . 176 |
177 | 178 | . 179 | -------------------------------------------------------------------------------- /tests/fixtures/myst_role.md: -------------------------------------------------------------------------------- 1 | 2 | Basic: 3 | . 4 | {abc}`xyz` 5 | . 6 |

{abc}[xyz]

7 | . 8 | 9 | Multiple: 10 | . 11 | {abc}`xyz`{def}`uvw` 12 | . 13 |

{abc}[xyz]{def}[uvw]

14 | . 15 | 16 | Surrounding Text: 17 | . 18 | a {abc}`xyz` b 19 | . 20 |

a {abc}[xyz] b

21 | . 22 | 23 | New lines: 24 | . 25 | {abc}`xy 26 | z` `d 27 | e` 28 | . 29 |

{abc}[xy z] d e

30 | . 31 | 32 | In Code: 33 | . 34 | `` {abc}`xyz` `` 35 | . 36 |

{abc}`xyz`

37 | . 38 | 39 | Empty content: 40 | . 41 | {name}`` a 42 | . 43 |

{name}`` a

44 | . 45 | 46 | Surrounding Code: 47 | . 48 | `a` {abc}`xyz` `b` 49 | . 50 |

a {abc}[xyz] b

51 | . 52 | 53 | In list: 54 | . 55 | - {abc}`xyz` 56 | . 57 |
    58 |
  • {abc}[xyz]
  • 59 |
60 | . 61 | 62 | In Quote: 63 | . 64 | > {abc}`xyz` b 65 | . 66 |
67 |

{abc}[xyz] b

68 |
69 | . 70 | 71 | Multiple ticks: 72 | . 73 | {abc}``xyz`` 74 | . 75 |

{abc}[xyz]

76 | . 77 | 78 | Inner tick: 79 | . 80 | {abc}``x`yz`` 81 | . 82 |

{abc}[x`yz]

83 | . 84 | 85 | Unbalanced ticks: 86 | . 87 | {abc}``xyz` 88 | . 89 |

{abc}``xyz`

90 | . 91 | 92 | Space in name: 93 | . 94 | {ab c}`xyz` 95 | . 96 |

{ab c}xyz

97 | . 98 | 99 | Escaped: 100 | . 101 | \{abc}`xyz` 102 | . 103 |

{abc}xyz

104 | . 105 | -------------------------------------------------------------------------------- /tests/fixtures/span.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/executablebooks/mdit-py-plugins/2236898619148b33c51bae3c1aca380618c82db7/tests/fixtures/span.md -------------------------------------------------------------------------------- /tests/fixtures/substitution.md: -------------------------------------------------------------------------------- 1 | Basic (space): 2 | . 3 | {{ block }} 4 | 5 | a {{ inline }} b 6 | . 7 |
8 |

a b

9 | . 10 | 11 | Basic (no space): 12 | . 13 | {{block}} 14 | 15 | a {{inline}} b 16 | . 17 |
18 |

a b

19 | . 20 | 21 | Same line: 22 | . 23 | {{a}}{{b}} 24 | . 25 |

26 | . 27 | 28 | No closing: 29 | . 30 | {{ a 31 | 32 | {{b} 33 | 34 | {{c} } 35 | . 36 |

{{ a

37 |

{{b}

38 |

{{c} }

39 | . 40 | 41 | Inside code 42 | . 43 | `{{ a }}` 44 | 45 | ```python 46 | {{b}} 47 | ``` 48 | . 49 |

{{ a }}

50 |
{{b}}
 51 | 
52 | . 53 | 54 | New line: 55 | . 56 | {{a}} 57 | {{b}} 58 | . 59 |
60 |
61 | . 62 | 63 | Blocks: 64 | . 65 | - {{a}} 66 | 67 | > {{b}} 68 | 69 | 1. {{c}} 70 | . 71 |
    72 |
  • 73 |
    74 |
  • 75 |
76 |
77 |
78 |
79 |
    80 |
  1. 81 |
    82 |
  2. 83 |
84 | . 85 | 86 | Inline: 87 | . 88 | - {{a}} x 89 | 90 | > {{b}} y 91 | 92 | 1. {{c}} z 93 | . 94 |
    95 |
  • x
  • 96 |
97 |
98 |

y

99 |
100 |
    101 |
  1. z
  2. 102 |
103 | . 104 | 105 | Tables: 106 | . 107 | | | | 108 | |-|-| 109 | |{{a}}|{{b}} c| 110 | . 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 |
c
125 | . 126 | -------------------------------------------------------------------------------- /tests/fixtures/tasklists.md: -------------------------------------------------------------------------------- 1 | 2 | bullet.md: 3 | 4 | . 5 | - [ ] unchecked item 1 6 | - [ ] unchecked item 2 7 | - [ ] unchecked item 3 8 | - [x] checked item 4 9 | . 10 |
    11 |
  • unchecked item 1
  • 12 |
  • unchecked item 2
  • 13 |
  • unchecked item 3
  • 14 |
  • checked item 4
  • 15 |
16 | 17 | . 18 | 19 | dirty.md: 20 | 21 | . 22 | - [ ] unchecked todo item 1 23 | - [ ] 24 | - [ ] not a todo item 2 25 | - [ x] not a todo item 3 26 | - [x ] not a todo item 4 27 | - [ x ] not a todo item 5 28 | - [x] todo item 6 29 | 30 | . 31 |
    32 |
  • unchecked todo item 1
  • 33 |
  • [ ]
  • 34 |
  • [ ] not a todo item 2
  • 35 |
  • [ x] not a todo item 3
  • 36 |
  • [x ] not a todo item 4
  • 37 |
  • [ x ] not a todo item 5
  • 38 |
  • todo item 6
  • 39 |
40 | 41 | . 42 | 43 | mixed-nested.md: 44 | 45 | . 46 | # Test 1 47 | 48 | 1. foo 49 | * [ ] nested unchecked item 1 50 | * not a todo item 2 51 | * not a todo item 3 52 | * [x] nested checked item 4 53 | 2. bar 54 | 3. spam 55 | 56 | # Test 2 57 | 58 | - foo 59 | - [ ] nested unchecked item 1 60 | - [ ] nested unchecked item 2 61 | - [x] nested checked item 3 62 | - [X] nested checked item 4 63 | 64 | 65 | . 66 |

Test 1

67 |
    68 |
  1. foo 69 |
      70 |
    • nested unchecked item 1
    • 71 |
    • not a todo item 2
    • 72 |
    • not a todo item 3
    • 73 |
    • nested checked item 4
    • 74 |
    75 |
  2. 76 |
  3. bar
  4. 77 |
  5. spam
  6. 78 |
79 |

Test 2

80 |
    81 |
  • foo 82 |
      83 |
    • nested unchecked item 1
    • 84 |
    • nested unchecked item 2
    • 85 |
    • nested checked item 3
    • 86 |
    • nested checked item 4
    • 87 |
    88 |
  • 89 |
90 | 91 | . 92 | 93 | oedered.md: 94 | 95 | . 96 | 1. [x] checked ordered 1 97 | 2. [ ] unchecked ordered 2 98 | 3. [x] checked ordered 3 99 | 4. [ ] unchecked ordered 4 100 | 101 | . 102 |
    103 |
  1. checked ordered 1
  2. 104 |
  3. unchecked ordered 2
  4. 105 |
  5. checked ordered 3
  6. 106 |
  7. unchecked ordered 4
  8. 107 |
108 | 109 | . 110 | 111 | Tab after task list item marker 112 | . 113 | + [x] item 1 114 | + [ ] item 2 115 | . 116 |
    117 |
  • item 1
  • 118 |
  • item 2
  • 119 |
120 | . 121 | 122 | Form feed after task list item marker 123 | . 124 | + [x] item 1 125 | + [ ] item 2 126 | . 127 |
    128 |
  • item 1
  • 129 |
  • item 2
  • 130 |
131 | . 132 | -------------------------------------------------------------------------------- /tests/fixtures/texmath_bracket.md: -------------------------------------------------------------------------------- 1 | single character inline equation. (valid=True) 2 | . 3 | \(a\) 4 | . 5 |

a

6 | . 7 | 8 | inline equation with single greek character (valid=True) 9 | . 10 | \(\\varphi\) 11 | . 12 |

\\varphi

13 | . 14 | 15 | simple equation starting and ending with numbers. (valid=True) 16 | . 17 | \(1+1=2\) 18 | . 19 |

1+1=2

20 | . 21 | 22 | simple equation including special html character. (valid=True) 23 | . 24 | \(1+1<3\) 25 | . 26 |

1+1<3

27 | . 28 | 29 | equation including backslashes. (valid=True) 30 | . 31 | \(a \\backslash\) 32 | . 33 |

a \\backslash

34 | . 35 | 36 | use of currency symbol (valid=True) 37 | . 38 | You get 3$ if you solve \(1+2\) 39 | . 40 |

You get 3$ if you solve 1+2

41 | . 42 | 43 | use of currency symbol (valid=True) 44 | . 45 | If you solve \(1+2\) you get $3 46 | . 47 |

If you solve 1+2 you get $3

48 | . 49 | 50 | inline fraction (valid=True) 51 | . 52 | \(\\frac{1}{2}\) 53 | . 54 |

\\frac{1}{2}

55 | . 56 | 57 | inline column vector (valid=True) 58 | . 59 | \(\\begin{pmatrix}x\\\\y\\end{pmatrix}\) 60 | . 61 |

\\begin{pmatrix}x\\\\y\\end{pmatrix}

62 | . 63 | 64 | inline bold vector notation (valid=True) 65 | . 66 | \({\\tilde\\bold e}_\\alpha\) 67 | . 68 |

{\\tilde\\bold e}_\\alpha

69 | . 70 | 71 | exponentiation (valid=True) 72 | . 73 | \(a^{b}\) 74 | . 75 |

a^{b}

76 | . 77 | 78 | conjugate complex (valid=True) 79 | . 80 | \(a^\*b\) with \(a^\*\) 81 | . 82 |

a^\*b with a^\*

83 | . 84 | 85 | Inline multi-line (valid=True) 86 | . 87 | a \(a 88 | \not=1\) b 89 | . 90 |

a a 91 | \not=1 b

92 | . 93 | 94 | Inline multi-line with newline (valid=False) 95 | . 96 | a \(a 97 | 98 | \not=1\) b 99 | . 100 |

a (a

101 |

\not=1) b

102 | . 103 | 104 | single block equation, greek index (valid=True) 105 | . 106 | \[e_\\alpha\] 107 | . 108 |
109 | e_\\alpha 110 |
111 | . 112 | 113 | display equation on its own single line. (valid=True) 114 | . 115 | \[1+1=2\] 116 | . 117 |
118 | 1+1=2 119 |
120 | . 121 | 122 | inline equation followed by block equation. (valid=True) 123 | . 124 | \({e}_x\) 125 | 126 | \[e_\\alpha\] 127 | . 128 |

{e}_x

129 |
130 | e_\\alpha 131 |
132 | . 133 | 134 | underline tests (valid=True) 135 | . 136 | \[c{\\bold e}_x = a{\\bold e}_\\alpha - b\\tilde{\\bold e}_\\alpha\] 137 | . 138 |
139 | c{\\bold e}_x = a{\\bold e}_\\alpha - b\\tilde{\\bold e}_\\alpha 140 |
141 | . 142 | 143 | non-numeric character before opening $ or 144 | after closing $ or both is allowed. (valid=True) 145 | . 146 | a\(1+1=2\) 147 | \(1+1=2\)b 148 | c\(x\)d 149 | . 150 |

a1+1=2 151 | 1+1=2b 152 | cxd

153 | . 154 | 155 | following dollar character '$' is allowed. (valid=True) 156 | . 157 | \(x\) $ 158 | . 159 |

x $

160 | . 161 | 162 | consecutive inline equations. (valid=True) 163 | . 164 | \(x\) \(y\) 165 | . 166 |

x y

167 | . 168 | 169 | inline equation after '-' sign in text. (valid=True) 170 | . 171 | so-what is \(x\) 172 | . 173 |

so-what is x

174 | . 175 | 176 | display equation with line breaks. (valid=True) 177 | . 178 | \[ 179 | 1+1=2 180 | \] 181 | . 182 |
183 | 184 | 1+1=2 185 | 186 |
187 | . 188 | 189 | multiple equations (valid=True) 190 | . 191 | \[ 192 | a = 1 193 | \] 194 | 195 | \[ 196 | b = 2 197 | \] 198 | . 199 |
200 | 201 | a = 1 202 | 203 |
204 |
205 | 206 | b = 2 207 | 208 |
209 | . 210 | 211 | equation followed by a labelled equation (valid=True) 212 | . 213 | \[ 214 | a = 1 215 | \] 216 | 217 | \[ 218 | b = 2 219 | \] (1) 220 | . 221 |
222 | 223 | a = 1 224 | 225 |
226 |
227 | 228 | b = 2 229 | 230 |
231 | . 232 | 233 | multiline equation. (valid=True) 234 | . 235 | \[\\begin{matrix} 236 | f & = & 2 + x + 3 \\ 237 | & = & 5 + x 238 | \\end{matrix}\] 239 | . 240 |
241 | \\begin{matrix} 242 | f & = & 2 + x + 3 \\ 243 | & = & 5 + x 244 | \\end{matrix} 245 |
246 | . 247 | 248 | vector equation. (valid=True) 249 | . 250 | \[\\begin{pmatrix}x_2 \\\\ y_2 \\end{pmatrix} = 251 | \\begin{pmatrix} A & B \\\\ C & D \\end{pmatrix}\\cdot 252 | \\begin{pmatrix} x_1 \\\\ y_1 \\end{pmatrix}\] 253 | . 254 |
255 | \\begin{pmatrix}x_2 \\\\ y_2 \\end{pmatrix} = 256 | \\begin{pmatrix} A & B \\\\ C & D \\end{pmatrix}\\cdot 257 | \\begin{pmatrix} x_1 \\\\ y_1 \\end{pmatrix} 258 |
259 | . 260 | 261 | display equation with equation number. (valid=True) 262 | . 263 | \[f(x) = x^2 - 1\] (1) 264 | . 265 |
266 | f(x) = x^2 - 1 267 |
268 | . 269 | 270 | inline equation following code section. (valid=True) 271 | . 272 | `code`\(a-b\) 273 | . 274 |

codea-b

275 | . 276 | 277 | equation following code block. (valid=True) 278 | . 279 | ``` 280 | code 281 | ``` 282 | \[a+b\] 283 | . 284 |
code
285 | 
286 |
287 | a+b 288 |
289 | . 290 | 291 | numbered equation following code block. (valid=True) 292 | . 293 | ``` 294 | code 295 | ``` 296 | \[a+b\](1) 297 | . 298 |
code
299 | 
300 |
301 | a+b 302 |
303 | . 304 | 305 | Equations in list. (valid=True) 306 | . 307 | 1. \(1+2\) 308 | 2. \(2+3\) 309 | 1. \(3+4\) 310 | . 311 |
    312 |
  1. 1+2
  2. 313 |
  3. 2+3 314 |
      315 |
    1. 3+4
    2. 316 |
    317 |
  4. 318 |
319 | . 320 | 321 | Inline sum. (valid=True) 322 | . 323 | \(\\sum\_{i=1}^n\) 324 | . 325 |

\\sum\_{i=1}^n

326 | . 327 | 328 | Sum without equation number. (valid=True) 329 | . 330 | \[\\sum\_{i=1}^n\] 331 | . 332 |
333 | \\sum\_{i=1}^n 334 |
335 | . 336 | 337 | Sum with equation number. (valid=True) 338 | . 339 | \[\\sum\_{i=1}\^n\] \(2\) 340 | . 341 |
342 | \\sum\_{i=1}\^n 343 |
344 | . 345 | 346 | equation number always vertically aligned. (valid=True) 347 | . 348 | \[{\\bold e}(\\varphi) = \\begin{pmatrix} 349 | \\cos\\varphi\\\\\\sin\\varphi 350 | \\end{pmatrix}\] (3) 351 | . 352 |
353 | {\\bold e}(\\varphi) = \\begin{pmatrix} 354 | \\cos\\varphi\\\\\\sin\\varphi 355 | \\end{pmatrix} 356 |
357 | . 358 | 359 | inline equations in blockquote. (valid=True) 360 | . 361 | > see \(a = b + c\) 362 | > \(c^2=a^2+b^2\) (2) 363 | > \(c^2=a^2+b^2\) 364 | . 365 |
366 |

see a = b + c 367 | c^2=a^2+b^2 (2) 368 | c^2=a^2+b^2

369 |
370 | . 371 | 372 | display equation in blockquote. (valid=True) 373 | . 374 | > formula 375 | > 376 | > \[ a+b=c\] (2) 377 | > 378 | > in blockquote. 379 | . 380 |
381 |

formula

382 |
383 | a+b=c 384 |
385 |

in blockquote.

386 |
387 | . 388 | 389 | Indented by 4 spaces 390 | . 391 | \[ 392 | a = 1 393 | \] 394 | . 395 |
\[
396 | a = 1
397 | \]
398 | 
399 | . 400 | 401 | Indented by 4 spaces, DISABLE-CODEBLOCKS 402 | . 403 | \[ 404 | a = 1 405 | \] 406 | . 407 |
408 | 409 | a = 1 410 | 411 |
412 | . 413 | -------------------------------------------------------------------------------- /tests/fixtures/wordcount.md: -------------------------------------------------------------------------------- 1 | syntax (text) 2 | . 3 | one two 4 | three four 5 | 6 | - five six 7 | 8 | > seven eight 9 | 10 | [nine ten](link) 11 | . 12 | { 13 | "minutes": 0, 14 | "text": [ 15 | "one two", 16 | "three four", 17 | "five six", 18 | "seven eight", 19 | "nine ten" 20 | ], 21 | "words": 10 22 | } 23 | . 24 | 25 | non-words 26 | . 27 | Geeksforgeeks, is best @# Computer Science Portal.!!! 28 | . 29 | { 30 | "minutes": 0, 31 | "words": 6 32 | } 33 | . 34 | 35 | lore ipsum 36 | . 37 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent imperdiet hendrerit dictum. Etiam diam turpis, cursus in varius dignissim, imperdiet nec nibh. Nulla nec finibus dui. Phasellus fermentum venenatis placerat. Donec ut dui in sem rhoncus molestie. Sed auctor sem dapibus augue varius facilisis. Maecenas at suscipit dolor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vestibulum ornare dui ac tristique ultricies. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque arcu erat, ultricies ac lorem at, semper ornare nisl. 38 | 39 | Donec massa magna, commodo et ultrices ac, rutrum non nulla. Nunc fermentum fringilla ultrices. Morbi ante nibh, accumsan ac viverra quis, gravida rutrum mi. Integer lobortis, purus id laoreet ornare, sapien odio placerat massa, vel vestibulum dolor ante id mi. Donec ex leo, ultricies non ante eget, pharetra dictum orci. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas vitae tortor ut nisi cursus egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Morbi et nunc posuere, pharetra est aliquet, iaculis libero. Aliquam leo nibh, posuere eget eros a, convallis bibendum nibh. Phasellus vulputate bibendum lacus sit amet varius. Integer ut rutrum dolor, ac finibus neque. Maecenas ultrices pretium augue vitae mollis. Fusce semper lorem eu mauris iaculis pulvinar. 40 | 41 | Morbi ac pretium nunc, ac faucibus enim. Duis consequat nibh metus, at sodales sem luctus nec. Donec id finibus ante. Duis tincidunt vulputate efficitur. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean porttitor augue consectetur, feugiat odio in, rutrum velit. Aliquam et mi lacinia, efficitur nisl nec, rutrum mauris. Mauris efficitur eros in maximus tempor. Suspendisse potenti. Quisque cursus non libero in faucibus. Etiam dignissim urna vel nibh feugiat, at vehicula dui vulputate. 42 | 43 | Praesent malesuada arcu quis neque condimentum vestibulum. Aliquam pretium eleifend neque, eget vulputate erat faucibus at. Quisque egestas nunc ac hendrerit fringilla. Vestibulum at tristique lacus, eget placerat risus. Aenean a metus ullamcorper, eleifend ante ut, feugiat lacus. Proin eget semper purus, ac vehicula nisl. Suspendisse eu mi enim. Nullam aliquam purus eu orci iaculis suscipit. Mauris dapibus non neque eu hendrerit. Sed eros purus, finibus ut ex ac, ultricies luctus enim. Quisque non lacus arcu. Ut dictum mauris ac tristique pulvinar. Aenean ut nisl massa. Donec nec dui scelerisque, egestas arcu sit amet, tempor eros. 44 | 45 | Donec sit amet faucibus tellus. Cras auctor mi id quam rhoncus, eget porttitor magna ultrices. Sed tristique ut augue in facilisis. Duis in finibus diam. In hac habitasse platea dictumst. Vestibulum in pulvinar orci. Sed a justo cursus enim ultrices egestas sed sit amet leo. Donec sed auctor urna. Praesent vitae dapibus ipsum. Nulla facilisi. Pellentesque non nisi sem. Sed ac mi rutrum, blandit purus ut, facilisis ipsum. 46 | . 47 | { 48 | "minutes": 2, 49 | "words": 458 50 | } 51 | . 52 | -------------------------------------------------------------------------------- /tests/test_admon.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.admon import admon_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent 11 | 12 | 13 | @pytest.mark.parametrize( 14 | "line,title,input,expected", 15 | read_fixture_file(FIXTURE_PATH.joinpath("fixtures", "admon.md")), 16 | ) 17 | def test_all(line, title, input, expected): 18 | md = MarkdownIt("commonmark").use(admon_plugin) 19 | if "DISABLE-CODEBLOCKS" in title: 20 | md.disable("code") 21 | md.options["xhtmlOut"] = False 22 | text = md.render(input) 23 | print(text) 24 | assert text.rstrip() == expected.rstrip() 25 | 26 | 27 | @pytest.mark.parametrize("text_idx", (0, 1, 2)) 28 | def test_plugin_parse(data_regression, text_idx): 29 | texts = [ 30 | "!!! note\n content 1", 31 | "??? note\n content 2", 32 | "???+ note\n content 3", 33 | ] 34 | md = MarkdownIt().use(admon_plugin) 35 | tokens = md.parse(dedent(texts[text_idx])) 36 | data_regression.check([t.as_dict() for t in tokens]) 37 | -------------------------------------------------------------------------------- /tests/test_admon/test_plugin_parse_0_.yml: -------------------------------------------------------------------------------- 1 | - attrs: 2 | - - class 3 | - admonition note 4 | block: true 5 | children: null 6 | content: Note 7 | hidden: false 8 | info: ' note' 9 | level: 0 10 | map: 11 | - 0 12 | - 2 13 | markup: '!!!' 14 | meta: 15 | tag: note 16 | nesting: 1 17 | tag: div 18 | type: admonition_open 19 | - attrs: 20 | - - class 21 | - admonition-title 22 | block: true 23 | children: null 24 | content: '' 25 | hidden: false 26 | info: '' 27 | level: 1 28 | map: 29 | - 0 30 | - 1 31 | markup: '!!! note' 32 | meta: {} 33 | nesting: 1 34 | tag: p 35 | type: admonition_title_open 36 | - attrs: null 37 | block: true 38 | children: 39 | - attrs: null 40 | block: false 41 | children: null 42 | content: Note 43 | hidden: false 44 | info: '' 45 | level: 0 46 | map: null 47 | markup: '' 48 | meta: {} 49 | nesting: 0 50 | tag: '' 51 | type: text 52 | content: Note 53 | hidden: false 54 | info: '' 55 | level: 2 56 | map: 57 | - 0 58 | - 1 59 | markup: '' 60 | meta: {} 61 | nesting: 0 62 | tag: '' 63 | type: inline 64 | - attrs: null 65 | block: true 66 | children: null 67 | content: '' 68 | hidden: false 69 | info: '' 70 | level: 1 71 | map: null 72 | markup: '' 73 | meta: {} 74 | nesting: -1 75 | tag: p 76 | type: admonition_title_close 77 | - attrs: null 78 | block: true 79 | children: null 80 | content: '' 81 | hidden: false 82 | info: '' 83 | level: 1 84 | map: 85 | - 1 86 | - 2 87 | markup: '' 88 | meta: {} 89 | nesting: 1 90 | tag: p 91 | type: paragraph_open 92 | - attrs: null 93 | block: true 94 | children: 95 | - attrs: null 96 | block: false 97 | children: null 98 | content: content 1 99 | hidden: false 100 | info: '' 101 | level: 0 102 | map: null 103 | markup: '' 104 | meta: {} 105 | nesting: 0 106 | tag: '' 107 | type: text 108 | content: content 1 109 | hidden: false 110 | info: '' 111 | level: 2 112 | map: 113 | - 1 114 | - 2 115 | markup: '' 116 | meta: {} 117 | nesting: 0 118 | tag: '' 119 | type: inline 120 | - attrs: null 121 | block: true 122 | children: null 123 | content: '' 124 | hidden: false 125 | info: '' 126 | level: 1 127 | map: null 128 | markup: '' 129 | meta: {} 130 | nesting: -1 131 | tag: p 132 | type: paragraph_close 133 | - attrs: null 134 | block: true 135 | children: null 136 | content: '' 137 | hidden: false 138 | info: '' 139 | level: 0 140 | map: null 141 | markup: '!!!' 142 | meta: {} 143 | nesting: -1 144 | tag: div 145 | type: admonition_close 146 | -------------------------------------------------------------------------------- /tests/test_admon/test_plugin_parse_1_.yml: -------------------------------------------------------------------------------- 1 | - attrs: 2 | - - class 3 | - admonition note is-collapsible collapsible-closed 4 | block: true 5 | children: null 6 | content: Note 7 | hidden: false 8 | info: ' note' 9 | level: 0 10 | map: 11 | - 0 12 | - 2 13 | markup: ??? 14 | meta: 15 | tag: note 16 | nesting: 1 17 | tag: div 18 | type: admonition_open 19 | - attrs: 20 | - - class 21 | - admonition-title 22 | block: true 23 | children: null 24 | content: '' 25 | hidden: false 26 | info: '' 27 | level: 1 28 | map: 29 | - 0 30 | - 1 31 | markup: ??? note 32 | meta: {} 33 | nesting: 1 34 | tag: p 35 | type: admonition_title_open 36 | - attrs: null 37 | block: true 38 | children: 39 | - attrs: null 40 | block: false 41 | children: null 42 | content: Note 43 | hidden: false 44 | info: '' 45 | level: 0 46 | map: null 47 | markup: '' 48 | meta: {} 49 | nesting: 0 50 | tag: '' 51 | type: text 52 | content: Note 53 | hidden: false 54 | info: '' 55 | level: 2 56 | map: 57 | - 0 58 | - 1 59 | markup: '' 60 | meta: {} 61 | nesting: 0 62 | tag: '' 63 | type: inline 64 | - attrs: null 65 | block: true 66 | children: null 67 | content: '' 68 | hidden: false 69 | info: '' 70 | level: 1 71 | map: null 72 | markup: '' 73 | meta: {} 74 | nesting: -1 75 | tag: p 76 | type: admonition_title_close 77 | - attrs: null 78 | block: true 79 | children: null 80 | content: '' 81 | hidden: false 82 | info: '' 83 | level: 1 84 | map: 85 | - 1 86 | - 2 87 | markup: '' 88 | meta: {} 89 | nesting: 1 90 | tag: p 91 | type: paragraph_open 92 | - attrs: null 93 | block: true 94 | children: 95 | - attrs: null 96 | block: false 97 | children: null 98 | content: content 2 99 | hidden: false 100 | info: '' 101 | level: 0 102 | map: null 103 | markup: '' 104 | meta: {} 105 | nesting: 0 106 | tag: '' 107 | type: text 108 | content: content 2 109 | hidden: false 110 | info: '' 111 | level: 2 112 | map: 113 | - 1 114 | - 2 115 | markup: '' 116 | meta: {} 117 | nesting: 0 118 | tag: '' 119 | type: inline 120 | - attrs: null 121 | block: true 122 | children: null 123 | content: '' 124 | hidden: false 125 | info: '' 126 | level: 1 127 | map: null 128 | markup: '' 129 | meta: {} 130 | nesting: -1 131 | tag: p 132 | type: paragraph_close 133 | - attrs: null 134 | block: true 135 | children: null 136 | content: '' 137 | hidden: false 138 | info: '' 139 | level: 0 140 | map: null 141 | markup: ??? 142 | meta: {} 143 | nesting: -1 144 | tag: div 145 | type: admonition_close 146 | -------------------------------------------------------------------------------- /tests/test_admon/test_plugin_parse_2_.yml: -------------------------------------------------------------------------------- 1 | - attrs: 2 | - - class 3 | - admonition note is-collapsible collapsible-open 4 | block: true 5 | children: null 6 | content: Note 7 | hidden: false 8 | info: ' note' 9 | level: 0 10 | map: 11 | - 0 12 | - 2 13 | markup: ???+ 14 | meta: 15 | tag: note 16 | nesting: 1 17 | tag: div 18 | type: admonition_open 19 | - attrs: 20 | - - class 21 | - admonition-title 22 | block: true 23 | children: null 24 | content: '' 25 | hidden: false 26 | info: '' 27 | level: 1 28 | map: 29 | - 0 30 | - 1 31 | markup: ???+ note 32 | meta: {} 33 | nesting: 1 34 | tag: p 35 | type: admonition_title_open 36 | - attrs: null 37 | block: true 38 | children: 39 | - attrs: null 40 | block: false 41 | children: null 42 | content: Note 43 | hidden: false 44 | info: '' 45 | level: 0 46 | map: null 47 | markup: '' 48 | meta: {} 49 | nesting: 0 50 | tag: '' 51 | type: text 52 | content: Note 53 | hidden: false 54 | info: '' 55 | level: 2 56 | map: 57 | - 0 58 | - 1 59 | markup: '' 60 | meta: {} 61 | nesting: 0 62 | tag: '' 63 | type: inline 64 | - attrs: null 65 | block: true 66 | children: null 67 | content: '' 68 | hidden: false 69 | info: '' 70 | level: 1 71 | map: null 72 | markup: '' 73 | meta: {} 74 | nesting: -1 75 | tag: p 76 | type: admonition_title_close 77 | - attrs: null 78 | block: true 79 | children: null 80 | content: '' 81 | hidden: false 82 | info: '' 83 | level: 1 84 | map: 85 | - 1 86 | - 2 87 | markup: '' 88 | meta: {} 89 | nesting: 1 90 | tag: p 91 | type: paragraph_open 92 | - attrs: null 93 | block: true 94 | children: 95 | - attrs: null 96 | block: false 97 | children: null 98 | content: content 3 99 | hidden: false 100 | info: '' 101 | level: 0 102 | map: null 103 | markup: '' 104 | meta: {} 105 | nesting: 0 106 | tag: '' 107 | type: text 108 | content: content 3 109 | hidden: false 110 | info: '' 111 | level: 2 112 | map: 113 | - 1 114 | - 2 115 | markup: '' 116 | meta: {} 117 | nesting: 0 118 | tag: '' 119 | type: inline 120 | - attrs: null 121 | block: true 122 | children: null 123 | content: '' 124 | hidden: false 125 | info: '' 126 | level: 1 127 | map: null 128 | markup: '' 129 | meta: {} 130 | nesting: -1 131 | tag: p 132 | type: paragraph_close 133 | - attrs: null 134 | block: true 135 | children: null 136 | content: '' 137 | hidden: false 138 | info: '' 139 | level: 0 140 | map: null 141 | markup: ???+ 142 | meta: {} 143 | nesting: -1 144 | tag: div 145 | type: admonition_close 146 | -------------------------------------------------------------------------------- /tests/test_amsmath.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.amsmath import amsmath_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent 11 | 12 | 13 | def test_plugin_parse(data_regression): 14 | md = MarkdownIt().use(amsmath_plugin) 15 | tokens = md.parse( 16 | dedent( 17 | """\ 18 | a 19 | \\begin{equation} 20 | b=1 21 | c=2 22 | \\end{equation} 23 | d 24 | """ 25 | ) 26 | ) 27 | data_regression.check([t.as_dict() for t in tokens]) 28 | 29 | 30 | def test_custom_renderer(data_regression): 31 | md = MarkdownIt().use(amsmath_plugin, renderer=lambda x: x + "!") 32 | output = md.render("\\begin{equation}\na\n\\end{equation}") 33 | assert ( 34 | output.strip() 35 | == dedent( 36 | """\ 37 |
38 | \\begin{equation} 39 | a 40 | \\end{equation}! 41 |
42 | """ 43 | ).strip() 44 | ) 45 | 46 | 47 | @pytest.mark.parametrize( 48 | "line,title,input,expected", 49 | read_fixture_file(FIXTURE_PATH.joinpath("fixtures", "amsmath.md")), 50 | ) 51 | def test_fixtures(line, title, input, expected): 52 | md = MarkdownIt("commonmark").use(amsmath_plugin) 53 | if "DISABLE-CODEBLOCKS" in title: 54 | md.disable("code") 55 | md.options["xhtmlOut"] = False 56 | text = md.render(input) 57 | print(text) 58 | assert text.rstrip() == expected.rstrip() 59 | -------------------------------------------------------------------------------- /tests/test_amsmath/test_plugin_parse.yml: -------------------------------------------------------------------------------- 1 | - attrs: null 2 | block: true 3 | children: null 4 | content: '' 5 | hidden: false 6 | info: '' 7 | level: 0 8 | map: 9 | - 0 10 | - 1 11 | markup: '' 12 | meta: {} 13 | nesting: 1 14 | tag: p 15 | type: paragraph_open 16 | - attrs: null 17 | block: true 18 | children: 19 | - attrs: null 20 | block: false 21 | children: null 22 | content: a 23 | hidden: false 24 | info: '' 25 | level: 0 26 | map: null 27 | markup: '' 28 | meta: {} 29 | nesting: 0 30 | tag: '' 31 | type: text 32 | content: a 33 | hidden: false 34 | info: '' 35 | level: 1 36 | map: 37 | - 0 38 | - 1 39 | markup: '' 40 | meta: {} 41 | nesting: 0 42 | tag: '' 43 | type: inline 44 | - attrs: null 45 | block: true 46 | children: null 47 | content: '' 48 | hidden: false 49 | info: '' 50 | level: 0 51 | map: null 52 | markup: '' 53 | meta: {} 54 | nesting: -1 55 | tag: p 56 | type: paragraph_close 57 | - attrs: null 58 | block: true 59 | children: null 60 | content: '\begin{equation} 61 | 62 | b=1 63 | 64 | c=2 65 | 66 | \end{equation}' 67 | hidden: false 68 | info: '' 69 | level: 0 70 | map: 71 | - 1 72 | - 4 73 | markup: '' 74 | meta: 75 | environment: equation 76 | numbered: '' 77 | nesting: 0 78 | tag: math 79 | type: amsmath 80 | - attrs: null 81 | block: true 82 | children: null 83 | content: '' 84 | hidden: false 85 | info: '' 86 | level: 0 87 | map: 88 | - 5 89 | - 6 90 | markup: '' 91 | meta: {} 92 | nesting: 1 93 | tag: p 94 | type: paragraph_open 95 | - attrs: null 96 | block: true 97 | children: 98 | - attrs: null 99 | block: false 100 | children: null 101 | content: d 102 | hidden: false 103 | info: '' 104 | level: 0 105 | map: null 106 | markup: '' 107 | meta: {} 108 | nesting: 0 109 | tag: '' 110 | type: text 111 | content: d 112 | hidden: false 113 | info: '' 114 | level: 1 115 | map: 116 | - 5 117 | - 6 118 | markup: '' 119 | meta: {} 120 | nesting: 0 121 | tag: '' 122 | type: inline 123 | - attrs: null 124 | block: true 125 | children: null 126 | content: '' 127 | hidden: false 128 | info: '' 129 | level: 0 130 | map: null 131 | markup: '' 132 | meta: {} 133 | nesting: -1 134 | tag: p 135 | type: paragraph_close 136 | -------------------------------------------------------------------------------- /tests/test_anchors.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from markdown_it import MarkdownIt 4 | from markdown_it.utils import read_fixture_file 5 | import pytest 6 | 7 | from mdit_py_plugins.anchors import anchors_plugin 8 | 9 | FIXTURE_PATH = Path(__file__).parent 10 | 11 | 12 | @pytest.mark.parametrize( 13 | "line,title,input,expected", 14 | read_fixture_file(FIXTURE_PATH.joinpath("fixtures", "anchors.md")), 15 | ) 16 | def test_fixtures(line, title, input, expected): 17 | md = MarkdownIt("commonmark").use( 18 | anchors_plugin, 19 | permalink="(permalink" in title, 20 | permalinkBefore="before)" in title, 21 | ) 22 | text = md.render(input) 23 | assert text.rstrip() == expected.rstrip() 24 | -------------------------------------------------------------------------------- /tests/test_attrs.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from markdown_it import MarkdownIt 4 | from markdown_it.utils import read_fixture_file 5 | import pytest 6 | 7 | from mdit_py_plugins.attrs import attrs_block_plugin, attrs_plugin 8 | 9 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures") 10 | 11 | 12 | @pytest.mark.parametrize( 13 | "line,title,input,expected", read_fixture_file(FIXTURE_PATH / "attrs.md") 14 | ) 15 | def test_attrs(line, title, input, expected): 16 | md = MarkdownIt("commonmark").use(attrs_plugin, spans=True).use(attrs_block_plugin) 17 | if "DISABLE-CODEBLOCKS" in title: 18 | md.disable("code") 19 | md.options["xhtmlOut"] = False 20 | text = md.render(input) 21 | print(text) 22 | assert text.rstrip() == expected.rstrip() 23 | 24 | 25 | def test_attrs_allowed(data_regression): 26 | allowed = ["safe"] 27 | md = ( 28 | MarkdownIt("commonmark") 29 | .use(attrs_plugin, allowed=allowed) 30 | .use(attrs_block_plugin, allowed=allowed) 31 | ) 32 | tokens = md.parse(""" 33 | {danger1=a safe=b} 34 | {danger2=c safe=d} 35 | # header 36 | 37 | `inline`{safe=a danger=b} 38 | """) 39 | data_regression.check([t.as_dict() for t in tokens]) 40 | -------------------------------------------------------------------------------- /tests/test_attrs/test_attrs_allowed.yml: -------------------------------------------------------------------------------- 1 | - attrs: 2 | - - safe 3 | - d 4 | block: true 5 | children: null 6 | content: '' 7 | hidden: false 8 | info: '' 9 | level: 0 10 | map: 11 | - 3 12 | - 4 13 | markup: '#' 14 | meta: 15 | insecure_attrs: 16 | danger1: a 17 | danger2: c 18 | nesting: 1 19 | tag: h1 20 | type: heading_open 21 | - attrs: null 22 | block: true 23 | children: 24 | - attrs: null 25 | block: false 26 | children: null 27 | content: header 28 | hidden: false 29 | info: '' 30 | level: 0 31 | map: null 32 | markup: '' 33 | meta: {} 34 | nesting: 0 35 | tag: '' 36 | type: text 37 | content: header 38 | hidden: false 39 | info: '' 40 | level: 1 41 | map: 42 | - 3 43 | - 4 44 | markup: '' 45 | meta: {} 46 | nesting: 0 47 | tag: '' 48 | type: inline 49 | - attrs: null 50 | block: true 51 | children: null 52 | content: '' 53 | hidden: false 54 | info: '' 55 | level: 0 56 | map: null 57 | markup: '#' 58 | meta: {} 59 | nesting: -1 60 | tag: h1 61 | type: heading_close 62 | - attrs: null 63 | block: true 64 | children: null 65 | content: '' 66 | hidden: false 67 | info: '' 68 | level: 0 69 | map: 70 | - 5 71 | - 6 72 | markup: '' 73 | meta: {} 74 | nesting: 1 75 | tag: p 76 | type: paragraph_open 77 | - attrs: null 78 | block: true 79 | children: 80 | - attrs: 81 | - - safe 82 | - a 83 | block: false 84 | children: null 85 | content: inline 86 | hidden: false 87 | info: '' 88 | level: 0 89 | map: null 90 | markup: '`' 91 | meta: 92 | insecure_attrs: 93 | danger: b 94 | nesting: 0 95 | tag: code 96 | type: code_inline 97 | content: '`inline`{safe=a danger=b}' 98 | hidden: false 99 | info: '' 100 | level: 1 101 | map: 102 | - 5 103 | - 6 104 | markup: '' 105 | meta: {} 106 | nesting: 0 107 | tag: '' 108 | type: inline 109 | - attrs: null 110 | block: true 111 | children: null 112 | content: '' 113 | hidden: false 114 | info: '' 115 | level: 0 116 | map: null 117 | markup: '' 118 | meta: {} 119 | nesting: -1 120 | tag: p 121 | type: paragraph_close 122 | -------------------------------------------------------------------------------- /tests/test_colon_fence.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.colon_fence import colon_fence_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "colon_fence.md") 11 | 12 | 13 | @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) 14 | def test_fixtures(line, title, input, expected): 15 | md = MarkdownIt("commonmark").use(colon_fence_plugin) 16 | if "DISABLE-CODEBLOCKS" in title: 17 | md.disable("code") 18 | md.options["xhtmlOut"] = False 19 | text = md.render(input) 20 | try: 21 | assert text.rstrip() == expected.rstrip() 22 | except AssertionError: 23 | print(text) 24 | raise 25 | 26 | 27 | def test_plugin_parse(data_regression): 28 | md = MarkdownIt().use(colon_fence_plugin) 29 | tokens = md.parse( 30 | dedent( 31 | """\ 32 | ::: name 33 | *content* 34 | ::: 35 | """ 36 | ) 37 | ) 38 | data_regression.check([t.as_dict() for t in tokens]) 39 | -------------------------------------------------------------------------------- /tests/test_colon_fence/test_plugin_parse.yml: -------------------------------------------------------------------------------- 1 | - attrs: null 2 | block: true 3 | children: null 4 | content: '*content* 5 | 6 | ' 7 | hidden: false 8 | info: ' name' 9 | level: 0 10 | map: 11 | - 0 12 | - 3 13 | markup: ':::' 14 | meta: {} 15 | nesting: 0 16 | tag: code 17 | type: colon_fence 18 | -------------------------------------------------------------------------------- /tests/test_container.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.container import container_plugin 9 | 10 | 11 | def test_plugin_parse(data_regression): 12 | md = MarkdownIt().use(container_plugin, "name") 13 | tokens = md.parse( 14 | dedent( 15 | """\ 16 | ::: name 17 | *content* 18 | ::: 19 | """ 20 | ) 21 | ) 22 | data_regression.check([t.as_dict() for t in tokens]) 23 | 24 | 25 | def test_no_new_line_issue(data_regression): 26 | """Fixed an IndexError when no newline on final line.""" 27 | md = MarkdownIt().use(container_plugin, "name") 28 | tokens = md.parse( 29 | dedent( 30 | """\ 31 | ::: name 32 | *content* 33 | :::""" 34 | ) 35 | ) 36 | data_regression.check([t.as_dict() for t in tokens]) 37 | 38 | 39 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "container.md") 40 | 41 | 42 | @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) 43 | def test_all(line, title, input, expected): 44 | md = MarkdownIt("commonmark").use(container_plugin, "name") 45 | if "DISABLE-CODEBLOCKS" in title: 46 | md.disable("code") 47 | md.options["xhtmlOut"] = False 48 | text = md.render(input) 49 | print(text) 50 | assert text.rstrip() == expected.rstrip() 51 | -------------------------------------------------------------------------------- /tests/test_container/test_no_new_line_issue.yml: -------------------------------------------------------------------------------- 1 | - attrs: null 2 | block: true 3 | children: null 4 | content: '' 5 | hidden: false 6 | info: ' name' 7 | level: 0 8 | map: 9 | - 0 10 | - 2 11 | markup: ':::' 12 | meta: {} 13 | nesting: 1 14 | tag: div 15 | type: container_name_open 16 | - attrs: null 17 | block: true 18 | children: null 19 | content: '' 20 | hidden: false 21 | info: '' 22 | level: 1 23 | map: 24 | - 1 25 | - 2 26 | markup: '' 27 | meta: {} 28 | nesting: 1 29 | tag: p 30 | type: paragraph_open 31 | - attrs: null 32 | block: true 33 | children: 34 | - attrs: null 35 | block: false 36 | children: null 37 | content: '' 38 | hidden: false 39 | info: '' 40 | level: 0 41 | map: null 42 | markup: '*' 43 | meta: {} 44 | nesting: 1 45 | tag: em 46 | type: em_open 47 | - attrs: null 48 | block: false 49 | children: null 50 | content: content 51 | hidden: false 52 | info: '' 53 | level: 1 54 | map: null 55 | markup: '' 56 | meta: {} 57 | nesting: 0 58 | tag: '' 59 | type: text 60 | - attrs: null 61 | block: false 62 | children: null 63 | content: '' 64 | hidden: false 65 | info: '' 66 | level: 0 67 | map: null 68 | markup: '*' 69 | meta: {} 70 | nesting: -1 71 | tag: em 72 | type: em_close 73 | content: '*content*' 74 | hidden: false 75 | info: '' 76 | level: 2 77 | map: 78 | - 1 79 | - 2 80 | markup: '' 81 | meta: {} 82 | nesting: 0 83 | tag: '' 84 | type: inline 85 | - attrs: null 86 | block: true 87 | children: null 88 | content: '' 89 | hidden: false 90 | info: '' 91 | level: 1 92 | map: null 93 | markup: '' 94 | meta: {} 95 | nesting: -1 96 | tag: p 97 | type: paragraph_close 98 | - attrs: null 99 | block: true 100 | children: null 101 | content: '' 102 | hidden: false 103 | info: '' 104 | level: 0 105 | map: null 106 | markup: ':::' 107 | meta: {} 108 | nesting: -1 109 | tag: div 110 | type: container_name_close 111 | -------------------------------------------------------------------------------- /tests/test_container/test_plugin_parse.yml: -------------------------------------------------------------------------------- 1 | - attrs: null 2 | block: true 3 | children: null 4 | content: '' 5 | hidden: false 6 | info: ' name' 7 | level: 0 8 | map: 9 | - 0 10 | - 2 11 | markup: ':::' 12 | meta: {} 13 | nesting: 1 14 | tag: div 15 | type: container_name_open 16 | - attrs: null 17 | block: true 18 | children: null 19 | content: '' 20 | hidden: false 21 | info: '' 22 | level: 1 23 | map: 24 | - 1 25 | - 2 26 | markup: '' 27 | meta: {} 28 | nesting: 1 29 | tag: p 30 | type: paragraph_open 31 | - attrs: null 32 | block: true 33 | children: 34 | - attrs: null 35 | block: false 36 | children: null 37 | content: '' 38 | hidden: false 39 | info: '' 40 | level: 0 41 | map: null 42 | markup: '*' 43 | meta: {} 44 | nesting: 1 45 | tag: em 46 | type: em_open 47 | - attrs: null 48 | block: false 49 | children: null 50 | content: content 51 | hidden: false 52 | info: '' 53 | level: 1 54 | map: null 55 | markup: '' 56 | meta: {} 57 | nesting: 0 58 | tag: '' 59 | type: text 60 | - attrs: null 61 | block: false 62 | children: null 63 | content: '' 64 | hidden: false 65 | info: '' 66 | level: 0 67 | map: null 68 | markup: '*' 69 | meta: {} 70 | nesting: -1 71 | tag: em 72 | type: em_close 73 | content: '*content*' 74 | hidden: false 75 | info: '' 76 | level: 2 77 | map: 78 | - 1 79 | - 2 80 | markup: '' 81 | meta: {} 82 | nesting: 0 83 | tag: '' 84 | type: inline 85 | - attrs: null 86 | block: true 87 | children: null 88 | content: '' 89 | hidden: false 90 | info: '' 91 | level: 1 92 | map: null 93 | markup: '' 94 | meta: {} 95 | nesting: -1 96 | tag: p 97 | type: paragraph_close 98 | - attrs: null 99 | block: true 100 | children: null 101 | content: '' 102 | hidden: false 103 | info: '' 104 | level: 0 105 | map: null 106 | markup: ':::' 107 | meta: {} 108 | nesting: -1 109 | tag: div 110 | type: container_name_close 111 | -------------------------------------------------------------------------------- /tests/test_deflist.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.deflist import deflist_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "deflist.md") 11 | 12 | 13 | def test_plugin_parse(data_regression): 14 | md = MarkdownIt().use(deflist_plugin) 15 | tokens = md.parse( 16 | dedent( 17 | """\ 18 | Term 1 19 | 20 | : Definition 1 21 | 22 | Term 2 23 | ~ Definition 2a 24 | ~ Definition 2b 25 | """ 26 | ) 27 | ) 28 | data_regression.check([t.as_dict() for t in tokens]) 29 | 30 | 31 | @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) 32 | def test_all(line, title, input, expected): 33 | md = MarkdownIt("commonmark").use(deflist_plugin) 34 | if "DISABLE-CODEBLOCKS" in title: 35 | md.disable("code") 36 | md.options["xhtmlOut"] = False 37 | text = md.render(input) 38 | print(text) 39 | assert text.rstrip() == expected.rstrip() 40 | -------------------------------------------------------------------------------- /tests/test_deflist/test_plugin_parse.yml: -------------------------------------------------------------------------------- 1 | - attrs: null 2 | block: true 3 | children: null 4 | content: '' 5 | hidden: false 6 | info: '' 7 | level: 0 8 | map: 9 | - 0 10 | - 7 11 | markup: '' 12 | meta: {} 13 | nesting: 1 14 | tag: dl 15 | type: dl_open 16 | - attrs: null 17 | block: true 18 | children: null 19 | content: '' 20 | hidden: false 21 | info: '' 22 | level: 1 23 | map: 24 | - 0 25 | - 0 26 | markup: '' 27 | meta: {} 28 | nesting: 1 29 | tag: dt 30 | type: dt_open 31 | - attrs: null 32 | block: true 33 | children: 34 | - attrs: null 35 | block: false 36 | children: null 37 | content: Term 1 38 | hidden: false 39 | info: '' 40 | level: 0 41 | map: null 42 | markup: '' 43 | meta: {} 44 | nesting: 0 45 | tag: '' 46 | type: text 47 | content: Term 1 48 | hidden: false 49 | info: '' 50 | level: 2 51 | map: 52 | - 0 53 | - 0 54 | markup: '' 55 | meta: {} 56 | nesting: 0 57 | tag: '' 58 | type: inline 59 | - attrs: null 60 | block: true 61 | children: null 62 | content: '' 63 | hidden: false 64 | info: '' 65 | level: 1 66 | map: null 67 | markup: '' 68 | meta: {} 69 | nesting: -1 70 | tag: dt 71 | type: dt_close 72 | - attrs: null 73 | block: true 74 | children: null 75 | content: '' 76 | hidden: false 77 | info: '' 78 | level: 1 79 | map: 80 | - 2 81 | - 4 82 | markup: '' 83 | meta: {} 84 | nesting: 1 85 | tag: dd 86 | type: dd_open 87 | - attrs: null 88 | block: true 89 | children: null 90 | content: '' 91 | hidden: true 92 | info: '' 93 | level: 2 94 | map: 95 | - 2 96 | - 3 97 | markup: '' 98 | meta: {} 99 | nesting: 1 100 | tag: p 101 | type: paragraph_open 102 | - attrs: null 103 | block: true 104 | children: 105 | - attrs: null 106 | block: false 107 | children: null 108 | content: Definition 1 109 | hidden: false 110 | info: '' 111 | level: 0 112 | map: null 113 | markup: '' 114 | meta: {} 115 | nesting: 0 116 | tag: '' 117 | type: text 118 | content: Definition 1 119 | hidden: false 120 | info: '' 121 | level: 3 122 | map: 123 | - 2 124 | - 3 125 | markup: '' 126 | meta: {} 127 | nesting: 0 128 | tag: '' 129 | type: inline 130 | - attrs: null 131 | block: true 132 | children: null 133 | content: '' 134 | hidden: true 135 | info: '' 136 | level: 2 137 | map: null 138 | markup: '' 139 | meta: {} 140 | nesting: -1 141 | tag: p 142 | type: paragraph_close 143 | - attrs: null 144 | block: true 145 | children: null 146 | content: '' 147 | hidden: false 148 | info: '' 149 | level: 1 150 | map: null 151 | markup: '' 152 | meta: {} 153 | nesting: -1 154 | tag: dd 155 | type: dd_close 156 | - attrs: null 157 | block: true 158 | children: null 159 | content: '' 160 | hidden: false 161 | info: '' 162 | level: 1 163 | map: 164 | - 4 165 | - 4 166 | markup: '' 167 | meta: {} 168 | nesting: 1 169 | tag: dt 170 | type: dt_open 171 | - attrs: null 172 | block: true 173 | children: 174 | - attrs: null 175 | block: false 176 | children: null 177 | content: Term 2 178 | hidden: false 179 | info: '' 180 | level: 0 181 | map: null 182 | markup: '' 183 | meta: {} 184 | nesting: 0 185 | tag: '' 186 | type: text 187 | content: Term 2 188 | hidden: false 189 | info: '' 190 | level: 2 191 | map: 192 | - 4 193 | - 4 194 | markup: '' 195 | meta: {} 196 | nesting: 0 197 | tag: '' 198 | type: inline 199 | - attrs: null 200 | block: true 201 | children: null 202 | content: '' 203 | hidden: false 204 | info: '' 205 | level: 1 206 | map: null 207 | markup: '' 208 | meta: {} 209 | nesting: -1 210 | tag: dt 211 | type: dt_close 212 | - attrs: null 213 | block: true 214 | children: null 215 | content: '' 216 | hidden: false 217 | info: '' 218 | level: 1 219 | map: 220 | - 4 221 | - 6 222 | markup: '' 223 | meta: {} 224 | nesting: 1 225 | tag: dd 226 | type: dd_open 227 | - attrs: null 228 | block: true 229 | children: null 230 | content: '' 231 | hidden: true 232 | info: '' 233 | level: 2 234 | map: 235 | - 5 236 | - 6 237 | markup: '' 238 | meta: {} 239 | nesting: 1 240 | tag: p 241 | type: paragraph_open 242 | - attrs: null 243 | block: true 244 | children: 245 | - attrs: null 246 | block: false 247 | children: null 248 | content: Definition 2a 249 | hidden: false 250 | info: '' 251 | level: 0 252 | map: null 253 | markup: '' 254 | meta: {} 255 | nesting: 0 256 | tag: '' 257 | type: text 258 | content: Definition 2a 259 | hidden: false 260 | info: '' 261 | level: 3 262 | map: 263 | - 5 264 | - 6 265 | markup: '' 266 | meta: {} 267 | nesting: 0 268 | tag: '' 269 | type: inline 270 | - attrs: null 271 | block: true 272 | children: null 273 | content: '' 274 | hidden: true 275 | info: '' 276 | level: 2 277 | map: null 278 | markup: '' 279 | meta: {} 280 | nesting: -1 281 | tag: p 282 | type: paragraph_close 283 | - attrs: null 284 | block: true 285 | children: null 286 | content: '' 287 | hidden: false 288 | info: '' 289 | level: 1 290 | map: null 291 | markup: '' 292 | meta: {} 293 | nesting: -1 294 | tag: dd 295 | type: dd_close 296 | - attrs: null 297 | block: true 298 | children: null 299 | content: '' 300 | hidden: false 301 | info: '' 302 | level: 1 303 | map: 304 | - 6 305 | - 7 306 | markup: '' 307 | meta: {} 308 | nesting: 1 309 | tag: dd 310 | type: dd_open 311 | - attrs: null 312 | block: true 313 | children: null 314 | content: '' 315 | hidden: true 316 | info: '' 317 | level: 2 318 | map: 319 | - 6 320 | - 7 321 | markup: '' 322 | meta: {} 323 | nesting: 1 324 | tag: p 325 | type: paragraph_open 326 | - attrs: null 327 | block: true 328 | children: 329 | - attrs: null 330 | block: false 331 | children: null 332 | content: Definition 2b 333 | hidden: false 334 | info: '' 335 | level: 0 336 | map: null 337 | markup: '' 338 | meta: {} 339 | nesting: 0 340 | tag: '' 341 | type: text 342 | content: Definition 2b 343 | hidden: false 344 | info: '' 345 | level: 3 346 | map: 347 | - 6 348 | - 7 349 | markup: '' 350 | meta: {} 351 | nesting: 0 352 | tag: '' 353 | type: inline 354 | - attrs: null 355 | block: true 356 | children: null 357 | content: '' 358 | hidden: true 359 | info: '' 360 | level: 2 361 | map: null 362 | markup: '' 363 | meta: {} 364 | nesting: -1 365 | tag: p 366 | type: paragraph_close 367 | - attrs: null 368 | block: true 369 | children: null 370 | content: '' 371 | hidden: false 372 | info: '' 373 | level: 1 374 | map: null 375 | markup: '' 376 | meta: {} 377 | nesting: -1 378 | tag: dd 379 | type: dd_close 380 | - attrs: null 381 | block: true 382 | children: null 383 | content: '' 384 | hidden: false 385 | info: '' 386 | level: 0 387 | map: null 388 | markup: '' 389 | meta: {} 390 | nesting: -1 391 | tag: dl 392 | type: dl_close 393 | -------------------------------------------------------------------------------- /tests/test_dollarmath.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.rules_block import StateBlock 6 | from markdown_it.rules_inline import StateInline 7 | from markdown_it.utils import read_fixture_file 8 | import pytest 9 | 10 | from mdit_py_plugins.dollarmath import dollarmath_plugin 11 | from mdit_py_plugins.dollarmath import index as main 12 | 13 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures") 14 | 15 | 16 | def test_inline_func(): 17 | inline_func = main.math_inline_dollar() 18 | 19 | md = MarkdownIt() 20 | src = r"$a=1$ $b=2$" 21 | tokens = [] 22 | state = StateInline(src, md, {}, tokens) 23 | inline_func(state, False) 24 | assert tokens[0].as_dict() == { 25 | "type": "math_inline", 26 | "tag": "math", 27 | "nesting": 0, 28 | "attrs": None, 29 | "map": None, 30 | "level": 0, 31 | "children": None, 32 | "content": "a=1", 33 | "markup": "$", 34 | "info": "", 35 | "meta": {}, 36 | "block": False, 37 | "hidden": False, 38 | } 39 | assert state.pos == 5 40 | 41 | 42 | def test_block_func(): 43 | block_func = main.math_block_dollar() 44 | md = MarkdownIt() 45 | src = r"$$\na=1\n\nc\nb=2$$ (abc)" 46 | tokens = [] 47 | state = StateBlock(src, md, {}, tokens) 48 | block_func(state, 0, 10, False) 49 | print(tokens[0].as_dict()) 50 | assert tokens[0].as_dict() == { 51 | "type": "math_block_label", 52 | "tag": "math", 53 | "nesting": 0, 54 | "attrs": None, 55 | "map": [0, 1], 56 | "level": 0, 57 | "children": None, 58 | "content": "\\na=1\\n\\nc\\nb=2", 59 | "markup": "$$", 60 | "info": "abc", 61 | "meta": {}, 62 | "block": True, 63 | "hidden": False, 64 | } 65 | 66 | 67 | def test_plugin_parse(data_regression): 68 | md = MarkdownIt().use(dollarmath_plugin) 69 | tokens = md.parse( 70 | dedent( 71 | """\ 72 | $$ 73 | a=1 74 | b=2 75 | $$ (abc) 76 | 77 | - ab $c=1$ d 78 | """ 79 | ) 80 | ) 81 | data_regression.check([t.as_dict() for t in tokens]) 82 | 83 | 84 | def test_custom_renderer(data_regression): 85 | md = MarkdownIt().use(dollarmath_plugin, renderer=lambda x, y: x) 86 | assert md.render("$x$").strip() == '

x

' 87 | 88 | 89 | @pytest.mark.parametrize( 90 | "line,title,input,expected", 91 | read_fixture_file(FIXTURE_PATH.joinpath("dollar_math.md")), 92 | ) 93 | def test_dollarmath_fixtures(line, title, input, expected): 94 | md = MarkdownIt("commonmark").use( 95 | dollarmath_plugin, 96 | allow_space=False, 97 | allow_digits=False, 98 | double_inline=True, 99 | allow_blank_lines=False, 100 | ) 101 | if "DISABLE-CODEBLOCKS" in title: 102 | md.disable("code") 103 | md.options.xhtmlOut = False 104 | text = md.render(input) 105 | print(text) 106 | assert text.rstrip() == expected.rstrip() 107 | -------------------------------------------------------------------------------- /tests/test_dollarmath/test_plugin_parse.yml: -------------------------------------------------------------------------------- 1 | - attrs: null 2 | block: true 3 | children: null 4 | content: ' 5 | 6 | a=1 7 | 8 | b=2 9 | 10 | ' 11 | hidden: false 12 | info: abc 13 | level: 0 14 | map: 15 | - 0 16 | - 4 17 | markup: $$ 18 | meta: {} 19 | nesting: 0 20 | tag: math 21 | type: math_block_label 22 | - attrs: null 23 | block: true 24 | children: null 25 | content: '' 26 | hidden: false 27 | info: '' 28 | level: 0 29 | map: 30 | - 5 31 | - 6 32 | markup: '-' 33 | meta: {} 34 | nesting: 1 35 | tag: ul 36 | type: bullet_list_open 37 | - attrs: null 38 | block: true 39 | children: null 40 | content: '' 41 | hidden: false 42 | info: '' 43 | level: 1 44 | map: 45 | - 5 46 | - 6 47 | markup: '-' 48 | meta: {} 49 | nesting: 1 50 | tag: li 51 | type: list_item_open 52 | - attrs: null 53 | block: true 54 | children: null 55 | content: '' 56 | hidden: true 57 | info: '' 58 | level: 2 59 | map: 60 | - 5 61 | - 6 62 | markup: '' 63 | meta: {} 64 | nesting: 1 65 | tag: p 66 | type: paragraph_open 67 | - attrs: null 68 | block: true 69 | children: 70 | - attrs: null 71 | block: false 72 | children: null 73 | content: 'ab ' 74 | hidden: false 75 | info: '' 76 | level: 0 77 | map: null 78 | markup: '' 79 | meta: {} 80 | nesting: 0 81 | tag: '' 82 | type: text 83 | - attrs: null 84 | block: false 85 | children: null 86 | content: c=1 87 | hidden: false 88 | info: '' 89 | level: 0 90 | map: null 91 | markup: $ 92 | meta: {} 93 | nesting: 0 94 | tag: math 95 | type: math_inline 96 | - attrs: null 97 | block: false 98 | children: null 99 | content: ' d' 100 | hidden: false 101 | info: '' 102 | level: 0 103 | map: null 104 | markup: '' 105 | meta: {} 106 | nesting: 0 107 | tag: '' 108 | type: text 109 | content: ab $c=1$ d 110 | hidden: false 111 | info: '' 112 | level: 3 113 | map: 114 | - 5 115 | - 6 116 | markup: '' 117 | meta: {} 118 | nesting: 0 119 | tag: '' 120 | type: inline 121 | - attrs: null 122 | block: true 123 | children: null 124 | content: '' 125 | hidden: true 126 | info: '' 127 | level: 2 128 | map: null 129 | markup: '' 130 | meta: {} 131 | nesting: -1 132 | tag: p 133 | type: paragraph_close 134 | - attrs: null 135 | block: true 136 | children: null 137 | content: '' 138 | hidden: false 139 | info: '' 140 | level: 1 141 | map: null 142 | markup: '-' 143 | meta: {} 144 | nesting: -1 145 | tag: li 146 | type: list_item_close 147 | - attrs: null 148 | block: true 149 | children: null 150 | content: '' 151 | hidden: false 152 | info: '' 153 | level: 0 154 | map: null 155 | markup: '-' 156 | meta: {} 157 | nesting: -1 158 | tag: ul 159 | type: bullet_list_close 160 | -------------------------------------------------------------------------------- /tests/test_field_list.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.field_list import fieldlist_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "field_list.md") 11 | 12 | 13 | def test_plugin_parse(data_regression): 14 | md = MarkdownIt().use(fieldlist_plugin) 15 | tokens = md.parse( 16 | dedent( 17 | """\ 18 | :abc: Content 19 | :def: Content 20 | """ 21 | ) 22 | ) 23 | data_regression.check([t.as_dict() for t in tokens]) 24 | 25 | 26 | fixtures = read_fixture_file(FIXTURE_PATH) 27 | 28 | 29 | @pytest.mark.parametrize( 30 | "line,title,input,expected", 31 | fixtures, 32 | ids=[f"{f[0]}-{f[1].replace(' ', '_')}" for f in fixtures], 33 | ) 34 | def test_all(line, title, input, expected): 35 | md = MarkdownIt("commonmark").use(fieldlist_plugin) 36 | if "DISABLE-CODEBLOCKS" in title: 37 | md.disable("code") 38 | md.options["xhtmlOut"] = False 39 | text = md.render(input) 40 | print(text) 41 | assert text.rstrip() == expected.rstrip() 42 | -------------------------------------------------------------------------------- /tests/test_field_list/test_plugin_parse.yml: -------------------------------------------------------------------------------- 1 | - attrs: 2 | - - class 3 | - field-list 4 | block: true 5 | children: null 6 | content: '' 7 | hidden: false 8 | info: '' 9 | level: 0 10 | map: 11 | - 0 12 | - 2 13 | markup: '' 14 | meta: {} 15 | nesting: 1 16 | tag: dl 17 | type: field_list_open 18 | - attrs: null 19 | block: true 20 | children: null 21 | content: '' 22 | hidden: false 23 | info: '' 24 | level: 1 25 | map: 26 | - 0 27 | - 0 28 | markup: '' 29 | meta: {} 30 | nesting: 1 31 | tag: dt 32 | type: fieldlist_name_open 33 | - attrs: null 34 | block: true 35 | children: 36 | - attrs: null 37 | block: false 38 | children: null 39 | content: abc 40 | hidden: false 41 | info: '' 42 | level: 0 43 | map: null 44 | markup: '' 45 | meta: {} 46 | nesting: 0 47 | tag: '' 48 | type: text 49 | content: abc 50 | hidden: false 51 | info: '' 52 | level: 2 53 | map: 54 | - 0 55 | - 0 56 | markup: '' 57 | meta: {} 58 | nesting: 0 59 | tag: '' 60 | type: inline 61 | - attrs: null 62 | block: true 63 | children: null 64 | content: '' 65 | hidden: false 66 | info: '' 67 | level: 1 68 | map: null 69 | markup: '' 70 | meta: {} 71 | nesting: -1 72 | tag: dt 73 | type: fieldlist_name_close 74 | - attrs: null 75 | block: true 76 | children: null 77 | content: '' 78 | hidden: false 79 | info: '' 80 | level: 1 81 | map: 82 | - 0 83 | - 1 84 | markup: '' 85 | meta: {} 86 | nesting: 1 87 | tag: dd 88 | type: fieldlist_body_open 89 | - attrs: null 90 | block: true 91 | children: null 92 | content: '' 93 | hidden: false 94 | info: '' 95 | level: 2 96 | map: 97 | - 0 98 | - 1 99 | markup: '' 100 | meta: {} 101 | nesting: 1 102 | tag: p 103 | type: paragraph_open 104 | - attrs: null 105 | block: true 106 | children: 107 | - attrs: null 108 | block: false 109 | children: null 110 | content: Content 111 | hidden: false 112 | info: '' 113 | level: 0 114 | map: null 115 | markup: '' 116 | meta: {} 117 | nesting: 0 118 | tag: '' 119 | type: text 120 | content: Content 121 | hidden: false 122 | info: '' 123 | level: 3 124 | map: 125 | - 0 126 | - 1 127 | markup: '' 128 | meta: {} 129 | nesting: 0 130 | tag: '' 131 | type: inline 132 | - attrs: null 133 | block: true 134 | children: null 135 | content: '' 136 | hidden: false 137 | info: '' 138 | level: 2 139 | map: null 140 | markup: '' 141 | meta: {} 142 | nesting: -1 143 | tag: p 144 | type: paragraph_close 145 | - attrs: null 146 | block: true 147 | children: null 148 | content: '' 149 | hidden: false 150 | info: '' 151 | level: 1 152 | map: null 153 | markup: '' 154 | meta: {} 155 | nesting: -1 156 | tag: dd 157 | type: fieldlist_body_close 158 | - attrs: null 159 | block: true 160 | children: null 161 | content: '' 162 | hidden: false 163 | info: '' 164 | level: 1 165 | map: 166 | - 1 167 | - 1 168 | markup: '' 169 | meta: {} 170 | nesting: 1 171 | tag: dt 172 | type: fieldlist_name_open 173 | - attrs: null 174 | block: true 175 | children: 176 | - attrs: null 177 | block: false 178 | children: null 179 | content: def 180 | hidden: false 181 | info: '' 182 | level: 0 183 | map: null 184 | markup: '' 185 | meta: {} 186 | nesting: 0 187 | tag: '' 188 | type: text 189 | content: def 190 | hidden: false 191 | info: '' 192 | level: 2 193 | map: 194 | - 1 195 | - 1 196 | markup: '' 197 | meta: {} 198 | nesting: 0 199 | tag: '' 200 | type: inline 201 | - attrs: null 202 | block: true 203 | children: null 204 | content: '' 205 | hidden: false 206 | info: '' 207 | level: 1 208 | map: null 209 | markup: '' 210 | meta: {} 211 | nesting: -1 212 | tag: dt 213 | type: fieldlist_name_close 214 | - attrs: null 215 | block: true 216 | children: null 217 | content: '' 218 | hidden: false 219 | info: '' 220 | level: 1 221 | map: 222 | - 1 223 | - 2 224 | markup: '' 225 | meta: {} 226 | nesting: 1 227 | tag: dd 228 | type: fieldlist_body_open 229 | - attrs: null 230 | block: true 231 | children: null 232 | content: '' 233 | hidden: false 234 | info: '' 235 | level: 2 236 | map: 237 | - 1 238 | - 2 239 | markup: '' 240 | meta: {} 241 | nesting: 1 242 | tag: p 243 | type: paragraph_open 244 | - attrs: null 245 | block: true 246 | children: 247 | - attrs: null 248 | block: false 249 | children: null 250 | content: Content 251 | hidden: false 252 | info: '' 253 | level: 0 254 | map: null 255 | markup: '' 256 | meta: {} 257 | nesting: 0 258 | tag: '' 259 | type: text 260 | content: Content 261 | hidden: false 262 | info: '' 263 | level: 3 264 | map: 265 | - 1 266 | - 2 267 | markup: '' 268 | meta: {} 269 | nesting: 0 270 | tag: '' 271 | type: inline 272 | - attrs: null 273 | block: true 274 | children: null 275 | content: '' 276 | hidden: false 277 | info: '' 278 | level: 2 279 | map: null 280 | markup: '' 281 | meta: {} 282 | nesting: -1 283 | tag: p 284 | type: paragraph_close 285 | - attrs: null 286 | block: true 287 | children: null 288 | content: '' 289 | hidden: false 290 | info: '' 291 | level: 1 292 | map: null 293 | markup: '' 294 | meta: {} 295 | nesting: -1 296 | tag: dd 297 | type: fieldlist_body_close 298 | - attrs: null 299 | block: true 300 | children: null 301 | content: '' 302 | hidden: false 303 | info: '' 304 | level: 0 305 | map: null 306 | markup: '' 307 | meta: {} 308 | nesting: -1 309 | tag: dl 310 | type: field_list_close 311 | -------------------------------------------------------------------------------- /tests/test_front_matter.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from markdown_it import MarkdownIt 4 | from markdown_it.token import Token 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.front_matter import front_matter_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "front_matter.md") 11 | 12 | 13 | @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) 14 | def test_all(line, title, input, expected): 15 | md = MarkdownIt("commonmark").use(front_matter_plugin) 16 | md.options["xhtmlOut"] = False 17 | text = md.render(input) 18 | print(text) 19 | assert text.rstrip() == expected.rstrip() 20 | 21 | 22 | def test_token(): 23 | md = MarkdownIt("commonmark").use(front_matter_plugin) 24 | tokens = md.parse("---\na: 1\n---") 25 | # print(tokens) 26 | assert tokens == [ 27 | Token( 28 | type="front_matter", 29 | tag="", 30 | nesting=0, 31 | attrs=None, 32 | map=[0, 3], 33 | level=0, 34 | children=None, 35 | content="a: 1", 36 | markup="---", 37 | info="", 38 | meta={}, 39 | block=True, 40 | hidden=True, 41 | ) 42 | ] 43 | 44 | 45 | def test_short_source(): 46 | md = MarkdownIt("commonmark").use(front_matter_plugin) 47 | 48 | # The code should not raise an IndexError. 49 | assert md.parse("-") 50 | -------------------------------------------------------------------------------- /tests/test_myst_block.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from markdown_it import MarkdownIt 4 | from markdown_it.token import Token 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.myst_blocks import myst_block_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "myst_block.md") 11 | 12 | 13 | @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) 14 | def test_all(line, title, input, expected): 15 | md = MarkdownIt("commonmark").use(myst_block_plugin) 16 | if "DISABLE-CODEBLOCKS" in title: 17 | md.disable("code") 18 | md.options["xhtmlOut"] = False 19 | text = md.render(input) 20 | print(text) 21 | assert text.rstrip() == expected.rstrip() 22 | 23 | 24 | def test_block_token(): 25 | md = MarkdownIt("commonmark").use(myst_block_plugin) 26 | tokens = md.parse("+++") 27 | expected_token = Token( 28 | type="myst_block_break", 29 | tag="hr", 30 | nesting=0, 31 | map=[0, 1], 32 | level=0, 33 | children=None, 34 | content="", 35 | markup="+++", 36 | info="", 37 | meta={}, 38 | block=True, 39 | hidden=False, 40 | ) 41 | expected_token.attrSet("class", "myst-block") 42 | assert tokens == [expected_token] 43 | 44 | tokens = md.parse("\n+ + + abc") 45 | expected_token = Token( 46 | type="myst_block_break", 47 | tag="hr", 48 | nesting=0, 49 | map=[1, 2], 50 | level=0, 51 | children=None, 52 | content="abc", 53 | markup="+++", 54 | info="", 55 | meta={}, 56 | block=True, 57 | hidden=False, 58 | ) 59 | expected_token.attrSet("class", "myst-block") 60 | assert tokens == [expected_token] 61 | 62 | 63 | def test_comment_token(): 64 | md = MarkdownIt("commonmark").use(myst_block_plugin) 65 | tokens = md.parse("\n\n% abc \n%def") 66 | expected_token = Token( 67 | type="myst_line_comment", 68 | tag="", 69 | nesting=0, 70 | map=[2, 4], 71 | level=0, 72 | children=None, 73 | content=" abc\ndef", 74 | markup="%", 75 | info="", 76 | meta={}, 77 | block=True, 78 | hidden=False, 79 | ) 80 | expected_token.attrSet("class", "myst-line-comment") 81 | assert tokens == [expected_token] 82 | -------------------------------------------------------------------------------- /tests/test_myst_role.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from markdown_it import MarkdownIt 4 | from markdown_it.token import Token 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.myst_role import myst_role_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "myst_role.md") 11 | 12 | 13 | def test_basic(): 14 | md = MarkdownIt().use(myst_role_plugin) 15 | src = "{abc}``` a ```" 16 | tokens = md.parse(src) 17 | print(tokens) 18 | assert tokens == [ 19 | Token( 20 | type="paragraph_open", 21 | tag="p", 22 | nesting=1, 23 | attrs=None, 24 | map=[0, 1], 25 | level=0, 26 | children=None, 27 | content="", 28 | markup="", 29 | info="", 30 | meta={}, 31 | block=True, 32 | hidden=False, 33 | ), 34 | Token( 35 | type="inline", 36 | tag="", 37 | nesting=0, 38 | attrs=None, 39 | map=[0, 1], 40 | level=1, 41 | children=[ 42 | Token( 43 | type="myst_role", 44 | tag="", 45 | nesting=0, 46 | attrs=None, 47 | map=None, 48 | level=0, 49 | children=None, 50 | content=" a ", 51 | markup="", 52 | info="", 53 | meta={"name": "abc"}, 54 | block=False, 55 | hidden=False, 56 | ) 57 | ], 58 | content="{abc}``` a ```", 59 | markup="", 60 | info="", 61 | meta={}, 62 | block=True, 63 | hidden=False, 64 | ), 65 | Token( 66 | type="paragraph_close", 67 | tag="p", 68 | nesting=-1, 69 | attrs=None, 70 | map=None, 71 | level=0, 72 | children=None, 73 | content="", 74 | markup="", 75 | info="", 76 | meta={}, 77 | block=True, 78 | hidden=False, 79 | ), 80 | ] 81 | 82 | 83 | @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) 84 | def test_all(line, title, input, expected): 85 | md = MarkdownIt("commonmark").use(myst_role_plugin) 86 | md.options["xhtmlOut"] = False 87 | text = md.render(input) 88 | print(text) 89 | assert text.rstrip() == expected.rstrip() 90 | -------------------------------------------------------------------------------- /tests/test_substitution.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | # from markdown_it.token import Token 9 | from mdit_py_plugins.substitution import substitution_plugin 10 | 11 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "substitution.md") 12 | 13 | 14 | @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) 15 | def test_fixtures(line, title, input, expected): 16 | md = MarkdownIt("commonmark").enable("table").use(substitution_plugin) 17 | text = md.render(input) 18 | print(text) 19 | assert text.rstrip() == expected.rstrip() 20 | 21 | 22 | def test_tokens(data_regression): 23 | md = MarkdownIt().use(substitution_plugin) 24 | tokens = md.parse( 25 | dedent( 26 | """\ 27 | {{ block }} 28 | 29 | a {{ inline }} b 30 | """ 31 | ) 32 | ) 33 | data_regression.check([t.as_dict() for t in tokens]) 34 | -------------------------------------------------------------------------------- /tests/test_substitution/test_tokens.yml: -------------------------------------------------------------------------------- 1 | - attrs: 2 | - - class 3 | - substitution 4 | - - text 5 | - block 6 | block: true 7 | children: null 8 | content: block 9 | hidden: false 10 | info: '' 11 | level: 0 12 | map: 13 | - 0 14 | - 1 15 | markup: '{}' 16 | meta: {} 17 | nesting: 0 18 | tag: div 19 | type: substitution_block 20 | - attrs: null 21 | block: true 22 | children: null 23 | content: '' 24 | hidden: false 25 | info: '' 26 | level: 0 27 | map: 28 | - 2 29 | - 3 30 | markup: '' 31 | meta: {} 32 | nesting: 1 33 | tag: p 34 | type: paragraph_open 35 | - attrs: null 36 | block: true 37 | children: 38 | - attrs: null 39 | block: false 40 | children: null 41 | content: 'a ' 42 | hidden: false 43 | info: '' 44 | level: 0 45 | map: null 46 | markup: '' 47 | meta: {} 48 | nesting: 0 49 | tag: '' 50 | type: text 51 | - attrs: 52 | - - class 53 | - substitution 54 | - - text 55 | - inline 56 | block: false 57 | children: null 58 | content: inline 59 | hidden: false 60 | info: '' 61 | level: 0 62 | map: null 63 | markup: '{}' 64 | meta: {} 65 | nesting: 0 66 | tag: span 67 | type: substitution_inline 68 | - attrs: null 69 | block: false 70 | children: null 71 | content: ' b' 72 | hidden: false 73 | info: '' 74 | level: 0 75 | map: null 76 | markup: '' 77 | meta: {} 78 | nesting: 0 79 | tag: '' 80 | type: text 81 | content: a {{ inline }} b 82 | hidden: false 83 | info: '' 84 | level: 1 85 | map: 86 | - 2 87 | - 3 88 | markup: '' 89 | meta: {} 90 | nesting: 0 91 | tag: '' 92 | type: inline 93 | - attrs: null 94 | block: true 95 | children: null 96 | content: '' 97 | hidden: false 98 | info: '' 99 | level: 0 100 | map: null 101 | markup: '' 102 | meta: {} 103 | nesting: -1 104 | tag: p 105 | type: paragraph_close 106 | -------------------------------------------------------------------------------- /tests/test_tasklists.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.tasklists import tasklists_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "tasklists.md") 11 | 12 | 13 | def test_plugin_parse(data_regression): 14 | md = MarkdownIt().use(tasklists_plugin) 15 | tokens = md.parse( 16 | dedent( 17 | """\ 18 | * [ ] Task incomplete 19 | * [x] Task complete 20 | * [ ] Indented task incomplete 21 | * [x] Indented task complete 22 | """ 23 | ) 24 | ) 25 | data_regression.check([t.as_dict() for t in tokens]) 26 | 27 | 28 | @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) 29 | def test_all(line, title, input, expected): 30 | md = MarkdownIt("commonmark").use(tasklists_plugin) 31 | md.options["xhtmlOut"] = False 32 | text = md.render(input) 33 | print(text) 34 | assert text.rstrip() == expected.rstrip() 35 | -------------------------------------------------------------------------------- /tests/test_texmath.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from textwrap import dedent 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.rules_block import StateBlock 6 | from markdown_it.rules_inline import StateInline 7 | from markdown_it.utils import read_fixture_file 8 | import pytest 9 | 10 | from mdit_py_plugins.texmath import index as main 11 | from mdit_py_plugins.texmath import texmath_plugin 12 | 13 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures") 14 | 15 | 16 | def test_inline_func(): 17 | inline_func = main.make_inline_func(main.rules["dollars"]["inline"][0]) 18 | 19 | md = MarkdownIt() 20 | src = r"$a=1$ $b=2$" 21 | tokens = [] 22 | state = StateInline(src, md, {}, tokens) 23 | inline_func(state, False) 24 | assert tokens[0].as_dict() == { 25 | "type": "math_inline", 26 | "tag": "math", 27 | "nesting": 0, 28 | "attrs": None, 29 | "map": None, 30 | "level": 0, 31 | "children": None, 32 | "content": "a=1", 33 | "markup": "$", 34 | "info": "", 35 | "meta": {}, 36 | "block": False, 37 | "hidden": False, 38 | } 39 | assert state.pos == 5 40 | 41 | 42 | def test_block_func(): 43 | block_func = main.make_block_func(main.rules["dollars"]["block"][0]) 44 | md = MarkdownIt() 45 | src = r"$$\na=1\n\nc\nb=2$$ (abc)" 46 | tokens = [] 47 | state = StateBlock(src, md, {}, tokens) 48 | block_func(state, 0, 10, False) 49 | assert tokens[0].as_dict() == { 50 | "type": "math_block_eqno", 51 | "tag": "math", 52 | "nesting": 0, 53 | "attrs": None, 54 | "map": None, 55 | "level": 0, 56 | "children": None, 57 | "content": "\\na=1\\n\\nc\\nb=2", 58 | "markup": "$$", 59 | "info": "abc", 60 | "meta": {}, 61 | "block": True, 62 | "hidden": False, 63 | } 64 | 65 | 66 | def test_plugin_parse(data_regression): 67 | md = MarkdownIt().use(texmath_plugin) 68 | tokens = md.parse( 69 | dedent( 70 | """\ 71 | $$ 72 | a=1 73 | b=2 74 | $$ (abc) 75 | 76 | - ab $c=1$ d 77 | """ 78 | ) 79 | ) 80 | data_regression.check([t.as_dict() for t in tokens]) 81 | 82 | 83 | @pytest.mark.parametrize( 84 | "line,title,input,expected", 85 | read_fixture_file(FIXTURE_PATH.joinpath("texmath_dollar.md")), 86 | ) 87 | def test_dollar_fixtures(line, title, input, expected): 88 | md = MarkdownIt("commonmark").use(texmath_plugin) 89 | if "DISABLE-CODEBLOCKS" in title: 90 | md.disable("code") 91 | md.options["xhtmlOut"] = False 92 | text = md.render(input) 93 | print(text) 94 | assert text.rstrip() == expected.rstrip() 95 | 96 | 97 | @pytest.mark.parametrize( 98 | "line,title,input,expected", 99 | read_fixture_file(FIXTURE_PATH.joinpath("texmath_bracket.md")), 100 | ) 101 | def test_bracket_fixtures(line, title, input, expected): 102 | md = MarkdownIt("commonmark").use(texmath_plugin, delimiters="brackets") 103 | if "DISABLE-CODEBLOCKS" in title: 104 | md.disable("code") 105 | md.options["xhtmlOut"] = False 106 | text = md.render(input) 107 | print(text) 108 | assert text.rstrip() == expected.rstrip() 109 | -------------------------------------------------------------------------------- /tests/test_texmath/test_plugin_parse.yml: -------------------------------------------------------------------------------- 1 | - attrs: null 2 | block: true 3 | children: null 4 | content: ' 5 | 6 | a=1 7 | 8 | b=2 9 | 10 | ' 11 | hidden: false 12 | info: abc 13 | level: 0 14 | map: null 15 | markup: $$ 16 | meta: {} 17 | nesting: 0 18 | tag: math 19 | type: math_block_eqno 20 | - attrs: null 21 | block: true 22 | children: null 23 | content: '' 24 | hidden: false 25 | info: '' 26 | level: 0 27 | map: 28 | - 5 29 | - 6 30 | markup: '-' 31 | meta: {} 32 | nesting: 1 33 | tag: ul 34 | type: bullet_list_open 35 | - attrs: null 36 | block: true 37 | children: null 38 | content: '' 39 | hidden: false 40 | info: '' 41 | level: 1 42 | map: 43 | - 5 44 | - 6 45 | markup: '-' 46 | meta: {} 47 | nesting: 1 48 | tag: li 49 | type: list_item_open 50 | - attrs: null 51 | block: true 52 | children: null 53 | content: '' 54 | hidden: true 55 | info: '' 56 | level: 2 57 | map: 58 | - 5 59 | - 6 60 | markup: '' 61 | meta: {} 62 | nesting: 1 63 | tag: p 64 | type: paragraph_open 65 | - attrs: null 66 | block: true 67 | children: 68 | - attrs: null 69 | block: false 70 | children: null 71 | content: 'ab ' 72 | hidden: false 73 | info: '' 74 | level: 0 75 | map: null 76 | markup: '' 77 | meta: {} 78 | nesting: 0 79 | tag: '' 80 | type: text 81 | - attrs: null 82 | block: false 83 | children: null 84 | content: c=1 85 | hidden: false 86 | info: '' 87 | level: 0 88 | map: null 89 | markup: $ 90 | meta: {} 91 | nesting: 0 92 | tag: math 93 | type: math_inline 94 | - attrs: null 95 | block: false 96 | children: null 97 | content: ' d' 98 | hidden: false 99 | info: '' 100 | level: 0 101 | map: null 102 | markup: '' 103 | meta: {} 104 | nesting: 0 105 | tag: '' 106 | type: text 107 | content: ab $c=1$ d 108 | hidden: false 109 | info: '' 110 | level: 3 111 | map: 112 | - 5 113 | - 6 114 | markup: '' 115 | meta: {} 116 | nesting: 0 117 | tag: '' 118 | type: inline 119 | - attrs: null 120 | block: true 121 | children: null 122 | content: '' 123 | hidden: true 124 | info: '' 125 | level: 2 126 | map: null 127 | markup: '' 128 | meta: {} 129 | nesting: -1 130 | tag: p 131 | type: paragraph_close 132 | - attrs: null 133 | block: true 134 | children: null 135 | content: '' 136 | hidden: false 137 | info: '' 138 | level: 1 139 | map: null 140 | markup: '-' 141 | meta: {} 142 | nesting: -1 143 | tag: li 144 | type: list_item_close 145 | - attrs: null 146 | block: true 147 | children: null 148 | content: '' 149 | hidden: false 150 | info: '' 151 | level: 0 152 | map: null 153 | markup: '-' 154 | meta: {} 155 | nesting: -1 156 | tag: ul 157 | type: bullet_list_close 158 | -------------------------------------------------------------------------------- /tests/test_wordcount.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from markdown_it import MarkdownIt 5 | from markdown_it.utils import read_fixture_file 6 | import pytest 7 | 8 | from mdit_py_plugins.wordcount import wordcount_plugin 9 | 10 | FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures", "wordcount.md") 11 | 12 | 13 | @pytest.mark.parametrize("line,title,input,expected", read_fixture_file(FIXTURE_PATH)) 14 | def test_all(line, title, input, expected): 15 | md = MarkdownIt("commonmark").use(wordcount_plugin, store_text="(text)" in title) 16 | env = {} 17 | md.render(input, env) 18 | data = json.dumps(env["wordcount"], indent=2, sort_keys=True) 19 | try: 20 | assert data.strip() == expected.strip() 21 | except AssertionError: 22 | print(data) 23 | raise 24 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # To use tox, see https://tox.readthedocs.io 2 | # Simply pip or conda install tox 3 | # If you use conda, you may also want to install tox-conda 4 | # then run `tox` or `tox -- {pytest args}` 5 | # run in parallel using `tox -p` 6 | [tox] 7 | envlist = py38 8 | 9 | [testenv] 10 | usedevelop = true 11 | 12 | [testenv:py{38,39,310,311,312}] 13 | extras = testing 14 | commands = pytest {posargs} 15 | 16 | [testenv:docs-{update,clean}] 17 | extras = rtd 18 | whitelist_externals = rm 19 | commands = 20 | clean: rm -rf docs/_build 21 | sphinx-build -nW --keep-going -b {posargs:html} docs/ docs/_build/{posargs:html} 22 | --------------------------------------------------------------------------------