pax_global_header00006660000000000000000000000064145406540440014520gustar00rootroot0000000000000052 comment=199651fafc98fa3ed8fb7f91a8877f776ecbe9c7 mdformat-0.7.17/000077500000000000000000000000001454065404400134255ustar00rootroot00000000000000mdformat-0.7.17/.bumpversion.cfg000066400000000000000000000015711454065404400165410ustar00rootroot00000000000000[bumpversion] commit = True tag = True tag_name = {new_version} current_version = 0.7.17 [bumpversion:file:pyproject.toml] search = version = "{current_version}" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT replace = version = "{new_version}" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT [bumpversion:file:src/mdformat/__init__.py] search = __version__ = "{current_version}" replace = __version__ = "{new_version}" [bumpversion:file:docs/users/installation_and_usage.md] search = rev: {current_version} # Use the ref you want to point at replace = rev: {new_version} # Use the ref you want to point at [bumpversion:file:docs/conf.py] search = release = '{current_version}' # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT replace = release = '{new_version}' # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT mdformat-0.7.17/.flake8000066400000000000000000000002331454065404400145760ustar00rootroot00000000000000[flake8] max-line-length = 88 max-complexity = 10 # These checks violate PEP8 so let's ignore them extend-ignore = E203 extend-exclude = */site-packages/* mdformat-0.7.17/.gitattributes000066400000000000000000000001361454065404400163200ustar00rootroot00000000000000# Don't do text manipulations (line ending changes). Tests require # unchanged files. * -text mdformat-0.7.17/.github/000077500000000000000000000000001454065404400147655ustar00rootroot00000000000000mdformat-0.7.17/.github/workflows/000077500000000000000000000000001454065404400170225ustar00rootroot00000000000000mdformat-0.7.17/.github/workflows/tests.yaml000066400000000000000000000042321454065404400210510ustar00rootroot00000000000000name: Tests on: push: branches: [ master ] tags: [ '[0-9]+.[0-9]+.[0-9]+*' ] pull_request: branches: [ master ] jobs: linters: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v3 with: python-version: '3.8' - name: Installation (deps and package) run: | pip install . pre-commit mypy==0.910 -r tests/requirements.txt - name: run linters run: | mdformat --check docs/ README.md mypy src/ tests/ pre-commit run -a pre-commit try-repo . mdformat --files README.md tests: runs-on: ${{ matrix.os }} strategy: matrix: python-version: ['3.8', '3.9', '3.10', '3.11', '3.12-dev'] os: [ubuntu-latest, macos-latest, windows-latest] continue-on-error: ${{ matrix.python-version == '3.12-dev' }} steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Installation (deps and package) run: | pip install . -r tests/requirements.txt - name: Test with pytest run: | pytest --cov --cov-fail-under=100 - name: Report coverage if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' uses: codecov/codecov-action@v2 allgood: runs-on: ubuntu-latest needs: - linters - tests steps: - run: echo "Great success!" pypi-publish: # Only publish if all other jobs succeed needs: [ allgood ] if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v3 with: python-version: '3.8' - name: Install build and publish tools run: | pip install build twine - name: Build and check run: | rm -rf dist/ && python -m build twine check --strict dist/* - name: Publish run: | twine upload dist/* env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} mdformat-0.7.17/.gitignore000066400000000000000000000034561454065404400154250ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # IntelliJ .idea/ # VS Code .vscode/ mdformat-0.7.17/.pre-commit-config.yaml000066400000000000000000000026221454065404400177100ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: f71fa2c1f9cf5cb705f73dffe4b21f7c61470ba9 # frozen: v4.4.0 hooks: - id: check-yaml - id: check-toml - repo: https://github.com/pre-commit/pygrep-hooks rev: 3a6eb0fadf60b3cccfd80bad9dbb6fae7e47b316 # frozen: v1.10.0 hooks: - id: python-use-type-annotations - id: python-check-blanket-noqa - id: python-check-blanket-type-ignore - repo: https://github.com/asottile/yesqa rev: f2ae90cf9e1661ade79d0881186ce4fd7ba6ee79 # frozen: v1.5.0 hooks: - id: yesqa additional_dependencies: - flake8-bugbear - flake8-builtins - flake8-comprehensions - repo: https://github.com/MarcoGorelli/absolufy-imports rev: 1683a63d850e065495c05b771e4a20db010a58e7 # frozen: v0.3.1 hooks: - id: absolufy-imports - repo: https://github.com/PyCQA/isort rev: dbf82f2dd09ae41d9355bcd7ab69187a19e6bf2f # frozen: 5.12.0 hooks: - id: isort - repo: https://github.com/psf/black rev: 193ee766ca496871f93621d6b58d57a6564ff81b # frozen: 23.7.0 hooks: - id: black - repo: https://github.com/myint/docformatter rev: dfefe062799848234b4cd60b04aa633c0608025e # frozen: v1.7.5 hooks: - id: docformatter - repo: https://github.com/PyCQA/flake8 rev: 10f4af6dbcf93456ba7df762278ae61ba3120dc6 # frozen: 6.1.0 hooks: - id: flake8 additional_dependencies: - flake8-bugbear - flake8-builtins - flake8-comprehensions mdformat-0.7.17/.pre-commit-hooks.yaml000066400000000000000000000002701454065404400175630ustar00rootroot00000000000000- id: mdformat name: mdformat description: "CommonMark compliant Markdown formatter" entry: mdformat language: python types: [markdown] minimum_pre_commit_version: '1.0.0' mdformat-0.7.17/.readthedocs.yaml000066400000000000000000000006311454065404400166540ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: fail_on_warning: true # Optionally set the version of Python and requirements required to build your docs python: version: '3.8' install: - requirements: docs/requirements.txt mdformat-0.7.17/LICENSE000066400000000000000000000020601454065404400144300ustar00rootroot00000000000000MIT License Copyright (c) 2021 Taneli Hukkinen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mdformat-0.7.17/MANIFEST.in000066400000000000000000000000361454065404400151620ustar00rootroot00000000000000include src/mdformat/py.typed mdformat-0.7.17/README.md000066400000000000000000000142201454065404400147030ustar00rootroot00000000000000
[![Documentation Status](https://readthedocs.org/projects/mdformat/badge/?version=latest)](https://mdformat.readthedocs.io/en/latest/?badge=latest) [![Build Status](https://github.com/executablebooks/mdformat/workflows/Tests/badge.svg?branch=master)](https://github.com/executablebooks/mdformat/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush) [![codecov.io](https://codecov.io/gh/executablebooks/mdformat/branch/master/graph/badge.svg)](https://codecov.io/gh/executablebooks/mdformat) [![PyPI version](https://img.shields.io/pypi/v/mdformat)](https://pypi.org/project/mdformat) # ![mdformat](https://raw.githubusercontent.com/executablebooks/mdformat/master/docs/_static/logo.svg) > CommonMark compliant Markdown formatter
Mdformat is an opinionated Markdown formatter that can be used to enforce a consistent style in Markdown files. Mdformat is a Unix-style command-line tool as well as a Python library. Find out more in the [docs](https://mdformat.readthedocs.io). ## Installing Install with [CommonMark](https://spec.commonmark.org/current/) support: ```bash pip install mdformat ``` Install with [GitHub Flavored Markdown (GFM)](https://github.github.com/gfm/) support: ```bash pip install mdformat-gfm ``` Note that GitHub's Markdown renderer supports syntax extensions not included in the GFM specification. For full GitHub support do: ```bash pip install mdformat-gfm mdformat-frontmatter mdformat-footnote ``` Install with [Markedly Structured Text (MyST)](https://myst-parser.readthedocs.io/en/latest/using/syntax.html) support: ```bash pip install mdformat-myst ``` ## Command line usage ### Format files Format files `README.md` and `CHANGELOG.md` in place ```bash mdformat README.md CHANGELOG.md ``` Format `.md` files in current working directory recursively ```bash mdformat . ``` Read Markdown from standard input until `EOF`. Write formatted Markdown to standard output. ```bash mdformat - ``` ### Check formatting ```bash mdformat --check README.md CHANGELOG.md ``` This will not apply any changes to the files. If a file is not properly formatted, the exit code will be non-zero. ### Options ```console foo@bar:~$ mdformat --help usage: mdformat [-h] [--check] [--version] [--number] [--wrap {keep,no,INTEGER}] [--end-of-line {lf,crlf,keep}] [paths ...] CommonMark compliant Markdown formatter positional arguments: paths files to format options: -h, --help show this help message and exit --check do not apply changes to files --version show program's version number and exit --number apply consecutive numbering to ordered lists --wrap {keep,no,INTEGER} paragraph word wrap mode (default: keep) --end-of-line {lf,crlf,keep} output file line ending mode (default: lf) ``` ## Documentation This README merely provides a quickstart guide for the command line interface. For more information refer to the [documentation](https://mdformat.readthedocs.io). Here's a few pointers to get you started: - [Style guide](https://mdformat.readthedocs.io/en/stable/users/style.html) - [Python API usage](https://mdformat.readthedocs.io/en/stable/users/installation_and_usage.html#python-api-usage) - [Usage as a pre-commit hook](https://mdformat.readthedocs.io/en/stable/users/installation_and_usage.html#usage-as-a-pre-commit-hook) - [Plugin usage](https://mdformat.readthedocs.io/en/stable/users/plugins.html) - [Plugin development guide](https://mdformat.readthedocs.io/en/stable/contributors/contributing.html) - [List of code block formatter plugins](https://mdformat.readthedocs.io/en/stable/users/plugins.html#existing-plugins) - [List of parser extension plugins](https://mdformat.readthedocs.io/en/stable/users/plugins.html#id1) - [Changelog](https://mdformat.readthedocs.io/en/stable/users/changelog.html) ## Frequently Asked Questions ### Why not use [Prettier](https://github.com/prettier/prettier) instead? Mdformat is pure Python code! Python is pre-installed on macOS and virtually any Linux distribution, meaning that typically little to no additional installations are required to run mdformat. This argument also holds true when using together with [pre-commit](https://github.com/pre-commit/pre-commit) (also Python). Prettier on the other hand requires Node.js/npm. Prettier suffers from [numerous](https://github.com/prettier/prettier/issues?q=is%3Aopen+label%3Alang%3Amarkdown+label%3Atype%3Abug+) bugs, many of which cause changes in Markdown AST and rendered HTML. Many of these bugs are a consequence of using [`remark-parse`](https://github.com/remarkjs/remark/tree/main/packages/remark-parse) v8.x as Markdown parser which, according to the author themselves, is [inferior to markdown-it](https://github.com/remarkjs/remark/issues/75#issuecomment-143532326) used by mdformat. `remark-parse` v9.x is advertised as CommonMark compliant and presumably would fix many of the issues, but is not used by Prettier (v2.4.0) yet. Prettier (v2.4.0), being able to format many languages other than Markdown, is a large package with 65 direct dependencies (mdformat only has one in Python 3.11+). This can be a disadvantage in many environments, one example being size optimized Docker images. Mdformat's parser extension plugin API allows not only customization of the Markdown specification in use, but also advanced features like [automatic table of contents generation](https://github.com/hukkin/mdformat-toc). Also provided is a code formatter plugin API enabling integration of embedded code formatting for any programming language. ### What's wrong with the mdformat logo? It renders incorrectly and is just terrible in general. Nope, the logo is actually pretty great – you're terrible. The logo is more a piece of art than a logo anyways, depicting the horrors of poorly formatted text documents. I made it myself! That said, if you have any graphic design skills and want to contribute a revised version, a PR is more than welcome 😄. mdformat-0.7.17/docs/000077500000000000000000000000001454065404400143555ustar00rootroot00000000000000mdformat-0.7.17/docs/_static/000077500000000000000000000000001454065404400160035ustar00rootroot00000000000000mdformat-0.7.17/docs/_static/logo-150px.png000066400000000000000000000133401454065404400203250ustar00rootroot00000000000000PNG  IHDRr(vsBIT|d pHYsbtEXtSoftwarewww.inkscape.org<]IDATx{UL2HHLkAPDP$JxqOPOW\v> $0T2dH I&3uGGwOO}2uԭݧuwt@0R*dpnܕ5Vd4 ~'/ҹUɍ2|.,VZj'`iMt2UK)JXҊT3 +@)5N>#\Wi%Ê8zʩ3X|zT#ըw"Gv(7^U Uʍf.2UtG?>2oir,ne#)|ŔKp%YNóqoqr+Vn"z9 綸UN /c^+ϕ[Jutρ^' ;=ԫ1T9ɨb,W8kN7uvʩW5bD\5ڌ]i> J킇eT0@RR+2#+ MLszU84P`ktQĚQ8xGM GgSjمQ *y2i(#2; G=zU\xeP#@ȡǗQO`XEv|$j{/ H^^-'`25b k]y6Semd.*b#Hۯdg[2ڨ)^#A=gVT%ʩH"0AJW+,H^SFF a W.7"߭D kȈ]2"kL{&5_;/pYync֔SJ0o|)TʫY kYfˬZY)Y;}bh )65KLkVZҊJ6^f|Ӊ^7t&UZNZufY~u-u:e,7/8j}.2G7'ԱhXmԫU/n` pܕ<< 348y 1]{UX\l-Y41p`$f?º`P8j[lYeX o[JQ`LqVoX䕕Bty<$0"2M=nb8(j>R~NLv +]ǥn}e/KVYx $F B"z1[)~ &P#w{,c$P]^n $E`Y8d)f;:+o*|*7OAjO]'ְ;F2U +WDG>uȫ +UeXyح\Ju#Mm@Fa•w=@:uFar>JB022IZDJ 5x/̐71h-;);Ž>dUk1Qh0,(,XVH?.iFa)ݬgV@4> | /UQaX,oc$Dt+jM"7S@qwC: FmRmGTp1j kPU|iS/,ؙLJ+Q Ta\Kbsv%ocgp+\RUc6)eՠn7"2ebK{dOb=(SգUeXD~Yep{>&gX ȡ€!0V@I +$PV^0U'|Nv u7ɷ2DZx?‰(&!(o`G;\{[EDxF!JVc5/j$'w.ӳ<ߍtGH 5IZ^L+Ƚb=I19T8K}$]EFz]3A=ᨲ'F hóPWj ~|JtM6S ץsZc*TzRs.8GU~ىcwKG=gFVfׄXcX~'#p_fl-t؃ S!+| mUv^[PaI‹ tqtU|͉Y%7?a!ꀓEXq4gÅB%B1Yʗ]|keF(⾾ZyX>Ò9czYTd} Mw `8/+,ZfĞVXf;:A.C3hdKON"?:;{#t%v&>˯gt]SF}7k(8Eԛt.@Ѱ'C4gJˇ['Փ;ڽfDD])mh&xsu$8Gt#]Cv"\ 7P]yJ,z}A|ɒ =hDX]1cAp5TaMmNr#Yx\L T{1$,󅷉ݎrW:;}oB/!=mרptbv`aw?ڣ2÷Tr$Zg& hW͜ +VBK wYi7ie׫=pp<N!I3>XWr |pj 14S:M AM>Q˭Rf3In_]C>ݕYqQr߸N7rf{3o3}\"Ot=F]^!w8~c-"i*9! .wRKF=G$<Z+o;gߥopU!غqu܃1jUڰ6դ 0K~uVf?l-(lg&hd\)Z#nnDsVbBdS8mp1b?>}L V*=25ٴztmk-Ԙ".۩wһx#h㱊 a[-`"Tk& iYlVxZ8 Z7T`jh aGOUʥYe;,C(a)ܗHpEM/^pL} NKlt>JDm'%5?JS"<^3ߴ-q(1M5p*W+Iqgxٕ3]-\BvXBz .z@,7.ǀ3tY$‚$ݧ,ѐ@MRDQjt H0=I9ţ~,.[`³`e2,5̥n]\LcaQhweEٞe*_x|jDy3ZQGpP+F .hsekѧ%v5w]vqtݖf^T6/">ﬡ#$ʺ!!$Mvi:=MP:'wKE6񲌱f,Pf-NUGm~5lUZSEC]OS/hK0^\KwfcN2A4!/=gVM2FpߔXY ˫ewiLa _~(T`%obZa6x#| _\hIݶ1Yf.2oV"&$W2zt70BVJ+GE 2^Exlf$^PNɬfL>O @LRL.p4Yz,!<:)\e?@-sÞ W`46DO!QÌL;PriZg/uxMrIm'//tH}߃)->"W,B*Ѹ+7Kcz`/.=Z{6z >^h>OkqMw#u V?R_SZA`Ycc7`S˞dV3gecIO$]y5EwCX̥ZAЋ2,gxGc@+J+VUZCJOaut̮TUPLDŕ.M+rs5Pi5JAnyZ̵B0S*vpEvhфoiKUr6 image/svg+xml mdformat-0.7.17/docs/conf.py000066400000000000000000000047611454065404400156640ustar00rootroot00000000000000# fmt: off # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'mdformat' copyright = '2021, Taneli Hukkinen' # noqa: A001 author = 'Taneli Hukkinen' # The full version, including alpha/beta/rc tags release = '0.7.17' # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['myst_parser'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'furo' html_logo = "_static/logo-150px.png" html_show_sphinx = False # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # -- Options for MyST parser ------------------------------------------------- # Disable MyST syntax incompatible with vanilla mdformat # (i.e. disable everything except "directives") myst_disable_syntax = [ "table", "front_matter", "myst_line_comment", "myst_block_break", "myst_target", "myst_role", "math_inline", "math_block", "footnote_def", "footnote_inline", "footnote_ref", "footnote_tail", ] mdformat-0.7.17/docs/contributors/000077500000000000000000000000001454065404400171125ustar00rootroot00000000000000mdformat-0.7.17/docs/contributors/contributing.md000066400000000000000000000074241454065404400221520ustar00rootroot00000000000000# Contributing Welcome to the mdformat developer docs! We're excited you're here and want to contribute. ✨ Please discuss new features in an issue before submitting a PR to make sure that the feature is wanted and will be merged. Note that mdformat is an opinionated tool that attempts to keep formatting style changing configuration to its minimum. New configuration will only be added for a very good reason and use case. Below are the basic development steps, and for further information also see the [EBP organisation guidelines](https://github.com/executablebooks/.github/blob/master/CONTRIBUTING.md). 1. Fork and clone the repository. 1. Install pre-commit hooks ```bash pre-commit install ``` 1. After making changes and having written tests, make sure tests pass: ```bash tox ``` 1. Test the pre-commit hook against the README.md file ```bash pre-commit try-repo . mdformat --files README.md ``` 1. Commit, push, and make a PR. ## Developing code formatter plugins Mdformat code formatter plugins need to define a formatter function that is of type `Callable[[str, str], str]`. The input arguments are the code block's unformatted code and info string, in that order. The return value should be formatted code. This function needs to be exposed via entry point distribution metadata. The entry point's group must be "mdformat.codeformatter", name must be name of the coding language it formats (as it appears in Markdown code block info strings), e.g. "python", and value has to point to the formatter function within the plugin package, e.g. "my_package.some_module:format_python" If using `setup.py` for packaging, the entry point configuration would have to be similar to: ```python import setuptools setuptools.setup( # other arguments here... entry_points={ "mdformat.codeformatter": ["python = my_package.some_module:format_python"] } ) ``` If using Poetry for packaging, the entry point configuration in `pyproject.toml` would need to be like: ```toml # other config here... [tool.poetry.plugins."mdformat.codeformatter"] "python" = "my_package.some_module:format_python" ``` For a real-world example plugin, see [mdformat-black](https://github.com/hukkin/mdformat-black), which formats Python code blocks with Black. ## Developing parser extension plugins The easiest way to get started on a plugin, is to use the template repository. Mdformat parser extension plugins need to adhere to the `mdformat.plugins.ParserExtensionInterface`: ```python from collections.abc import Mapping from markdown_it import MarkdownIt from mdformat.renderer.typing import Render def update_mdit(mdit: MarkdownIt) -> None: """Update the parser, e.g. by adding a plugin: `mdit.use(myplugin)`""" # A mapping from `RenderTreeNode.type` value to a `Render` function that can # render the given `RenderTreeNode` type. These functions override the default # `Render` funcs defined in `mdformat.renderer.DEFAULT_RENDERERS`. RENDERERS: Mapping[str, Render] ``` This interface needs to be exposed via entry point distribution metadata. The entry point's group must be "mdformat.parser_extension". If using `setup.py` for packaging, the entry point configuration would have to be similar to: ```python import setuptools setuptools.setup( # other arguments here... entry_points={ "mdformat.parser_extension": ["myextension = my_package:ext_module_or_class"] } ) ``` If using Poetry or Flit for packaging, the entry point configuration in `pyproject.toml` would need to be like: ```toml # other config here... [tool.poetry.plugins."mdformat.parser_extension"] "myextension" = "my_package:ext_module_or_class" # or [tool.flit.plugins."mdformat.parser_extension"] "myextension" = "my_package:ext_module_or_class" ``` mdformat-0.7.17/docs/index.md000066400000000000000000000025021454065404400160050ustar00rootroot00000000000000
# ![mdformat](_static/logo.svg) > CommonMark compliant Markdown formatter
```{include} ../README.md :start-after: :end-before: ``` The features/opinions of the formatter include: - Consistent indentation and whitespace across the board - Always use ATX style headings - Move all link references to the bottom of the document (sorted by label) - Reformat indented code blocks as fenced code blocks - Use `1.` as the ordered list marker if possible, also for noninitial list items Mdformat will not change word wrapping by default. The rationale for this is to support [Semantic Line Breaks](https://sembr.org/). For a comprehensive description and rationalization of the style, read [the style guide](users/style.md). ```{include} ../README.md :start-after: :end-before: ``` ```{toctree} --- caption: For users hidden: true --- users/installation_and_usage.md users/configuration_file.md users/plugins.md users/style.md users/changelog.md ``` ```{toctree} --- caption: For contributors hidden: true --- contributors/contributing.md ``` ```{toctree} --- caption: External links hidden: true --- GitHub repository PyPI page ``` mdformat-0.7.17/docs/requirements.txt000066400000000000000000000002161454065404400176400ustar00rootroot00000000000000# List dependencies in a format that readthedocs.org understands. docutils == 0.18.1 sphinx == 5.1.1 myst-parser == 0.18.0 furo == 2022.6.21 mdformat-0.7.17/docs/users/000077500000000000000000000000001454065404400155165ustar00rootroot00000000000000mdformat-0.7.17/docs/users/changelog.md000066400000000000000000000224271454065404400177760ustar00rootroot00000000000000# Changelog This log documents all Python API or CLI breaking backwards incompatible changes. Note that there is currently no guarantee for a stable Markdown formatting style across versions. ## 0.7.17 - Added - Do not update mtime if formatting result is identical to the file. Thank you, [Pierre Augier](https://github.com/paugier), for the issue and the PR. - Fixed - An error on empty paragraph (Unicode space only). Thank you, [Nico Schlömer](https://github.com/nschloe), for the issue. - File write fails if no permissions to write to the directory. Fixed by removing atomic writes. Thank you, [Guy Kisel](https://github.com/guykisel), for the issue. - File permissions change on rewrite. Thank you, [Keiichi Watanabe](https://github.com/keiichiw), for the issue. - Removed - Python 3.7 support ## 0.7.16 - Added - Option to keep line ending sequence from source file (`--end-of-line=keep`). Thank you, [Mark Tsuchida](https://github.com/marktsuchida), for the issue and [Jan Wille](https://github.com/Cube707) for the PR. - Fixed - `--check` not working with `--end-of-line=crlf`. Thank you, [Jan Wille](https://github.com/Cube707), for the issue. - Insignificant Unicode whitespace causing unstable formatting. Thank you, [Yamada_Ika](https://github.com/Yamada-Ika), for the issue. ## 0.7.15 - Fixed - `--wrap` converts Unicode whitespace to regular spaces and line feeds. Thank you, [Nico Schlömer](https://github.com/nschloe), for the issue. - Packaging - Use `setuptools` as build backend ## 0.7.14 - Added - Accept `os.PathLike[str]` as `mdformat.file` input. - Improved - Add filepath to warning message on code formatter plugin error. - Use `tomllib` in Python 3.11+. - Changed - Style: Sort numeric link references numerically. Thank you [Ryan Delaney](https://github.com/rpdelaney) for the PR. ## 0.7.13 - Fixed - Don't indent inline HTML that looks like type 7 block HTML. Thank you [Philip May](https://github.com/PhilipMay) for the issue. ## 0.7.12 - Fixed - Fix unstable formatting when a paragraph line starts with inline HTML. Thank you [Gabriel Nützi](https://github.com/gabyx) for the issue. ## 0.7.11 - Added - Support for `markdown-it-py` v2 - Fixed - Fix an error when a code fence info string starts with a tilde or a backtick. Thank you [Jonathan Newnham](https://github.com/jnnnnn) for the issue. ## 0.7.10 - Added - Support for configuration in a `.mdformat.toml` file - Removed - Python 3.6 support ## 0.7.9 - Fixed - Fix an error when an autolink contains URL encoded spaces. Thank you [Chris Butler](https://github.com/butler54) for the issue. ## 0.7.8 - Fixed - Fix a case where indented Markdown nested inside indented raw HTML tags would alter AST. Thank you [Jirka Borovec](https://github.com/Borda) for the issue. ## 0.7.7 - Fixed - Output `lf` line endings on all platforms. Thank you [Scott Gudeman](https://github.com/DragonCrafted87) for the issue and the PR. - Added - Configuration option for outputting `crlf` line endings: `--end-of-line=crlf` - Resolve symlinks and modify the link destination file only. ## 0.7.6 - Changed - Style: Reduce wrap width by indent size in lists and quotes ## 0.7.5 - Fixed - Error rendering a hard break in a heading - Some obscure leading/trailing whitespace issues - Style: Convert image description newlines to spaces in wrap altering modes ## 0.7.4 - Added - `mdformat.renderer.WRAP_POINT` for plugins to show where word wrap is allowed to occur. - `mdformat.renderer.RenderContext.do_wrap` for plugins to check whether word wrap is enabled. - Changed - Style: Emphasis and strong emphasis are now wrapped. - Style: Word wrap width target is now respected more precisely in a few edge cases. ## 0.7.3 - Fixed - Style: Convert link text newlines to spaces in wrap altering modes. - Changed - Style: No longer escape line starting hashes not followed by a space. ## 0.7.2 - Fixed - Style: Stop adding a newline character to empty documents. ## 0.7.1 - Added - `RenderContext.with_default_renderer_for`: A convenience method for copying a render context with a set of renderers set to defaults ## 0.7.0 **NOTE:** Parser extension plugin API has changed in this release. - Added - `POSTPROCESSORS` mapping to parser extension plugin API (i.e. `ParserExtensionInterface`), providing a way for plugins to render syntax collaboratively. - `Postprocess` type alias to `mdformat.renderer.typing` - `mdformat.renderer.RenderContext`: a context object passed as input to `Render` and `Postprocess` functions - Changed - Renamed `ParserExtensionInterface.RENDERER_FUNCS` as `ParserExtensionInterface.RENDERERS` - Renamed `mdformat.renderer.typing.RendererFunc` as `mdformat.renderer.typing.Render` - `mdformat.renderer.typing.Render` signature changed. Now takes `RenderContext` as input. - Renamed `mdformat.renderer.DEFAULT_RENDERER_FUNCS` as `mdformat.renderer.DEFAULT_RENDERERS` ## 0.6.4 - Fixed - Warnings being printed twice when wrap mode is other than "keep" ([#167](https://github.com/executablebooks/mdformat/pull/167)) - An extra newline being added when consecutive lines' width equals wrap width ([#166](https://github.com/executablebooks/mdformat/pull/166)) ## 0.6.3 - Added - A list of installed plugins and their versions in the output of `--help` and `--version` CLI commands - `mdformat.codepoints` as public API ## 0.6.2 - Added - Sphinx docs - Atomic file writes. Markdown content now stays on disk every nanosecond of the formatting process. ## 0.6.1 - Fixed - A line starting blockquote marker (">") is now escaped. ## 0.6.0 **NOTE:** Parser extension plugin API has changed in this release. - Removed - `mdformat.renderer.MARKERS` - `mdformat.plugins.ParserExtensionInterface.render_token` - `start` and `stop` keyword arguments removed from `mdformat.renderer.MDRenderer.render`. Use `mdformat.renderer.MDRenderer.render_tree` to render a part of a Markdown document. - Added - Modes for setting a word wrap width and removing word wrap - `mdformat.plugins.ParserExtensionInterface.RENDERER_FUNCS` - A class for representing linear `markdown-it` token stream as a tree: `mdformat.renderer.RenderTreeNode` - `mdformat.renderer.MDRenderer.render_tree` for rendering a `RenderTreeNode` ## 0.5.7 - Fixed - CLI crash when formatting standard error output and the operating system reports a terminal window width of zero or less ([#131](https://github.com/executablebooks/mdformat/issues/131)). Thank you [ehontoria](https://github.com/ehontoria) for the issue. ## 0.5.6 - Changed - Style: Reduce asterisk escaping ([#120](https://github.com/executablebooks/mdformat/issues/120)) - Style: Reduce underscore escaping ([#119](https://github.com/executablebooks/mdformat/issues/119)). Thank you [dustinmichels](https://github.com/dustinmichels) for the issue. ## 0.5.5 - Changed - Style: Don't convert shortcut reference links into full reference links ([#111](https://github.com/executablebooks/mdformat/issues/111)) ## 0.5.4 - Changed - Style: Reduce hash (`#`) escaping ## 0.5.0 - Changed - Style: Convert list marker types. Prefer "-" for bullet lists and "." for ordered lists. - Style: Remove trailing whitespace from empty list items. ## 0.4.0 - Changed - Style: Only surround link destination with angle brackets if required by CommonMark spec - Style: Thematic breaks are now 70 characters wide ## 0.3.5 - Fixed - Markdown equality validation falsely triggering when code formatter plugins were used. Thanks [chrisjsewell](https://github.com/chrisjsewell) for writing the tests to find the bug. ## 0.3.3 - Added - `CHANGES_AST` to extension plugin API. The feature allows plugins that alter Markdown AST to skip validation ([#49](https://github.com/executablebooks/mdformat/pull/49)). ## 0.3.2 - Changed - Style: Keep reference links as reference links ([#32](https://github.com/executablebooks/mdformat/issues/32)). Thank you [chrisjsewell](https://github.com/chrisjsewell) for the issue and the PR. - Added - Option to number ordered list items consecutively using the `--number` flag ([#33](https://github.com/executablebooks/mdformat/issues/33)). Thank you [chrisjsewell](https://github.com/chrisjsewell) for the issue and the PR. - Parser extension plugins can now add their own CLI / Python API options ([#35](https://github.com/executablebooks/mdformat/pull/35)). Thanks [chrisjsewell](https://github.com/chrisjsewell) for the PR. - Fixed - Image links that require surrounding angle brackets no longer break formatting ([#40](https://github.com/executablebooks/mdformat/issues/40)). ## 0.3.1 - Added - Plugin system for extending the parser ([#13](https://github.com/executablebooks/mdformat/issues/13)). Thank you [chrisjsewell](https://github.com/chrisjsewell) for the issue and the PR. - Exported `mdformat.renderer.MDRenderer` and `mdformat.renderer.MARKERS` ## 0.3.0 - Changed - Code formatter plugin function signature changed to `Callable[[str, str], str]`. The second input argument is full info string of the code block. ## 0.2.0 - Changed - Style: Use backtick for code fences whenever possible ## 0.1.2 - Added - Support for code formatter plugins ## 0.1.0 - Added - Initial mdformat release mdformat-0.7.17/docs/users/configuration_file.md000066400000000000000000000015051454065404400217070ustar00rootroot00000000000000# Configuration file Mdformat allows configuration in a [TOML](https://toml.io) file named `.mdformat.toml`. The configuration file will be resolved starting from the location of the file being formatted, and searching up the file tree until a config file is (or isn't) found. When formatting standard input stream, resolution will be started from current working directory. Command line interface arguments take precedence over the configuration file. ## Example configuration ```toml # .mdformat.toml # # This file shows the default values and is equivalent to having # no configuration file at all. Change the values for non-default # behavior. # wrap = "keep" # possible values: {"keep", "no", INTEGER} number = false # possible values: {false, true} end_of_line = "lf" # possible values: {"lf", "crlf", "keep"} ``` mdformat-0.7.17/docs/users/installation_and_usage.md000066400000000000000000000031271454065404400225520ustar00rootroot00000000000000# Installation and usage ```{include} ../../README.md :start-after: :end-before: ``` ```{warning} The formatting style produced by mdformat may change in each version. It is recommended to pin mdformat dependency version. ``` ```{include} ../../README.md :start-after: :end-before: ``` ## Python API usage ### Format text ```python import mdformat unformatted = "\n\n# A header\n\n" formatted = mdformat.text(unformatted) assert formatted == "# A header\n" ``` ### Format a file Format file `README.md` in place: ```python import mdformat # Input filepath as a string... mdformat.file("README.md") # ...or a pathlib.Path object import pathlib filepath = pathlib.Path("README.md") mdformat.file(filepath) ``` ### Options All formatting style modifying options available in the CLI are also available in the Python API, with equivalent option names: ```python import mdformat mdformat.file( "FILENAME.md", options={ "number": True, # switch on consecutive numbering of ordered lists "wrap": 60, # set word wrap width to 60 characters } ) ``` ## Usage as a pre-commit hook `mdformat` can be used as a [pre-commit](https://github.com/pre-commit/pre-commit) hook. Add the following to your project's `.pre-commit-config.yaml` to enable this: ```yaml - repo: https://github.com/executablebooks/mdformat rev: 0.7.17 # Use the ref you want to point at hooks: - id: mdformat # Optionally add plugins additional_dependencies: - mdformat-gfm - mdformat-black ``` mdformat-0.7.17/docs/users/plugins.md000066400000000000000000000130621454065404400175230ustar00rootroot00000000000000# Plugins Mdformat offers an extensible plugin system for both code fence content formatting and Markdown parser extensions (like GFM tables). This document explains how to use plugins. If you want to create a new plugin, refer to the [contributing](../contributors/contributing.md) docs. ## Code formatter plugins Mdformat features a plugin system to support formatting of Markdown code blocks where the coding language has been labeled. For instance, if [`mdformat-black`](https://github.com/hukkin/mdformat-black) plugin is installed in the environment, mdformat CLI will automatically format Python code blocks with [Black](https://github.com/psf/black). For stability, mdformat Python API behavior will not change simply due to a plugin being installed. Code formatters will have to be explicitly enabled in addition to being installed: ````python import mdformat unformatted = "```python\n'''black converts quotes'''\n```\n" # Pass in `codeformatters` here! It is an iterable of coding languages # that should be formatted formatted = mdformat.text(unformatted, codeformatters={"python"}) assert formatted == '```python\n"""black converts quotes"""\n```\n' ```` ### Existing plugins
Plugin Supported languages Notes
mdformat-beautysh bash, sh
mdformat-black python
mdformat-config json, toml, yaml
mdformat-gofmt go Requires Go installation
mdformat-rustfmt rust Requires rustfmt installation
mdformat-shfmt bash, sh Requires either shfmt or Docker installation
mdformat-web javascript, js, css, html, xml
## Parser extension plugins Markdown-it-py offers a range of useful extensions to the base CommonMark parser (see the [documented list](https://markdown-it-py.readthedocs.io/en/latest/plugins.html)). Mdformat features a plugin system to support the loading and rendering of such extensions. For stability, mdformat Python API behavior will not change simply due to a plugin being installed. Extensions will have to be explicitly enabled in addition to being installed: ```python import mdformat unformatted = "content...\n" # Pass in `extensions` here! It is an iterable of extensions that should be loaded formatted = mdformat.text(unformatted, extensions={"tables"}) ``` ### Existing plugins
Plugin Syntax Extensions Description
mdformat-admon admonition Adds support for python-markdown admonitions
mdformat-deflist deflist Adds support for Pandoc-style definition lists
mdformat-footnote footnote Adds support for Pandoc-style footnotes
mdformat-frontmatter frontmatter Adds support for front matter, and formats YAML front matter
mdformat-gfm gfm Changes target specification to GitHub Flavored Markdown (GFM)
mdformat-mkdocs mkdocs Changes target specification to MKDocs. Indents lists with 4-spaces instead of 2
mdformat-myst myst Changes target specification to MyST
mdformat-tables tables Adds support for GitHub Flavored Markdown style tables
mdformat-toc toc Adds the capability to auto-generate a table of contents
mdformat-0.7.17/docs/users/style.md000066400000000000000000000130421454065404400172000ustar00rootroot00000000000000# Formatting style This document describes, demonstrates, and rationalizes the formatting style that mdformat follows. Mdformat's formatting style is crafted so that writing, editing and collaborating on Markdown documents is as smooth as possible. The style is consistent, and minimizes diffs (for ease of reviewing changes), sometimes at the cost of some readability. Mdformat makes sure to only change style, not content. Once converted to HTML and rendered on screen, formatted Markdown should yield a result that is visually identical to the unformatted document. Mdformat CLI includes a safety check that will error and refuse to apply changes to a file if Markdown AST is not equal before and after formatting. ## Headings For consistency, only ATX headings are used. Setext headings are reformatted using the ATX style. ATX headings are used because they can be consistently used for any heading level, whereas setext headings only allow level 1 and 2 headings. Input: ```markdown First level heading === Second level heading --- ``` Output: ```markdown # First level heading ## Second level heading ``` ## Bullet lists Mdformat uses `-` as the bullet list marker. In the case of consecutive bullet lists, mdformat alternates between `-` and `*` markers. ## Ordered lists Mdformat uses `.` as ordered list marker type. In the case of consecutive ordered lists, mdformat alternates between `.` and `)` types. Mdformat uses `1.` or `1)` as the ordered list marker, also for noninital list items. Input: ```markdown 1. Item A 2. Item B 3. Item C ``` Output: ```markdown 1. Item A 1. Item B 1. Item C ``` This "non-numbering" style was chosen to minimize diffs. But how exactly? Lets imagine we are listing the alphabets, using a proper consecutive numbering style: ```markdown 1. b 2. c 3. d ``` Now we notice an error was made, and that the first character "a" is missing. We add it as the first item in the list. As a result, the numbering of every subsequent item in the list will increase by one, meaning that the diff will touch every line in the list. The non-numbering style solves this issue: only the added line will show up in the diff. Mdformat allows consecutive numbering via configuration. ## Code blocks Only fenced code blocks are allowed. Indented code blocks are reformatted as fenced code blocks. Fenced code blocks are preferred because they allow setting an info string, which indented code blocks do not support. ## Code spans Length of a code span starting/ending backtick string is reduced to minimum. Needless space characters are stripped from the front and back, unless the content contains backticks. Input: `````markdown ````Backtick string is reduced.```` ` Space is stripped from the front and back... ` ```` ...unless a "`" character is present. ```` ````` Output: ```markdown `Backtick string is reduced.` `Space is stripped from the front and back...` `` ...unless a "`" character is present. `` ``` ## Inline links Redundant angle brackets surrounding a link destination will be removed. Input: ```markdown [Python]() ``` Output: ```markdown [Python](https://python.org) ``` ## Reference links All link reference definitions are moved to the bottom of the document, sorted by label. Unused and duplicate references are removed. Input: ```markdown [dupe ref]: https://gitlab.com [dupe ref]: link1 [unused ref]: link2 Here's a link to [GitLab][dupe ref] ``` Output: ```markdown Here's a link to [GitLab][dupe ref] [dupe ref]: https://gitlab.com ``` ## Paragraph word wrapping Mdformat by default will not change word wrapping. The rationale for this is to encourage and support [Semantic Line Breaks](https://sembr.org/), a technique described by Brian Kernighan in the early 1970s, yet still as relevant as ever today: > **Hints for Preparing Documents** > > Most documents go through several versions (always more than you > expected) before they are finally finished. Accordingly, you should > do whatever possible to make the job of changing them easy. > > First, when you do the purely mechanical operations of typing, type > so subsequent editing will be easy. Start each sentence on a new line. > Make lines short, and break lines at natural places, such as after > commas and semicolons, rather than > randomly. Since > most people change documents by rewriting phrases and adding, deleting > and rearranging sentences, these precautions simplify any editing you > have to do later. > > _— Brian W. Kernighan. "UNIX for Beginners". 1974_ Mdformat allows removing word wrap or setting a target wrap width via configuration. ## Thematic breaks Thematic breaks are formatted as a 70 character wide string of underscores. A wide thematic break is distinguishable, and visually resembles how a corresponding HTML `
` tag is typically rendered. ## Whitespace Mdformat applies consistent whitespace across the board: - Convert line endings to a single newline character - Strip paragraph trailing and leading whitespace - Indent contents of block quotes and list items consistently - Always separate blocks with a single empty line (an exception being tight lists where the separator is a single newline character) - Always end the document in a single newline character (an exception being an empty document) ## Hard line breaks Hard line breaks are always a backslash preceding a line ending. The alternative syntax, two or more spaces before a line ending, is not used because it is not visible. Input: ```markdown Hard line break is here: Can you see it? ``` Output: ```markdown Hard line break is here:\ Can you see it? ``` mdformat-0.7.17/fuzzer/000077500000000000000000000000001454065404400147525ustar00rootroot00000000000000mdformat-0.7.17/fuzzer/fuzz.py000066400000000000000000000027621454065404400163310ustar00rootroot00000000000000import atheris with atheris.instrument_imports(): import hashlib import sys import warnings import mdformat from mdformat._util import is_md_equal # Suppress all warnings. warnings.simplefilter("ignore") def test_one_input(input_bytes: bytes) -> None: # We need a Unicode string, not bytes fdp = atheris.FuzzedDataProvider(input_bytes) data = fdp.ConsumeUnicode(sys.maxsize) try: formatted_data = mdformat.text(data) except BaseException: handle_err(data) raise if not is_md_equal(data, formatted_data): handle_err(data) raise Exception("Formatted Markdown not equal!") def handle_err(data): codepoints = [hex(ord(x)) for x in data] sys.stderr.write(f"Input was {type(data)}:\n{data}\nCodepoints:\n{codepoints}\n") # Atheris already writes crash data to a file, but it seems it is not UTF-8 encoded. # I'm not sure what the encoding is exactly. Anyway, let's write another file here # that is guaranteed to be valid UTF-8. data_bytes = data.encode() filename = "crash-utf8-" + hashlib.sha256(data_bytes).hexdigest() with open(filename, "wb") as f: f.write(data_bytes) sys.stderr.write(f"Wrote UTF-8 encoded data to {filename}\n") sys.stderr.flush() def main(): # For possible options, see https://llvm.org/docs/LibFuzzer.html#options fuzzer_options = sys.argv atheris.Setup(fuzzer_options, test_one_input) atheris.Fuzz() if __name__ == "__main__": main() mdformat-0.7.17/fuzzer/requirements.txt000066400000000000000000000000631454065404400202350ustar00rootroot00000000000000# sudo apt-get install clang wheel atheris==2.0.12 mdformat-0.7.17/pyproject.toml000066400000000000000000000113771454065404400163520ustar00rootroot00000000000000[build-system] requires = ["setuptools>=61.0.0"] build-backend = "setuptools.build_meta" [project] name = "mdformat" version = "0.7.17" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT description = "CommonMark compliant Markdown formatter" authors = [ { name = "Taneli Hukkinen", email = "hukkin@users.noreply.github.com" }, ] license = { file = "LICENSE" } requires-python = ">=3.8" dependencies = [ 'markdown-it-py >=1.0.0,<4.0.0', 'tomli >=1.1.0; python_version < "3.11"', 'importlib-metadata >=3.6.0; python_version < "3.10"', ] readme = "README.md" classifiers = [ "Environment :: Console", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Topic :: Documentation", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Markup", "Typing :: Typed", ] keywords = ["mdformat", "markdown", "commonmark", "formatter", "pre-commit"] [project.urls] "Homepage" = "https://github.com/executablebooks/mdformat" "Documentation" = "https://mdformat.readthedocs.io" "Style guide" = "https://mdformat.readthedocs.io/en/stable/users/style.html" "Changelog" = "https://mdformat.readthedocs.io/en/stable/users/changelog.html" [project.scripts] mdformat = "mdformat.__main__:run" [tool.isort] # Force imports to be sorted by module, independent of import type force_sort_within_sections = true # Group first party and local folder imports together no_lines_before = ["LOCALFOLDER"] # Configure isort to work without access to site-packages known_first_party = ["mdformat", "tests"] # Settings for Black compatibility profile = "black" [tool.pytest.ini_options] addopts = "--strict-markers --strict-config" xfail_strict = true [tool.tox] legacy_tox_ini = ''' [tox] # Only run pytest envs when no args given to tox envlist = py{38,39,310,311} isolated_build = True [testenv:py{38,39,310,311}] description = run tests deps = -r tests/requirements.txt commands = pytest {posargs} [testenv:profile] description = run profiler (use e.g. `firefox .tox/prof/combined.svg` to open) deps = -r tests/requirements.txt pytest-profiling commands = pytest tests/test_for_profiler.py --profile-svg --pstats-dir "{toxworkdir}/prof" python -c 'import pathlib; print("profiler svg output under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "prof" / "combined.svg"))' [testenv:pre-commit] description = run linters (except mypy) skip_install = True deps = pre-commit commands = pre-commit run {posargs:--all} [testenv:mypy] description = run mypy basepython = python3.8 deps = -r tests/requirements.txt mypy==0.910 commands = mypy {posargs:src/ tests/} [testenv:hook] description = test mdformat's own pre-commit hook against the README file skip_install = True deps = pre-commit commands = pre-commit try-repo . mdformat --files README.md [testenv:cli] description = run mdformat's own CLI commands = mdformat {posargs} [testenv:docs] description = invoke sphinx-build to build the HTML docs skip_install = True deps = -r docs/requirements.txt commands = sphinx-build -d "{toxworkdir}/docs_doctree" docs/ "{toxworkdir}/docs_out" -W -b html {posargs} python -c 'import pathlib; print("documentation available under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "docs_out" / "index.html"))' [testenv:fuzz] description = run the fuzzer (needs "apt install clang") deps = -r fuzzer/requirements.txt allowlist_externals = mkdir cp commands = # Create a folder for persistent corpus and use README.md as initial seed mkdir -p {toxworkdir}/fuzzer-corpus cp -n README.md {toxworkdir}/fuzzer-corpus/README.md # Run fuzzer python fuzzer/fuzz.py {toxworkdir}/fuzzer-corpus {posargs:-len_control=10000} ''' [tool.coverage.run] source = ["mdformat"] omit = ["*/__main__.py"] [tool.coverage.report] # Regexes for lines to exclude from consideration exclude_lines = [ # Re-enable the standard pragma (with extra strictness) '# pragma: no cover\b', # Ellipsis lines after @typing.overload '^ +\.\.\.$', # Code for static type checkers "if TYPE_CHECKING:", # Scripts 'if __name__ == .__main__.:', ] [tool.mypy] show_error_codes = true warn_unreachable = true warn_unused_ignores = true warn_redundant_casts = true warn_unused_configs = true # Disabling incremental mode is required for `warn_unused_configs = true` to work incremental = false disallow_untyped_defs = true check_untyped_defs = true strict_equality = true implicit_reexport = false no_implicit_optional = true [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false [[tool.mypy.overrides]] # This matches `fuzzer/fuzz.py`. module = "fuzz" ignore_errors = true mdformat-0.7.17/src/000077500000000000000000000000001454065404400142145ustar00rootroot00000000000000mdformat-0.7.17/src/mdformat/000077500000000000000000000000001454065404400160255ustar00rootroot00000000000000mdformat-0.7.17/src/mdformat/__init__.py000066400000000000000000000002321454065404400201330ustar00rootroot00000000000000__all__ = ("file", "text") __version__ = "0.7.17" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT from mdformat._api import file, text mdformat-0.7.17/src/mdformat/__main__.py000066400000000000000000000003051454065404400201150ustar00rootroot00000000000000import sys from typing import NoReturn import mdformat._cli def run() -> NoReturn: exit_code = mdformat._cli.run(sys.argv[1:]) sys.exit(exit_code) if __name__ == "__main__": run() mdformat-0.7.17/src/mdformat/_api.py000066400000000000000000000044621454065404400173150ustar00rootroot00000000000000from __future__ import annotations from collections.abc import Iterable, Mapping from contextlib import AbstractContextManager from os import PathLike from pathlib import Path from typing import Any from mdformat._conf import DEFAULT_OPTS from mdformat._util import EMPTY_MAP, NULL_CTX, build_mdit, detect_newline_type from mdformat.renderer import MDRenderer def text( md: str, *, options: Mapping[str, Any] = EMPTY_MAP, extensions: Iterable[str] = (), codeformatters: Iterable[str] = (), _first_pass_contextmanager: AbstractContextManager = NULL_CTX, _filename: str = "", ) -> str: """Format a Markdown string.""" with _first_pass_contextmanager: mdit = build_mdit( MDRenderer, mdformat_opts={**options, **{"filename": _filename}}, extensions=extensions, codeformatters=codeformatters, ) rendering = mdit.render(md) # If word wrap is changed, add a second pass of rendering. # Some escapes will be different depending on word wrap, so # rendering after 1st and 2nd pass will be different. Rendering # twice seems like the easiest way to achieve stable formatting. if options.get("wrap", DEFAULT_OPTS["wrap"]) != "keep": rendering = mdit.render(rendering) return rendering def file( f: str | PathLike[str], *, options: Mapping[str, Any] = EMPTY_MAP, extensions: Iterable[str] = (), codeformatters: Iterable[str] = (), ) -> None: """Format a Markdown file in place.""" f = Path(f) try: is_file = f.is_file() except OSError: # Catch "OSError: [WinError 123]" on Windows # pragma: no cover is_file = False if not is_file: raise ValueError(f'Cannot format "{f}". It is not a file.') if f.is_symlink(): raise ValueError(f'Cannot format "{f}". It is a symlink.') original_md = f.read_bytes().decode() formatted_md = text( original_md, options=options, extensions=extensions, codeformatters=codeformatters, _filename=str(f), ) newline = detect_newline_type( original_md, options.get("end_of_line", DEFAULT_OPTS["end_of_line"]) ) formatted_md = formatted_md.replace("\n", newline) if formatted_md != original_md: f.write_bytes(formatted_md.encode()) mdformat-0.7.17/src/mdformat/_cli.py000066400000000000000000000232211454065404400173050ustar00rootroot00000000000000from __future__ import annotations import argparse from collections.abc import Callable, Generator, Iterable, Mapping, Sequence import contextlib import itertools import logging from pathlib import Path import shutil import sys import textwrap import mdformat from mdformat._compat import importlib_metadata from mdformat._conf import DEFAULT_OPTS, InvalidConfError, read_toml_opts from mdformat._util import detect_newline_type, is_md_equal import mdformat.plugins import mdformat.renderer class RendererWarningPrinter(logging.Handler): def emit(self, record: logging.LogRecord) -> None: if record.levelno >= logging.WARNING: sys.stderr.write(f"Warning: {record.msg}\n") def run(cli_args: Sequence[str]) -> int: # noqa: C901 # Enable all parser plugins enabled_parserplugins = mdformat.plugins.PARSER_EXTENSIONS # Enable code formatting for all languages that have a plugin installed enabled_codeformatters = mdformat.plugins.CODEFORMATTERS changes_ast = any( getattr(plugin, "CHANGES_AST", False) for plugin in enabled_parserplugins.values() ) arg_parser = make_arg_parser(enabled_parserplugins, enabled_codeformatters) cli_opts = { k: v for k, v in vars(arg_parser.parse_args(cli_args)).items() if v is not None } if not cli_opts["paths"]: print_paragraphs(["No files have been passed in. Doing nothing."]) return 0 try: file_paths = resolve_file_paths(cli_opts["paths"]) except InvalidPath as e: arg_parser.error(f'File "{e.path}" does not exist.') format_errors_found = False renderer_warning_printer = RendererWarningPrinter() for path in file_paths: try: toml_opts = read_toml_opts(path.parent if path else Path.cwd()) except InvalidConfError as e: print_error(str(e)) return 1 opts: Mapping = {**DEFAULT_OPTS, **toml_opts, **cli_opts} if path: path_str = str(path) # Unlike `path.read_text(encoding="utf-8")`, this preserves # line ending type. original_str = path.read_bytes().decode() else: path_str = "-" original_str = sys.stdin.read() formatted_str = mdformat.text( original_str, options=opts, extensions=enabled_parserplugins, codeformatters=enabled_codeformatters, _first_pass_contextmanager=log_handler_applied( mdformat.renderer.LOGGER, renderer_warning_printer ), _filename=path_str, ) newline = detect_newline_type(original_str, opts["end_of_line"]) formatted_str = formatted_str.replace("\n", newline) if opts["check"]: if formatted_str != original_str: format_errors_found = True print_error(f'File "{path_str}" is not formatted.') else: if not changes_ast and not is_md_equal( original_str, formatted_str, options=opts, extensions=enabled_parserplugins, codeformatters=enabled_codeformatters, ): print_error( f'Could not format "{path_str}".', paragraphs=[ "The formatted Markdown renders to different HTML than the input Markdown. " # noqa: E501 "This is likely a bug in mdformat. " "Please create an issue report here, including the input Markdown: " # noqa: E501 "https://github.com/executablebooks/mdformat/issues", ], ) return 1 if path: if formatted_str != original_str: path.write_bytes(formatted_str.encode()) else: sys.stdout.buffer.write(formatted_str.encode()) if format_errors_found: return 1 return 0 def validate_wrap_arg(value: str) -> str | int: if value in {"keep", "no"}: return value width = int(value) if width < 1: raise ValueError("wrap width must be a positive integer") return width def make_arg_parser( parser_extensions: Mapping[str, mdformat.plugins.ParserExtensionInterface], codeformatters: Mapping[str, Callable[[str, str], str]], ) -> argparse.ArgumentParser: plugin_versions_str = get_plugin_versions_str(parser_extensions, codeformatters) parser = argparse.ArgumentParser( description="CommonMark compliant Markdown formatter", epilog=f"Installed plugins: {plugin_versions_str}" if plugin_versions_str else None, ) parser.add_argument("paths", nargs="*", help="files to format") parser.add_argument( "--check", action="store_true", help="do not apply changes to files" ) version_str = f"mdformat {mdformat.__version__}" if plugin_versions_str: version_str += f" ({plugin_versions_str})" parser.add_argument("--version", action="version", version=version_str) parser.add_argument( "--number", action="store_const", const=True, help="apply consecutive numbering to ordered lists", ) parser.add_argument( "--wrap", type=validate_wrap_arg, metavar="{keep,no,INTEGER}", help="paragraph word wrap mode (default: keep)", ) parser.add_argument( "--end-of-line", choices=("lf", "crlf", "keep"), help="output file line ending mode (default: lf)", ) for plugin in parser_extensions.values(): if hasattr(plugin, "add_cli_options"): plugin.add_cli_options(parser) return parser class InvalidPath(Exception): """Exception raised when a path does not exist.""" def __init__(self, path: Path): self.path = path def resolve_file_paths(path_strings: Iterable[str]) -> list[None | Path]: """Resolve pathlib.Path objects from filepath strings. Convert path strings to pathlib.Path objects. Resolve symlinks. Check that all paths are either files, directories or stdin. If not, raise InvalidPath. Resolve directory paths to a list of file paths (ending with ".md"). """ file_paths: list[None | Path] = [] # Path to file or None for stdin/stdout for path_str in path_strings: if path_str == "-": file_paths.append(None) continue path_obj = Path(path_str) path_obj = _resolve_path(path_obj) if path_obj.is_dir(): for p in path_obj.glob("**/*.md"): p = _resolve_path(p) file_paths.append(p) else: file_paths.append(path_obj) return file_paths def _resolve_path(path: Path) -> Path: """Resolve path. Resolve symlinks. Raise `InvalidPath` if the path does not exist. """ try: path = path.resolve() # resolve symlinks path_exists = path.exists() except OSError: # Catch "OSError: [WinError 123]" on Windows # pragma: no cover path_exists = False if not path_exists: raise InvalidPath(path) return path def print_paragraphs(paragraphs: Iterable[str]) -> None: assert not isinstance(paragraphs, str) sys.stderr.write(wrap_paragraphs(paragraphs)) def print_error(title: str, paragraphs: Iterable[str] = ()) -> None: assert not isinstance(paragraphs, str) assert not title.lower().startswith("error") title = "Error: " + title paragraphs = [title] + list(paragraphs) print_paragraphs(paragraphs) def wrap_paragraphs(paragraphs: Iterable[str]) -> str: """Wrap and concatenate paragraphs. Take an iterable of paragraphs as input. Return a string where the paragraphs are concatenated (empty line as separator) and wrapped. End the string in a newline. """ terminal_width, _ = shutil.get_terminal_size() if 0 < terminal_width < 80: wrap_width = terminal_width else: wrap_width = 80 wrapper = textwrap.TextWrapper( break_long_words=False, break_on_hyphens=False, width=wrap_width ) return "\n\n".join(wrapper.fill(p) for p in paragraphs) + "\n" @contextlib.contextmanager def log_handler_applied( logger: logging.Logger, handler: logging.Handler ) -> Generator[None, None, None]: logger.addHandler(handler) try: yield finally: logger.removeHandler(handler) def get_package_name(obj: object) -> str: # Packages and modules should have `__package__` if hasattr(obj, "__package__"): package_name = obj.__package__ # type: ignore[attr-defined] else: # class or function module_name = obj.__module__ package_name = module_name.split(".", maxsplit=1)[0] return package_name def get_plugin_versions( parser_extensions: Mapping[str, mdformat.plugins.ParserExtensionInterface], codeformatters: Mapping[str, Callable[[str, str], str]], ) -> dict[str, str]: versions = {} for iface in itertools.chain(parser_extensions.values(), codeformatters.values()): package_name = get_package_name(iface) try: package_version = importlib_metadata.version(package_name) except importlib_metadata.PackageNotFoundError: # In test scenarios the package may not exist package_version = "unknown" versions[package_name] = package_version return versions def get_plugin_versions_str( parser_extensions: Mapping[str, mdformat.plugins.ParserExtensionInterface], codeformatters: Mapping[str, Callable[[str, str], str]], ) -> str: plugin_versions = get_plugin_versions(parser_extensions, codeformatters) return ", ".join(f"{name}: {version}" for name, version in plugin_versions.items()) mdformat-0.7.17/src/mdformat/_compat.py000066400000000000000000000005341454065404400200230ustar00rootroot00000000000000__all__ = ("importlib_metadata", "tomllib") import sys if sys.version_info >= (3, 11): # pragma: no cover import tomllib else: # pragma: no cover import tomli as tomllib if sys.version_info >= (3, 10): # pragma: no cover from importlib import metadata as importlib_metadata else: # pragma: no cover import importlib_metadata mdformat-0.7.17/src/mdformat/_conf.py000066400000000000000000000036321454065404400174670ustar00rootroot00000000000000from __future__ import annotations import functools from pathlib import Path from typing import Mapping from mdformat._compat import tomllib DEFAULT_OPTS = { "wrap": "keep", "number": False, "end_of_line": "lf", } class InvalidConfError(Exception): """Error raised on invalid TOML configuration. Will be raised on: - invalid TOML - invalid conf key - invalid conf value """ @functools.lru_cache() def read_toml_opts(conf_dir: Path) -> Mapping: conf_path = conf_dir / ".mdformat.toml" if not conf_path.is_file(): parent_dir = conf_dir.parent if conf_dir == parent_dir: return {} return read_toml_opts(parent_dir) with open(conf_path, "rb") as f: try: toml_opts = tomllib.load(f) except tomllib.TOMLDecodeError as e: raise InvalidConfError(f"Invalid TOML syntax: {e}") _validate_keys(toml_opts, conf_path) _validate_values(toml_opts, conf_path) return toml_opts def _validate_values(opts: Mapping, conf_path: Path) -> None: if "wrap" in opts: wrap_value = opts["wrap"] if not ( (isinstance(wrap_value, int) and wrap_value > 1) or wrap_value in {"keep", "no"} ): raise InvalidConfError(f"Invalid 'wrap' value in {conf_path}") if "end_of_line" in opts: if opts["end_of_line"] not in {"crlf", "lf", "keep"}: raise InvalidConfError(f"Invalid 'end_of_line' value in {conf_path}") if "number" in opts: if not isinstance(opts["number"], bool): raise InvalidConfError(f"Invalid 'number' value in {conf_path}") def _validate_keys(opts: Mapping, conf_path: Path) -> None: for key in opts: if key not in DEFAULT_OPTS: raise InvalidConfError( f"Invalid key {key!r} in {conf_path}." f" Keys must be one of {set(DEFAULT_OPTS)}." ) mdformat-0.7.17/src/mdformat/_util.py000066400000000000000000000076501454065404400175230ustar00rootroot00000000000000from __future__ import annotations from collections.abc import Iterable, Mapping from contextlib import nullcontext import re from types import MappingProxyType from typing import Any, Literal from markdown_it import MarkdownIt from markdown_it.renderer import RendererHTML import mdformat.plugins NULL_CTX = nullcontext() EMPTY_MAP: MappingProxyType = MappingProxyType({}) RE_NEWLINES = re.compile(r"\r\n|\r|\n") def build_mdit( renderer_cls: Any, *, mdformat_opts: Mapping[str, Any] = EMPTY_MAP, extensions: Iterable[str] = (), codeformatters: Iterable[str] = (), ) -> MarkdownIt: mdit = MarkdownIt(renderer_cls=renderer_cls) mdit.options["mdformat"] = mdformat_opts # store reference labels in link/image tokens mdit.options["store_labels"] = True mdit.options["parser_extension"] = [] for name in extensions: plugin = mdformat.plugins.PARSER_EXTENSIONS[name] if plugin not in mdit.options["parser_extension"]: mdit.options["parser_extension"].append(plugin) plugin.update_mdit(mdit) mdit.options["codeformatters"] = { lang: mdformat.plugins.CODEFORMATTERS[lang] for lang in codeformatters } return mdit def is_md_equal( md1: str, md2: str, *, options: Mapping[str, Any] = EMPTY_MAP, extensions: Iterable[str] = (), codeformatters: Iterable[str] = (), ) -> bool: """Check if two Markdown produce the same HTML. Renders HTML from both Markdown strings, reduces consecutive whitespace to a single space and checks equality. Note that this is not a perfect solution, as there can be meaningful whitespace in HTML, e.g. in a block. """ html_texts = {} mdit = build_mdit(RendererHTML, mdformat_opts=options, extensions=extensions) for key, text in [("md1", md1), ("md2", md2)]: html = mdit.render(text) # The HTML can start with whitespace if Markdown starts with raw HTML # preceded by whitespace. This whitespace should be safe to lstrip. # Also, the trailing newline we add at the end of a document that ends # in a raw html block not followed by a newline, seems to propagate to # an HTML rendering. This newline should be safe to rstrip. html = html.strip() # Remove codeblocks because code formatter plugins do arbitrary changes. for codeclass in codeformatters: html = re.sub( f'.*', "", html, flags=re.DOTALL, ) # Reduce all whitespace to a single space html = re.sub(r"\s+", " ", html) # Strip insignificant paragraph leading/trailing whitespace html = html.replace("

", "

") html = html.replace("

", "

") # Also strip whitespace leading/trailing the

elements so that we can # safely remove empty paragraphs below without introducing extra whitespace. html = html.replace("

", "

") html = html.replace("

", "

") # empty p elements should be ignored by user agents # (https://www.w3.org/TR/REC-html40/struct/text.html#edef-P) html = html.replace("

", "") # If it's nothing but whitespace, it's equal html = re.sub(r"^\s+$", "", html) html_texts[key] = html return html_texts["md1"] == html_texts["md2"] def detect_newline_type(md: str, eol_setting: str) -> Literal["\n", "\r\n"]: """Returns the newline-character to be used for output. If `eol_setting == "keep"`, the newline character used in the passed markdown is detected and returned. Otherwise the character matching the passed setting is returned. """ if eol_setting == "keep": first_eol = RE_NEWLINES.search(md) return "\r\n" if first_eol and first_eol.group() == "\r\n" else "\n" if eol_setting == "crlf": return "\r\n" return "\n" mdformat-0.7.17/src/mdformat/codepoints/000077500000000000000000000000001454065404400201745ustar00rootroot00000000000000mdformat-0.7.17/src/mdformat/codepoints/__init__.py000066400000000000000000000006051454065404400223060ustar00rootroot00000000000000__all__ = ( "UNICODE_PUNCTUATION", "UNICODE_WHITESPACE", "ASCII_CTRL", "ASCII_WHITESPACE", ) from mdformat.codepoints._unicode_punctuation import UNICODE_PUNCTUATION from mdformat.codepoints._unicode_whitespace import UNICODE_WHITESPACE ASCII_CTRL = frozenset(chr(i) for i in range(32)) ASCII_WHITESPACE = frozenset({chr(9), chr(10), chr(11), chr(12), chr(13), chr(32)}) mdformat-0.7.17/src/mdformat/codepoints/_unicode_punctuation.py000066400000000000000000000305211454065404400247650ustar00rootroot00000000000000"""Pre-generated unicode punctuation characters. Run this module to generate and print an up-to-date set of characters. """ UNICODE_PUNCTUATION = frozenset( ( "!", '"', "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "@", "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~", "¡", "§", "«", "¶", "·", "»", "¿", ";", "·", "՚", "՛", "՜", "՝", "՞", "՟", "։", "֊", "־", "׀", "׃", "׆", "׳", "״", "؉", "؊", "،", "؍", "؛", "؞", "؟", "٪", "٫", "٬", "٭", "۔", "܀", "܁", "܂", "܃", "܄", "܅", "܆", "܇", "܈", "܉", "܊", "܋", "܌", "܍", "߷", "߸", "߹", "࠰", "࠱", "࠲", "࠳", "࠴", "࠵", "࠶", "࠷", "࠸", "࠹", "࠺", "࠻", "࠼", "࠽", "࠾", "࡞", "।", "॥", "॰", "৽", "੶", "૰", "౷", "಄", "෴", "๏", "๚", "๛", "༄", "༅", "༆", "༇", "༈", "༉", "༊", "་", "༌", "།", "༎", "༏", "༐", "༑", "༒", "༔", "༺", "༻", "༼", "༽", "྅", "࿐", "࿑", "࿒", "࿓", "࿔", "࿙", "࿚", "၊", "။", "၌", "၍", "၎", "၏", "჻", "፠", "፡", "።", "፣", "፤", "፥", "፦", "፧", "፨", "᐀", "᙮", "᚛", "᚜", "᛫", "᛬", "᛭", "᜵", "᜶", "។", "៕", "៖", "៘", "៙", "៚", "᠀", "᠁", "᠂", "᠃", "᠄", "᠅", "᠆", "᠇", "᠈", "᠉", "᠊", "᥄", "᥅", "᨞", "᨟", "᪠", "᪡", "᪢", "᪣", "᪤", "᪥", "᪦", "᪨", "᪩", "᪪", "᪫", "᪬", "᪭", "᭚", "᭛", "᭜", "᭝", "᭞", "᭟", "᭠", "᯼", "᯽", "᯾", "᯿", "᰻", "᰼", "᰽", "᰾", "᰿", "᱾", "᱿", "᳀", "᳁", "᳂", "᳃", "᳄", "᳅", "᳆", "᳇", "᳓", "‐", "‑", "‒", "–", "—", "―", "‖", "‗", "‘", "’", "‚", "‛", "“", "”", "„", "‟", "†", "‡", "•", "‣", "․", "‥", "…", "‧", "‰", "‱", "′", "″", "‴", "‵", "‶", "‷", "‸", "‹", "›", "※", "‼", "‽", "‾", "‿", "⁀", "⁁", "⁂", "⁃", "⁅", "⁆", "⁇", "⁈", "⁉", "⁊", "⁋", "⁌", "⁍", "⁎", "⁏", "⁐", "⁑", "⁓", "⁔", "⁕", "⁖", "⁗", "⁘", "⁙", "⁚", "⁛", "⁜", "⁝", "⁞", "⁽", "⁾", "₍", "₎", "⌈", "⌉", "⌊", "⌋", "〈", "〉", "❨", "❩", "❪", "❫", "❬", "❭", "❮", "❯", "❰", "❱", "❲", "❳", "❴", "❵", "⟅", "⟆", "⟦", "⟧", "⟨", "⟩", "⟪", "⟫", "⟬", "⟭", "⟮", "⟯", "⦃", "⦄", "⦅", "⦆", "⦇", "⦈", "⦉", "⦊", "⦋", "⦌", "⦍", "⦎", "⦏", "⦐", "⦑", "⦒", "⦓", "⦔", "⦕", "⦖", "⦗", "⦘", "⧘", "⧙", "⧚", "⧛", "⧼", "⧽", "⳹", "⳺", "⳻", "⳼", "⳾", "⳿", "⵰", "⸀", "⸁", "⸂", "⸃", "⸄", "⸅", "⸆", "⸇", "⸈", "⸉", "⸊", "⸋", "⸌", "⸍", "⸎", "⸏", "⸐", "⸑", "⸒", "⸓", "⸔", "⸕", "⸖", "⸗", "⸘", "⸙", "⸚", "⸛", "⸜", "⸝", "⸞", "⸟", "⸠", "⸡", "⸢", "⸣", "⸤", "⸥", "⸦", "⸧", "⸨", "⸩", "⸪", "⸫", "⸬", "⸭", "⸮", "⸰", "⸱", "⸲", "⸳", "⸴", "⸵", "⸶", "⸷", "⸸", "⸹", "⸺", "⸻", "⸼", "⸽", "⸾", "⸿", "⹀", "⹁", "⹂", "⹃", "⹄", "⹅", "⹆", "⹇", "⹈", "⹉", "⹊", "⹋", "⹌", "⹍", "⹎", "⹏", "、", "。", "〃", "〈", "〉", "《", "》", "「", "」", "『", "』", "【", "】", "〔", "〕", "〖", "〗", "〘", "〙", "〚", "〛", "〜", "〝", "〞", "〟", "〰", "〽", "゠", "・", "꓾", "꓿", "꘍", "꘎", "꘏", "꙳", "꙾", "꛲", "꛳", "꛴", "꛵", "꛶", "꛷", "꡴", "꡵", "꡶", "꡷", "꣎", "꣏", "꣸", "꣹", "꣺", "꣼", "꤮", "꤯", "꥟", "꧁", "꧂", "꧃", "꧄", "꧅", "꧆", "꧇", "꧈", "꧉", "꧊", "꧋", "꧌", "꧍", "꧞", "꧟", "꩜", "꩝", "꩞", "꩟", "꫞", "꫟", "꫰", "꫱", "꯫", "﴾", "﴿", "︐", "︑", "︒", "︓", "︔", "︕", "︖", "︗", "︘", "︙", "︰", "︱", "︲", "︳", "︴", "︵", "︶", "︷", "︸", "︹", "︺", "︻", "︼", "︽", "︾", "︿", "﹀", "﹁", "﹂", "﹃", "﹄", "﹅", "﹆", "﹇", "﹈", "﹉", "﹊", "﹋", "﹌", "﹍", "﹎", "﹏", "﹐", "﹑", "﹒", "﹔", "﹕", "﹖", "﹗", "﹘", "﹙", "﹚", "﹛", "﹜", "﹝", "﹞", "﹟", "﹠", "﹡", "﹣", "﹨", "﹪", "﹫", "!", """, "#", "%", "&", "'", "(", ")", "*", ",", "-", ".", "/", ":", ";", "?", "@", "[", "\", "]", "_", "{", "}", "⦅", "⦆", "。", "「", "」", "、", "・", "𐄀", "𐄁", "𐄂", "𐎟", "𐏐", "𐕯", "𐡗", "𐤟", "𐤿", "𐩐", "𐩑", "𐩒", "𐩓", "𐩔", "𐩕", "𐩖", "𐩗", "𐩘", "𐩿", "𐫰", "𐫱", "𐫲", "𐫳", "𐫴", "𐫵", "𐫶", "𐬹", "𐬺", "𐬻", "𐬼", "𐬽", "𐬾", "𐬿", "𐮙", "𐮚", "𐮛", "𐮜", "𐽕", "𐽖", "𐽗", "𐽘", "𐽙", "𑁇", "𑁈", "𑁉", "𑁊", "𑁋", "𑁌", "𑁍", "𑂻", "𑂼", "𑂾", "𑂿", "𑃀", "𑃁", "𑅀", "𑅁", "𑅂", "𑅃", "𑅴", "𑅵", "𑇅", "𑇆", "𑇇", "𑇈", "𑇍", "𑇛", "𑇝", "𑇞", "𑇟", "𑈸", "𑈹", "𑈺", "𑈻", "𑈼", "𑈽", "𑊩", "𑑋", "𑑌", "𑑍", "𑑎", "𑑏", "𑑛", "𑑝", "𑓆", "𑗁", "𑗂", "𑗃", "𑗄", "𑗅", "𑗆", "𑗇", "𑗈", "𑗉", "𑗊", "𑗋", "𑗌", "𑗍", "𑗎", "𑗏", "𑗐", "𑗑", "𑗒", "𑗓", "𑗔", "𑗕", "𑗖", "𑗗", "𑙁", "𑙂", "𑙃", "𑙠", "𑙡", "𑙢", "𑙣", "𑙤", "𑙥", "𑙦", "𑙧", "𑙨", "𑙩", "𑙪", "𑙫", "𑙬", "𑜼", "𑜽", "𑜾", "𑠻", "𑧢", "𑨿", "𑩀", "𑩁", "𑩂", "𑩃", "𑩄", "𑩅", "𑩆", "𑪚", "𑪛", "𑪜", "𑪞", "𑪟", "𑪠", "𑪡", "𑪢", "𑱁", "𑱂", "𑱃", "𑱄", "𑱅", "𑱰", "𑱱", "𑻷", "𑻸", "𑿿", "𒑰", "𒑱", "𒑲", "𒑳", "𒑴", "𖩮", "𖩯", "𖫵", "𖬷", "𖬸", "𖬹", "𖬺", "𖬻", "𖭄", "𖺗", "𖺘", "𖺙", "𖺚", "𖿢", "𛲟", "𝪇", "𝪈", "𝪉", "𝪊", "𝪋", "𞥞", "𞥟", ) ) if __name__ == "__main__": import string import sys import unicodedata UNICODE_CHARS = frozenset(chr(c) for c in range(sys.maxunicode + 1)) UNICODE_PUNCTUATION = frozenset( c for c in UNICODE_CHARS if unicodedata.category(c).startswith("P") ) | frozenset(string.punctuation) print(f"frozenset({tuple(sorted(UNICODE_PUNCTUATION))})") mdformat-0.7.17/src/mdformat/codepoints/_unicode_whitespace.py000066400000000000000000000016071454065404400245530ustar00rootroot00000000000000"""Pre-generated unicode whitespace characters. Run this module to generate and print an up-to-date set of characters. """ UNICODE_WHITESPACE = frozenset( ( "\t", "\n", "\x0b", "\x0c", "\r", " ", "\xa0", "\u1680", "\u2000", "\u2001", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009", "\u200a", "\u202f", "\u205f", "\u3000", ) ) if __name__ == "__main__": import string import sys import unicodedata UNICODE_CHARS = frozenset(chr(c) for c in range(sys.maxunicode + 1)) UNICODE_WHITESPACE = frozenset( c for c in UNICODE_CHARS if unicodedata.category(c) == "Zs" ) | frozenset(string.whitespace) print(f"frozenset({tuple(sorted(UNICODE_WHITESPACE))})") mdformat-0.7.17/src/mdformat/plugins.py000066400000000000000000000041401454065404400200570ustar00rootroot00000000000000from __future__ import annotations import argparse from collections.abc import Callable, Mapping from typing import Protocol from markdown_it import MarkdownIt from mdformat._compat import importlib_metadata from mdformat.renderer.typing import Postprocess, Render def _load_codeformatters() -> dict[str, Callable[[str, str], str]]: codeformatter_entrypoints = importlib_metadata.entry_points( group="mdformat.codeformatter" ) return {ep.name: ep.load() for ep in codeformatter_entrypoints} CODEFORMATTERS: Mapping[str, Callable[[str, str], str]] = _load_codeformatters() class ParserExtensionInterface(Protocol): """An interface for parser extension plugins.""" # Does the plugin's formatting change Markdown AST or not? # (optional, default: False) CHANGES_AST: bool = False # A mapping from `RenderTreeNode.type` to a `Render` function that can # render the given `RenderTreeNode` type. These override the default # `Render` funcs defined in `mdformat.renderer.DEFAULT_RENDERERS`. RENDERERS: Mapping[str, Render] # A mapping from `RenderTreeNode.type` to a `Postprocess` that does # postprocessing for the output of the `Render` function. Unlike # `Render` funcs, `Postprocess` funcs are collaborative: any number of # plugins can define a postprocessor for a syntax type and all of them # will run in series. # (optional) POSTPROCESSORS: Mapping[str, Postprocess] @staticmethod def add_cli_options(parser: argparse.ArgumentParser) -> None: """Add options to the mdformat CLI, to be stored in mdit.options["mdformat"] (optional)""" @staticmethod def update_mdit(mdit: MarkdownIt) -> None: """Update the parser, e.g. by adding a plugin: `mdit.use(myplugin)`""" def _load_parser_extensions() -> dict[str, ParserExtensionInterface]: parser_extension_entrypoints = importlib_metadata.entry_points( group="mdformat.parser_extension" ) return {ep.name: ep.load() for ep in parser_extension_entrypoints} PARSER_EXTENSIONS: Mapping[str, ParserExtensionInterface] = _load_parser_extensions() mdformat-0.7.17/src/mdformat/py.typed000066400000000000000000000000321454065404400175170ustar00rootroot00000000000000# Marker file for PEP 561 mdformat-0.7.17/src/mdformat/renderer/000077500000000000000000000000001454065404400176335ustar00rootroot00000000000000mdformat-0.7.17/src/mdformat/renderer/__init__.py000066400000000000000000000101061454065404400217420ustar00rootroot00000000000000from __future__ import annotations __all__ = ( "MDRenderer", "LOGGER", "RenderTreeNode", "DEFAULT_RENDERERS", "RenderContext", "WRAP_POINT", ) from collections.abc import Mapping, MutableMapping, Sequence import logging import string from types import MappingProxyType from typing import Any from markdown_it.token import Token from mdformat.renderer._context import DEFAULT_RENDERERS, WRAP_POINT, RenderContext from mdformat.renderer._tree import RenderTreeNode from mdformat.renderer.typing import Postprocess LOGGER = logging.getLogger(__name__) class MDRenderer: """Markdown renderer. A renderer class that outputs formatted Markdown. Compatible with `markdown_it.MarkdownIt`. """ __output__ = "md" def __init__(self, parser: Any = None): """__init__ must have `parser` parameter for markdown-it-py compatibility.""" def render( self, tokens: Sequence[Token], options: Mapping[str, Any], env: MutableMapping, *, finalize: bool = True, ) -> str: """Takes token stream and generates Markdown. Args: tokens: A sequence of block tokens to render options: Params of parser instance env: Additional data from parsed input finalize: write references and add trailing newline """ tree = RenderTreeNode(tokens) return self.render_tree(tree, options, env, finalize=finalize) def render_tree( self, tree: RenderTreeNode, options: Mapping[str, Any], env: MutableMapping, *, finalize: bool = True, ) -> str: self._prepare_env(env) # Update RENDERER_MAP defaults with renderer functions defined # by plugins. updated_renderers = {} postprocessors: dict[str, tuple[Postprocess, ...]] = {} for plugin in options.get("parser_extension", []): for syntax_name, renderer_func in plugin.RENDERERS.items(): if syntax_name in updated_renderers: LOGGER.warning( "Plugin conflict. More than one plugin defined a renderer" f' for "{syntax_name}" syntax.' ) else: updated_renderers[syntax_name] = renderer_func for syntax_name, pp in getattr(plugin, "POSTPROCESSORS", {}).items(): if syntax_name not in postprocessors: postprocessors[syntax_name] = (pp,) else: postprocessors[syntax_name] += (pp,) renderer_map = MappingProxyType({**DEFAULT_RENDERERS, **updated_renderers}) postprocessor_map = MappingProxyType(postprocessors) render_context = RenderContext(renderer_map, postprocessor_map, options, env) text = tree.render(render_context) if finalize: if env["used_refs"]: text += "\n\n" text += self._write_references(env) if text: text += "\n" assert "\x00" not in text, "null bytes should be removed by now" return text @staticmethod def _write_references(env: MutableMapping) -> str: def label_sort_key(label: str) -> str: assert label, "link label cannot be empty string" if all(c in string.digits for c in label): label_max_len = 999 # This is from CommonMark v0.30 return label.rjust(label_max_len, "0") return label ref_list = [] for label in sorted(env["used_refs"], key=label_sort_key): ref = env["references"][label] destination = ref["href"] if ref["href"] else "<>" item = f"[{label.lower()}]: {destination}" title = ref["title"] if title: title = title.replace('"', '\\"') item += f' "{title}"' ref_list.append(item) return "\n".join(ref_list) def _prepare_env(self, env: MutableMapping) -> None: env["indent_width"] = 0 env["used_refs"] = set() mdformat-0.7.17/src/mdformat/renderer/_context.py000066400000000000000000000541071454065404400220370ustar00rootroot00000000000000from __future__ import annotations from collections import defaultdict from collections.abc import Generator, Iterable, Mapping, MutableMapping from contextlib import contextmanager import logging import re import textwrap from types import MappingProxyType from typing import TYPE_CHECKING, Any, Literal, NamedTuple from markdown_it.rules_block.html_block import HTML_SEQUENCES from mdformat import codepoints from mdformat._conf import DEFAULT_OPTS from mdformat.renderer._util import ( RE_CHAR_REFERENCE, decimalify_leading, decimalify_trailing, escape_asterisk_emphasis, escape_underscore_emphasis, get_list_marker_type, is_tight_list, is_tight_list_item, longest_consecutive_sequence, maybe_add_link_brackets, ) from mdformat.renderer.typing import Postprocess, Render if TYPE_CHECKING: from mdformat.renderer import RenderTreeNode LOGGER = logging.getLogger(__name__) # A marker used to point a location where word wrap is allowed # to occur. WRAP_POINT = "\x00" # A marker used to indicate location of a character that should be preserved # during word wrap. Should be converted to the actual character after wrap. PRESERVE_CHAR = "\x00" def make_render_children(separator: str) -> Render: def render_children( node: RenderTreeNode, context: RenderContext, ) -> str: render_outputs = (child.render(context) for child in node.children) return separator.join(out for out in render_outputs if out) return render_children def hr(node: RenderTreeNode, context: RenderContext) -> str: thematic_break_width = 70 return "_" * thematic_break_width def code_inline(node: RenderTreeNode, context: RenderContext) -> str: code = node.content all_chars_are_whitespace = not code.strip() longest_backtick_seq = longest_consecutive_sequence(code, "`") if longest_backtick_seq: separator = "`" * (longest_backtick_seq + 1) return f"{separator} {code} {separator}" if code.startswith(" ") and code.endswith(" ") and not all_chars_are_whitespace: return f"` {code} `" return f"`{code}`" def html_block(node: RenderTreeNode, context: RenderContext) -> str: content = node.content.rstrip("\n") # Need to strip leading spaces because we do so for regular Markdown too. # Without the stripping the raw HTML and Markdown get unaligned and # semantic may change. content = content.lstrip() return content def html_inline(node: RenderTreeNode, context: RenderContext) -> str: return node.content def _in_block(block_name: str, node: RenderTreeNode) -> bool: while node.parent: if node.parent.type == block_name: return True node = node.parent return False def hardbreak(node: RenderTreeNode, context: RenderContext) -> str: if _in_block("heading", node): return "
" return "\\" + "\n" def softbreak(node: RenderTreeNode, context: RenderContext) -> str: if context.do_wrap and _in_block("paragraph", node): return WRAP_POINT return "\n" def text(node: RenderTreeNode, context: RenderContext) -> str: """Process a text token. Text should always be a child of an inline token. An inline token should always be enclosed by a heading or a paragraph. """ text = node.content # Escape backslash to prevent it from making unintended escapes. # This escape has to be first, else we start multiplying backslashes. text = text.replace("\\", "\\\\") text = escape_asterisk_emphasis(text) # Escape emphasis/strong marker. text = escape_underscore_emphasis(text) # Escape emphasis/strong marker. text = text.replace("[", "\\[") # Escape link label enclosure text = text.replace("]", "\\]") # Escape link label enclosure text = text.replace("<", "\\<") # Escape URI enclosure text = text.replace("`", "\\`") # Escape code span marker # Escape "&" if it starts a sequence that can be interpreted as # a character reference. text = RE_CHAR_REFERENCE.sub(r"\\\g<0>", text) # The parser can give us consecutive newlines which can break # the markdown structure. Replace two or more consecutive newlines # with newline character's decimal reference. text = text.replace("\n\n", " ") # If the last character is a "!" and the token next up is a link, we # have to escape the "!" or else the link will be interpreted as image. next_sibling = node.next_sibling if text.endswith("!") and next_sibling and next_sibling.type == "link": text = text[:-1] + "\\!" if context.do_wrap and _in_block("paragraph", node): text = re.sub(r"[ \t\n]+", WRAP_POINT, text) return text def fence(node: RenderTreeNode, context: RenderContext) -> str: info_str = node.info.strip() lang = info_str.split(maxsplit=1)[0] if info_str else "" code_block = node.content # Info strings of backtick code fences cannot contain backticks. # If that is the case, we make a tilde code fence instead. fence_char = "~" if "`" in info_str else "`" # Format the code block using enabled codeformatter funcs if lang in context.options.get("codeformatters", {}): fmt_func = context.options["codeformatters"][lang] try: code_block = fmt_func(code_block, info_str) except Exception: # Swallow exceptions so that formatter errors (e.g. due to # invalid code) do not crash mdformat. assert node.map is not None, "A fence token must have `map` attribute set" filename = context.options.get("mdformat", {}).get("filename", "") warn_msg = ( f"Failed formatting content of a {lang} code block " f"(line {node.map[0] + 1} before formatting)" ) if filename: warn_msg += f". Filename: {filename}" LOGGER.warning(warn_msg) # The code block must not include as long or longer sequence of `fence_char`s # as the fence string itself fence_len = max(3, longest_consecutive_sequence(code_block, fence_char) + 1) fence_str = fence_char * fence_len return f"{fence_str}{info_str}\n{code_block}{fence_str}" def code_block(node: RenderTreeNode, context: RenderContext) -> str: return fence(node, context) def image(node: RenderTreeNode, context: RenderContext) -> str: description = _render_inline_as_text(node, context) if context.do_wrap: # Prevent line breaks description = description.replace(WRAP_POINT, " ") ref_label = node.meta.get("label") if ref_label: context.env["used_refs"].add(ref_label) ref_label_repr = ref_label.lower() if description.lower() == ref_label_repr: return f"![{description}]" return f"![{description}][{ref_label_repr}]" uri = node.attrs["src"] assert isinstance(uri, str) uri = maybe_add_link_brackets(uri) title = node.attrs.get("title") if title is not None: return f'![{description}]({uri} "{title}")' return f"![{description}]({uri})" def _render_inline_as_text(node: RenderTreeNode, context: RenderContext) -> str: """Special kludge for image `alt` attributes to conform CommonMark spec. Don't try to use it! Spec requires to show `alt` content with stripped markup, instead of simple escaping. """ def text_renderer(node: RenderTreeNode, context: RenderContext) -> str: return node.content def image_renderer(node: RenderTreeNode, context: RenderContext) -> str: return _render_inline_as_text(node, context) inline_renderers: Mapping[str, Render] = defaultdict( lambda: make_render_children(""), { "text": text_renderer, "image": image_renderer, "link": link, "softbreak": softbreak, }, ) inline_context = RenderContext( inline_renderers, context.postprocessors, context.options, context.env ) return make_render_children("")(node, inline_context) def link(node: RenderTreeNode, context: RenderContext) -> str: if node.info == "auto": autolink_url = node.attrs["href"] assert isinstance(autolink_url, str) # The parser adds a "mailto:" prefix to autolink email href. We remove the # prefix if it wasn't there in the source. if autolink_url.startswith("mailto:") and not node.children[ 0 ].content.startswith("mailto:"): autolink_url = autolink_url[7:] return "<" + autolink_url + ">" text = "".join(child.render(context) for child in node.children) if context.do_wrap: # Prevent line breaks text = text.replace(WRAP_POINT, " ") ref_label = node.meta.get("label") if ref_label: context.env["used_refs"].add(ref_label) ref_label_repr = ref_label.lower() if text.lower() == ref_label_repr: return f"[{text}]" return f"[{text}][{ref_label_repr}]" uri = node.attrs["href"] assert isinstance(uri, str) uri = maybe_add_link_brackets(uri) title = node.attrs.get("title") if title is None: return f"[{text}]({uri})" assert isinstance(title, str) title = title.replace('"', '\\"') return f'[{text}]({uri} "{title}")' def em(node: RenderTreeNode, context: RenderContext) -> str: text = make_render_children(separator="")(node, context) indicator = node.markup return indicator + text + indicator def strong(node: RenderTreeNode, context: RenderContext) -> str: text = make_render_children(separator="")(node, context) indicator = node.markup return indicator + text + indicator def heading(node: RenderTreeNode, context: RenderContext) -> str: text = make_render_children(separator="")(node, context) if node.markup == "=": prefix = "# " elif node.markup == "-": prefix = "## " else: # ATX heading prefix = node.markup + " " # There can be newlines in setext headers, but we make an ATX # header always. Convert newlines to spaces. text = text.replace("\n", " ") # If the text ends in a sequence of hashes (#), the hashes will be # interpreted as an optional closing sequence of the heading, and # will not be rendered. Escape a line ending hash to prevent this. if text.endswith("#"): text = text[:-1] + "\\#" return prefix + text def blockquote(node: RenderTreeNode, context: RenderContext) -> str: marker = "> " with context.indented(len(marker)): text = make_render_children(separator="\n\n")(node, context) lines = text.splitlines() if not lines: return ">" quoted_lines = (f"{marker}{line}" if line else ">" for line in lines) quoted_str = "\n".join(quoted_lines) return quoted_str def _wrap(text: str, *, width: int | Literal["no"]) -> str: """Wrap text at locations pointed by `WRAP_POINT`s. Converts `WRAP_POINT`s to either a space or newline character, thus wrapping the text. Already existing whitespace will be preserved as is. """ text, replacements = _prepare_wrap(text) if width == "no": return _recover_preserve_chars(text, replacements) wrapper = textwrap.TextWrapper( break_long_words=False, break_on_hyphens=False, width=width, expand_tabs=False, replace_whitespace=False, ) wrapped = wrapper.fill(text) wrapped = _recover_preserve_chars(wrapped, replacements) return " " + wrapped if text.startswith(" ") else wrapped def _prepare_wrap(text: str) -> tuple[str, str]: """Prepare text for wrap. Convert `WRAP_POINT`s to spaces. Convert whitespace to `PRESERVE_CHAR`s. Return a tuple with the prepared string, and another string consisting of replacement characters for `PRESERVE_CHAR`s. """ result = "" replacements = "" for c in text: if c == WRAP_POINT: if not result or result[-1] != " ": result += " " elif c in codepoints.UNICODE_WHITESPACE: result += PRESERVE_CHAR replacements += c else: result += c return result, replacements def _recover_preserve_chars(text: str, replacements: str) -> str: replacement_iterator = iter(replacements) return "".join( next(replacement_iterator) if c == PRESERVE_CHAR else c for c in text ) def paragraph(node: RenderTreeNode, context: RenderContext) -> str: # noqa: C901 inline_node = node.children[0] text = inline_node.render(context) if context.do_wrap: wrap_mode = context.options["mdformat"]["wrap"] if isinstance(wrap_mode, int): wrap_mode -= context.env["indent_width"] wrap_mode = max(1, wrap_mode) text = _wrap(text, width=wrap_mode) # A paragraph can start or end in whitespace e.g. if the whitespace was # in decimal representation form. We need to re-decimalify it, one reason being # to enable "empty" paragraphs with whitespace only. text = decimalify_leading(codepoints.UNICODE_WHITESPACE, text) text = decimalify_trailing(codepoints.UNICODE_WHITESPACE, text) lines = text.split("\n") for i in range(len(lines)): # Strip whitespace to prevent issues like a line starting tab that is # interpreted as start of a code block. lines[i] = lines[i].strip() # If a line looks like an ATX heading, escape the first hash. if re.match(r"#{1,6}( |\t|$)", lines[i]): lines[i] = f"\\{lines[i]}" # Make sure a paragraph line does not start with ">" # (otherwise it will be interpreted as a block quote). if lines[i].startswith(">"): lines[i] = f"\\{lines[i]}" # Make sure a paragraph line does not start with "*", "-" or "+" # followed by a space, tab, or end of line. # (otherwise it will be interpreted as list item). if re.match(r"[-*+]( |\t|$)", lines[i]): lines[i] = f"\\{lines[i]}" # If a line starts with a number followed by "." or ")" followed by # a space, tab or end of line, escape the "." or ")" or it will be # interpreted as ordered list item. if re.match(r"[0-9]+\)( |\t|$)", lines[i]): lines[i] = lines[i].replace(")", "\\)", 1) if re.match(r"[0-9]+\.( |\t|$)", lines[i]): lines[i] = lines[i].replace(".", "\\.", 1) # Consecutive "-", "*" or "_" sequences can be interpreted as thematic # break. Escape them. space_removed = lines[i].replace(" ", "").replace("\t", "") if len(space_removed) >= 3: if all(c == "*" for c in space_removed): lines[i] = lines[i].replace("*", "\\*", 1) # pragma: no cover elif all(c == "-" for c in space_removed): lines[i] = lines[i].replace("-", "\\-", 1) elif all(c == "_" for c in space_removed): lines[i] = lines[i].replace("_", "\\_", 1) # pragma: no cover # A stripped line where all characters are "=" or "-" will be # interpreted as a setext heading. Escape. stripped = lines[i].strip(" \t") if all(c == "-" for c in stripped): lines[i] = lines[i].replace("-", "\\-", 1) elif all(c == "=" for c in stripped): lines[i] = lines[i].replace("=", "\\=", 1) # Check if the line could be interpreted as an HTML block. # If yes, prefix it with 4 spaces to prevent this. for html_seq_tuple in HTML_SEQUENCES: can_break_paragraph = html_seq_tuple[2] opening_re = html_seq_tuple[0] if can_break_paragraph and opening_re.search(lines[i]): lines[i] = f" {lines[i]}" break text = "\n".join(lines) return text def list_item(node: RenderTreeNode, context: RenderContext) -> str: """Return one list item as string. This returns just the content. List item markers and indentation are added in `bullet_list` and `ordered_list` renderers. """ block_separator = "\n" if is_tight_list_item(node) else "\n\n" text = make_render_children(block_separator)(node, context) if not text.strip(): return "" return text def bullet_list(node: RenderTreeNode, context: RenderContext) -> str: marker_type = get_list_marker_type(node) first_line_indent = " " indent = " " * len(marker_type + first_line_indent) block_separator = "\n" if is_tight_list(node) else "\n\n" with context.indented(len(indent)): text = "" for child_idx, child in enumerate(node.children): list_item = child.render(context) formatted_lines = [] line_iterator = iter(list_item.split("\n")) first_line = next(line_iterator) formatted_lines.append( f"{marker_type}{first_line_indent}{first_line}" if first_line else marker_type ) for line in line_iterator: formatted_lines.append(f"{indent}{line}" if line else "") text += "\n".join(formatted_lines) if child_idx != len(node.children) - 1: text += block_separator return text def ordered_list(node: RenderTreeNode, context: RenderContext) -> str: consecutive_numbering = context.options.get("mdformat", {}).get( "number", DEFAULT_OPTS["number"] ) marker_type = get_list_marker_type(node) first_line_indent = " " block_separator = "\n" if is_tight_list(node) else "\n\n" list_len = len(node.children) starting_number = node.attrs.get("start") if starting_number is None: starting_number = 1 assert isinstance(starting_number, int) if consecutive_numbering: indent_width = len( f"{list_len + starting_number - 1}{marker_type}{first_line_indent}" ) else: indent_width = len(f"{starting_number}{marker_type}{first_line_indent}") text = "" with context.indented(indent_width): for list_item_index, list_item in enumerate(node.children): list_item_text = list_item.render(context) formatted_lines = [] line_iterator = iter(list_item_text.split("\n")) first_line = next(line_iterator) if consecutive_numbering: # Prefix first line of the list item with consecutive numbering, # padded with zeros to make all markers of even length. # E.g. # 002. This is the first list item # 003. Second item # ... # 112. Last item number = starting_number + list_item_index pad = len(str(list_len + starting_number - 1)) number_str = str(number).rjust(pad, "0") formatted_lines.append( f"{number_str}{marker_type}{first_line_indent}{first_line}" if first_line else f"{number_str}{marker_type}" ) else: # Prefix first line of first item with the starting number of the # list. Prefix following list items with the number one # prefixed by zeros to make the list item marker of even length # with the first one. # E.g. # 5321. This is the first list item # 0001. Second item # 0001. Third item first_item_marker = f"{starting_number}{marker_type}" other_item_marker = ( "0" * (len(str(starting_number)) - 1) + "1" + marker_type ) if list_item_index == 0: formatted_lines.append( f"{first_item_marker}{first_line_indent}{first_line}" if first_line else first_item_marker ) else: formatted_lines.append( f"{other_item_marker}{first_line_indent}{first_line}" if first_line else other_item_marker ) for line in line_iterator: formatted_lines.append(" " * indent_width + line if line else "") text += "\n".join(formatted_lines) if list_item_index != len(node.children) - 1: text += block_separator return text DEFAULT_RENDERERS: Mapping[str, Render] = MappingProxyType( { "inline": make_render_children(""), "root": make_render_children("\n\n"), "hr": hr, "code_inline": code_inline, "html_block": html_block, "html_inline": html_inline, "hardbreak": hardbreak, "softbreak": softbreak, "text": text, "fence": fence, "code_block": code_block, "link": link, "image": image, "em": em, "strong": strong, "heading": heading, "blockquote": blockquote, "paragraph": paragraph, "bullet_list": bullet_list, "ordered_list": ordered_list, "list_item": list_item, } ) class RenderContext(NamedTuple): """A collection of data that is passed as input to `Render` and `Postprocess` functions.""" renderers: Mapping[str, Render] postprocessors: Mapping[str, Iterable[Postprocess]] options: Mapping[str, Any] env: MutableMapping @contextmanager def indented(self, width: int) -> Generator[None, None, None]: self.env["indent_width"] += width try: yield finally: self.env["indent_width"] -= width @property def do_wrap(self) -> bool: wrap_mode = self.options.get("mdformat", {}).get("wrap", DEFAULT_OPTS["wrap"]) return isinstance(wrap_mode, int) or wrap_mode == "no" def with_default_renderer_for(self, *syntax_names: str) -> RenderContext: renderers = dict(self.renderers) for syntax in syntax_names: if syntax in DEFAULT_RENDERERS: renderers[syntax] = DEFAULT_RENDERERS[syntax] else: renderers.pop(syntax, None) return RenderContext( MappingProxyType(renderers), self.postprocessors, self.options, self.env ) mdformat-0.7.17/src/mdformat/renderer/_tree.py000066400000000000000000000007641454065404400213120ustar00rootroot00000000000000from markdown_it.tree import SyntaxTreeNode from mdformat.renderer._context import RenderContext class RenderTreeNode(SyntaxTreeNode): """A syntax tree node capable of making a text rendering of itself.""" def render(self, context: RenderContext) -> str: renderer = context.renderers[self.type] text = renderer(self, context) for postprocessor in context.postprocessors.get(self.type, ()): text = postprocessor(text, self, context) return text mdformat-0.7.17/src/mdformat/renderer/_util.py000066400000000000000000000131421454065404400213220ustar00rootroot00000000000000from __future__ import annotations from collections.abc import Iterable import html.entities import re from typing import TYPE_CHECKING from mdformat import codepoints if TYPE_CHECKING: from mdformat.renderer import RenderTreeNode # Regex that finds character references. # The reference can be either # 1. decimal representation, e.g. # 2. hex representation, e.g.  # 3. HTML5 entity reference, e.g.   RE_CHAR_REFERENCE = re.compile( "&(?:" + "#[0-9]{1,7}" + "|" + "#[Xx][0-9A-Fa-f]{1,6}" + "|" + "|".join({c.rstrip(";") for c in html.entities.html5}) + ");" ) def is_tight_list(node: RenderTreeNode) -> bool: assert node.type in {"bullet_list", "ordered_list"} # The list has list items at level +1 so paragraphs in those list # items must be at level +2 (grand children) for child in node.children: for grand_child in child.children: if grand_child.type != "paragraph": continue is_tight = grand_child.hidden if not is_tight: return False return True def is_tight_list_item(node: RenderTreeNode) -> bool: assert node.type == "list_item" assert node.parent is not None return is_tight_list(node.parent) def longest_consecutive_sequence(seq: str, char: str) -> int: """Return length of the longest consecutive sequence of `char` characters in string `seq`.""" assert len(char) == 1 longest = 0 current_streak = 0 for c in seq: if c == char: current_streak += 1 else: current_streak = 0 if current_streak > longest: longest = current_streak return longest def maybe_add_link_brackets(link: str) -> str: """Surround URI with brackets if required by spec.""" if not link or ( codepoints.ASCII_CTRL | codepoints.ASCII_WHITESPACE | {"(", ")"} ).intersection(link): return "<" + link + ">" return link def get_list_marker_type(node: RenderTreeNode) -> str: if node.type == "bullet_list": mode = "bullet" primary_marker = "-" secondary_marker = "*" else: mode = "ordered" primary_marker = "." secondary_marker = ")" consecutive_lists_count = 1 current = node while True: previous_sibling = current.previous_sibling if previous_sibling is None: return primary_marker if consecutive_lists_count % 2 else secondary_marker prev_type = previous_sibling.type if (mode == "bullet" and prev_type == "bullet_list") or ( mode == "ordered" and prev_type == "ordered_list" ): consecutive_lists_count += 1 current = previous_sibling else: return primary_marker if consecutive_lists_count % 2 else secondary_marker def escape_asterisk_emphasis(text: str) -> str: """Escape asterisks to prevent unexpected emphasis/strong emphasis. Currently we escape all asterisks unless both previous and next character are Unicode whitespace. """ # Fast exit to improve performance if "*" not in text: return text escaped_text = "" text_length = len(text) for i, current_char in enumerate(text): if current_char != "*": escaped_text += current_char continue prev_char = text[i - 1] if (i - 1) >= 0 else None next_char = text[i + 1] if (i + 1) < text_length else None if ( prev_char in codepoints.UNICODE_WHITESPACE and next_char in codepoints.UNICODE_WHITESPACE ): escaped_text += current_char continue escaped_text += "\\" + current_char return escaped_text def escape_underscore_emphasis(text: str) -> str: """Escape underscores to prevent unexpected emphasis/strong emphasis. Currently we escape all underscores unless: - Neither of the surrounding characters are one of Unicode whitespace, start or end of line, or Unicode punctuation - Both surrounding characters are Unicode whitespace """ # Fast exit to improve performance if "_" not in text: return text bad_neighbor_chars = ( codepoints.UNICODE_WHITESPACE | codepoints.UNICODE_PUNCTUATION | frozenset({None}) ) escaped_text = "" text_length = len(text) for i, current_char in enumerate(text): if current_char != "_": escaped_text += current_char continue prev_char = text[i - 1] if (i - 1) >= 0 else None next_char = text[i + 1] if (i + 1) < text_length else None if ( prev_char in codepoints.UNICODE_WHITESPACE and next_char in codepoints.UNICODE_WHITESPACE ) or ( prev_char not in bad_neighbor_chars and next_char not in bad_neighbor_chars ): escaped_text += current_char continue escaped_text += "\\" + current_char return escaped_text def decimalify_leading(char_set: Iterable[str], text: str) -> str: """Replace first character with decimal representation if it's included in `char_set`.""" if not char_set or not text: return text first_char = text[0] if first_char in char_set: return f"&#{ord(first_char)};{text[1:]}" return text def decimalify_trailing(char_set: Iterable[str], text: str) -> str: """Replace last character with decimal representation if it's included in `char_set`.""" if not char_set or not text: return text last_char = text[-1] if last_char in char_set: return f"{text[:-1]}&#{ord(last_char)};" return text mdformat-0.7.17/src/mdformat/renderer/typing.py000066400000000000000000000007261454065404400215240ustar00rootroot00000000000000from typing import TYPE_CHECKING, Any, Callable if TYPE_CHECKING: from mdformat.renderer import RenderTreeNode # There should be `mdformat.renderer.RenderContext` instead of `Any` # here and in `Postprocess` below but that results in recursive typing # which mypy doesn't support until # https://github.com/python/mypy/issues/731 is implemented. Render = Callable[ ["RenderTreeNode", Any], str, ] Postprocess = Callable[[str, "RenderTreeNode", Any], str] mdformat-0.7.17/tests/000077500000000000000000000000001454065404400145675ustar00rootroot00000000000000mdformat-0.7.17/tests/__init__.py000066400000000000000000000000001454065404400166660ustar00rootroot00000000000000mdformat-0.7.17/tests/data/000077500000000000000000000000001454065404400155005ustar00rootroot00000000000000mdformat-0.7.17/tests/data/commonmark_spec_v0.30.json000066400000000000000000004222101454065404400223770ustar00rootroot00000000000000[ { "markdown": "\tfoo\tbaz\t\tbim\n", "html": "
foo\tbaz\t\tbim\n
\n", "example": 1, "start_line": 356, "end_line": 361, "section": "Tabs" }, { "markdown": " \tfoo\tbaz\t\tbim\n", "html": "
foo\tbaz\t\tbim\n
\n", "example": 2, "start_line": 363, "end_line": 368, "section": "Tabs" }, { "markdown": " a\ta\n ὐ\ta\n", "html": "
a\ta\nὐ\ta\n
\n", "example": 3, "start_line": 370, "end_line": 377, "section": "Tabs" }, { "markdown": " - foo\n\n\tbar\n", "html": "
    \n
  • \n

    foo

    \n

    bar

    \n
  • \n
\n", "example": 4, "start_line": 383, "end_line": 394, "section": "Tabs" }, { "markdown": "- foo\n\n\t\tbar\n", "html": "
    \n
  • \n

    foo

    \n
      bar\n
    \n
  • \n
\n", "example": 5, "start_line": 396, "end_line": 408, "section": "Tabs" }, { "markdown": ">\t\tfoo\n", "html": "
\n
  foo\n
\n
\n", "example": 6, "start_line": 419, "end_line": 426, "section": "Tabs" }, { "markdown": "-\t\tfoo\n", "html": "
    \n
  • \n
      foo\n
    \n
  • \n
\n", "example": 7, "start_line": 428, "end_line": 437, "section": "Tabs" }, { "markdown": " foo\n\tbar\n", "html": "
foo\nbar\n
\n", "example": 8, "start_line": 440, "end_line": 447, "section": "Tabs" }, { "markdown": " - foo\n - bar\n\t - baz\n", "html": "
    \n
  • foo\n
      \n
    • bar\n
        \n
      • baz
      • \n
      \n
    • \n
    \n
  • \n
\n", "example": 9, "start_line": 449, "end_line": 465, "section": "Tabs" }, { "markdown": "#\tFoo\n", "html": "

Foo

\n", "example": 10, "start_line": 467, "end_line": 471, "section": "Tabs" }, { "markdown": "*\t*\t*\t\n", "html": "
\n", "example": 11, "start_line": 473, "end_line": 477, "section": "Tabs" }, { "markdown": "\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~\n", "html": "

!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~

\n", "example": 12, "start_line": 490, "end_line": 494, "section": "Backslash escapes" }, { "markdown": "\\\t\\A\\a\\ \\3\\φ\\«\n", "html": "

\\\t\\A\\a\\ \\3\\φ\\«

\n", "example": 13, "start_line": 500, "end_line": 504, "section": "Backslash escapes" }, { "markdown": "\\*not emphasized*\n\\
not a tag\n\\[not a link](/foo)\n\\`not code`\n1\\. not a list\n\\* not a list\n\\# not a heading\n\\[foo]: /url \"not a reference\"\n\\ö not a character entity\n", "html": "

*not emphasized*\n<br/> not a tag\n[not a link](/foo)\n`not code`\n1. not a list\n* not a list\n# not a heading\n[foo]: /url "not a reference"\n&ouml; not a character entity

\n", "example": 14, "start_line": 510, "end_line": 530, "section": "Backslash escapes" }, { "markdown": "\\\\*emphasis*\n", "html": "

\\emphasis

\n", "example": 15, "start_line": 535, "end_line": 539, "section": "Backslash escapes" }, { "markdown": "foo\\\nbar\n", "html": "

foo
\nbar

\n", "example": 16, "start_line": 544, "end_line": 550, "section": "Backslash escapes" }, { "markdown": "`` \\[\\` ``\n", "html": "

\\[\\`

\n", "example": 17, "start_line": 556, "end_line": 560, "section": "Backslash escapes" }, { "markdown": " \\[\\]\n", "html": "
\\[\\]\n
\n", "example": 18, "start_line": 563, "end_line": 568, "section": "Backslash escapes" }, { "markdown": "~~~\n\\[\\]\n~~~\n", "html": "
\\[\\]\n
\n", "example": 19, "start_line": 571, "end_line": 578, "section": "Backslash escapes" }, { "markdown": "\n", "html": "

http://example.com?find=\\*

\n", "example": 20, "start_line": 581, "end_line": 585, "section": "Backslash escapes" }, { "markdown": "\n", "html": "\n", "example": 21, "start_line": 588, "end_line": 592, "section": "Backslash escapes" }, { "markdown": "[foo](/bar\\* \"ti\\*tle\")\n", "html": "

foo

\n", "example": 22, "start_line": 598, "end_line": 602, "section": "Backslash escapes" }, { "markdown": "[foo]\n\n[foo]: /bar\\* \"ti\\*tle\"\n", "html": "

foo

\n", "example": 23, "start_line": 605, "end_line": 611, "section": "Backslash escapes" }, { "markdown": "``` foo\\+bar\nfoo\n```\n", "html": "
foo\n
\n", "example": 24, "start_line": 614, "end_line": 621, "section": "Backslash escapes" }, { "markdown": "  & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸\n", "html": "

  & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸

\n", "example": 25, "start_line": 650, "end_line": 658, "section": "Entity and numeric character references" }, { "markdown": "# Ӓ Ϡ �\n", "html": "

# Ӓ Ϡ �

\n", "example": 26, "start_line": 669, "end_line": 673, "section": "Entity and numeric character references" }, { "markdown": "" ആ ಫ\n", "html": "

" ആ ಫ

\n", "example": 27, "start_line": 682, "end_line": 686, "section": "Entity and numeric character references" }, { "markdown": "  &x; &#; &#x;\n�\n&#abcdef0;\n&ThisIsNotDefined; &hi?;\n", "html": "

&nbsp &x; &#; &#x;\n&#87654321;\n&#abcdef0;\n&ThisIsNotDefined; &hi?;

\n", "example": 28, "start_line": 691, "end_line": 701, "section": "Entity and numeric character references" }, { "markdown": "©\n", "html": "

&copy

\n", "example": 29, "start_line": 708, "end_line": 712, "section": "Entity and numeric character references" }, { "markdown": "&MadeUpEntity;\n", "html": "

&MadeUpEntity;

\n", "example": 30, "start_line": 718, "end_line": 722, "section": "Entity and numeric character references" }, { "markdown": "\n", "html": "\n", "example": 31, "start_line": 729, "end_line": 733, "section": "Entity and numeric character references" }, { "markdown": "[foo](/föö \"föö\")\n", "html": "

foo

\n", "example": 32, "start_line": 736, "end_line": 740, "section": "Entity and numeric character references" }, { "markdown": "[foo]\n\n[foo]: /föö \"föö\"\n", "html": "

foo

\n", "example": 33, "start_line": 743, "end_line": 749, "section": "Entity and numeric character references" }, { "markdown": "``` föö\nfoo\n```\n", "html": "
foo\n
\n", "example": 34, "start_line": 752, "end_line": 759, "section": "Entity and numeric character references" }, { "markdown": "`föö`\n", "html": "

f&ouml;&ouml;

\n", "example": 35, "start_line": 765, "end_line": 769, "section": "Entity and numeric character references" }, { "markdown": " föfö\n", "html": "
f&ouml;f&ouml;\n
\n", "example": 36, "start_line": 772, "end_line": 777, "section": "Entity and numeric character references" }, { "markdown": "*foo*\n*foo*\n", "html": "

*foo*\nfoo

\n", "example": 37, "start_line": 784, "end_line": 790, "section": "Entity and numeric character references" }, { "markdown": "* foo\n\n* foo\n", "html": "

* foo

\n
    \n
  • foo
  • \n
\n", "example": 38, "start_line": 792, "end_line": 801, "section": "Entity and numeric character references" }, { "markdown": "foo bar\n", "html": "

foo\n\nbar

\n", "example": 39, "start_line": 803, "end_line": 809, "section": "Entity and numeric character references" }, { "markdown": " foo\n", "html": "

\tfoo

\n", "example": 40, "start_line": 811, "end_line": 815, "section": "Entity and numeric character references" }, { "markdown": "[a](url "tit")\n", "html": "

[a](url "tit")

\n", "example": 41, "start_line": 818, "end_line": 822, "section": "Entity and numeric character references" }, { "markdown": "- `one\n- two`\n", "html": "
    \n
  • `one
  • \n
  • two`
  • \n
\n", "example": 42, "start_line": 841, "end_line": 849, "section": "Precedence" }, { "markdown": "***\n---\n___\n", "html": "
\n
\n
\n", "example": 43, "start_line": 880, "end_line": 888, "section": "Thematic breaks" }, { "markdown": "+++\n", "html": "

+++

\n", "example": 44, "start_line": 893, "end_line": 897, "section": "Thematic breaks" }, { "markdown": "===\n", "html": "

===

\n", "example": 45, "start_line": 900, "end_line": 904, "section": "Thematic breaks" }, { "markdown": "--\n**\n__\n", "html": "

--\n**\n__

\n", "example": 46, "start_line": 909, "end_line": 917, "section": "Thematic breaks" }, { "markdown": " ***\n ***\n ***\n", "html": "
\n
\n
\n", "example": 47, "start_line": 922, "end_line": 930, "section": "Thematic breaks" }, { "markdown": " ***\n", "html": "
***\n
\n", "example": 48, "start_line": 935, "end_line": 940, "section": "Thematic breaks" }, { "markdown": "Foo\n ***\n", "html": "

Foo\n***

\n", "example": 49, "start_line": 943, "end_line": 949, "section": "Thematic breaks" }, { "markdown": "_____________________________________\n", "html": "
\n", "example": 50, "start_line": 954, "end_line": 958, "section": "Thematic breaks" }, { "markdown": " - - -\n", "html": "
\n", "example": 51, "start_line": 963, "end_line": 967, "section": "Thematic breaks" }, { "markdown": " ** * ** * ** * **\n", "html": "
\n", "example": 52, "start_line": 970, "end_line": 974, "section": "Thematic breaks" }, { "markdown": "- - - -\n", "html": "
\n", "example": 53, "start_line": 977, "end_line": 981, "section": "Thematic breaks" }, { "markdown": "- - - - \n", "html": "
\n", "example": 54, "start_line": 986, "end_line": 990, "section": "Thematic breaks" }, { "markdown": "_ _ _ _ a\n\na------\n\n---a---\n", "html": "

_ _ _ _ a

\n

a------

\n

---a---

\n", "example": 55, "start_line": 995, "end_line": 1005, "section": "Thematic breaks" }, { "markdown": " *-*\n", "html": "

-

\n", "example": 56, "start_line": 1011, "end_line": 1015, "section": "Thematic breaks" }, { "markdown": "- foo\n***\n- bar\n", "html": "
    \n
  • foo
  • \n
\n
\n
    \n
  • bar
  • \n
\n", "example": 57, "start_line": 1020, "end_line": 1032, "section": "Thematic breaks" }, { "markdown": "Foo\n***\nbar\n", "html": "

Foo

\n
\n

bar

\n", "example": 58, "start_line": 1037, "end_line": 1045, "section": "Thematic breaks" }, { "markdown": "Foo\n---\nbar\n", "html": "

Foo

\n

bar

\n", "example": 59, "start_line": 1054, "end_line": 1061, "section": "Thematic breaks" }, { "markdown": "* Foo\n* * *\n* Bar\n", "html": "
    \n
  • Foo
  • \n
\n
\n
    \n
  • Bar
  • \n
\n", "example": 60, "start_line": 1067, "end_line": 1079, "section": "Thematic breaks" }, { "markdown": "- Foo\n- * * *\n", "html": "
    \n
  • Foo
  • \n
  • \n
    \n
  • \n
\n", "example": 61, "start_line": 1084, "end_line": 1094, "section": "Thematic breaks" }, { "markdown": "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n", "html": "

foo

\n

foo

\n

foo

\n

foo

\n
foo
\n
foo
\n", "example": 62, "start_line": 1113, "end_line": 1127, "section": "ATX headings" }, { "markdown": "####### foo\n", "html": "

####### foo

\n", "example": 63, "start_line": 1132, "end_line": 1136, "section": "ATX headings" }, { "markdown": "#5 bolt\n\n#hashtag\n", "html": "

#5 bolt

\n

#hashtag

\n", "example": 64, "start_line": 1147, "end_line": 1154, "section": "ATX headings" }, { "markdown": "\\## foo\n", "html": "

## foo

\n", "example": 65, "start_line": 1159, "end_line": 1163, "section": "ATX headings" }, { "markdown": "# foo *bar* \\*baz\\*\n", "html": "

foo bar *baz*

\n", "example": 66, "start_line": 1168, "end_line": 1172, "section": "ATX headings" }, { "markdown": "# foo \n", "html": "

foo

\n", "example": 67, "start_line": 1177, "end_line": 1181, "section": "ATX headings" }, { "markdown": " ### foo\n ## foo\n # foo\n", "html": "

foo

\n

foo

\n

foo

\n", "example": 68, "start_line": 1186, "end_line": 1194, "section": "ATX headings" }, { "markdown": " # foo\n", "html": "
# foo\n
\n", "example": 69, "start_line": 1199, "end_line": 1204, "section": "ATX headings" }, { "markdown": "foo\n # bar\n", "html": "

foo\n# bar

\n", "example": 70, "start_line": 1207, "end_line": 1213, "section": "ATX headings" }, { "markdown": "## foo ##\n ### bar ###\n", "html": "

foo

\n

bar

\n", "example": 71, "start_line": 1218, "end_line": 1224, "section": "ATX headings" }, { "markdown": "# foo ##################################\n##### foo ##\n", "html": "

foo

\n
foo
\n", "example": 72, "start_line": 1229, "end_line": 1235, "section": "ATX headings" }, { "markdown": "### foo ### \n", "html": "

foo

\n", "example": 73, "start_line": 1240, "end_line": 1244, "section": "ATX headings" }, { "markdown": "### foo ### b\n", "html": "

foo ### b

\n", "example": 74, "start_line": 1251, "end_line": 1255, "section": "ATX headings" }, { "markdown": "# foo#\n", "html": "

foo#

\n", "example": 75, "start_line": 1260, "end_line": 1264, "section": "ATX headings" }, { "markdown": "### foo \\###\n## foo #\\##\n# foo \\#\n", "html": "

foo ###

\n

foo ###

\n

foo #

\n", "example": 76, "start_line": 1270, "end_line": 1278, "section": "ATX headings" }, { "markdown": "****\n## foo\n****\n", "html": "
\n

foo

\n
\n", "example": 77, "start_line": 1284, "end_line": 1292, "section": "ATX headings" }, { "markdown": "Foo bar\n# baz\nBar foo\n", "html": "

Foo bar

\n

baz

\n

Bar foo

\n", "example": 78, "start_line": 1295, "end_line": 1303, "section": "ATX headings" }, { "markdown": "## \n#\n### ###\n", "html": "

\n

\n

\n", "example": 79, "start_line": 1308, "end_line": 1316, "section": "ATX headings" }, { "markdown": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n", "html": "

Foo bar

\n

Foo bar

\n", "example": 80, "start_line": 1351, "end_line": 1360, "section": "Setext headings" }, { "markdown": "Foo *bar\nbaz*\n====\n", "html": "

Foo bar\nbaz

\n", "example": 81, "start_line": 1365, "end_line": 1372, "section": "Setext headings" }, { "markdown": " Foo *bar\nbaz*\t\n====\n", "html": "

Foo bar\nbaz

\n", "example": 82, "start_line": 1379, "end_line": 1386, "section": "Setext headings" }, { "markdown": "Foo\n-------------------------\n\nFoo\n=\n", "html": "

Foo

\n

Foo

\n", "example": 83, "start_line": 1391, "end_line": 1400, "section": "Setext headings" }, { "markdown": " Foo\n---\n\n Foo\n-----\n\n Foo\n ===\n", "html": "

Foo

\n

Foo

\n

Foo

\n", "example": 84, "start_line": 1406, "end_line": 1419, "section": "Setext headings" }, { "markdown": " Foo\n ---\n\n Foo\n---\n", "html": "
Foo\n---\n\nFoo\n
\n
\n", "example": 85, "start_line": 1424, "end_line": 1437, "section": "Setext headings" }, { "markdown": "Foo\n ---- \n", "html": "

Foo

\n", "example": 86, "start_line": 1443, "end_line": 1448, "section": "Setext headings" }, { "markdown": "Foo\n ---\n", "html": "

Foo\n---

\n", "example": 87, "start_line": 1453, "end_line": 1459, "section": "Setext headings" }, { "markdown": "Foo\n= =\n\nFoo\n--- -\n", "html": "

Foo\n= =

\n

Foo

\n
\n", "example": 88, "start_line": 1464, "end_line": 1475, "section": "Setext headings" }, { "markdown": "Foo \n-----\n", "html": "

Foo

\n", "example": 89, "start_line": 1480, "end_line": 1485, "section": "Setext headings" }, { "markdown": "Foo\\\n----\n", "html": "

Foo\\

\n", "example": 90, "start_line": 1490, "end_line": 1495, "section": "Setext headings" }, { "markdown": "`Foo\n----\n`\n\n\n", "html": "

`Foo

\n

`

\n

<a title="a lot

\n

of dashes"/>

\n", "example": 91, "start_line": 1501, "end_line": 1514, "section": "Setext headings" }, { "markdown": "> Foo\n---\n", "html": "
\n

Foo

\n
\n
\n", "example": 92, "start_line": 1520, "end_line": 1528, "section": "Setext headings" }, { "markdown": "> foo\nbar\n===\n", "html": "
\n

foo\nbar\n===

\n
\n", "example": 93, "start_line": 1531, "end_line": 1541, "section": "Setext headings" }, { "markdown": "- Foo\n---\n", "html": "
    \n
  • Foo
  • \n
\n
\n", "example": 94, "start_line": 1544, "end_line": 1552, "section": "Setext headings" }, { "markdown": "Foo\nBar\n---\n", "html": "

Foo\nBar

\n", "example": 95, "start_line": 1559, "end_line": 1566, "section": "Setext headings" }, { "markdown": "---\nFoo\n---\nBar\n---\nBaz\n", "html": "
\n

Foo

\n

Bar

\n

Baz

\n", "example": 96, "start_line": 1572, "end_line": 1584, "section": "Setext headings" }, { "markdown": "\n====\n", "html": "

====

\n", "example": 97, "start_line": 1589, "end_line": 1594, "section": "Setext headings" }, { "markdown": "---\n---\n", "html": "
\n
\n", "example": 98, "start_line": 1601, "end_line": 1607, "section": "Setext headings" }, { "markdown": "- foo\n-----\n", "html": "
    \n
  • foo
  • \n
\n
\n", "example": 99, "start_line": 1610, "end_line": 1618, "section": "Setext headings" }, { "markdown": " foo\n---\n", "html": "
foo\n
\n
\n", "example": 100, "start_line": 1621, "end_line": 1628, "section": "Setext headings" }, { "markdown": "> foo\n-----\n", "html": "
\n

foo

\n
\n
\n", "example": 101, "start_line": 1631, "end_line": 1639, "section": "Setext headings" }, { "markdown": "\\> foo\n------\n", "html": "

> foo

\n", "example": 102, "start_line": 1645, "end_line": 1650, "section": "Setext headings" }, { "markdown": "Foo\n\nbar\n---\nbaz\n", "html": "

Foo

\n

bar

\n

baz

\n", "example": 103, "start_line": 1676, "end_line": 1686, "section": "Setext headings" }, { "markdown": "Foo\nbar\n\n---\n\nbaz\n", "html": "

Foo\nbar

\n
\n

baz

\n", "example": 104, "start_line": 1692, "end_line": 1704, "section": "Setext headings" }, { "markdown": "Foo\nbar\n* * *\nbaz\n", "html": "

Foo\nbar

\n
\n

baz

\n", "example": 105, "start_line": 1710, "end_line": 1720, "section": "Setext headings" }, { "markdown": "Foo\nbar\n\\---\nbaz\n", "html": "

Foo\nbar\n---\nbaz

\n", "example": 106, "start_line": 1725, "end_line": 1735, "section": "Setext headings" }, { "markdown": " a simple\n indented code block\n", "html": "
a simple\n  indented code block\n
\n", "example": 107, "start_line": 1753, "end_line": 1760, "section": "Indented code blocks" }, { "markdown": " - foo\n\n bar\n", "html": "
    \n
  • \n

    foo

    \n

    bar

    \n
  • \n
\n", "example": 108, "start_line": 1767, "end_line": 1778, "section": "Indented code blocks" }, { "markdown": "1. foo\n\n - bar\n", "html": "
    \n
  1. \n

    foo

    \n
      \n
    • bar
    • \n
    \n
  2. \n
\n", "example": 109, "start_line": 1781, "end_line": 1794, "section": "Indented code blocks" }, { "markdown": "
\n *hi*\n\n - one\n", "html": "
<a/>\n*hi*\n\n- one\n
\n", "example": 110, "start_line": 1801, "end_line": 1812, "section": "Indented code blocks" }, { "markdown": " chunk1\n\n chunk2\n \n \n \n chunk3\n", "html": "
chunk1\n\nchunk2\n\n\n\nchunk3\n
\n", "example": 111, "start_line": 1817, "end_line": 1834, "section": "Indented code blocks" }, { "markdown": " chunk1\n \n chunk2\n", "html": "
chunk1\n  \n  chunk2\n
\n", "example": 112, "start_line": 1840, "end_line": 1849, "section": "Indented code blocks" }, { "markdown": "Foo\n bar\n\n", "html": "

Foo\nbar

\n", "example": 113, "start_line": 1855, "end_line": 1862, "section": "Indented code blocks" }, { "markdown": " foo\nbar\n", "html": "
foo\n
\n

bar

\n", "example": 114, "start_line": 1869, "end_line": 1876, "section": "Indented code blocks" }, { "markdown": "# Heading\n foo\nHeading\n------\n foo\n----\n", "html": "

Heading

\n
foo\n
\n

Heading

\n
foo\n
\n
\n", "example": 115, "start_line": 1882, "end_line": 1897, "section": "Indented code blocks" }, { "markdown": " foo\n bar\n", "html": "
    foo\nbar\n
\n", "example": 116, "start_line": 1902, "end_line": 1909, "section": "Indented code blocks" }, { "markdown": "\n \n foo\n \n\n", "html": "
foo\n
\n", "example": 117, "start_line": 1915, "end_line": 1924, "section": "Indented code blocks" }, { "markdown": " foo \n", "html": "
foo  \n
\n", "example": 118, "start_line": 1929, "end_line": 1934, "section": "Indented code blocks" }, { "markdown": "```\n<\n >\n```\n", "html": "
<\n >\n
\n", "example": 119, "start_line": 1984, "end_line": 1993, "section": "Fenced code blocks" }, { "markdown": "~~~\n<\n >\n~~~\n", "html": "
<\n >\n
\n", "example": 120, "start_line": 1998, "end_line": 2007, "section": "Fenced code blocks" }, { "markdown": "``\nfoo\n``\n", "html": "

foo

\n", "example": 121, "start_line": 2011, "end_line": 2017, "section": "Fenced code blocks" }, { "markdown": "```\naaa\n~~~\n```\n", "html": "
aaa\n~~~\n
\n", "example": 122, "start_line": 2022, "end_line": 2031, "section": "Fenced code blocks" }, { "markdown": "~~~\naaa\n```\n~~~\n", "html": "
aaa\n```\n
\n", "example": 123, "start_line": 2034, "end_line": 2043, "section": "Fenced code blocks" }, { "markdown": "````\naaa\n```\n``````\n", "html": "
aaa\n```\n
\n", "example": 124, "start_line": 2048, "end_line": 2057, "section": "Fenced code blocks" }, { "markdown": "~~~~\naaa\n~~~\n~~~~\n", "html": "
aaa\n~~~\n
\n", "example": 125, "start_line": 2060, "end_line": 2069, "section": "Fenced code blocks" }, { "markdown": "```\n", "html": "
\n", "example": 126, "start_line": 2075, "end_line": 2079, "section": "Fenced code blocks" }, { "markdown": "`````\n\n```\naaa\n", "html": "
\n```\naaa\n
\n", "example": 127, "start_line": 2082, "end_line": 2092, "section": "Fenced code blocks" }, { "markdown": "> ```\n> aaa\n\nbbb\n", "html": "
\n
aaa\n
\n
\n

bbb

\n", "example": 128, "start_line": 2095, "end_line": 2106, "section": "Fenced code blocks" }, { "markdown": "```\n\n \n```\n", "html": "
\n  \n
\n", "example": 129, "start_line": 2111, "end_line": 2120, "section": "Fenced code blocks" }, { "markdown": "```\n```\n", "html": "
\n", "example": 130, "start_line": 2125, "end_line": 2130, "section": "Fenced code blocks" }, { "markdown": " ```\n aaa\naaa\n```\n", "html": "
aaa\naaa\n
\n", "example": 131, "start_line": 2137, "end_line": 2146, "section": "Fenced code blocks" }, { "markdown": " ```\naaa\n aaa\naaa\n ```\n", "html": "
aaa\naaa\naaa\n
\n", "example": 132, "start_line": 2149, "end_line": 2160, "section": "Fenced code blocks" }, { "markdown": " ```\n aaa\n aaa\n aaa\n ```\n", "html": "
aaa\n aaa\naaa\n
\n", "example": 133, "start_line": 2163, "end_line": 2174, "section": "Fenced code blocks" }, { "markdown": " ```\n aaa\n ```\n", "html": "
```\naaa\n```\n
\n", "example": 134, "start_line": 2179, "end_line": 2188, "section": "Fenced code blocks" }, { "markdown": "```\naaa\n ```\n", "html": "
aaa\n
\n", "example": 135, "start_line": 2194, "end_line": 2201, "section": "Fenced code blocks" }, { "markdown": " ```\naaa\n ```\n", "html": "
aaa\n
\n", "example": 136, "start_line": 2204, "end_line": 2211, "section": "Fenced code blocks" }, { "markdown": "```\naaa\n ```\n", "html": "
aaa\n    ```\n
\n", "example": 137, "start_line": 2216, "end_line": 2224, "section": "Fenced code blocks" }, { "markdown": "``` ```\naaa\n", "html": "

\naaa

\n", "example": 138, "start_line": 2230, "end_line": 2236, "section": "Fenced code blocks" }, { "markdown": "~~~~~~\naaa\n~~~ ~~\n", "html": "
aaa\n~~~ ~~\n
\n", "example": 139, "start_line": 2239, "end_line": 2247, "section": "Fenced code blocks" }, { "markdown": "foo\n```\nbar\n```\nbaz\n", "html": "

foo

\n
bar\n
\n

baz

\n", "example": 140, "start_line": 2253, "end_line": 2264, "section": "Fenced code blocks" }, { "markdown": "foo\n---\n~~~\nbar\n~~~\n# baz\n", "html": "

foo

\n
bar\n
\n

baz

\n", "example": 141, "start_line": 2270, "end_line": 2282, "section": "Fenced code blocks" }, { "markdown": "```ruby\ndef foo(x)\n return 3\nend\n```\n", "html": "
def foo(x)\n  return 3\nend\n
\n", "example": 142, "start_line": 2292, "end_line": 2303, "section": "Fenced code blocks" }, { "markdown": "~~~~ ruby startline=3 $%@#$\ndef foo(x)\n return 3\nend\n~~~~~~~\n", "html": "
def foo(x)\n  return 3\nend\n
\n", "example": 143, "start_line": 2306, "end_line": 2317, "section": "Fenced code blocks" }, { "markdown": "````;\n````\n", "html": "
\n", "example": 144, "start_line": 2320, "end_line": 2325, "section": "Fenced code blocks" }, { "markdown": "``` aa ```\nfoo\n", "html": "

aa\nfoo

\n", "example": 145, "start_line": 2330, "end_line": 2336, "section": "Fenced code blocks" }, { "markdown": "~~~ aa ``` ~~~\nfoo\n~~~\n", "html": "
foo\n
\n", "example": 146, "start_line": 2341, "end_line": 2348, "section": "Fenced code blocks" }, { "markdown": "```\n``` aaa\n```\n", "html": "
``` aaa\n
\n", "example": 147, "start_line": 2353, "end_line": 2360, "section": "Fenced code blocks" }, { "markdown": "
\n
\n**Hello**,\n\n_world_.\n
\n
\n", "html": "
\n
\n**Hello**,\n

world.\n

\n
\n", "example": 148, "start_line": 2432, "end_line": 2447, "section": "HTML blocks" }, { "markdown": "\n \n \n \n
\n hi\n
\n\nokay.\n", "html": "\n \n \n \n
\n hi\n
\n

okay.

\n", "example": 149, "start_line": 2461, "end_line": 2480, "section": "HTML blocks" }, { "markdown": "
\n*foo*\n", "example": 151, "start_line": 2496, "end_line": 2502, "section": "HTML blocks" }, { "markdown": "
\n\n*Markdown*\n\n
\n", "html": "
\n

Markdown

\n
\n", "example": 152, "start_line": 2507, "end_line": 2517, "section": "HTML blocks" }, { "markdown": "
\n
\n", "html": "
\n
\n", "example": 153, "start_line": 2523, "end_line": 2531, "section": "HTML blocks" }, { "markdown": "
\n
\n", "html": "
\n
\n", "example": 154, "start_line": 2534, "end_line": 2542, "section": "HTML blocks" }, { "markdown": "
\n*foo*\n\n*bar*\n", "html": "
\n*foo*\n

bar

\n", "example": 155, "start_line": 2546, "end_line": 2555, "section": "HTML blocks" }, { "markdown": "
\n", "html": "\n", "example": 159, "start_line": 2595, "end_line": 2599, "section": "HTML blocks" }, { "markdown": "
\nfoo\n
\n", "html": "
\nfoo\n
\n", "example": 160, "start_line": 2602, "end_line": 2610, "section": "HTML blocks" }, { "markdown": "
\n``` c\nint x = 33;\n```\n", "html": "
\n``` c\nint x = 33;\n```\n", "example": 161, "start_line": 2619, "end_line": 2629, "section": "HTML blocks" }, { "markdown": "\n*bar*\n\n", "html": "\n*bar*\n\n", "example": 162, "start_line": 2636, "end_line": 2644, "section": "HTML blocks" }, { "markdown": "\n*bar*\n\n", "html": "\n*bar*\n\n", "example": 163, "start_line": 2649, "end_line": 2657, "section": "HTML blocks" }, { "markdown": "\n*bar*\n\n", "html": "\n*bar*\n\n", "example": 164, "start_line": 2660, "end_line": 2668, "section": "HTML blocks" }, { "markdown": "\n*bar*\n", "html": "\n*bar*\n", "example": 165, "start_line": 2671, "end_line": 2677, "section": "HTML blocks" }, { "markdown": "\n*foo*\n\n", "html": "\n*foo*\n\n", "example": 166, "start_line": 2686, "end_line": 2694, "section": "HTML blocks" }, { "markdown": "\n\n*foo*\n\n\n", "html": "\n

foo

\n
\n", "example": 167, "start_line": 2701, "end_line": 2711, "section": "HTML blocks" }, { "markdown": "*foo*\n", "html": "

foo

\n", "example": 168, "start_line": 2719, "end_line": 2723, "section": "HTML blocks" }, { "markdown": "
\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n
\nokay\n", "html": "
\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n
\n

okay

\n", "example": 169, "start_line": 2735, "end_line": 2751, "section": "HTML blocks" }, { "markdown": "\nokay\n", "html": "\n

okay

\n", "example": 170, "start_line": 2756, "end_line": 2770, "section": "HTML blocks" }, { "markdown": "\n", "html": "\n", "example": 171, "start_line": 2775, "end_line": 2791, "section": "HTML blocks" }, { "markdown": "\nh1 {color:red;}\n\np {color:blue;}\n\nokay\n", "html": "\nh1 {color:red;}\n\np {color:blue;}\n\n

okay

\n", "example": 172, "start_line": 2795, "end_line": 2811, "section": "HTML blocks" }, { "markdown": "\n\nfoo\n", "html": "\n\nfoo\n", "example": 173, "start_line": 2818, "end_line": 2828, "section": "HTML blocks" }, { "markdown": ">
\n> foo\n\nbar\n", "html": "
\n
\nfoo\n
\n

bar

\n", "example": 174, "start_line": 2831, "end_line": 2842, "section": "HTML blocks" }, { "markdown": "-
\n- foo\n", "html": "
    \n
  • \n
    \n
  • \n
  • foo
  • \n
\n", "example": 175, "start_line": 2845, "end_line": 2855, "section": "HTML blocks" }, { "markdown": "\n*foo*\n", "html": "\n

foo

\n", "example": 176, "start_line": 2860, "end_line": 2866, "section": "HTML blocks" }, { "markdown": "*bar*\n*baz*\n", "html": "*bar*\n

baz

\n", "example": 177, "start_line": 2869, "end_line": 2875, "section": "HTML blocks" }, { "markdown": "1. *bar*\n", "html": "1. *bar*\n", "example": 178, "start_line": 2881, "end_line": 2889, "section": "HTML blocks" }, { "markdown": "\nokay\n", "html": "\n

okay

\n", "example": 179, "start_line": 2894, "end_line": 2906, "section": "HTML blocks" }, { "markdown": "';\n\n?>\nokay\n", "html": "';\n\n?>\n

okay

\n", "example": 180, "start_line": 2912, "end_line": 2926, "section": "HTML blocks" }, { "markdown": "\n", "html": "\n", "example": 181, "start_line": 2931, "end_line": 2935, "section": "HTML blocks" }, { "markdown": "\nokay\n", "html": "\n

okay

\n", "example": 182, "start_line": 2940, "end_line": 2968, "section": "HTML blocks" }, { "markdown": " \n\n \n", "html": " \n
<!-- foo -->\n
\n", "example": 183, "start_line": 2974, "end_line": 2982, "section": "HTML blocks" }, { "markdown": "
\n\n
\n", "html": "
\n
<div>\n
\n", "example": 184, "start_line": 2985, "end_line": 2993, "section": "HTML blocks" }, { "markdown": "Foo\n
\nbar\n
\n", "html": "

Foo

\n
\nbar\n
\n", "example": 185, "start_line": 2999, "end_line": 3009, "section": "HTML blocks" }, { "markdown": "
\nbar\n
\n*foo*\n", "html": "
\nbar\n
\n*foo*\n", "example": 186, "start_line": 3016, "end_line": 3026, "section": "HTML blocks" }, { "markdown": "Foo\n\nbaz\n", "html": "

Foo\n\nbaz

\n", "example": 187, "start_line": 3031, "end_line": 3039, "section": "HTML blocks" }, { "markdown": "
\n\n*Emphasized* text.\n\n
\n", "html": "
\n

Emphasized text.

\n
\n", "example": 188, "start_line": 3072, "end_line": 3082, "section": "HTML blocks" }, { "markdown": "
\n*Emphasized* text.\n
\n", "html": "
\n*Emphasized* text.\n
\n", "example": 189, "start_line": 3085, "end_line": 3093, "section": "HTML blocks" }, { "markdown": "\n\n\n\n\n\n\n\n
\nHi\n
\n", "html": "\n\n\n\n
\nHi\n
\n", "example": 190, "start_line": 3107, "end_line": 3127, "section": "HTML blocks" }, { "markdown": "\n\n \n\n \n\n \n\n
\n Hi\n
\n", "html": "\n \n
<td>\n  Hi\n</td>\n
\n \n
\n", "example": 191, "start_line": 3134, "end_line": 3155, "section": "HTML blocks" }, { "markdown": "[foo]: /url \"title\"\n\n[foo]\n", "html": "

foo

\n", "example": 192, "start_line": 3183, "end_line": 3189, "section": "Link reference definitions" }, { "markdown": " [foo]: \n /url \n 'the title' \n\n[foo]\n", "html": "

foo

\n", "example": 193, "start_line": 3192, "end_line": 3200, "section": "Link reference definitions" }, { "markdown": "[Foo*bar\\]]:my_(url) 'title (with parens)'\n\n[Foo*bar\\]]\n", "html": "

Foo*bar]

\n", "example": 194, "start_line": 3203, "end_line": 3209, "section": "Link reference definitions" }, { "markdown": "[Foo bar]:\n\n'title'\n\n[Foo bar]\n", "html": "

Foo bar

\n", "example": 195, "start_line": 3212, "end_line": 3220, "section": "Link reference definitions" }, { "markdown": "[foo]: /url '\ntitle\nline1\nline2\n'\n\n[foo]\n", "html": "

foo

\n", "example": 196, "start_line": 3225, "end_line": 3239, "section": "Link reference definitions" }, { "markdown": "[foo]: /url 'title\n\nwith blank line'\n\n[foo]\n", "html": "

[foo]: /url 'title

\n

with blank line'

\n

[foo]

\n", "example": 197, "start_line": 3244, "end_line": 3254, "section": "Link reference definitions" }, { "markdown": "[foo]:\n/url\n\n[foo]\n", "html": "

foo

\n", "example": 198, "start_line": 3259, "end_line": 3266, "section": "Link reference definitions" }, { "markdown": "[foo]:\n\n[foo]\n", "html": "

[foo]:

\n

[foo]

\n", "example": 199, "start_line": 3271, "end_line": 3278, "section": "Link reference definitions" }, { "markdown": "[foo]: <>\n\n[foo]\n", "html": "

foo

\n", "example": 200, "start_line": 3283, "end_line": 3289, "section": "Link reference definitions" }, { "markdown": "[foo]: (baz)\n\n[foo]\n", "html": "

[foo]: (baz)

\n

[foo]

\n", "example": 201, "start_line": 3294, "end_line": 3301, "section": "Link reference definitions" }, { "markdown": "[foo]: /url\\bar\\*baz \"foo\\\"bar\\baz\"\n\n[foo]\n", "html": "

foo

\n", "example": 202, "start_line": 3307, "end_line": 3313, "section": "Link reference definitions" }, { "markdown": "[foo]\n\n[foo]: url\n", "html": "

foo

\n", "example": 203, "start_line": 3318, "end_line": 3324, "section": "Link reference definitions" }, { "markdown": "[foo]\n\n[foo]: first\n[foo]: second\n", "html": "

foo

\n", "example": 204, "start_line": 3330, "end_line": 3337, "section": "Link reference definitions" }, { "markdown": "[FOO]: /url\n\n[Foo]\n", "html": "

Foo

\n", "example": 205, "start_line": 3343, "end_line": 3349, "section": "Link reference definitions" }, { "markdown": "[ΑΓΩ]: /φου\n\n[αγω]\n", "html": "

αγω

\n", "example": 206, "start_line": 3352, "end_line": 3358, "section": "Link reference definitions" }, { "markdown": "[foo]: /url\n", "html": "", "example": 207, "start_line": 3367, "end_line": 3370, "section": "Link reference definitions" }, { "markdown": "[\nfoo\n]: /url\nbar\n", "html": "

bar

\n", "example": 208, "start_line": 3375, "end_line": 3382, "section": "Link reference definitions" }, { "markdown": "[foo]: /url \"title\" ok\n", "html": "

[foo]: /url "title" ok

\n", "example": 209, "start_line": 3388, "end_line": 3392, "section": "Link reference definitions" }, { "markdown": "[foo]: /url\n\"title\" ok\n", "html": "

"title" ok

\n", "example": 210, "start_line": 3397, "end_line": 3402, "section": "Link reference definitions" }, { "markdown": " [foo]: /url \"title\"\n\n[foo]\n", "html": "
[foo]: /url "title"\n
\n

[foo]

\n", "example": 211, "start_line": 3408, "end_line": 3416, "section": "Link reference definitions" }, { "markdown": "```\n[foo]: /url\n```\n\n[foo]\n", "html": "
[foo]: /url\n
\n

[foo]

\n", "example": 212, "start_line": 3422, "end_line": 3432, "section": "Link reference definitions" }, { "markdown": "Foo\n[bar]: /baz\n\n[bar]\n", "html": "

Foo\n[bar]: /baz

\n

[bar]

\n", "example": 213, "start_line": 3437, "end_line": 3446, "section": "Link reference definitions" }, { "markdown": "# [Foo]\n[foo]: /url\n> bar\n", "html": "

Foo

\n
\n

bar

\n
\n", "example": 214, "start_line": 3452, "end_line": 3461, "section": "Link reference definitions" }, { "markdown": "[foo]: /url\nbar\n===\n[foo]\n", "html": "

bar

\n

foo

\n", "example": 215, "start_line": 3463, "end_line": 3471, "section": "Link reference definitions" }, { "markdown": "[foo]: /url\n===\n[foo]\n", "html": "

===\nfoo

\n", "example": 216, "start_line": 3473, "end_line": 3480, "section": "Link reference definitions" }, { "markdown": "[foo]: /foo-url \"foo\"\n[bar]: /bar-url\n \"bar\"\n[baz]: /baz-url\n\n[foo],\n[bar],\n[baz]\n", "html": "

foo,\nbar,\nbaz

\n", "example": 217, "start_line": 3486, "end_line": 3499, "section": "Link reference definitions" }, { "markdown": "[foo]\n\n> [foo]: /url\n", "html": "

foo

\n
\n
\n", "example": 218, "start_line": 3507, "end_line": 3515, "section": "Link reference definitions" }, { "markdown": "aaa\n\nbbb\n", "html": "

aaa

\n

bbb

\n", "example": 219, "start_line": 3529, "end_line": 3536, "section": "Paragraphs" }, { "markdown": "aaa\nbbb\n\nccc\nddd\n", "html": "

aaa\nbbb

\n

ccc\nddd

\n", "example": 220, "start_line": 3541, "end_line": 3552, "section": "Paragraphs" }, { "markdown": "aaa\n\n\nbbb\n", "html": "

aaa

\n

bbb

\n", "example": 221, "start_line": 3557, "end_line": 3565, "section": "Paragraphs" }, { "markdown": " aaa\n bbb\n", "html": "

aaa\nbbb

\n", "example": 222, "start_line": 3570, "end_line": 3576, "section": "Paragraphs" }, { "markdown": "aaa\n bbb\n ccc\n", "html": "

aaa\nbbb\nccc

\n", "example": 223, "start_line": 3582, "end_line": 3590, "section": "Paragraphs" }, { "markdown": " aaa\nbbb\n", "html": "

aaa\nbbb

\n", "example": 224, "start_line": 3596, "end_line": 3602, "section": "Paragraphs" }, { "markdown": " aaa\nbbb\n", "html": "
aaa\n
\n

bbb

\n", "example": 225, "start_line": 3605, "end_line": 3612, "section": "Paragraphs" }, { "markdown": "aaa \nbbb \n", "html": "

aaa
\nbbb

\n", "example": 226, "start_line": 3619, "end_line": 3625, "section": "Paragraphs" }, { "markdown": " \n\naaa\n \n\n# aaa\n\n \n", "html": "

aaa

\n

aaa

\n", "example": 227, "start_line": 3636, "end_line": 3648, "section": "Blank lines" }, { "markdown": "> # Foo\n> bar\n> baz\n", "html": "
\n

Foo

\n

bar\nbaz

\n
\n", "example": 228, "start_line": 3704, "end_line": 3714, "section": "Block quotes" }, { "markdown": "># Foo\n>bar\n> baz\n", "html": "
\n

Foo

\n

bar\nbaz

\n
\n", "example": 229, "start_line": 3719, "end_line": 3729, "section": "Block quotes" }, { "markdown": " > # Foo\n > bar\n > baz\n", "html": "
\n

Foo

\n

bar\nbaz

\n
\n", "example": 230, "start_line": 3734, "end_line": 3744, "section": "Block quotes" }, { "markdown": " > # Foo\n > bar\n > baz\n", "html": "
> # Foo\n> bar\n> baz\n
\n", "example": 231, "start_line": 3749, "end_line": 3758, "section": "Block quotes" }, { "markdown": "> # Foo\n> bar\nbaz\n", "html": "
\n

Foo

\n

bar\nbaz

\n
\n", "example": 232, "start_line": 3764, "end_line": 3774, "section": "Block quotes" }, { "markdown": "> bar\nbaz\n> foo\n", "html": "
\n

bar\nbaz\nfoo

\n
\n", "example": 233, "start_line": 3780, "end_line": 3790, "section": "Block quotes" }, { "markdown": "> foo\n---\n", "html": "
\n

foo

\n
\n
\n", "example": 234, "start_line": 3804, "end_line": 3812, "section": "Block quotes" }, { "markdown": "> - foo\n- bar\n", "html": "
\n
    \n
  • foo
  • \n
\n
\n
    \n
  • bar
  • \n
\n", "example": 235, "start_line": 3824, "end_line": 3836, "section": "Block quotes" }, { "markdown": "> foo\n bar\n", "html": "
\n
foo\n
\n
\n
bar\n
\n", "example": 236, "start_line": 3842, "end_line": 3852, "section": "Block quotes" }, { "markdown": "> ```\nfoo\n```\n", "html": "
\n
\n
\n

foo

\n
\n", "example": 237, "start_line": 3855, "end_line": 3865, "section": "Block quotes" }, { "markdown": "> foo\n - bar\n", "html": "
\n

foo\n- bar

\n
\n", "example": 238, "start_line": 3871, "end_line": 3879, "section": "Block quotes" }, { "markdown": ">\n", "html": "
\n
\n", "example": 239, "start_line": 3895, "end_line": 3900, "section": "Block quotes" }, { "markdown": ">\n> \n> \n", "html": "
\n
\n", "example": 240, "start_line": 3903, "end_line": 3910, "section": "Block quotes" }, { "markdown": ">\n> foo\n> \n", "html": "
\n

foo

\n
\n", "example": 241, "start_line": 3915, "end_line": 3923, "section": "Block quotes" }, { "markdown": "> foo\n\n> bar\n", "html": "
\n

foo

\n
\n
\n

bar

\n
\n", "example": 242, "start_line": 3928, "end_line": 3939, "section": "Block quotes" }, { "markdown": "> foo\n> bar\n", "html": "
\n

foo\nbar

\n
\n", "example": 243, "start_line": 3950, "end_line": 3958, "section": "Block quotes" }, { "markdown": "> foo\n>\n> bar\n", "html": "
\n

foo

\n

bar

\n
\n", "example": 244, "start_line": 3963, "end_line": 3972, "section": "Block quotes" }, { "markdown": "foo\n> bar\n", "html": "

foo

\n
\n

bar

\n
\n", "example": 245, "start_line": 3977, "end_line": 3985, "section": "Block quotes" }, { "markdown": "> aaa\n***\n> bbb\n", "html": "
\n

aaa

\n
\n
\n
\n

bbb

\n
\n", "example": 246, "start_line": 3991, "end_line": 4003, "section": "Block quotes" }, { "markdown": "> bar\nbaz\n", "html": "
\n

bar\nbaz

\n
\n", "example": 247, "start_line": 4009, "end_line": 4017, "section": "Block quotes" }, { "markdown": "> bar\n\nbaz\n", "html": "
\n

bar

\n
\n

baz

\n", "example": 248, "start_line": 4020, "end_line": 4029, "section": "Block quotes" }, { "markdown": "> bar\n>\nbaz\n", "html": "
\n

bar

\n
\n

baz

\n", "example": 249, "start_line": 4032, "end_line": 4041, "section": "Block quotes" }, { "markdown": "> > > foo\nbar\n", "html": "
\n
\n
\n

foo\nbar

\n
\n
\n
\n", "example": 250, "start_line": 4048, "end_line": 4060, "section": "Block quotes" }, { "markdown": ">>> foo\n> bar\n>>baz\n", "html": "
\n
\n
\n

foo\nbar\nbaz

\n
\n
\n
\n", "example": 251, "start_line": 4063, "end_line": 4077, "section": "Block quotes" }, { "markdown": "> code\n\n> not code\n", "html": "
\n
code\n
\n
\n
\n

not code

\n
\n", "example": 252, "start_line": 4085, "end_line": 4097, "section": "Block quotes" }, { "markdown": "A paragraph\nwith two lines.\n\n indented code\n\n> A block quote.\n", "html": "

A paragraph\nwith two lines.

\n
indented code\n
\n
\n

A block quote.

\n
\n", "example": 253, "start_line": 4139, "end_line": 4154, "section": "List items" }, { "markdown": "1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", "example": 254, "start_line": 4161, "end_line": 4180, "section": "List items" }, { "markdown": "- one\n\n two\n", "html": "
    \n
  • one
  • \n
\n

two

\n", "example": 255, "start_line": 4194, "end_line": 4203, "section": "List items" }, { "markdown": "- one\n\n two\n", "html": "
    \n
  • \n

    one

    \n

    two

    \n
  • \n
\n", "example": 256, "start_line": 4206, "end_line": 4217, "section": "List items" }, { "markdown": " - one\n\n two\n", "html": "
    \n
  • one
  • \n
\n
 two\n
\n", "example": 257, "start_line": 4220, "end_line": 4230, "section": "List items" }, { "markdown": " - one\n\n two\n", "html": "
    \n
  • \n

    one

    \n

    two

    \n
  • \n
\n", "example": 258, "start_line": 4233, "end_line": 4244, "section": "List items" }, { "markdown": " > > 1. one\n>>\n>> two\n", "html": "
\n
\n
    \n
  1. \n

    one

    \n

    two

    \n
  2. \n
\n
\n
\n", "example": 259, "start_line": 4255, "end_line": 4270, "section": "List items" }, { "markdown": ">>- one\n>>\n > > two\n", "html": "
\n
\n
    \n
  • one
  • \n
\n

two

\n
\n
\n", "example": 260, "start_line": 4282, "end_line": 4295, "section": "List items" }, { "markdown": "-one\n\n2.two\n", "html": "

-one

\n

2.two

\n", "example": 261, "start_line": 4301, "end_line": 4308, "section": "List items" }, { "markdown": "- foo\n\n\n bar\n", "html": "
    \n
  • \n

    foo

    \n

    bar

    \n
  • \n
\n", "example": 262, "start_line": 4314, "end_line": 4326, "section": "List items" }, { "markdown": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n", "html": "
    \n
  1. \n

    foo

    \n
    bar\n
    \n

    baz

    \n
    \n

    bam

    \n
    \n
  2. \n
\n", "example": 263, "start_line": 4331, "end_line": 4353, "section": "List items" }, { "markdown": "- Foo\n\n bar\n\n\n baz\n", "html": "
    \n
  • \n

    Foo

    \n
    bar\n\n\nbaz\n
    \n
  • \n
\n", "example": 264, "start_line": 4359, "end_line": 4377, "section": "List items" }, { "markdown": "123456789. ok\n", "html": "
    \n
  1. ok
  2. \n
\n", "example": 265, "start_line": 4381, "end_line": 4387, "section": "List items" }, { "markdown": "1234567890. not ok\n", "html": "

1234567890. not ok

\n", "example": 266, "start_line": 4390, "end_line": 4394, "section": "List items" }, { "markdown": "0. ok\n", "html": "
    \n
  1. ok
  2. \n
\n", "example": 267, "start_line": 4399, "end_line": 4405, "section": "List items" }, { "markdown": "003. ok\n", "html": "
    \n
  1. ok
  2. \n
\n", "example": 268, "start_line": 4408, "end_line": 4414, "section": "List items" }, { "markdown": "-1. not ok\n", "html": "

-1. not ok

\n", "example": 269, "start_line": 4419, "end_line": 4423, "section": "List items" }, { "markdown": "- foo\n\n bar\n", "html": "
    \n
  • \n

    foo

    \n
    bar\n
    \n
  • \n
\n", "example": 270, "start_line": 4442, "end_line": 4454, "section": "List items" }, { "markdown": " 10. foo\n\n bar\n", "html": "
    \n
  1. \n

    foo

    \n
    bar\n
    \n
  2. \n
\n", "example": 271, "start_line": 4459, "end_line": 4471, "section": "List items" }, { "markdown": " indented code\n\nparagraph\n\n more code\n", "html": "
indented code\n
\n

paragraph

\n
more code\n
\n", "example": 272, "start_line": 4478, "end_line": 4490, "section": "List items" }, { "markdown": "1. indented code\n\n paragraph\n\n more code\n", "html": "
    \n
  1. \n
    indented code\n
    \n

    paragraph

    \n
    more code\n
    \n
  2. \n
\n", "example": 273, "start_line": 4493, "end_line": 4509, "section": "List items" }, { "markdown": "1. indented code\n\n paragraph\n\n more code\n", "html": "
    \n
  1. \n
     indented code\n
    \n

    paragraph

    \n
    more code\n
    \n
  2. \n
\n", "example": 274, "start_line": 4515, "end_line": 4531, "section": "List items" }, { "markdown": " foo\n\nbar\n", "html": "

foo

\n

bar

\n", "example": 275, "start_line": 4542, "end_line": 4549, "section": "List items" }, { "markdown": "- foo\n\n bar\n", "html": "
    \n
  • foo
  • \n
\n

bar

\n", "example": 276, "start_line": 4552, "end_line": 4561, "section": "List items" }, { "markdown": "- foo\n\n bar\n", "html": "
    \n
  • \n

    foo

    \n

    bar

    \n
  • \n
\n", "example": 277, "start_line": 4569, "end_line": 4580, "section": "List items" }, { "markdown": "-\n foo\n-\n ```\n bar\n ```\n-\n baz\n", "html": "
    \n
  • foo
  • \n
  • \n
    bar\n
    \n
  • \n
  • \n
    baz\n
    \n
  • \n
\n", "example": 278, "start_line": 4596, "end_line": 4617, "section": "List items" }, { "markdown": "- \n foo\n", "html": "
    \n
  • foo
  • \n
\n", "example": 279, "start_line": 4622, "end_line": 4629, "section": "List items" }, { "markdown": "-\n\n foo\n", "html": "
    \n
  • \n
\n

foo

\n", "example": 280, "start_line": 4636, "end_line": 4645, "section": "List items" }, { "markdown": "- foo\n-\n- bar\n", "html": "
    \n
  • foo
  • \n
  • \n
  • bar
  • \n
\n", "example": 281, "start_line": 4650, "end_line": 4660, "section": "List items" }, { "markdown": "- foo\n- \n- bar\n", "html": "
    \n
  • foo
  • \n
  • \n
  • bar
  • \n
\n", "example": 282, "start_line": 4665, "end_line": 4675, "section": "List items" }, { "markdown": "1. foo\n2.\n3. bar\n", "html": "
    \n
  1. foo
  2. \n
  3. \n
  4. bar
  5. \n
\n", "example": 283, "start_line": 4680, "end_line": 4690, "section": "List items" }, { "markdown": "*\n", "html": "
    \n
  • \n
\n", "example": 284, "start_line": 4695, "end_line": 4701, "section": "List items" }, { "markdown": "foo\n*\n\nfoo\n1.\n", "html": "

foo\n*

\n

foo\n1.

\n", "example": 285, "start_line": 4705, "end_line": 4716, "section": "List items" }, { "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", "example": 286, "start_line": 4727, "end_line": 4746, "section": "List items" }, { "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", "example": 287, "start_line": 4751, "end_line": 4770, "section": "List items" }, { "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", "example": 288, "start_line": 4775, "end_line": 4794, "section": "List items" }, { "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", "html": "
1.  A paragraph\n    with two lines.\n\n        indented code\n\n    > A block quote.\n
\n", "example": 289, "start_line": 4799, "end_line": 4814, "section": "List items" }, { "markdown": " 1. A paragraph\nwith two lines.\n\n indented code\n\n > A block quote.\n", "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", "example": 290, "start_line": 4829, "end_line": 4848, "section": "List items" }, { "markdown": " 1. A paragraph\n with two lines.\n", "html": "
    \n
  1. A paragraph\nwith two lines.
  2. \n
\n", "example": 291, "start_line": 4853, "end_line": 4861, "section": "List items" }, { "markdown": "> 1. > Blockquote\ncontinued here.\n", "html": "
\n
    \n
  1. \n
    \n

    Blockquote\ncontinued here.

    \n
    \n
  2. \n
\n
\n", "example": 292, "start_line": 4866, "end_line": 4880, "section": "List items" }, { "markdown": "> 1. > Blockquote\n> continued here.\n", "html": "
\n
    \n
  1. \n
    \n

    Blockquote\ncontinued here.

    \n
    \n
  2. \n
\n
\n", "example": 293, "start_line": 4883, "end_line": 4897, "section": "List items" }, { "markdown": "- foo\n - bar\n - baz\n - boo\n", "html": "
    \n
  • foo\n
      \n
    • bar\n
        \n
      • baz\n
          \n
        • boo
        • \n
        \n
      • \n
      \n
    • \n
    \n
  • \n
\n", "example": 294, "start_line": 4911, "end_line": 4932, "section": "List items" }, { "markdown": "- foo\n - bar\n - baz\n - boo\n", "html": "
    \n
  • foo
  • \n
  • bar
  • \n
  • baz
  • \n
  • boo
  • \n
\n", "example": 295, "start_line": 4937, "end_line": 4949, "section": "List items" }, { "markdown": "10) foo\n - bar\n", "html": "
    \n
  1. foo\n
      \n
    • bar
    • \n
    \n
  2. \n
\n", "example": 296, "start_line": 4954, "end_line": 4965, "section": "List items" }, { "markdown": "10) foo\n - bar\n", "html": "
    \n
  1. foo
  2. \n
\n
    \n
  • bar
  • \n
\n", "example": 297, "start_line": 4970, "end_line": 4980, "section": "List items" }, { "markdown": "- - foo\n", "html": "
    \n
  • \n
      \n
    • foo
    • \n
    \n
  • \n
\n", "example": 298, "start_line": 4985, "end_line": 4995, "section": "List items" }, { "markdown": "1. - 2. foo\n", "html": "
    \n
  1. \n
      \n
    • \n
        \n
      1. foo
      2. \n
      \n
    • \n
    \n
  2. \n
\n", "example": 299, "start_line": 4998, "end_line": 5012, "section": "List items" }, { "markdown": "- # Foo\n- Bar\n ---\n baz\n", "html": "
    \n
  • \n

    Foo

    \n
  • \n
  • \n

    Bar

    \nbaz
  • \n
\n", "example": 300, "start_line": 5017, "end_line": 5031, "section": "List items" }, { "markdown": "- foo\n- bar\n+ baz\n", "html": "
    \n
  • foo
  • \n
  • bar
  • \n
\n
    \n
  • baz
  • \n
\n", "example": 301, "start_line": 5253, "end_line": 5265, "section": "Lists" }, { "markdown": "1. foo\n2. bar\n3) baz\n", "html": "
    \n
  1. foo
  2. \n
  3. bar
  4. \n
\n
    \n
  1. baz
  2. \n
\n", "example": 302, "start_line": 5268, "end_line": 5280, "section": "Lists" }, { "markdown": "Foo\n- bar\n- baz\n", "html": "

Foo

\n
    \n
  • bar
  • \n
  • baz
  • \n
\n", "example": 303, "start_line": 5287, "end_line": 5297, "section": "Lists" }, { "markdown": "The number of windows in my house is\n14. The number of doors is 6.\n", "html": "

The number of windows in my house is\n14. The number of doors is 6.

\n", "example": 304, "start_line": 5364, "end_line": 5370, "section": "Lists" }, { "markdown": "The number of windows in my house is\n1. The number of doors is 6.\n", "html": "

The number of windows in my house is

\n
    \n
  1. The number of doors is 6.
  2. \n
\n", "example": 305, "start_line": 5374, "end_line": 5382, "section": "Lists" }, { "markdown": "- foo\n\n- bar\n\n\n- baz\n", "html": "
    \n
  • \n

    foo

    \n
  • \n
  • \n

    bar

    \n
  • \n
  • \n

    baz

    \n
  • \n
\n", "example": 306, "start_line": 5388, "end_line": 5407, "section": "Lists" }, { "markdown": "- foo\n - bar\n - baz\n\n\n bim\n", "html": "
    \n
  • foo\n
      \n
    • bar\n
        \n
      • \n

        baz

        \n

        bim

        \n
      • \n
      \n
    • \n
    \n
  • \n
\n", "example": 307, "start_line": 5409, "end_line": 5431, "section": "Lists" }, { "markdown": "- foo\n- bar\n\n\n\n- baz\n- bim\n", "html": "
    \n
  • foo
  • \n
  • bar
  • \n
\n\n
    \n
  • baz
  • \n
  • bim
  • \n
\n", "example": 308, "start_line": 5439, "end_line": 5457, "section": "Lists" }, { "markdown": "- foo\n\n notcode\n\n- foo\n\n\n\n code\n", "html": "
    \n
  • \n

    foo

    \n

    notcode

    \n
  • \n
  • \n

    foo

    \n
  • \n
\n\n
code\n
\n", "example": 309, "start_line": 5460, "end_line": 5483, "section": "Lists" }, { "markdown": "- a\n - b\n - c\n - d\n - e\n - f\n- g\n", "html": "
    \n
  • a
  • \n
  • b
  • \n
  • c
  • \n
  • d
  • \n
  • e
  • \n
  • f
  • \n
  • g
  • \n
\n", "example": 310, "start_line": 5491, "end_line": 5509, "section": "Lists" }, { "markdown": "1. a\n\n 2. b\n\n 3. c\n", "html": "
    \n
  1. \n

    a

    \n
  2. \n
  3. \n

    b

    \n
  4. \n
  5. \n

    c

    \n
  6. \n
\n", "example": 311, "start_line": 5512, "end_line": 5530, "section": "Lists" }, { "markdown": "- a\n - b\n - c\n - d\n - e\n", "html": "
    \n
  • a
  • \n
  • b
  • \n
  • c
  • \n
  • d\n- e
  • \n
\n", "example": 312, "start_line": 5536, "end_line": 5550, "section": "Lists" }, { "markdown": "1. a\n\n 2. b\n\n 3. c\n", "html": "
    \n
  1. \n

    a

    \n
  2. \n
  3. \n

    b

    \n
  4. \n
\n
3. c\n
\n", "example": 313, "start_line": 5556, "end_line": 5573, "section": "Lists" }, { "markdown": "- a\n- b\n\n- c\n", "html": "
    \n
  • \n

    a

    \n
  • \n
  • \n

    b

    \n
  • \n
  • \n

    c

    \n
  • \n
\n", "example": 314, "start_line": 5579, "end_line": 5596, "section": "Lists" }, { "markdown": "* a\n*\n\n* c\n", "html": "
    \n
  • \n

    a

    \n
  • \n
  • \n
  • \n

    c

    \n
  • \n
\n", "example": 315, "start_line": 5601, "end_line": 5616, "section": "Lists" }, { "markdown": "- a\n- b\n\n c\n- d\n", "html": "
    \n
  • \n

    a

    \n
  • \n
  • \n

    b

    \n

    c

    \n
  • \n
  • \n

    d

    \n
  • \n
\n", "example": 316, "start_line": 5623, "end_line": 5642, "section": "Lists" }, { "markdown": "- a\n- b\n\n [ref]: /url\n- d\n", "html": "
    \n
  • \n

    a

    \n
  • \n
  • \n

    b

    \n
  • \n
  • \n

    d

    \n
  • \n
\n", "example": 317, "start_line": 5645, "end_line": 5663, "section": "Lists" }, { "markdown": "- a\n- ```\n b\n\n\n ```\n- c\n", "html": "
    \n
  • a
  • \n
  • \n
    b\n\n\n
    \n
  • \n
  • c
  • \n
\n", "example": 318, "start_line": 5668, "end_line": 5687, "section": "Lists" }, { "markdown": "- a\n - b\n\n c\n- d\n", "html": "
    \n
  • a\n
      \n
    • \n

      b

      \n

      c

      \n
    • \n
    \n
  • \n
  • d
  • \n
\n", "example": 319, "start_line": 5694, "end_line": 5712, "section": "Lists" }, { "markdown": "* a\n > b\n >\n* c\n", "html": "
    \n
  • a\n
    \n

    b

    \n
    \n
  • \n
  • c
  • \n
\n", "example": 320, "start_line": 5718, "end_line": 5732, "section": "Lists" }, { "markdown": "- a\n > b\n ```\n c\n ```\n- d\n", "html": "
    \n
  • a\n
    \n

    b

    \n
    \n
    c\n
    \n
  • \n
  • d
  • \n
\n", "example": 321, "start_line": 5738, "end_line": 5756, "section": "Lists" }, { "markdown": "- a\n", "html": "
    \n
  • a
  • \n
\n", "example": 322, "start_line": 5761, "end_line": 5767, "section": "Lists" }, { "markdown": "- a\n - b\n", "html": "
    \n
  • a\n
      \n
    • b
    • \n
    \n
  • \n
\n", "example": 323, "start_line": 5770, "end_line": 5781, "section": "Lists" }, { "markdown": "1. ```\n foo\n ```\n\n bar\n", "html": "
    \n
  1. \n
    foo\n
    \n

    bar

    \n
  2. \n
\n", "example": 324, "start_line": 5787, "end_line": 5801, "section": "Lists" }, { "markdown": "* foo\n * bar\n\n baz\n", "html": "
    \n
  • \n

    foo

    \n
      \n
    • bar
    • \n
    \n

    baz

    \n
  • \n
\n", "example": 325, "start_line": 5806, "end_line": 5821, "section": "Lists" }, { "markdown": "- a\n - b\n - c\n\n- d\n - e\n - f\n", "html": "
    \n
  • \n

    a

    \n
      \n
    • b
    • \n
    • c
    • \n
    \n
  • \n
  • \n

    d

    \n
      \n
    • e
    • \n
    • f
    • \n
    \n
  • \n
\n", "example": 326, "start_line": 5824, "end_line": 5849, "section": "Lists" }, { "markdown": "`hi`lo`\n", "html": "

hilo`

\n", "example": 327, "start_line": 5858, "end_line": 5862, "section": "Inlines" }, { "markdown": "`foo`\n", "html": "

foo

\n", "example": 328, "start_line": 5890, "end_line": 5894, "section": "Code spans" }, { "markdown": "`` foo ` bar ``\n", "html": "

foo ` bar

\n", "example": 329, "start_line": 5901, "end_line": 5905, "section": "Code spans" }, { "markdown": "` `` `\n", "html": "

``

\n", "example": 330, "start_line": 5911, "end_line": 5915, "section": "Code spans" }, { "markdown": "` `` `\n", "html": "

``

\n", "example": 331, "start_line": 5919, "end_line": 5923, "section": "Code spans" }, { "markdown": "` a`\n", "html": "

a

\n", "example": 332, "start_line": 5928, "end_line": 5932, "section": "Code spans" }, { "markdown": "` b `\n", "html": "

 b 

\n", "example": 333, "start_line": 5937, "end_line": 5941, "section": "Code spans" }, { "markdown": "` `\n` `\n", "html": "

 \n

\n", "example": 334, "start_line": 5945, "end_line": 5951, "section": "Code spans" }, { "markdown": "``\nfoo\nbar \nbaz\n``\n", "html": "

foo bar baz

\n", "example": 335, "start_line": 5956, "end_line": 5964, "section": "Code spans" }, { "markdown": "``\nfoo \n``\n", "html": "

foo

\n", "example": 336, "start_line": 5966, "end_line": 5972, "section": "Code spans" }, { "markdown": "`foo bar \nbaz`\n", "html": "

foo bar baz

\n", "example": 337, "start_line": 5977, "end_line": 5982, "section": "Code spans" }, { "markdown": "`foo\\`bar`\n", "html": "

foo\\bar`

\n", "example": 338, "start_line": 5994, "end_line": 5998, "section": "Code spans" }, { "markdown": "``foo`bar``\n", "html": "

foo`bar

\n", "example": 339, "start_line": 6005, "end_line": 6009, "section": "Code spans" }, { "markdown": "` foo `` bar `\n", "html": "

foo `` bar

\n", "example": 340, "start_line": 6011, "end_line": 6015, "section": "Code spans" }, { "markdown": "*foo`*`\n", "html": "

*foo*

\n", "example": 341, "start_line": 6023, "end_line": 6027, "section": "Code spans" }, { "markdown": "[not a `link](/foo`)\n", "html": "

[not a link](/foo)

\n", "example": 342, "start_line": 6032, "end_line": 6036, "section": "Code spans" }, { "markdown": "``\n", "html": "

<a href="">`

\n", "example": 343, "start_line": 6042, "end_line": 6046, "section": "Code spans" }, { "markdown": "
`\n", "html": "

`

\n", "example": 344, "start_line": 6051, "end_line": 6055, "section": "Code spans" }, { "markdown": "``\n", "html": "

<http://foo.bar.baz>`

\n", "example": 345, "start_line": 6060, "end_line": 6064, "section": "Code spans" }, { "markdown": "`\n", "html": "

http://foo.bar.`baz`

\n", "example": 346, "start_line": 6069, "end_line": 6073, "section": "Code spans" }, { "markdown": "```foo``\n", "html": "

```foo``

\n", "example": 347, "start_line": 6079, "end_line": 6083, "section": "Code spans" }, { "markdown": "`foo\n", "html": "

`foo

\n", "example": 348, "start_line": 6086, "end_line": 6090, "section": "Code spans" }, { "markdown": "`foo``bar``\n", "html": "

`foobar

\n", "example": 349, "start_line": 6095, "end_line": 6099, "section": "Code spans" }, { "markdown": "*foo bar*\n", "html": "

foo bar

\n", "example": 350, "start_line": 6312, "end_line": 6316, "section": "Emphasis and strong emphasis" }, { "markdown": "a * foo bar*\n", "html": "

a * foo bar*

\n", "example": 351, "start_line": 6322, "end_line": 6326, "section": "Emphasis and strong emphasis" }, { "markdown": "a*\"foo\"*\n", "html": "

a*"foo"*

\n", "example": 352, "start_line": 6333, "end_line": 6337, "section": "Emphasis and strong emphasis" }, { "markdown": "* a *\n", "html": "

* a *

\n", "example": 353, "start_line": 6342, "end_line": 6346, "section": "Emphasis and strong emphasis" }, { "markdown": "foo*bar*\n", "html": "

foobar

\n", "example": 354, "start_line": 6351, "end_line": 6355, "section": "Emphasis and strong emphasis" }, { "markdown": "5*6*78\n", "html": "

5678

\n", "example": 355, "start_line": 6358, "end_line": 6362, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo bar_\n", "html": "

foo bar

\n", "example": 356, "start_line": 6367, "end_line": 6371, "section": "Emphasis and strong emphasis" }, { "markdown": "_ foo bar_\n", "html": "

_ foo bar_

\n", "example": 357, "start_line": 6377, "end_line": 6381, "section": "Emphasis and strong emphasis" }, { "markdown": "a_\"foo\"_\n", "html": "

a_"foo"_

\n", "example": 358, "start_line": 6387, "end_line": 6391, "section": "Emphasis and strong emphasis" }, { "markdown": "foo_bar_\n", "html": "

foo_bar_

\n", "example": 359, "start_line": 6396, "end_line": 6400, "section": "Emphasis and strong emphasis" }, { "markdown": "5_6_78\n", "html": "

5_6_78

\n", "example": 360, "start_line": 6403, "end_line": 6407, "section": "Emphasis and strong emphasis" }, { "markdown": "пристаням_стремятся_\n", "html": "

пристаням_стремятся_

\n", "example": 361, "start_line": 6410, "end_line": 6414, "section": "Emphasis and strong emphasis" }, { "markdown": "aa_\"bb\"_cc\n", "html": "

aa_"bb"_cc

\n", "example": 362, "start_line": 6420, "end_line": 6424, "section": "Emphasis and strong emphasis" }, { "markdown": "foo-_(bar)_\n", "html": "

foo-(bar)

\n", "example": 363, "start_line": 6431, "end_line": 6435, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo*\n", "html": "

_foo*

\n", "example": 364, "start_line": 6443, "end_line": 6447, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo bar *\n", "html": "

*foo bar *

\n", "example": 365, "start_line": 6453, "end_line": 6457, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo bar\n*\n", "html": "

*foo bar\n*

\n", "example": 366, "start_line": 6462, "end_line": 6468, "section": "Emphasis and strong emphasis" }, { "markdown": "*(*foo)\n", "html": "

*(*foo)

\n", "example": 367, "start_line": 6475, "end_line": 6479, "section": "Emphasis and strong emphasis" }, { "markdown": "*(*foo*)*\n", "html": "

(foo)

\n", "example": 368, "start_line": 6485, "end_line": 6489, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo*bar\n", "html": "

foobar

\n", "example": 369, "start_line": 6494, "end_line": 6498, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo bar _\n", "html": "

_foo bar _

\n", "example": 370, "start_line": 6507, "end_line": 6511, "section": "Emphasis and strong emphasis" }, { "markdown": "_(_foo)\n", "html": "

_(_foo)

\n", "example": 371, "start_line": 6517, "end_line": 6521, "section": "Emphasis and strong emphasis" }, { "markdown": "_(_foo_)_\n", "html": "

(foo)

\n", "example": 372, "start_line": 6526, "end_line": 6530, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo_bar\n", "html": "

_foo_bar

\n", "example": 373, "start_line": 6535, "end_line": 6539, "section": "Emphasis and strong emphasis" }, { "markdown": "_пристаням_стремятся\n", "html": "

_пристаням_стремятся

\n", "example": 374, "start_line": 6542, "end_line": 6546, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo_bar_baz_\n", "html": "

foo_bar_baz

\n", "example": 375, "start_line": 6549, "end_line": 6553, "section": "Emphasis and strong emphasis" }, { "markdown": "_(bar)_.\n", "html": "

(bar).

\n", "example": 376, "start_line": 6560, "end_line": 6564, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo bar**\n", "html": "

foo bar

\n", "example": 377, "start_line": 6569, "end_line": 6573, "section": "Emphasis and strong emphasis" }, { "markdown": "** foo bar**\n", "html": "

** foo bar**

\n", "example": 378, "start_line": 6579, "end_line": 6583, "section": "Emphasis and strong emphasis" }, { "markdown": "a**\"foo\"**\n", "html": "

a**"foo"**

\n", "example": 379, "start_line": 6590, "end_line": 6594, "section": "Emphasis and strong emphasis" }, { "markdown": "foo**bar**\n", "html": "

foobar

\n", "example": 380, "start_line": 6599, "end_line": 6603, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo bar__\n", "html": "

foo bar

\n", "example": 381, "start_line": 6608, "end_line": 6612, "section": "Emphasis and strong emphasis" }, { "markdown": "__ foo bar__\n", "html": "

__ foo bar__

\n", "example": 382, "start_line": 6618, "end_line": 6622, "section": "Emphasis and strong emphasis" }, { "markdown": "__\nfoo bar__\n", "html": "

__\nfoo bar__

\n", "example": 383, "start_line": 6626, "end_line": 6632, "section": "Emphasis and strong emphasis" }, { "markdown": "a__\"foo\"__\n", "html": "

a__"foo"__

\n", "example": 384, "start_line": 6638, "end_line": 6642, "section": "Emphasis and strong emphasis" }, { "markdown": "foo__bar__\n", "html": "

foo__bar__

\n", "example": 385, "start_line": 6647, "end_line": 6651, "section": "Emphasis and strong emphasis" }, { "markdown": "5__6__78\n", "html": "

5__6__78

\n", "example": 386, "start_line": 6654, "end_line": 6658, "section": "Emphasis and strong emphasis" }, { "markdown": "пристаням__стремятся__\n", "html": "

пристаням__стремятся__

\n", "example": 387, "start_line": 6661, "end_line": 6665, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo, __bar__, baz__\n", "html": "

foo, bar, baz

\n", "example": 388, "start_line": 6668, "end_line": 6672, "section": "Emphasis and strong emphasis" }, { "markdown": "foo-__(bar)__\n", "html": "

foo-(bar)

\n", "example": 389, "start_line": 6679, "end_line": 6683, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo bar **\n", "html": "

**foo bar **

\n", "example": 390, "start_line": 6692, "end_line": 6696, "section": "Emphasis and strong emphasis" }, { "markdown": "**(**foo)\n", "html": "

**(**foo)

\n", "example": 391, "start_line": 6705, "end_line": 6709, "section": "Emphasis and strong emphasis" }, { "markdown": "*(**foo**)*\n", "html": "

(foo)

\n", "example": 392, "start_line": 6715, "end_line": 6719, "section": "Emphasis and strong emphasis" }, { "markdown": "**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**\n", "html": "

Gomphocarpus (Gomphocarpus physocarpus, syn.\nAsclepias physocarpa)

\n", "example": 393, "start_line": 6722, "end_line": 6728, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo \"*bar*\" foo**\n", "html": "

foo "bar" foo

\n", "example": 394, "start_line": 6731, "end_line": 6735, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo**bar\n", "html": "

foobar

\n", "example": 395, "start_line": 6740, "end_line": 6744, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo bar __\n", "html": "

__foo bar __

\n", "example": 396, "start_line": 6752, "end_line": 6756, "section": "Emphasis and strong emphasis" }, { "markdown": "__(__foo)\n", "html": "

__(__foo)

\n", "example": 397, "start_line": 6762, "end_line": 6766, "section": "Emphasis and strong emphasis" }, { "markdown": "_(__foo__)_\n", "html": "

(foo)

\n", "example": 398, "start_line": 6772, "end_line": 6776, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo__bar\n", "html": "

__foo__bar

\n", "example": 399, "start_line": 6781, "end_line": 6785, "section": "Emphasis and strong emphasis" }, { "markdown": "__пристаням__стремятся\n", "html": "

__пристаням__стремятся

\n", "example": 400, "start_line": 6788, "end_line": 6792, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo__bar__baz__\n", "html": "

foo__bar__baz

\n", "example": 401, "start_line": 6795, "end_line": 6799, "section": "Emphasis and strong emphasis" }, { "markdown": "__(bar)__.\n", "html": "

(bar).

\n", "example": 402, "start_line": 6806, "end_line": 6810, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo [bar](/url)*\n", "html": "

foo bar

\n", "example": 403, "start_line": 6818, "end_line": 6822, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo\nbar*\n", "html": "

foo\nbar

\n", "example": 404, "start_line": 6825, "end_line": 6831, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo __bar__ baz_\n", "html": "

foo bar baz

\n", "example": 405, "start_line": 6837, "end_line": 6841, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo _bar_ baz_\n", "html": "

foo bar baz

\n", "example": 406, "start_line": 6844, "end_line": 6848, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo_ bar_\n", "html": "

foo bar

\n", "example": 407, "start_line": 6851, "end_line": 6855, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo *bar**\n", "html": "

foo bar

\n", "example": 408, "start_line": 6858, "end_line": 6862, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo **bar** baz*\n", "html": "

foo bar baz

\n", "example": 409, "start_line": 6865, "end_line": 6869, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo**bar**baz*\n", "html": "

foobarbaz

\n", "example": 410, "start_line": 6871, "end_line": 6875, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo**bar*\n", "html": "

foo**bar

\n", "example": 411, "start_line": 6895, "end_line": 6899, "section": "Emphasis and strong emphasis" }, { "markdown": "***foo** bar*\n", "html": "

foo bar

\n", "example": 412, "start_line": 6908, "end_line": 6912, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo **bar***\n", "html": "

foo bar

\n", "example": 413, "start_line": 6915, "end_line": 6919, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo**bar***\n", "html": "

foobar

\n", "example": 414, "start_line": 6922, "end_line": 6926, "section": "Emphasis and strong emphasis" }, { "markdown": "foo***bar***baz\n", "html": "

foobarbaz

\n", "example": 415, "start_line": 6933, "end_line": 6937, "section": "Emphasis and strong emphasis" }, { "markdown": "foo******bar*********baz\n", "html": "

foobar***baz

\n", "example": 416, "start_line": 6939, "end_line": 6943, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo **bar *baz* bim** bop*\n", "html": "

foo bar baz bim bop

\n", "example": 417, "start_line": 6948, "end_line": 6952, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo [*bar*](/url)*\n", "html": "

foo bar

\n", "example": 418, "start_line": 6955, "end_line": 6959, "section": "Emphasis and strong emphasis" }, { "markdown": "** is not an empty emphasis\n", "html": "

** is not an empty emphasis

\n", "example": 419, "start_line": 6964, "end_line": 6968, "section": "Emphasis and strong emphasis" }, { "markdown": "**** is not an empty strong emphasis\n", "html": "

**** is not an empty strong emphasis

\n", "example": 420, "start_line": 6971, "end_line": 6975, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo [bar](/url)**\n", "html": "

foo bar

\n", "example": 421, "start_line": 6984, "end_line": 6988, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo\nbar**\n", "html": "

foo\nbar

\n", "example": 422, "start_line": 6991, "end_line": 6997, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo _bar_ baz__\n", "html": "

foo bar baz

\n", "example": 423, "start_line": 7003, "end_line": 7007, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo __bar__ baz__\n", "html": "

foo bar baz

\n", "example": 424, "start_line": 7010, "end_line": 7014, "section": "Emphasis and strong emphasis" }, { "markdown": "____foo__ bar__\n", "html": "

foo bar

\n", "example": 425, "start_line": 7017, "end_line": 7021, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo **bar****\n", "html": "

foo bar

\n", "example": 426, "start_line": 7024, "end_line": 7028, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo *bar* baz**\n", "html": "

foo bar baz

\n", "example": 427, "start_line": 7031, "end_line": 7035, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo*bar*baz**\n", "html": "

foobarbaz

\n", "example": 428, "start_line": 7038, "end_line": 7042, "section": "Emphasis and strong emphasis" }, { "markdown": "***foo* bar**\n", "html": "

foo bar

\n", "example": 429, "start_line": 7045, "end_line": 7049, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo *bar***\n", "html": "

foo bar

\n", "example": 430, "start_line": 7052, "end_line": 7056, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo *bar **baz**\nbim* bop**\n", "html": "

foo bar baz\nbim bop

\n", "example": 431, "start_line": 7061, "end_line": 7067, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo [*bar*](/url)**\n", "html": "

foo bar

\n", "example": 432, "start_line": 7070, "end_line": 7074, "section": "Emphasis and strong emphasis" }, { "markdown": "__ is not an empty emphasis\n", "html": "

__ is not an empty emphasis

\n", "example": 433, "start_line": 7079, "end_line": 7083, "section": "Emphasis and strong emphasis" }, { "markdown": "____ is not an empty strong emphasis\n", "html": "

____ is not an empty strong emphasis

\n", "example": 434, "start_line": 7086, "end_line": 7090, "section": "Emphasis and strong emphasis" }, { "markdown": "foo ***\n", "html": "

foo ***

\n", "example": 435, "start_line": 7096, "end_line": 7100, "section": "Emphasis and strong emphasis" }, { "markdown": "foo *\\**\n", "html": "

foo *

\n", "example": 436, "start_line": 7103, "end_line": 7107, "section": "Emphasis and strong emphasis" }, { "markdown": "foo *_*\n", "html": "

foo _

\n", "example": 437, "start_line": 7110, "end_line": 7114, "section": "Emphasis and strong emphasis" }, { "markdown": "foo *****\n", "html": "

foo *****

\n", "example": 438, "start_line": 7117, "end_line": 7121, "section": "Emphasis and strong emphasis" }, { "markdown": "foo **\\***\n", "html": "

foo *

\n", "example": 439, "start_line": 7124, "end_line": 7128, "section": "Emphasis and strong emphasis" }, { "markdown": "foo **_**\n", "html": "

foo _

\n", "example": 440, "start_line": 7131, "end_line": 7135, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo*\n", "html": "

*foo

\n", "example": 441, "start_line": 7142, "end_line": 7146, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo**\n", "html": "

foo*

\n", "example": 442, "start_line": 7149, "end_line": 7153, "section": "Emphasis and strong emphasis" }, { "markdown": "***foo**\n", "html": "

*foo

\n", "example": 443, "start_line": 7156, "end_line": 7160, "section": "Emphasis and strong emphasis" }, { "markdown": "****foo*\n", "html": "

***foo

\n", "example": 444, "start_line": 7163, "end_line": 7167, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo***\n", "html": "

foo*

\n", "example": 445, "start_line": 7170, "end_line": 7174, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo****\n", "html": "

foo***

\n", "example": 446, "start_line": 7177, "end_line": 7181, "section": "Emphasis and strong emphasis" }, { "markdown": "foo ___\n", "html": "

foo ___

\n", "example": 447, "start_line": 7187, "end_line": 7191, "section": "Emphasis and strong emphasis" }, { "markdown": "foo _\\__\n", "html": "

foo _

\n", "example": 448, "start_line": 7194, "end_line": 7198, "section": "Emphasis and strong emphasis" }, { "markdown": "foo _*_\n", "html": "

foo *

\n", "example": 449, "start_line": 7201, "end_line": 7205, "section": "Emphasis and strong emphasis" }, { "markdown": "foo _____\n", "html": "

foo _____

\n", "example": 450, "start_line": 7208, "end_line": 7212, "section": "Emphasis and strong emphasis" }, { "markdown": "foo __\\___\n", "html": "

foo _

\n", "example": 451, "start_line": 7215, "end_line": 7219, "section": "Emphasis and strong emphasis" }, { "markdown": "foo __*__\n", "html": "

foo *

\n", "example": 452, "start_line": 7222, "end_line": 7226, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo_\n", "html": "

_foo

\n", "example": 453, "start_line": 7229, "end_line": 7233, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo__\n", "html": "

foo_

\n", "example": 454, "start_line": 7240, "end_line": 7244, "section": "Emphasis and strong emphasis" }, { "markdown": "___foo__\n", "html": "

_foo

\n", "example": 455, "start_line": 7247, "end_line": 7251, "section": "Emphasis and strong emphasis" }, { "markdown": "____foo_\n", "html": "

___foo

\n", "example": 456, "start_line": 7254, "end_line": 7258, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo___\n", "html": "

foo_

\n", "example": 457, "start_line": 7261, "end_line": 7265, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo____\n", "html": "

foo___

\n", "example": 458, "start_line": 7268, "end_line": 7272, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo**\n", "html": "

foo

\n", "example": 459, "start_line": 7278, "end_line": 7282, "section": "Emphasis and strong emphasis" }, { "markdown": "*_foo_*\n", "html": "

foo

\n", "example": 460, "start_line": 7285, "end_line": 7289, "section": "Emphasis and strong emphasis" }, { "markdown": "__foo__\n", "html": "

foo

\n", "example": 461, "start_line": 7292, "end_line": 7296, "section": "Emphasis and strong emphasis" }, { "markdown": "_*foo*_\n", "html": "

foo

\n", "example": 462, "start_line": 7299, "end_line": 7303, "section": "Emphasis and strong emphasis" }, { "markdown": "****foo****\n", "html": "

foo

\n", "example": 463, "start_line": 7309, "end_line": 7313, "section": "Emphasis and strong emphasis" }, { "markdown": "____foo____\n", "html": "

foo

\n", "example": 464, "start_line": 7316, "end_line": 7320, "section": "Emphasis and strong emphasis" }, { "markdown": "******foo******\n", "html": "

foo

\n", "example": 465, "start_line": 7327, "end_line": 7331, "section": "Emphasis and strong emphasis" }, { "markdown": "***foo***\n", "html": "

foo

\n", "example": 466, "start_line": 7336, "end_line": 7340, "section": "Emphasis and strong emphasis" }, { "markdown": "_____foo_____\n", "html": "

foo

\n", "example": 467, "start_line": 7343, "end_line": 7347, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo _bar* baz_\n", "html": "

foo _bar baz_

\n", "example": 468, "start_line": 7352, "end_line": 7356, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo __bar *baz bim__ bam*\n", "html": "

foo bar *baz bim bam

\n", "example": 469, "start_line": 7359, "end_line": 7363, "section": "Emphasis and strong emphasis" }, { "markdown": "**foo **bar baz**\n", "html": "

**foo bar baz

\n", "example": 470, "start_line": 7368, "end_line": 7372, "section": "Emphasis and strong emphasis" }, { "markdown": "*foo *bar baz*\n", "html": "

*foo bar baz

\n", "example": 471, "start_line": 7375, "end_line": 7379, "section": "Emphasis and strong emphasis" }, { "markdown": "*[bar*](/url)\n", "html": "

*bar*

\n", "example": 472, "start_line": 7384, "end_line": 7388, "section": "Emphasis and strong emphasis" }, { "markdown": "_foo [bar_](/url)\n", "html": "

_foo bar_

\n", "example": 473, "start_line": 7391, "end_line": 7395, "section": "Emphasis and strong emphasis" }, { "markdown": "*\n", "html": "

*

\n", "example": 474, "start_line": 7398, "end_line": 7402, "section": "Emphasis and strong emphasis" }, { "markdown": "**\n", "html": "

**

\n", "example": 475, "start_line": 7405, "end_line": 7409, "section": "Emphasis and strong emphasis" }, { "markdown": "__\n", "html": "

__

\n", "example": 476, "start_line": 7412, "end_line": 7416, "section": "Emphasis and strong emphasis" }, { "markdown": "*a `*`*\n", "html": "

a *

\n", "example": 477, "start_line": 7419, "end_line": 7423, "section": "Emphasis and strong emphasis" }, { "markdown": "_a `_`_\n", "html": "

a _

\n", "example": 478, "start_line": 7426, "end_line": 7430, "section": "Emphasis and strong emphasis" }, { "markdown": "**a\n", "html": "

**ahttp://foo.bar/?q=**

\n", "example": 479, "start_line": 7433, "end_line": 7437, "section": "Emphasis and strong emphasis" }, { "markdown": "__a\n", "html": "

__ahttp://foo.bar/?q=__

\n", "example": 480, "start_line": 7440, "end_line": 7444, "section": "Emphasis and strong emphasis" }, { "markdown": "[link](/uri \"title\")\n", "html": "

link

\n", "example": 481, "start_line": 7528, "end_line": 7532, "section": "Links" }, { "markdown": "[link](/uri)\n", "html": "

link

\n", "example": 482, "start_line": 7538, "end_line": 7542, "section": "Links" }, { "markdown": "[](./target.md)\n", "html": "

\n", "example": 483, "start_line": 7544, "end_line": 7548, "section": "Links" }, { "markdown": "[link]()\n", "html": "

link

\n", "example": 484, "start_line": 7551, "end_line": 7555, "section": "Links" }, { "markdown": "[link](<>)\n", "html": "

link

\n", "example": 485, "start_line": 7558, "end_line": 7562, "section": "Links" }, { "markdown": "[]()\n", "html": "

\n", "example": 486, "start_line": 7565, "end_line": 7569, "section": "Links" }, { "markdown": "[link](/my uri)\n", "html": "

[link](/my uri)

\n", "example": 487, "start_line": 7574, "end_line": 7578, "section": "Links" }, { "markdown": "[link](
)\n", "html": "

link

\n", "example": 488, "start_line": 7580, "end_line": 7584, "section": "Links" }, { "markdown": "[link](foo\nbar)\n", "html": "

[link](foo\nbar)

\n", "example": 489, "start_line": 7589, "end_line": 7595, "section": "Links" }, { "markdown": "[link]()\n", "html": "

[link]()

\n", "example": 490, "start_line": 7597, "end_line": 7603, "section": "Links" }, { "markdown": "[a]()\n", "html": "

a

\n", "example": 491, "start_line": 7608, "end_line": 7612, "section": "Links" }, { "markdown": "[link]()\n", "html": "

[link](<foo>)

\n", "example": 492, "start_line": 7616, "end_line": 7620, "section": "Links" }, { "markdown": "[a](\n[a](c)\n", "html": "

[a](<b)c\n[a](<b)c>\n[a](c)

\n", "example": 493, "start_line": 7625, "end_line": 7633, "section": "Links" }, { "markdown": "[link](\\(foo\\))\n", "html": "

link

\n", "example": 494, "start_line": 7637, "end_line": 7641, "section": "Links" }, { "markdown": "[link](foo(and(bar)))\n", "html": "

link

\n", "example": 495, "start_line": 7646, "end_line": 7650, "section": "Links" }, { "markdown": "[link](foo(and(bar))\n", "html": "

[link](foo(and(bar))

\n", "example": 496, "start_line": 7655, "end_line": 7659, "section": "Links" }, { "markdown": "[link](foo\\(and\\(bar\\))\n", "html": "

link

\n", "example": 497, "start_line": 7662, "end_line": 7666, "section": "Links" }, { "markdown": "[link]()\n", "html": "

link

\n", "example": 498, "start_line": 7669, "end_line": 7673, "section": "Links" }, { "markdown": "[link](foo\\)\\:)\n", "html": "

link

\n", "example": 499, "start_line": 7679, "end_line": 7683, "section": "Links" }, { "markdown": "[link](#fragment)\n\n[link](http://example.com#fragment)\n\n[link](http://example.com?foo=3#frag)\n", "html": "

link

\n

link

\n

link

\n", "example": 500, "start_line": 7688, "end_line": 7698, "section": "Links" }, { "markdown": "[link](foo\\bar)\n", "html": "

link

\n", "example": 501, "start_line": 7704, "end_line": 7708, "section": "Links" }, { "markdown": "[link](foo%20bä)\n", "html": "

link

\n", "example": 502, "start_line": 7720, "end_line": 7724, "section": "Links" }, { "markdown": "[link](\"title\")\n", "html": "

link

\n", "example": 503, "start_line": 7731, "end_line": 7735, "section": "Links" }, { "markdown": "[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))\n", "html": "

link\nlink\nlink

\n", "example": 504, "start_line": 7740, "end_line": 7748, "section": "Links" }, { "markdown": "[link](/url \"title \\\""\")\n", "html": "

link

\n", "example": 505, "start_line": 7754, "end_line": 7758, "section": "Links" }, { "markdown": "[link](/url \"title\")\n", "html": "

link

\n", "example": 506, "start_line": 7765, "end_line": 7769, "section": "Links" }, { "markdown": "[link](/url \"title \"and\" title\")\n", "html": "

[link](/url "title "and" title")

\n", "example": 507, "start_line": 7774, "end_line": 7778, "section": "Links" }, { "markdown": "[link](/url 'title \"and\" title')\n", "html": "

link

\n", "example": 508, "start_line": 7783, "end_line": 7787, "section": "Links" }, { "markdown": "[link]( /uri\n \"title\" )\n", "html": "

link

\n", "example": 509, "start_line": 7808, "end_line": 7813, "section": "Links" }, { "markdown": "[link] (/uri)\n", "html": "

[link] (/uri)

\n", "example": 510, "start_line": 7819, "end_line": 7823, "section": "Links" }, { "markdown": "[link [foo [bar]]](/uri)\n", "html": "

link [foo [bar]]

\n", "example": 511, "start_line": 7829, "end_line": 7833, "section": "Links" }, { "markdown": "[link] bar](/uri)\n", "html": "

[link] bar](/uri)

\n", "example": 512, "start_line": 7836, "end_line": 7840, "section": "Links" }, { "markdown": "[link [bar](/uri)\n", "html": "

[link bar

\n", "example": 513, "start_line": 7843, "end_line": 7847, "section": "Links" }, { "markdown": "[link \\[bar](/uri)\n", "html": "

link [bar

\n", "example": 514, "start_line": 7850, "end_line": 7854, "section": "Links" }, { "markdown": "[link *foo **bar** `#`*](/uri)\n", "html": "

link foo bar #

\n", "example": 515, "start_line": 7859, "end_line": 7863, "section": "Links" }, { "markdown": "[![moon](moon.jpg)](/uri)\n", "html": "

\"moon\"

\n", "example": 516, "start_line": 7866, "end_line": 7870, "section": "Links" }, { "markdown": "[foo [bar](/uri)](/uri)\n", "html": "

[foo bar](/uri)

\n", "example": 517, "start_line": 7875, "end_line": 7879, "section": "Links" }, { "markdown": "[foo *[bar [baz](/uri)](/uri)*](/uri)\n", "html": "

[foo [bar baz](/uri)](/uri)

\n", "example": 518, "start_line": 7882, "end_line": 7886, "section": "Links" }, { "markdown": "![[[foo](uri1)](uri2)](uri3)\n", "html": "

\"[foo](uri2)\"

\n", "example": 519, "start_line": 7889, "end_line": 7893, "section": "Links" }, { "markdown": "*[foo*](/uri)\n", "html": "

*foo*

\n", "example": 520, "start_line": 7899, "end_line": 7903, "section": "Links" }, { "markdown": "[foo *bar](baz*)\n", "html": "

foo *bar

\n", "example": 521, "start_line": 7906, "end_line": 7910, "section": "Links" }, { "markdown": "*foo [bar* baz]\n", "html": "

foo [bar baz]

\n", "example": 522, "start_line": 7916, "end_line": 7920, "section": "Links" }, { "markdown": "[foo \n", "html": "

[foo

\n", "example": 523, "start_line": 7926, "end_line": 7930, "section": "Links" }, { "markdown": "[foo`](/uri)`\n", "html": "

[foo](/uri)

\n", "example": 524, "start_line": 7933, "end_line": 7937, "section": "Links" }, { "markdown": "[foo\n", "html": "

[foohttp://example.com/?search=](uri)

\n", "example": 525, "start_line": 7940, "end_line": 7944, "section": "Links" }, { "markdown": "[foo][bar]\n\n[bar]: /url \"title\"\n", "html": "

foo

\n", "example": 526, "start_line": 7978, "end_line": 7984, "section": "Links" }, { "markdown": "[link [foo [bar]]][ref]\n\n[ref]: /uri\n", "html": "

link [foo [bar]]

\n", "example": 527, "start_line": 7993, "end_line": 7999, "section": "Links" }, { "markdown": "[link \\[bar][ref]\n\n[ref]: /uri\n", "html": "

link [bar

\n", "example": 528, "start_line": 8002, "end_line": 8008, "section": "Links" }, { "markdown": "[link *foo **bar** `#`*][ref]\n\n[ref]: /uri\n", "html": "

link foo bar #

\n", "example": 529, "start_line": 8013, "end_line": 8019, "section": "Links" }, { "markdown": "[![moon](moon.jpg)][ref]\n\n[ref]: /uri\n", "html": "

\"moon\"

\n", "example": 530, "start_line": 8022, "end_line": 8028, "section": "Links" }, { "markdown": "[foo [bar](/uri)][ref]\n\n[ref]: /uri\n", "html": "

[foo bar]ref

\n", "example": 531, "start_line": 8033, "end_line": 8039, "section": "Links" }, { "markdown": "[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri\n", "html": "

[foo bar baz]ref

\n", "example": 532, "start_line": 8042, "end_line": 8048, "section": "Links" }, { "markdown": "*[foo*][ref]\n\n[ref]: /uri\n", "html": "

*foo*

\n", "example": 533, "start_line": 8057, "end_line": 8063, "section": "Links" }, { "markdown": "[foo *bar][ref]*\n\n[ref]: /uri\n", "html": "

foo *bar*

\n", "example": 534, "start_line": 8066, "end_line": 8072, "section": "Links" }, { "markdown": "[foo \n\n[ref]: /uri\n", "html": "

[foo

\n", "example": 535, "start_line": 8078, "end_line": 8084, "section": "Links" }, { "markdown": "[foo`][ref]`\n\n[ref]: /uri\n", "html": "

[foo][ref]

\n", "example": 536, "start_line": 8087, "end_line": 8093, "section": "Links" }, { "markdown": "[foo\n\n[ref]: /uri\n", "html": "

[foohttp://example.com/?search=][ref]

\n", "example": 537, "start_line": 8096, "end_line": 8102, "section": "Links" }, { "markdown": "[foo][BaR]\n\n[bar]: /url \"title\"\n", "html": "

foo

\n", "example": 538, "start_line": 8107, "end_line": 8113, "section": "Links" }, { "markdown": "[ẞ]\n\n[SS]: /url\n", "html": "

\n", "example": 539, "start_line": 8118, "end_line": 8124, "section": "Links" }, { "markdown": "[Foo\n bar]: /url\n\n[Baz][Foo bar]\n", "html": "

Baz

\n", "example": 540, "start_line": 8130, "end_line": 8137, "section": "Links" }, { "markdown": "[foo] [bar]\n\n[bar]: /url \"title\"\n", "html": "

[foo] bar

\n", "example": 541, "start_line": 8143, "end_line": 8149, "section": "Links" }, { "markdown": "[foo]\n[bar]\n\n[bar]: /url \"title\"\n", "html": "

[foo]\nbar

\n", "example": 542, "start_line": 8152, "end_line": 8160, "section": "Links" }, { "markdown": "[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]\n", "html": "

bar

\n", "example": 543, "start_line": 8193, "end_line": 8201, "section": "Links" }, { "markdown": "[bar][foo\\!]\n\n[foo!]: /url\n", "html": "

[bar][foo!]

\n", "example": 544, "start_line": 8208, "end_line": 8214, "section": "Links" }, { "markdown": "[foo][ref[]\n\n[ref[]: /uri\n", "html": "

[foo][ref[]

\n

[ref[]: /uri

\n", "example": 545, "start_line": 8220, "end_line": 8227, "section": "Links" }, { "markdown": "[foo][ref[bar]]\n\n[ref[bar]]: /uri\n", "html": "

[foo][ref[bar]]

\n

[ref[bar]]: /uri

\n", "example": 546, "start_line": 8230, "end_line": 8237, "section": "Links" }, { "markdown": "[[[foo]]]\n\n[[[foo]]]: /url\n", "html": "

[[[foo]]]

\n

[[[foo]]]: /url

\n", "example": 547, "start_line": 8240, "end_line": 8247, "section": "Links" }, { "markdown": "[foo][ref\\[]\n\n[ref\\[]: /uri\n", "html": "

foo

\n", "example": 548, "start_line": 8250, "end_line": 8256, "section": "Links" }, { "markdown": "[bar\\\\]: /uri\n\n[bar\\\\]\n", "html": "

bar\\

\n", "example": 549, "start_line": 8261, "end_line": 8267, "section": "Links" }, { "markdown": "[]\n\n[]: /uri\n", "html": "

[]

\n

[]: /uri

\n", "example": 550, "start_line": 8273, "end_line": 8280, "section": "Links" }, { "markdown": "[\n ]\n\n[\n ]: /uri\n", "html": "

[\n]

\n

[\n]: /uri

\n", "example": 551, "start_line": 8283, "end_line": 8294, "section": "Links" }, { "markdown": "[foo][]\n\n[foo]: /url \"title\"\n", "html": "

foo

\n", "example": 552, "start_line": 8306, "end_line": 8312, "section": "Links" }, { "markdown": "[*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", "html": "

foo bar

\n", "example": 553, "start_line": 8315, "end_line": 8321, "section": "Links" }, { "markdown": "[Foo][]\n\n[foo]: /url \"title\"\n", "html": "

Foo

\n", "example": 554, "start_line": 8326, "end_line": 8332, "section": "Links" }, { "markdown": "[foo] \n[]\n\n[foo]: /url \"title\"\n", "html": "

foo\n[]

\n", "example": 555, "start_line": 8339, "end_line": 8347, "section": "Links" }, { "markdown": "[foo]\n\n[foo]: /url \"title\"\n", "html": "

foo

\n", "example": 556, "start_line": 8359, "end_line": 8365, "section": "Links" }, { "markdown": "[*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", "html": "

foo bar

\n", "example": 557, "start_line": 8368, "end_line": 8374, "section": "Links" }, { "markdown": "[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"\n", "html": "

[foo bar]

\n", "example": 558, "start_line": 8377, "end_line": 8383, "section": "Links" }, { "markdown": "[[bar [foo]\n\n[foo]: /url\n", "html": "

[[bar foo

\n", "example": 559, "start_line": 8386, "end_line": 8392, "section": "Links" }, { "markdown": "[Foo]\n\n[foo]: /url \"title\"\n", "html": "

Foo

\n", "example": 560, "start_line": 8397, "end_line": 8403, "section": "Links" }, { "markdown": "[foo] bar\n\n[foo]: /url\n", "html": "

foo bar

\n", "example": 561, "start_line": 8408, "end_line": 8414, "section": "Links" }, { "markdown": "\\[foo]\n\n[foo]: /url \"title\"\n", "html": "

[foo]

\n", "example": 562, "start_line": 8420, "end_line": 8426, "section": "Links" }, { "markdown": "[foo*]: /url\n\n*[foo*]\n", "html": "

*foo*

\n", "example": 563, "start_line": 8432, "end_line": 8438, "section": "Links" }, { "markdown": "[foo][bar]\n\n[foo]: /url1\n[bar]: /url2\n", "html": "

foo

\n", "example": 564, "start_line": 8444, "end_line": 8451, "section": "Links" }, { "markdown": "[foo][]\n\n[foo]: /url1\n", "html": "

foo

\n", "example": 565, "start_line": 8453, "end_line": 8459, "section": "Links" }, { "markdown": "[foo]()\n\n[foo]: /url1\n", "html": "

foo

\n", "example": 566, "start_line": 8463, "end_line": 8469, "section": "Links" }, { "markdown": "[foo](not a link)\n\n[foo]: /url1\n", "html": "

foo(not a link)

\n", "example": 567, "start_line": 8471, "end_line": 8477, "section": "Links" }, { "markdown": "[foo][bar][baz]\n\n[baz]: /url\n", "html": "

[foo]bar

\n", "example": 568, "start_line": 8482, "end_line": 8488, "section": "Links" }, { "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2\n", "html": "

foobaz

\n", "example": 569, "start_line": 8494, "end_line": 8501, "section": "Links" }, { "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2\n", "html": "

[foo]bar

\n", "example": 570, "start_line": 8507, "end_line": 8514, "section": "Links" }, { "markdown": "![foo](/url \"title\")\n", "html": "

\"foo\"

\n", "example": 571, "start_line": 8530, "end_line": 8534, "section": "Images" }, { "markdown": "![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", "html": "

\"foo

\n", "example": 572, "start_line": 8537, "end_line": 8543, "section": "Images" }, { "markdown": "![foo ![bar](/url)](/url2)\n", "html": "

\"foo

\n", "example": 573, "start_line": 8546, "end_line": 8550, "section": "Images" }, { "markdown": "![foo [bar](/url)](/url2)\n", "html": "

\"foo

\n", "example": 574, "start_line": 8553, "end_line": 8557, "section": "Images" }, { "markdown": "![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", "html": "

\"foo

\n", "example": 575, "start_line": 8567, "end_line": 8573, "section": "Images" }, { "markdown": "![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"\n", "html": "

\"foo

\n", "example": 576, "start_line": 8576, "end_line": 8582, "section": "Images" }, { "markdown": "![foo](train.jpg)\n", "html": "

\"foo\"

\n", "example": 577, "start_line": 8585, "end_line": 8589, "section": "Images" }, { "markdown": "My ![foo bar](/path/to/train.jpg \"title\" )\n", "html": "

My \"foo

\n", "example": 578, "start_line": 8592, "end_line": 8596, "section": "Images" }, { "markdown": "![foo]()\n", "html": "

\"foo\"

\n", "example": 579, "start_line": 8599, "end_line": 8603, "section": "Images" }, { "markdown": "![](/url)\n", "html": "

\"\"

\n", "example": 580, "start_line": 8606, "end_line": 8610, "section": "Images" }, { "markdown": "![foo][bar]\n\n[bar]: /url\n", "html": "

\"foo\"

\n", "example": 581, "start_line": 8615, "end_line": 8621, "section": "Images" }, { "markdown": "![foo][bar]\n\n[BAR]: /url\n", "html": "

\"foo\"

\n", "example": 582, "start_line": 8624, "end_line": 8630, "section": "Images" }, { "markdown": "![foo][]\n\n[foo]: /url \"title\"\n", "html": "

\"foo\"

\n", "example": 583, "start_line": 8635, "end_line": 8641, "section": "Images" }, { "markdown": "![*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", "html": "

\"foo

\n", "example": 584, "start_line": 8644, "end_line": 8650, "section": "Images" }, { "markdown": "![Foo][]\n\n[foo]: /url \"title\"\n", "html": "

\"Foo\"

\n", "example": 585, "start_line": 8655, "end_line": 8661, "section": "Images" }, { "markdown": "![foo] \n[]\n\n[foo]: /url \"title\"\n", "html": "

\"foo\"\n[]

\n", "example": 586, "start_line": 8667, "end_line": 8675, "section": "Images" }, { "markdown": "![foo]\n\n[foo]: /url \"title\"\n", "html": "

\"foo\"

\n", "example": 587, "start_line": 8680, "end_line": 8686, "section": "Images" }, { "markdown": "![*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", "html": "

\"foo

\n", "example": 588, "start_line": 8689, "end_line": 8695, "section": "Images" }, { "markdown": "![[foo]]\n\n[[foo]]: /url \"title\"\n", "html": "

![[foo]]

\n

[[foo]]: /url "title"

\n", "example": 589, "start_line": 8700, "end_line": 8707, "section": "Images" }, { "markdown": "![Foo]\n\n[foo]: /url \"title\"\n", "html": "

\"Foo\"

\n", "example": 590, "start_line": 8712, "end_line": 8718, "section": "Images" }, { "markdown": "!\\[foo]\n\n[foo]: /url \"title\"\n", "html": "

![foo]

\n", "example": 591, "start_line": 8724, "end_line": 8730, "section": "Images" }, { "markdown": "\\![foo]\n\n[foo]: /url \"title\"\n", "html": "

!foo

\n", "example": 592, "start_line": 8736, "end_line": 8742, "section": "Images" }, { "markdown": "\n", "html": "

http://foo.bar.baz

\n", "example": 593, "start_line": 8769, "end_line": 8773, "section": "Autolinks" }, { "markdown": "\n", "html": "

http://foo.bar.baz/test?q=hello&id=22&boolean

\n", "example": 594, "start_line": 8776, "end_line": 8780, "section": "Autolinks" }, { "markdown": "\n", "html": "

irc://foo.bar:2233/baz

\n", "example": 595, "start_line": 8783, "end_line": 8787, "section": "Autolinks" }, { "markdown": "\n", "html": "

MAILTO:FOO@BAR.BAZ

\n", "example": 596, "start_line": 8792, "end_line": 8796, "section": "Autolinks" }, { "markdown": "\n", "html": "

a+b+c:d

\n", "example": 597, "start_line": 8804, "end_line": 8808, "section": "Autolinks" }, { "markdown": "\n", "html": "

made-up-scheme://foo,bar

\n", "example": 598, "start_line": 8811, "end_line": 8815, "section": "Autolinks" }, { "markdown": "\n", "html": "

http://../

\n", "example": 599, "start_line": 8818, "end_line": 8822, "section": "Autolinks" }, { "markdown": "\n", "html": "

localhost:5001/foo

\n", "example": 600, "start_line": 8825, "end_line": 8829, "section": "Autolinks" }, { "markdown": "\n", "html": "

<http://foo.bar/baz bim>

\n", "example": 601, "start_line": 8834, "end_line": 8838, "section": "Autolinks" }, { "markdown": "\n", "html": "

http://example.com/\\[\\

\n", "example": 602, "start_line": 8843, "end_line": 8847, "section": "Autolinks" }, { "markdown": "\n", "html": "

foo@bar.example.com

\n", "example": 603, "start_line": 8865, "end_line": 8869, "section": "Autolinks" }, { "markdown": "\n", "html": "

foo+special@Bar.baz-bar0.com

\n", "example": 604, "start_line": 8872, "end_line": 8876, "section": "Autolinks" }, { "markdown": "\n", "html": "

<foo+@bar.example.com>

\n", "example": 605, "start_line": 8881, "end_line": 8885, "section": "Autolinks" }, { "markdown": "<>\n", "html": "

<>

\n", "example": 606, "start_line": 8890, "end_line": 8894, "section": "Autolinks" }, { "markdown": "< http://foo.bar >\n", "html": "

< http://foo.bar >

\n", "example": 607, "start_line": 8897, "end_line": 8901, "section": "Autolinks" }, { "markdown": "\n", "html": "

<m:abc>

\n", "example": 608, "start_line": 8904, "end_line": 8908, "section": "Autolinks" }, { "markdown": "\n", "html": "

<foo.bar.baz>

\n", "example": 609, "start_line": 8911, "end_line": 8915, "section": "Autolinks" }, { "markdown": "http://example.com\n", "html": "

http://example.com

\n", "example": 610, "start_line": 8918, "end_line": 8922, "section": "Autolinks" }, { "markdown": "foo@bar.example.com\n", "html": "

foo@bar.example.com

\n", "example": 611, "start_line": 8925, "end_line": 8929, "section": "Autolinks" }, { "markdown": "\n", "html": "

\n", "example": 612, "start_line": 9006, "end_line": 9010, "section": "Raw HTML" }, { "markdown": "\n", "html": "

\n", "example": 613, "start_line": 9015, "end_line": 9019, "section": "Raw HTML" }, { "markdown": "\n", "html": "

\n", "example": 614, "start_line": 9024, "end_line": 9030, "section": "Raw HTML" }, { "markdown": "\n", "html": "

\n", "example": 615, "start_line": 9035, "end_line": 9041, "section": "Raw HTML" }, { "markdown": "Foo \n", "html": "

Foo

\n", "example": 616, "start_line": 9046, "end_line": 9050, "section": "Raw HTML" }, { "markdown": "<33> <__>\n", "html": "

<33> <__>

\n", "example": 617, "start_line": 9055, "end_line": 9059, "section": "Raw HTML" }, { "markdown": "
\n", "html": "

<a h*#ref="hi">

\n", "example": 618, "start_line": 9064, "end_line": 9068, "section": "Raw HTML" }, { "markdown": "
\n", "html": "

<a href="hi'> <a href=hi'>

\n", "example": 619, "start_line": 9073, "end_line": 9077, "section": "Raw HTML" }, { "markdown": "< a><\nfoo>\n\n", "html": "

< a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop />

\n", "example": 620, "start_line": 9082, "end_line": 9092, "section": "Raw HTML" }, { "markdown": "
\n", "html": "

<a href='bar'title=title>

\n", "example": 621, "start_line": 9097, "end_line": 9101, "section": "Raw HTML" }, { "markdown": "
\n", "html": "

\n", "example": 622, "start_line": 9106, "end_line": 9110, "section": "Raw HTML" }, { "markdown": "\n", "html": "

</a href="foo">

\n", "example": 623, "start_line": 9115, "end_line": 9119, "section": "Raw HTML" }, { "markdown": "foo \n", "html": "

foo

\n", "example": 624, "start_line": 9124, "end_line": 9130, "section": "Raw HTML" }, { "markdown": "foo \n", "html": "

foo <!-- not a comment -- two hyphens -->

\n", "example": 625, "start_line": 9133, "end_line": 9137, "section": "Raw HTML" }, { "markdown": "foo foo -->\n\nfoo \n", "html": "

foo <!--> foo -->

\n

foo <!-- foo--->

\n", "example": 626, "start_line": 9142, "end_line": 9149, "section": "Raw HTML" }, { "markdown": "foo \n", "html": "

foo

\n", "example": 627, "start_line": 9154, "end_line": 9158, "section": "Raw HTML" }, { "markdown": "foo \n", "html": "

foo

\n", "example": 628, "start_line": 9163, "end_line": 9167, "section": "Raw HTML" }, { "markdown": "foo &<]]>\n", "html": "

foo &<]]>

\n", "example": 629, "start_line": 9172, "end_line": 9176, "section": "Raw HTML" }, { "markdown": "foo \n", "html": "

foo

\n", "example": 630, "start_line": 9182, "end_line": 9186, "section": "Raw HTML" }, { "markdown": "foo \n", "html": "

foo

\n", "example": 631, "start_line": 9191, "end_line": 9195, "section": "Raw HTML" }, { "markdown": "\n", "html": "

<a href=""">

\n", "example": 632, "start_line": 9198, "end_line": 9202, "section": "Raw HTML" }, { "markdown": "foo \nbaz\n", "html": "

foo
\nbaz

\n", "example": 633, "start_line": 9212, "end_line": 9218, "section": "Hard line breaks" }, { "markdown": "foo\\\nbaz\n", "html": "

foo
\nbaz

\n", "example": 634, "start_line": 9224, "end_line": 9230, "section": "Hard line breaks" }, { "markdown": "foo \nbaz\n", "html": "

foo
\nbaz

\n", "example": 635, "start_line": 9235, "end_line": 9241, "section": "Hard line breaks" }, { "markdown": "foo \n bar\n", "html": "

foo
\nbar

\n", "example": 636, "start_line": 9246, "end_line": 9252, "section": "Hard line breaks" }, { "markdown": "foo\\\n bar\n", "html": "

foo
\nbar

\n", "example": 637, "start_line": 9255, "end_line": 9261, "section": "Hard line breaks" }, { "markdown": "*foo \nbar*\n", "html": "

foo
\nbar

\n", "example": 638, "start_line": 9267, "end_line": 9273, "section": "Hard line breaks" }, { "markdown": "*foo\\\nbar*\n", "html": "

foo
\nbar

\n", "example": 639, "start_line": 9276, "end_line": 9282, "section": "Hard line breaks" }, { "markdown": "`code \nspan`\n", "html": "

code span

\n", "example": 640, "start_line": 9287, "end_line": 9292, "section": "Hard line breaks" }, { "markdown": "`code\\\nspan`\n", "html": "

code\\ span

\n", "example": 641, "start_line": 9295, "end_line": 9300, "section": "Hard line breaks" }, { "markdown": "
\n", "html": "

\n", "example": 642, "start_line": 9305, "end_line": 9311, "section": "Hard line breaks" }, { "markdown": "\n", "html": "

\n", "example": 643, "start_line": 9314, "end_line": 9320, "section": "Hard line breaks" }, { "markdown": "foo\\\n", "html": "

foo\\

\n", "example": 644, "start_line": 9327, "end_line": 9331, "section": "Hard line breaks" }, { "markdown": "foo \n", "html": "

foo

\n", "example": 645, "start_line": 9334, "end_line": 9338, "section": "Hard line breaks" }, { "markdown": "### foo\\\n", "html": "

foo\\

\n", "example": 646, "start_line": 9341, "end_line": 9345, "section": "Hard line breaks" }, { "markdown": "### foo \n", "html": "

foo

\n", "example": 647, "start_line": 9348, "end_line": 9352, "section": "Hard line breaks" }, { "markdown": "foo\nbaz\n", "html": "

foo\nbaz

\n", "example": 648, "start_line": 9363, "end_line": 9369, "section": "Soft line breaks" }, { "markdown": "foo \n baz\n", "html": "

foo\nbaz

\n", "example": 649, "start_line": 9375, "end_line": 9381, "section": "Soft line breaks" }, { "markdown": "hello $.;'there\n", "html": "

hello $.;'there

\n", "example": 650, "start_line": 9395, "end_line": 9399, "section": "Textual content" }, { "markdown": "Foo χρῆν\n", "html": "

Foo χρῆν

\n", "example": 651, "start_line": 9402, "end_line": 9406, "section": "Textual content" }, { "markdown": "Multiple spaces\n", "html": "

Multiple spaces

\n", "example": 652, "start_line": 9411, "end_line": 9415, "section": "Textual content" } ] mdformat-0.7.17/tests/data/consecutive_numbering.md000066400000000000000000000006711454065404400224230ustar00rootroot00000000000000numbered lists [consecutive] . 1. a 2. b 3. c d . 1. a 2. b 3. c d . numbered lists starting number [consecutive] . 099. a 100. b 101. c d . 099. a 100. b 101. c d . numbered lists nested [consecutive] . 1. a 2. b 1. x 2. y z . 1. a 2. b 1. x 2. y z . 0 prefix . Prefix needed: 999. a 1000. b No prefix needed: 998. a 999. b . Prefix needed: 0999. a 1000. b No prefix needed: 998. a 999. b . mdformat-0.7.17/tests/data/default_style.md000066400000000000000000000124661454065404400206770ustar00rootroot00000000000000strip paragraph lines . trailing whitespace at the end of paragraph lines should be stripped . trailing whitespace at the end of paragraph lines should be stripped . strip quotes . > Paragraph 1 > > Paragraph 2 . > Paragraph 1 > > Paragraph 2 . no escape ampersand . R&B, rock & roll . R&B, rock & roll . list whitespaces . - item one - item two - sublist - sublist . - item one - item two - sublist - sublist . convert setext to ATX heading . Top level heading ========= 2nd level heading --------- . # Top level heading ## 2nd level heading . Lists with different bullets . - a - b * c . - a - b * c . numbered lists . 1. a 2. b 3. c d . 1. a 1. b 1. c d . numbered lists starting number . 099. a 100. b 101. c d . 99. a 01. b 01. c d . numbered lists nested . 1. a 2. b 1. x 2. y z . 1. a 1. b 1. x 1. y z . references: . [ref2]: link3 "title" [text](link1) [text](link2 "title") [ref1] [ref2] [text][ref1] ![text](link1) ![text](link2 "title") ![ref1] ![ref2] ![text][ref1] [ref1]: link4 [unused]: link5 . [text](link1) [text](link2 "title") [ref1] [ref2] [text][ref1] ![text](link1) ![text](link2 "title") ![ref1] ![ref2] ![text][ref1] [ref1]: link4 [ref2]: link3 "title" . thematic breaks . something something --- something something . something something ______________________________________________________________________ something something . Ordered list marker type . 1) a 2) b 1. x 2. y . 1. a 1. b 1) x 1) y . Bullet list marker type . * a * b + x + y - c - d + e + f . - a - b * x * y - c - d * e * f . Empty list item . - next item is empty - - whitespace should be stripped 1. next item is empty 1. 1. whitespace should be stripped . - next item is empty - - whitespace should be stripped 1. next item is empty 1. 1. whitespace should be stripped . Empty ref link destination . [foo]: <> [foo] . [foo] [foo]: <> . Don't escape hash . - Recalculate secondary dependencies between rounds (#378) . - Recalculate secondary dependencies between rounds (#378) . Only escape first ")" and "." . 1\) Only the first "\)" of a line should be escaped in this paragraph. 1\. Only the first "\." of a line should be escaped in this paragraph. First \. or \) char should not be escaped here because this line does not look like a list. . 1\) Only the first ")" of a line should be escaped in this paragraph. 1\. Only the first "." of a line should be escaped in this paragraph. First . or ) char should not be escaped here because this line does not look like a list. . Don't escape list item marker if not followed by whitespace or end of line . 1.No need to escape the dot here 1)No need to escape the closing bracket here 1. No need to escape the dot here (there is a no-break-space, not space) This needs escaping (end of line after the dot) 1. This needs escaping (space after the closing bracket) 1) . 1.No need to escape the dot here 1)No need to escape the closing bracket here 1. No need to escape the dot here (there is a no-break-space, not space) This needs escaping (end of line after the dot) 1\. This needs escaping (space after the closing bracket) 1\) . Escape line starting "+" and "-" . \+ \+No need to escape plus +No need to escape plus \- \-No need to escape dash -No need to escape dash . \+ +No need to escape plus +No need to escape plus \- -No need to escape dash -No need to escape dash . Escape ! preceding a link . We must escape the exclamation here \![link](https://www.debian.org/)!!! . We must escape the exclamation here \![link](https://www.debian.org/)!!! . Asterisk escapes . Escape*asterisk\* Don't * escape * asterisk . Escape\*asterisk\* Don't * escape * asterisk . Underscore escapes . Do _escape Don't esc_ape Don't _ escape _ underscore . Do \_escape Don't esc_ape Don't _ escape _ underscore . Keep shortcut reference links (dont convert to full reference) . ![Image] [iMaGe]: train.jpg [Foo] [fOO]: /url "title" . ![Image] [Foo] [foo]: /url "title" [image]: train.jpg . Empty document has no newline . . . Don't escape hash not followed by space . #No space after hash -> no need to escape . #No space after hash -> no need to escape . Indented raw HTML contains Markdown .
- list item
.
- list item
. Autolink with percentage encoded space . . . Keep mailto: prefix in autolink . > . > . Tilde in info string . ```~/.gitconfig [user] ``` . ```~/.gitconfig [user] ``` . Info string starts with tilde and contains backtick . ~~~\~` 123 ~~~ . ~~~\~` 123 ~~~ . Keep inline HTML as inline, and block HTML as block . A
A
. A
A
. Keep HTML blocks of type 7 unindented (they do not interrupt a paragraph) . text
text . text
text . sort digit references numerically . (References)[1] (should)[2] (sort)[10] (numerically)[word], if[🤪] they[⅕] are[𐩂] digits[!]. [🤪]: . [2]: . [word]: . [10]: . [⅕]: . [1]: . [𐩂]: . [!]: . . (References)[1] (should)[2] (sort)[10] (numerically)[word], if[🤪] they[⅕] are[𐩂] digits[!]. [!]: . [1]: . [2]: . [10]: . [word]: . [⅕]: . [𐩂]: . [🤪]: . . Unicode space (U+3000) after heading . # hoge   . # hoge . mdformat-0.7.17/tests/data/wrap_width_50.md000066400000000000000000000071741454065404400205070ustar00rootroot00000000000000Wrap before no-wrap section . We want to wrap before the inline code block `THIS IS THE LONG INLINE CODE BLOCK. IT SHOULD BE ON ITS OWN LINE`. No need to wrap before the emphasis section _THIS IS THE LONG EMPHASIS SECTION. ITS CONTENT SHOULD BE WRAPPED LIKE NORMAL TEXT_ We want to wrap before the link [THIS IS THE LINK THAT SHOULD BE ON ITS OWN LINE](https://www.python.org/) . We want to wrap before the inline code block `THIS IS THE LONG INLINE CODE BLOCK. IT SHOULD BE ON ITS OWN LINE`. No need to wrap before the emphasis section _THIS IS THE LONG EMPHASIS SECTION. ITS CONTENT SHOULD BE WRAPPED LIKE NORMAL TEXT_ We want to wrap before the link [THIS IS THE LINK THAT SHOULD BE ON ITS OWN LINE](https://www.python.org/) . Wrap boundary . z zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz z zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz . z zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz z zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz . No newline in image description . Blii blaa. There is no newline in this image ![here it is](https://github.com/executablebooks/) . Blii blaa. There is no newline in this image ![here it is](https://github.com/executablebooks/) . No newline in link text . Blii blaa. There is no newline in this link [here it is](https://github.com/executablebooks/) . Blii blaa. There is no newline in this link [here it is](https://github.com/executablebooks/) . Max width lines . Brackets are always dedented and that a trailing Aproduces smaller diffs; when you add or remove an So, having the closing bracket dedented provides a Sections of the code that otherwise share the same List and the docstring in the example above). . Brackets are always dedented and that a trailing Aproduces smaller diffs; when you add or remove an So, having the closing bracket dedented provides a Sections of the code that otherwise share the same List and the docstring in the example above). . No space between normal section and no-break section . As around `:` operators for "simple expressions" (`ham[lower:upper]`), and extra. . As around `:` operators for "simple expressions" (`ham[lower:upper]`), and extra. . Hard break in emphasized link . _[hard\ break](python.org)_ . _[hard\ break](python.org)_ . Link in emphasis . _[do not add line breaks in a link. That's the style, at least currently](python.org)_ . _[do not add line breaks in a link. That's the style, at least currently](python.org)_ . Indented blocks . no wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a do wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a - no wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a - do wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a 10. no wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a 11. do wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a > no wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a > do wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a . no wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a do wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a - no wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a - do wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a 10. no wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a 01. do wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a > no wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a > do wrap aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa > a . Only use space, tab and line feed as wrap points . aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa   No-wrap-points-until-now: Unicode whitespace shouldnt act as wrap point, the normal space should . aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa   No-wrap-points-until-now: Unicode whitespace shouldnt act as wrap point, the normal space should . mdformat-0.7.17/tests/requirements.txt000066400000000000000000000000421454065404400200470ustar00rootroot00000000000000pytest pytest-randomly pytest-cov mdformat-0.7.17/tests/test_api.py000066400000000000000000000066541454065404400167640ustar00rootroot00000000000000import os import pytest import mdformat from mdformat._util import is_md_equal UNFORMATTED_MARKDOWN = "\n\n# A header\n\n" FORMATTED_MARKDOWN = "# A header\n" def test_fmt_file(tmp_path): file_path = tmp_path / "test_markdown.md" # Use string argument file_path.write_text(UNFORMATTED_MARKDOWN) mdformat.file(str(file_path)) assert file_path.read_text() == FORMATTED_MARKDOWN # Use pathlib.Path argument file_path.write_text(UNFORMATTED_MARKDOWN) mdformat.file(file_path) assert file_path.read_text() == FORMATTED_MARKDOWN def test_fmt_file__invalid_filename(): with pytest.raises(ValueError) as exc_info: mdformat.file("this is not a valid filepath?`=|><@{[]\\/,.%¤#'") assert "not a file" in str(exc_info.value) def test_fmt_file__symlink(tmp_path): file_path = tmp_path / "test_markdown.md" file_path.write_text(UNFORMATTED_MARKDOWN) symlink_path = tmp_path / "symlink.md" symlink_path.symlink_to(file_path) with pytest.raises(ValueError) as exc_info: mdformat.file(symlink_path) assert "It is a symlink" in str(exc_info.value) def test_fmt_string(): assert mdformat.text(UNFORMATTED_MARKDOWN) == FORMATTED_MARKDOWN @pytest.mark.parametrize( "input_", [ pytest.param("\x0b"), # vertical tab only pytest.param("\t##"), # no trailing newline in codeblock pytest.param("a\n\n\xa0\n\nb"), # lone NBSP between two paragraphs pytest.param("\xa0\n\n# heading"), # lone NBSP followed by a heading pytest.param( "```\na\n```\n\u2003\n# A\n", marks=pytest.mark.xfail() ), # em space surrounded by code and header ], ) def test_output_is_equal(input_): output = mdformat.text(input_) assert is_md_equal(input_, output) @pytest.mark.parametrize( "input_", [ pytest.param("\x1c\n\na"), pytest.param(">\x0b"), pytest.param("<@{[]\\/,.%¤#'",)) assert exc_info.value.code == 2 captured = capsys.readouterr() assert "does not exist" in captured.err def test_check(tmp_path): file_path = tmp_path / "test_markdown.md" file_path.write_bytes(FORMATTED_MARKDOWN.encode()) assert run((str(file_path), "--check")) == 0 def test_check__fail(tmp_path): file_path = tmp_path / "test_markdown.md" file_path.write_text(UNFORMATTED_MARKDOWN) assert run((str(file_path), "--check")) == 1 def test_check__multi_fail(capsys, tmp_path): """Test for --check flag when multiple files are unformatted. Test that the names of all unformatted files are listed when using --check. """ file_path1 = tmp_path / "test_markdown1.md" file_path2 = tmp_path / "test_markdown2.md" file_path1.write_text(UNFORMATTED_MARKDOWN) file_path2.write_text(UNFORMATTED_MARKDOWN) assert run((str(tmp_path), "--check")) == 1 captured = capsys.readouterr() assert str(file_path1) in captured.err assert str(file_path2) in captured.err def example_formatter(code, info): return "dummy\n" def test_formatter_plugin(tmp_path, monkeypatch): monkeypatch.setitem(CODEFORMATTERS, "lang", example_formatter) file_path = tmp_path / "test_markdown.md" file_path.write_text("```lang\nother\n```\n") assert run((str(file_path),)) == 0 assert file_path.read_text() == "```lang\ndummy\n```\n" def test_dash_stdin(capfd, monkeypatch): monkeypatch.setattr(sys, "stdin", StringIO(UNFORMATTED_MARKDOWN)) assert run(("-",)) == 0 captured = capfd.readouterr() assert captured.out == FORMATTED_MARKDOWN def test_wrap_paragraphs(): with patch("shutil.get_terminal_size", return_value=(72, 24)): assert wrap_paragraphs( [ 'Error: Could not format "/home/user/file_name_longer_than_wrap_width--------------------------------------.md".', # noqa: E501 "The formatted Markdown renders to different HTML than the input Markdown. " # noqa: E501 "This is likely a bug in mdformat. " "Please create an issue report here: " "https://github.com/executablebooks/mdformat/issues", ] ) == ( "Error: Could not format\n" '"/home/user/file_name_longer_than_wrap_width--------------------------------------.md".\n' # noqa: E501 "\n" "The formatted Markdown renders to different HTML than the input\n" "Markdown. This is likely a bug in mdformat. Please create an issue\n" "report here: https://github.com/executablebooks/mdformat/issues\n" ) def test_version(capsys): with pytest.raises(SystemExit) as exc_info: run(["--version"]) assert exc_info.value.code == 0 captured = capsys.readouterr() assert captured.out.startswith(f"mdformat {mdformat.__version__}") def test_no_wrap(tmp_path): file_path = tmp_path / "test_markdown.md" file_path.write_text( "all\n" "these newlines\n" "except the one in this hardbreak \n" "should be\n" "removed because they are\n" "in the same paragraph\n" "\n" "This however is the next\n" "paragraph. Whitespace should be collapsed\n" " \t here\n" ) assert run([str(file_path), "--wrap=no"]) == 0 assert ( file_path.read_text() == "all these newlines except the one in this hardbreak\\\n" "should be removed because they are in the same paragraph\n" "\n" "This however is the next paragraph. Whitespace should be collapsed here\n" ) def test_wrap(tmp_path): file_path = tmp_path / "test_markdown.md" file_path.write_text( "This\n" "text\n" "should\n" "be wrapped again so that wrap width is whatever is defined below. " "Also whitespace should\t\tbe collapsed. " "Next up a second paragraph:\n" "\n" "This paragraph should also be wrapped. " "Here's some more text to wrap. " "Here's some more text to wrap. " "Here's some more text to wrap. " ) assert run([str(file_path), "--wrap=60"]) == 0 assert ( file_path.read_text() == "This text should be wrapped again so that wrap width is\n" "whatever is defined below. Also whitespace should be\n" "collapsed. Next up a second paragraph:\n" "\n" "This paragraph should also be wrapped. Here's some more text\n" "to wrap. Here's some more text to wrap. Here's some more\n" "text to wrap.\n" ) def test_consecutive_wrap_width_lines(tmp_path): """Test a case where two consecutive lines' width equals wrap width. There was a bug where this would cause an extra newline and split the paragraph, hence the test. """ wrap_width = 20 file_path = tmp_path / "test_markdown.md" text = "A" * wrap_width + "\n" + "A" * wrap_width + "\n" file_path.write_text(text) assert run([str(file_path), f"--wrap={wrap_width}"]) == 0 assert file_path.read_text() == text @pytest.mark.xfail(reason="https://github.com/executablebooks/mdformat/issues/326") def test_wrap__hard_break(tmp_path): file_path = tmp_path / "test_markdown.md" file_path.write_text( "This\n" "text\n" "should\n" "be wrapped again\\\n" "so that wrap width is whatever is defined below. " "Also whitespace should\t\tbe collapsed." ) assert run([str(file_path), "--wrap=60"]) == 0 assert ( file_path.read_text() == "This text should be wrapped again\\\n" "so that wrap width is whatever is defined below. Also\n" "whitespace should be collapsed.\n" ) def test_bad_wrap_width(capsys): with pytest.raises(SystemExit) as exc_info: run(["some-path.md", "--wrap=-1"]) assert exc_info.value.code == 2 captured = capsys.readouterr() assert "error: argument --wrap" in captured.err def test_eol__lf(tmp_path): file_path = tmp_path / "test.md" file_path.write_bytes(b"Oi\r\n") assert run([str(file_path)]) == 0 assert file_path.read_bytes() == b"Oi\n" def test_eol__crlf(tmp_path): file_path = tmp_path / "test.md" file_path.write_bytes(b"Oi\n") assert run([str(file_path), "--end-of-line=crlf"]) == 0 assert file_path.read_bytes() == b"Oi\r\n" def test_eol__keep_lf(tmp_path): file_path = tmp_path / "test.md" file_path.write_bytes(b"Oi\n") assert run([str(file_path), "--end-of-line=keep"]) == 0 assert file_path.read_bytes() == b"Oi\n" def test_eol__keep_crlf(tmp_path): file_path = tmp_path / "test.md" file_path.write_bytes(b"Oi\r\n") assert run([str(file_path), "--end-of-line=keep"]) == 0 assert file_path.read_bytes() == b"Oi\r\n" def test_eol__crlf_stdin(capfd, monkeypatch): monkeypatch.setattr(sys, "stdin", StringIO("Oi\n")) assert run(["-", "--end-of-line=crlf"]) == 0 captured = capfd.readouterr() assert captured.out == "Oi\r\n" def test_eol__check_lf(tmp_path): file_path = tmp_path / "test.md" file_path.write_bytes(b"lol\r\n") assert run((str(file_path), "--check")) == 1 file_path.write_bytes(b"lol\n") assert run((str(file_path), "--check")) == 0 def test_eol__check_crlf(tmp_path): file_path = tmp_path / "test.md" file_path.write_bytes(b"lol\n") assert run((str(file_path), "--check", "--end-of-line=crlf")) == 1 file_path.write_bytes(b"lol\r\n") assert run((str(file_path), "--check", "--end-of-line=crlf")) == 0 def test_eol__check_keep_lf(tmp_path): file_path = tmp_path / "test.md" file_path.write_bytes(b"lol\n") assert run((str(file_path), "--check", "--end-of-line=keep")) == 0 file_path.write_bytes(b"mixed\nEOLs\r") assert run((str(file_path), "--check", "--end-of-line=keep")) == 1 def test_eol__check_keep_crlf(tmp_path): file_path = tmp_path / "test.md" file_path.write_bytes(b"lol\r\n") assert run((str(file_path), "--check", "--end-of-line=keep")) == 0 file_path.write_bytes(b"mixed\r\nEOLs\n") assert run((str(file_path), "--check", "--end-of-line=keep")) == 1 def test_get_package_name(): # Test a function/class assert get_package_name(patch) == "unittest" # Test a package/module assert get_package_name(mdformat) == "mdformat" def test_no_timestamp_modify(tmp_path): file_path = tmp_path / "test.md" file_path.write_bytes(b"lol\n") initial_access_time = 0 initial_mod_time = 0 os.utime(file_path, (initial_access_time, initial_mod_time)) # Assert that modification time does not change when no changes are applied assert run([str(file_path)]) == 0 assert os.path.getmtime(file_path) == initial_mod_time mdformat-0.7.17/tests/test_commonmark_spec.py000066400000000000000000000115041454065404400213560ustar00rootroot00000000000000import json from pathlib import Path from _pytest.mark import ParameterSet import pytest import mdformat from mdformat._util import is_md_equal SPECTESTS_PATH = Path(__file__).parent / "data" / "commonmark_spec_v0.30.json" SPECTESTS_CASES = tuple( {"name": str(entry["example"]), "md": entry["markdown"]} for entry in json.loads(SPECTESTS_PATH.read_text(encoding="utf-8")) ) EXTRA_CASES = ( { "name": "titles", "md": "# BIG Title\n" "## smaller\n" "### even smaller\n" "#### 4th level\n" "##### 5th level\n" "###### the smallest (6th level)\n", }, {"name": "emphasis", "md": "*emphasized*\n" "**STRONGLY emphasized**\n"}, { "name": "simple", "md": "# BIG Title\n" "> a quote here\n\n" "Paragraph is here.\n", }, {"name": "strikethrough", "md": "# Testing strikethrough\n" "~~here goes~~\n"}, {"name": "code span", "md": "`print('Hello World!)`\n"}, { "name": "code span: double spaces", "md": "` starts and ends with double spaces `\n", }, {"name": "escape char", "md": "\\*not emphasized\\*\n"}, { "name": "fenced code", "md": "## Here's a code block\n" "```python\n" "print('Hello world!')\n" "```\n", }, {"name": "paragraph line feeds", "md": "first paragraph\n\n" "second one\n"}, {"name": "thematic break", "md": "thematic break\n\n" "---\n" "above\n"}, {"name": "autolink", "md": "\n"}, {"name": "link", "md": '[link](/uri "title")\n' "[link](/uri)\n" "[link]()\n"}, {"name": "image", "md": '![foo](/url "title")\n' "![foo [bar](/url)](/url2)\n"}, {"name": "unordered list", "md": "- item1\n" "- item2\n"}, {"name": "unordered loose list", "md": "- item1\n" "\n" "- item2\n"}, {"name": "ordered list", "md": "1. item1\n" "1. item2\n"}, {"name": "ordered list 2", "md": "10021. item1\n" "10021. item2\n"}, {"name": "ordered list zero", "md": "0. item1\n" "0. item2\n"}, {"name": "whitespace preserve", "md": "- foo\n\n\tbar\n"}, {"name": "debug", "md": "> ```\n> a\n> \n> \n> ```\n"}, {"name": "list indentation", "md": "- foo\n\n\t\tbar\n"}, {"name": "list in quote", "md": "> -"}, {"name": "starts with >", "md": "\\>"}, {"name": "reference link", "md": '[foo][bar]\n\n[bar]: /url "title"\n'}, {"name": "empty file", "md": ""}, {"name": "whitespace only", "md": " \n\n \n \n"}, {"name": "starts with em space", "md": " \n"}, {"name": "starts with space", "md": " \n"}, {"name": "trailing space", "md": "trailing space \n"}, {"name": "soft breaks", "md": "this is\nall one\nparagraph\n"}, {"name": "escape underscore", "md": "# foo _bar_ \\_baz\\_\n"}, { "name": "extend spectest 300", "md": "\\_not emphasized_\n" "1\\) not a list\n" "\\- not a list\n" "\\+ not a list\n", }, {"name": "image link with brackets 1", "md": "![link]()\n"}, {"name": "image link with brackets 2", "md": "![link](foo\\(and\\(bar\\))\n"}, {"name": "image link with brackets 3", "md": "![link](foo\\)\\:)\n"}, {"name": "image link with brackets 4", "md": "![a]()\n"}, {"name": "image link with brackets 5", "md": '![a]( "some title")\n'}, {"name": "escaped thematic break (hyphen)", "md": "\\-\\-\\-\n"}, {"name": "escaped thematic break (underscore v1)", "md": "\\_\\_\\_\n"}, {"name": "escaped thematic break (underscore v2)", "md": " _ _ _ \n"}, {"name": "escaped thematic break (asterisk v1)", "md": "\\*\\*\\*\n"}, {"name": "escaped thematic break (asterisk v2)", "md": " * * * \n"}, {"name": "Hard break in setext heading", "md": "line1\\\nline2\n====\n"}, { "name": "a mix of leading and trailing whitespace", "md": " foo\n" " foo \n" " foo \t\n" " foo \n" "\u2005foo\u2005\n" "foo\n", }, ) ALL_CASES = EXTRA_CASES + SPECTESTS_CASES @pytest.mark.parametrize("wrap", ["keep", "no", 60]) @pytest.mark.parametrize("number", [True, False]) @pytest.mark.parametrize( "entry", ALL_CASES, ids=[ c.values[0]["name"] # type: ignore[index] if isinstance(c, ParameterSet) else c["name"] for c in ALL_CASES ], ) def test_commonmark_spec(wrap, number, entry): """mdformat.text() against the Commonmark spec. Test that: 1. Markdown AST is the same before and after 1 pass of formatting 2. Markdown after 1st pass and 2nd pass of formatting are equal """ options = {"wrap": wrap, "number": number} md_original = entry["md"] md_new = mdformat.text(md_original, options=options) md_2nd_pass = mdformat.text(md_new, options=options) assert is_md_equal(md_original, md_new, options=options) assert md_new == md_2nd_pass mdformat-0.7.17/tests/test_config_file.py000066400000000000000000000051731454065404400204520ustar00rootroot00000000000000from io import StringIO import sys from unittest import mock import pytest from mdformat._cli import run from mdformat._conf import read_toml_opts def test_cli_override(tmp_path): config_path = tmp_path / ".mdformat.toml" config_path.write_text("wrap = 'no'") file_path = tmp_path / "test_markdown.md" file_path.write_text("remove\nthis\nwrap\n") assert run((str(file_path), "--wrap=keep")) == 0 assert file_path.read_text() == "remove\nthis\nwrap\n" assert run((str(file_path),)) == 0 assert file_path.read_text() == "remove this wrap\n" def test_conf_in_parent_dir(tmp_path): config_path = tmp_path / ".mdformat.toml" config_path.write_text("wrap = 'no'") subdir_path = tmp_path / "subdir" subdir_path.mkdir() file_path = subdir_path / "test_markdown.md" file_path.write_text("remove\nthis\nwrap") assert run((str(file_path),)) == 0 assert file_path.read_text() == "remove this wrap\n" def test_invalid_conf_key(tmp_path, capsys): config_path = tmp_path / ".mdformat.toml" config_path.write_text("numberr = true") file_path = tmp_path / "test_markdown.md" file_path.write_text("1. one\n1. two\n1. three") assert run((str(file_path),)) == 1 captured = capsys.readouterr() assert "Invalid key 'numberr'" in captured.err def test_invalid_toml(tmp_path, capsys): config_path = tmp_path / ".mdformat.toml" config_path.write_text("]invalid TOML[") file_path = tmp_path / "test_markdown.md" file_path.write_text("some markdown\n") assert run((str(file_path),)) == 1 captured = capsys.readouterr() assert "Invalid TOML syntax" in captured.err @pytest.mark.parametrize( "conf_key, bad_conf", [ ("wrap", "wrap = -3"), ("end_of_line", "end_of_line = 'lol'"), ("number", "number = 0"), ], ) def test_invalid_conf_value(bad_conf, conf_key, tmp_path, capsys): config_path = tmp_path / ".mdformat.toml" config_path.write_text(bad_conf) file_path = tmp_path / "test_markdown.md" file_path.write_text("# Test Markdown") assert run((str(file_path),)) == 1 captured = capsys.readouterr() assert f"Invalid '{conf_key}' value" in captured.err def test_conf_with_stdin(tmp_path, capfd, monkeypatch): read_toml_opts.cache_clear() config_path = tmp_path / ".mdformat.toml" config_path.write_text("number = true") monkeypatch.setattr(sys, "stdin", StringIO("1. one\n1. two\n1. three")) with mock.patch("mdformat._cli.Path.cwd", return_value=tmp_path): assert run(("-",)) == 0 captured = capfd.readouterr() assert captured.out == "1. one\n2. two\n3. three\n" mdformat-0.7.17/tests/test_context.py000066400000000000000000000011661454065404400176700ustar00rootroot00000000000000from mdformat.renderer import DEFAULT_RENDERERS from mdformat.renderer._context import RenderContext def render_fake_syntax(node, ctx): return "blaa" def test_with_default_renderer_for(): original_renderers = { "fake_syntax": render_fake_syntax, "fake_syntax_2": render_fake_syntax, } ctx = RenderContext( renderers=original_renderers, postprocessors={}, options={}, env={} ) ctx_2 = ctx.with_default_renderer_for("fake_syntax", "paragraph") assert ctx_2.renderers == { "fake_syntax_2": render_fake_syntax, "paragraph": DEFAULT_RENDERERS["paragraph"], } mdformat-0.7.17/tests/test_for_profiler.py000066400000000000000000000016131454065404400206710ustar00rootroot00000000000000"""Test for profiling. This test can be useful for profiling, as most of the execution time will be spent parsing and rendering instead of managing pytest execution environment. The test simply ensures that the README and Markdown docs in this project are formatted. To get and read profiler results: - `tox -e profile` - `firefox .tox/prof/combined.svg` """ from pathlib import Path from mdformat._cli import run PROJECT_ROOT = Path(__file__).parent.parent # Make a few asserts to ensure this actually is the project root # (a safeguard against refactorings where this file is moved). assert (PROJECT_ROOT / "docs").exists() assert (PROJECT_ROOT / "README.md").exists() assert (PROJECT_ROOT / "src" / "mdformat").exists() def test_for_profiler(): docs_path = PROJECT_ROOT / "docs" readme_path = PROJECT_ROOT / "README.md" assert run([str(docs_path), str(readme_path), "--check"]) == 0 mdformat-0.7.17/tests/test_plugins.py000066400000000000000000000177001454065404400176660ustar00rootroot00000000000000import argparse import json from textwrap import dedent from unittest.mock import patch from markdown_it import MarkdownIt import pytest import mdformat from mdformat._cli import run from mdformat.plugins import CODEFORMATTERS, PARSER_EXTENSIONS from mdformat.renderer import MDRenderer, RenderContext, RenderTreeNode def example_formatter(code, info): return "dummy\n" def test_code_formatter(monkeypatch): monkeypatch.setitem(CODEFORMATTERS, "lang", example_formatter) text = mdformat.text( dedent( """\ ```lang a ``` """ ), codeformatters={"lang"}, ) assert text == dedent( """\ ```lang dummy ``` """ ) class TextEditorPlugin: """A plugin that makes all text the same.""" @staticmethod def update_mdit(mdit: MarkdownIt): pass def _text_renderer( # type: ignore[misc] tree: RenderTreeNode, context: RenderContext ) -> str: return "All text is like this now!" RENDERERS = {"text": _text_renderer} def test_single_token_extension(monkeypatch): """Test the front matter plugin, as a single token extension example.""" plugin_name = "text_editor" monkeypatch.setitem(PARSER_EXTENSIONS, plugin_name, TextEditorPlugin) text = mdformat.text( dedent( """\ # Example Heading Example paragraph. """ ), extensions=[plugin_name], ) assert text == dedent( """\ # All text is like this now! All text is like this now! """ ) class ExampleTablePlugin: """A plugin that adds table extension to the parser.""" @staticmethod def update_mdit(mdit: MarkdownIt): mdit.enable("table") def _table_renderer( # type: ignore[misc] tree: RenderTreeNode, context: RenderContext ) -> str: return "dummy 21" RENDERERS = {"table": _table_renderer} def test_table(monkeypatch): """Test the table plugin, as a multi-token extension example.""" monkeypatch.setitem(PARSER_EXTENSIONS, "table", ExampleTablePlugin) text = mdformat.text( dedent( """\ |a|b| |-|-| |c|d| other text """ ), extensions=["table"], ) assert text == dedent( """\ dummy 21 other text """ ) class ExamplePluginWithCli: """A plugin that adds CLI options.""" @staticmethod def update_mdit(mdit: MarkdownIt): mdit.enable("table") @staticmethod def add_cli_options(parser: argparse.ArgumentParser) -> None: parser.add_argument("--o1", type=str) parser.add_argument("--o2", type=str, default="a") parser.add_argument("--o3", dest="arg_name", type=int) def test_cli_options(monkeypatch, tmp_path): """Test that CLI arguments added by plugins are correctly added to the options dict.""" monkeypatch.setitem(PARSER_EXTENSIONS, "table", ExamplePluginWithCli) file_path = tmp_path / "test_markdown.md" file_path.touch() with patch.object(MDRenderer, "render", return_value="") as mock_render: assert run((str(file_path), "--o1", "other", "--o3", "4")) == 0 (call_,) = mock_render.call_args_list posargs = call_[0] # Options is the second positional arg of MDRender.render opts = posargs[1] assert opts["mdformat"]["o1"] == "other" assert opts["mdformat"]["o2"] == "a" assert opts["mdformat"]["arg_name"] == 4 class ExampleASTChangingPlugin: """A plugin that makes AST breaking formatting changes.""" CHANGES_AST = True TEXT_REPLACEMENT = "Content replaced completely. AST is now broken!" @staticmethod def update_mdit(mdit: MarkdownIt): pass def _text_renderer( # type: ignore[misc] tree: RenderTreeNode, context: RenderContext ) -> str: return ExampleASTChangingPlugin.TEXT_REPLACEMENT RENDERERS = {"text": _text_renderer} def test_ast_changing_plugin(monkeypatch, tmp_path): plugin = ExampleASTChangingPlugin() monkeypatch.setitem(PARSER_EXTENSIONS, "ast_changer", plugin) file_path = tmp_path / "test_markdown.md" # Test that the AST changing formatting is applied successfully # under normal operation. file_path.write_text("Some markdown here\n") assert run((str(file_path),)) == 0 assert file_path.read_text() == plugin.TEXT_REPLACEMENT + "\n" # Set the plugin's `CHANGES_AST` flag to False and test that the # equality check triggers, notices the AST breaking changes and a # non-zero error code is returned. plugin.CHANGES_AST = False file_path.write_text("Some markdown here\n") assert run((str(file_path),)) == 1 assert file_path.read_text() == "Some markdown here\n" class JSONFormatterPlugin: """A code formatter plugin that formats JSON.""" @staticmethod def format_json(unformatted: str, _info_str: str) -> str: parsed = json.loads(unformatted) return json.dumps(parsed, indent=2) + "\n" def test_code_format_warnings(monkeypatch, tmp_path, capsys): monkeypatch.setitem(CODEFORMATTERS, "json", JSONFormatterPlugin.format_json) file_path = tmp_path / "test_markdown.md" file_path.write_text("```json\nthis is invalid json\n```\n") assert run([str(file_path)]) == 0 captured = capsys.readouterr() assert ( captured.err == f"Warning: Failed formatting content of a json code block (line 1 before formatting). Filename: {file_path}\n" # noqa: E501 ) def test_plugin_conflict(monkeypatch, tmp_path, capsys): """Test a warning when plugins try to render same syntax.""" plugin_name_1 = "plug1" plugin_name_2 = "plug2" monkeypatch.setitem(PARSER_EXTENSIONS, plugin_name_1, TextEditorPlugin) monkeypatch.setitem(PARSER_EXTENSIONS, plugin_name_2, ExampleASTChangingPlugin) file_path = tmp_path / "test_markdown.md" file_path.write_text("some markdown here") assert run([str(file_path)]) == 0 captured = capsys.readouterr() assert ( captured.err == 'Warning: Plugin conflict. More than one plugin defined a renderer for "text" syntax.\n' # noqa: E501 ) def test_plugin_versions_in_cli_help(monkeypatch, capsys): monkeypatch.setitem(PARSER_EXTENSIONS, "table", ExampleTablePlugin) with pytest.raises(SystemExit) as exc_info: run(["--help"]) assert exc_info.value.code == 0 captured = capsys.readouterr() assert "Installed plugins:" in captured.out assert "tests: unknown" in captured.out class PrefixPostprocessPlugin: """A plugin that postprocesses text, adding a prefix.""" CHANGES_AST = True @staticmethod def update_mdit(mdit: MarkdownIt): pass def _text_postprocess( # type: ignore[misc] text: str, tree: RenderTreeNode, context: RenderContext ) -> str: return "Prefixed!" + text RENDERERS: dict = {} POSTPROCESSORS = {"text": _text_postprocess} class SuffixPostprocessPlugin: """A plugin that postprocesses text, adding a suffix.""" CHANGES_AST = True @staticmethod def update_mdit(mdit: MarkdownIt): pass def _text_postprocess( # type: ignore[misc] text: str, tree: RenderTreeNode, context: RenderContext ) -> str: return text + "Suffixed!" RENDERERS: dict = {} POSTPROCESSORS = {"text": _text_postprocess} def test_postprocess_plugins(monkeypatch): """Test that postprocessors work collaboratively.""" suffix_plugin_name = "suffixer" prefix_plugin_name = "prefixer" monkeypatch.setitem(PARSER_EXTENSIONS, suffix_plugin_name, SuffixPostprocessPlugin) monkeypatch.setitem(PARSER_EXTENSIONS, prefix_plugin_name, PrefixPostprocessPlugin) text = mdformat.text( dedent( """\ # Example Heading. Example paragraph. """ ), extensions=[suffix_plugin_name, prefix_plugin_name], ) assert text == dedent( """\ # Prefixed!Example Heading.Suffixed! Prefixed!Example paragraph.Suffixed! """ ) mdformat-0.7.17/tests/test_style.py000066400000000000000000000016461454065404400173470ustar00rootroot00000000000000from pathlib import Path from markdown_it.utils import read_fixture_file import pytest import mdformat import mdformat._cli @pytest.mark.parametrize( "fixture_file,options", [ ("default_style.md", []), ("consecutive_numbering.md", ["--number"]), ("wrap_width_50.md", ["--wrap=50"]), ], ) def test_style(fixture_file, options, tmp_path): """Test Markdown renderer renders expected style.""" file_path = tmp_path / "test_markdown.md" cases = read_fixture_file(Path(__file__).parent / "data" / fixture_file) for case in cases: line, title, text, expected = case file_path.write_bytes(text.encode()) assert mdformat._cli.run([str(file_path), *options]) == 0 md_new = file_path.read_bytes().decode() if md_new != expected: print("Formatted (unexpected) Markdown below:") print(md_new) assert md_new == expected