| # Contribute | |
| We welcome all contributions! ✨ | |
| See the [EBP Contributing Guide](https://executablebooks.org/en/latest/contribute/) for general details, and below for guidance specific to markdown-it-py. | |
| Before continuing, make sure you've read: | |
| 1. [Architecture description](md/architecture) | |
| 2. [Security considerations](md/security) | |
| 3. [API documentation](api/markdown_it) | |
| ## Development guidance | |
| Details of the port can be found in the `markdown_it/port.yaml` and in `port.yaml` files, within the extension folders. | |
| ## Code Style | |
| Code style is tested using [ruff](https://github.com/astral-sh/ruff), with the configuration set in `pyproject.toml`, and code formatted with [black](https://github.com/ambv/black). | |
| Installing with `markdown-it-py[code_style]` makes the [pre-commit](https://pre-commit.com/) package available, which will ensure this style is met before commits are submitted, by reformatting the code and testing for lint errors. | |
| It can be setup by: | |
| ```shell | |
| >> cd markdown-it-py | |
| >> pre-commit install | |
| ``` | |
| Editors like VS Code also have automatic code reformat utilities, which can adhere to this standard. | |
| All functions and class methods should be annotated with types and include a docstring. | |
| ## Testing | |
| For code tests, markdown-it-py uses [pytest](https://docs.pytest.org): | |
| ```shell | |
| >> cd markdown-it-py | |
| >> pytest | |
| ``` | |
| You can also use [tox](https://tox.readthedocs.io), to run the tests in multiple isolated environments (see the `tox.ini` file for available test environments): | |
| ```shell | |
| >> cd markdown-it-py | |
| >> tox -p | |
| ``` | |
| This can also be used to run benchmarking tests using [pytest-benchmark](https://pytest-benchmark.readthedocs.io): | |
| ```shell | |
| >> cd markdown-it-py | |
| tox -e py310-bench-packages -- --benchmark-min-rounds 50 | |
| ``` | |
| For documentation build tests: | |
| ```shell | |
| >> cd markdown-it-py/docs | |
| >> make clean | |
| >> make html-strict | |
| ``` | |
| ### Updating the commonmark specification | |
| If you need to update the commonmark specification, you can do so by running: | |
| ```shell | |
| >> cd markdown-it-py | |
| >> python tests/test_cmark_spec/get_cmark_spec.py | |
| ``` | |
| or | |
| ```shell | |
| >> cd markdown-it-py | |
| >> uv run tests/test_cmark_spec/get_cmark_spec.py | |
| ``` | |
| ## Contributing a plugin | |
| 1. Does it already exist as JavaScript implementation ([see npm](https://www.npmjs.com/search?q=keywords:markdown-it-plugin))? | |
| Where possible try to port directly from that. | |
| It is usually better to modify existing code, instead of writing all from scratch. | |
| 2. Try to find the right place for your plugin rule: | |
| - Will it conflict with existing markup (by priority)? | |
| - If yes - you need to write an inline or block rule. | |
| - If no - you can morph tokens within core chains. | |
| - Remember that token morphing in core chains is always more simple than writing | |
| block or inline rules, if you don't copy existing ones. However, | |
| block and inline rules are usually faster. | |
| - Sometimes, it's enough to only modify the renderer, for example, to add | |
| header IDs or `target="_blank"` for the links. | |
| ## FAQ | |
| ### I need async rule, how to do it? | |
| Sorry. You can't do it directly. All complex parsers are sync by nature. But you | |
| can use workarounds: | |
| 1. On parse phase, replace content by random number and store it in `env`. | |
| 2. Do async processing over collected data. | |
| 3. Render content and replace those random numbers with text; or replace first, then render. | |
| Alternatively, you can render HTML, then parse it to DOM, or | |
| [cheerio](https://github.com/cheeriojs/cheerio) AST, and apply transformations | |
| in a more convenient way. | |
| ### How to replace part of text token with link? | |
| The right sequence is to split text to several tokens and add link tokens in between. | |
| The result will be: `text` + `link_open` + `text` + `link_close` + `text`. | |
| See implementations of [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.mjs) and [emoji](https://github.com/markdown-it/markdown-it-emoji/blob/master/lib/replace.mjs) - those do text token splits. | |
| __Note:__ Don't try to replace text with HTML markup! That's not secure. | |
| ### Why is my inline rule not executed? | |
| The inline parser skips pieces of texts to optimize speed. It stops only on [a small set of chars](https://github.com/executablebooks/markdown-it-py/blob/master/markdown_it/parser_inline.py), which can be tokens. | |
| If your inline rule needs to trigger on a character that is not in the default terminator set, you can register it via `md.inline.add_terminator_char`: | |
| ```python | |
| def my_plugin(md: MarkdownIt) -> None: | |
| md.inline.add_terminator_char("w") # stop text rule on 'w' | |
| md.inline.ruler.push("my_rule", my_inline_rule) | |
| ``` |