From 15a3ec6f6591ad0c2a659a51819288b9ee052e39 Mon Sep 17 00:00:00 2001 From: Kyle King Date: Wed, 11 Jan 2023 19:54:47 -0500 Subject: [PATCH] feat: support real world use cases (#1) --- .pre-commit-config.yaml | 3 +- .tool-versions | 1 + README.md | 21 +-- mdformat_mkdocs/__init__.py | 2 +- mdformat_mkdocs/plugin.py | 78 ++++++++--- pyproject.toml | 11 +- tests/fixtures.md | 154 ++++++++++++++++++++- tests/pre-commit-test-recommended.md | 17 +++ tests/pre-commit-test.md | 196 ++++++++++++++------------- tox.ini | 19 ++- 10 files changed, 361 insertions(+), 141 deletions(-) create mode 100644 .tool-versions create mode 100644 tests/pre-commit-test-recommended.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c8972c8..93b98bd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - id: pretty-format-json args: [--autofix, --indent=4] - id: trailing-whitespace - exclude: tests/fixtures\.md + exclude: tests/fixtures.*\.md - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.9.0 hooks: @@ -56,6 +56,7 @@ repos: - mdformat-beautysh - mdformat-black - mdformat-config + - mdformat-footnote - mdformat-frontmatter - mdformat-gfm - mdformat-tables diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..4a280b6 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.10.5 3.8.4 3.9.4 3.7.12 diff --git a/README.md b/README.md index 3f09e32..58964e7 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,16 @@ Add this package wherever you use `mdformat` and the plugin will be auto-recogni Tip: this package has a pip extra, `recommended`, of plugins that work well with mkdocs: -- mdformat-admon -- mdformat-beautysh -- mdformat-black -- mdformat-config -- mdformat-frontmatter -- mdformat-gfm -- mdformat-tables -- mdformat-toc -- mdformat-web +- [mdformat-admon](https://pypi.org/project/mdformat-admon) +- [mdformat-beautysh](https://pypi.org/project/mdformat-beautysh) +- [mdformat-black](https://pypi.org/project/mdformat-black) +- [mdformat-config](https://pypi.org/project/mdformat-config) +- [mdformat-footnote](https://pypi.org/project/mdformat-footnote) +- [mdformat-frontmatter](https://pypi.org/project/mdformat-frontmatter) +- [mdformat-simple-breaks](https://pypi.org/project/mdformat-simple-breaks) +- [mdformat-tables](https://pypi.org/project/mdformat-tables) +- [mdformat-toc](https://pypi.org/project/mdformat-toc) +- [mdformat-web](https://pypi.org/project/mdformat-web) ### Pre-commit @@ -49,7 +50,7 @@ repos: pipx install mdformat pipx inject mdformat mdformat-mkdocs # Or -# pipx inject mdformat-gfm "mdformat-mkdocs[recommended]" +# pipx inject mdformat "mdformat-mkdocs[recommended]" ``` ## Caveats diff --git a/mdformat_mkdocs/__init__.py b/mdformat_mkdocs/__init__.py index a8bbd88..adc377c 100644 --- a/mdformat_mkdocs/__init__.py +++ b/mdformat_mkdocs/__init__.py @@ -2,4 +2,4 @@ __version__ = "0.0.1" -from .plugin import RENDERERS, update_mdit # noqa: F401 +from .plugin import POSTPROCESSORS, RENDERERS, update_mdit # noqa: F401 diff --git a/mdformat_mkdocs/plugin.py b/mdformat_mkdocs/plugin.py index 1c15aa4..9cf1889 100644 --- a/mdformat_mkdocs/plugin.py +++ b/mdformat_mkdocs/plugin.py @@ -1,32 +1,70 @@ -from functools import partial -from typing import Mapping +import re +from typing import Dict, Mapping from markdown_it import MarkdownIt from mdformat.renderer import RenderContext, RenderTreeNode from mdformat.renderer.typing import Render +_MKDOCS_INDENT_COUNT = 4 +"""Use 4-spaces for mkdocs.""" + def update_mdit(mdit: MarkdownIt) -> None: - """No changes to markdown parsing are necessary.""" + """Ensure that 4-spaces are converted to HTML correctly.""" ... -def _render_list(node: RenderTreeNode, context: RenderContext, bullet: str) -> str: - """Render a `RenderTreeNode` consistent with `mkdocs`.""" +_RE_INDENT = re.compile(r"(?P\s*)(?P.*)") +"""Match `indent` and `content` against line`.""" + +_RE_LIST_ITEM = re.compile(r"(?P[\-\*\d\.]+)\s+(?P.+)") +"""Match `bullet` and `item` against `content`.""" + + +def _normalize_list(text: str, node: RenderTreeNode, context: RenderContext) -> str: + """No changes to markdown parsing are necessary.""" + eol = "\n" # PLANNED: What about carriage returns? + indent = " " * _MKDOCS_INDENT_COUNT + rendered = "" - indent = " " * 4 - with context.indented(len(indent)): # Modifies context.env['indent_width'] - inner_indent = indent * (context.env["indent_width"] // len(indent) - 1) - for child in node.children: - content = child.render(context) - rendered += f"{inner_indent}{bullet} {content}\n" - return rendered - - -# A mapping from syntax tree node type to a function that renders it. -# This can be used to overwrite renderer functions of existing syntax -# or add support for new syntax. -RENDERERS: Mapping[str, Render] = { - "bullet_list": partial(_render_list, bullet="-"), - "ordered_list": partial(_render_list, bullet="1."), + last_indent = "" + indent_counter = 0 + + indent_lookup: Dict[str, int] = {} + for line in text.split(eol): + match = _RE_INDENT.match(line) + assert match is not None # for pylint + list_match = _RE_LIST_ITEM.match(match["content"]) + new_line = line + if list_match: + new_bullet = "-" if list_match["bullet"] in {"-", "*"} else "1." + new_line = f'{new_bullet} {list_match["item"]}' + + this_indent = match["indent"] + if this_indent: + indent_diff = len(this_indent) - len(last_indent) + if indent_diff == 0: + ... + elif this_indent in indent_lookup: + indent_counter = indent_lookup[this_indent] + elif indent_diff > 0: + indent_counter += 1 + indent_lookup[this_indent] = indent_counter + else: + raise NotImplementedError(f"Error in indentation of: `{line}`") + else: + indent_counter = 0 + last_indent = match["indent"] + new_indent = indent * indent_counter + rendered += f"{new_indent}{new_line.strip()}{eol}" + return rendered.rstrip() + + +# # A mapping from syntax tree node type to a function that renders it. +# # This can be used to overwrite renderer functions of existing syntax +# # or add support for new syntax. +RENDERERS: Mapping[str, Render] = {} +POSTPROCESSORS = { + "bullet_list": _normalize_list, + "ordered_list": _normalize_list, } diff --git a/pyproject.toml b/pyproject.toml index 1a31cca..dcbb6db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,25 +17,28 @@ classifiers = [ keywords = ["mdformat", "markdown", "markdown-it"] requires-python = ">=3.7.2" dependencies = [ - "mdformat >=0.7.0,<0.8.0", - "mdit-py-plugins >=0.3.2", + "mdformat >= 0.7.16", + "mdformat-gfm >= 0.3.5", + "mdit-py-plugins[linkify] >= 0.3.3", ] dynamic = ["version", "description"] [project.optional-dependencies] recommended = [ + # Keep in-sync with README "mdformat-admon", "mdformat-beautysh", "mdformat-black", "mdformat-config", + "mdformat-footnote", "mdformat-frontmatter", - "mdformat-gfm", + "mdformat-simple-breaks", "mdformat-tables", "mdformat-toc", "mdformat-web", ] test = [ - "pytest>=6.0", + "pytest >= 7.0", "pytest-cov", ] dev = ["pre-commit"] diff --git a/tests/fixtures.md b/tests/fixtures.md index 873cb54..a874c80 100644 --- a/tests/fixtures.md +++ b/tests/fixtures.md @@ -69,7 +69,70 @@ Corrected Indentation from 5x - item 4 . -FIXME: List with (what should be converted to a) code block +Handle Jagged Indents 2x +. +- item 1 + - item 2 + - item 3 + - item 4 + - item 5 + - item 6 + - item 7 +- item 8 +. +- item 1 + - item 2 + - item 3 + - item 4 + - item 5 + - item 6 + - item 7 +- item 8 +. + +Handle Jagged Indents 5x +. +- item 1 + - item 2 + - item 3 + - item 4 + - item 5 + - item 6 + - item 7 +- item 8 +. +- item 1 + - item 2 + - item 3 + - item 4 + - item 5 + - item 6 + - item 7 +- item 8 +. + +Handle Mixed Indents +. +- item 1 + - item 2 + - item 3 + - item 4 + - item 5 + - item 6 + - item 7 +- item 8 +. +- item 1 + - item 2 + - item 3 + - item 4 + - item 5 + - item 6 + - item 7 +- item 8 +. + +List with (what should be converted to a) code block . - item 1 @@ -77,20 +140,97 @@ FIXME: List with (what should be converted to a) code block . - item 1 -code block + code block . -FIXME: List with explicit code block (that should keep indentation) +List with explicit code block (that should keep indentation) . - item 1 - ``` + ```txt code block ``` . - item 1 -``` -code block -``` + ```txt + code block + ``` +. + +Table +. +| Label | Rating | Comment | +|:---------------|---------:|:---------------------| +| Name | 2| | +. +| Label | Rating | Comment | +|:---------------|---------:|:---------------------| +| Name | 2| | +. + +Floating Link +. +> Based on [External Link] + +[external link]: https://github.com/czuli/github-markdown-example/tree/7326f19c94be992319394e5bfeaa07b30f858e46 +. +> Based on [External Link] + +[external link]: https://github.com/czuli/github-markdown-example/tree/7326f19c94be992319394e5bfeaa07b30f858e46 +. + +Headings +. +# [h1] The largest heading + +## [h2] heading + +### [h3] heading + +#### [h4] heading + +##### [h5] heading + +###### [h6] The smallest heading +. +# \[h1\] The largest heading + +## \[h2\] heading + +### \[h3\] heading + +#### \[h4\] heading + +##### \[h5\] heading + +###### \[h6\] The smallest heading +. + +Task List / Check List (WARN: escaping is prevented by mdformat-gfm. Tested by py#-hook) +. +- [x] #739 + - [ ] Add delight to the experience when all tasks are complete :tada: +. +- \[x\] #739 + - \[ \] Add delight to the experience when all tasks are complete :tada: +. + +Footnotes (WARN: escaping is prevented by mdformat-gfm. Tested by py#-hook) +. +Here is a simple footnote[^1]. + +You can also use words, to fit your writing style more closely[^note]. + + [^1]: My reference. + [^note]: Named footnotes will still render with numbers instead of the text but allow easier identification and linking.\ + This footnote also has been made with a different syntax using 4 spaces for new lines. +. +Here is a simple footnote\[^1\]. + +You can also use words, to fit your writing style more closely\[^note\]. + +\[^1\]: My reference. +\[^note\]: Named footnotes will still render with numbers instead of the text but allow easier identification and linking.\ +This footnote also has been made with a different syntax using 4 spaces for new lines. . diff --git a/tests/pre-commit-test-recommended.md b/tests/pre-commit-test-recommended.md new file mode 100644 index 0000000..f0f4d69 --- /dev/null +++ b/tests/pre-commit-test-recommended.md @@ -0,0 +1,17 @@ +# Other Tests + +## Footnotes + +FYI: Requires `mdformat-footnote`: + +Here is a simple footnote[^1]. + +A footnote can also have multiple lines[^2]. + +You can also use words, to fit your writing style more closely[^note]. + + [^1]: My reference. + [^2]: Every new line should be prefixed with 2 spaces.\ + This allows you to have a footnote with multiple lines. + [^note]: Named footnotes will still render with numbers instead of the text but allow easier identification and linking.\ + This footnote also has been made with a different syntax using 4 spaces for new lines. diff --git a/tests/pre-commit-test.md b/tests/pre-commit-test.md index 6f919ea..1d8eeab 100644 --- a/tests/pre-commit-test.md +++ b/tests/pre-commit-test.md @@ -2,87 +2,78 @@ Testing `mdformat-mkdocs` as a `pre-commit` hook (`tox -e py#-hook`) -## TODO List - -- [ ] Task item - - [x] Completed Task item - - [x] Another Completed Task item -- [ ] Task item - - [ ] Task item -- [ ] Task item with code snippet `echo "hello world"` - -## Mixed List - -1. Prepare - - Indented item - - Further indented - - [ ] Task - - [ ] [Linked File](./fixtures.md) -1. Done - # Table -| Label | Rating | Comment | -|:---------------|---------:|:---------------------| -| Name | 2| | +| Label | Rating | Comment | +| :---- | -----: | :--------------- | +| Name | 2 | | ## Floating Link > Based on [External Link] - [External Link]: https://github.com/czuli/github-markdown-example/tree/7326f19c94be992319394e5bfeaa07b30f858e46 - ---- +______________________________________________________________________ ## Arbitrary Markdown thanks to `czuli/github-markdown-example` ### **Typo** -# [h1] The largest heading -## [h2] heading -### [h3] heading -#### [h4] heading -##### [h5] heading -###### [h6] The smallest heading +# \[h1\] The largest heading + +## \[h2\] heading + +### \[h3\] heading + +#### \[h4\] heading ------ +##### \[h5\] heading + +###### \[h6\] The smallest heading + +______________________________________________________________________ ### Bold **This is bold text** ------ +______________________________________________________________________ ### Italic -*This text is italicized* +*This text is italicized* + +______________________________________________________________________ ------ ### Strikethrough -~~This was mistaken text~~ +~~This was mistaken text~~ + +______________________________________________________________________ ------ ### Bold and nested italic -**This text is _extremely_ important** +**This text is _extremely_ important** + +______________________________________________________________________ ------ ### All bold and italic -***All this text is important*** +***All this text is important*** + +______________________________________________________________________ ------ ### Subscript -This is a subscript text +This is a subscript text + +______________________________________________________________________ ------ ### Superscript -This is a superscript text +This is a superscript text + +______________________________________________________________________ ------ ### Quote Text that is not a quote @@ -91,7 +82,8 @@ Text that is not a quote > Text that is a quote > Text that is a quote ------ +______________________________________________________________________ + ### Quoting code Use `git status` to list all new or modified files that haven't yet been committed. @@ -99,7 +91,8 @@ Use `git status` to list all new or modified files that haven't yet been committ #### Code without highlighting Some basic Git commands are: -``` + +```sh git status git add git commit @@ -108,11 +101,13 @@ git commit #### Syntax highlighting #### ruby code + ```ruby require 'redcarpet' markdown = Redcarpet.new("Hello World!") puts markdown.to_html ``` + #### bash code ```bash @@ -182,7 +177,8 @@ USER root CMD [ "sh", "-c", "cron && apache2-foreground" ] ``` ------ +______________________________________________________________________ + ### Paragraphs ### Never store dependencies and compiled artifacts in the repository @@ -197,30 +193,34 @@ CMD [ "sh", "-c", "cron && apache2-foreground" ] Okay, so now that we know keeping artifacts and dependencies in the repository is not a good idea, the question is: how *should* we deploy our application to the server? Without a Continuous Deployment tool, it usually looked like this: 1. The application is uploaded to the server via SFTP/SCP or Git and built with a script that will download the dependencies and run the tasks directly on the server -2. In case the SSH access is not available (eg. the server is FTP) the application must be built in a compatible environment before the deployment +1. In case the SSH access is not available (eg. the server is FTP) the application must be built in a compatible environment before the deployment +______________________________________________________________________ ------ ### Links This site was built using [GitHub Pages](https://pages.github.com/). ------ -### Section links +______________________________________________________________________ +### Section links [Contribution guidelines for this project](#table) ------ +______________________________________________________________________ + ### Image #### image from internet + ![This is an image](https://buddy.works/assets/svg/brands/buddy.svg) #### image from repo with link + [![](assets/buddy-podcast.png)](https://buddy.works) ------ +______________________________________________________________________ + ### Specifying the theme an image is shown to @@ -229,8 +229,8 @@ This site was built using [GitHub Pages](https://pages.github.com/). Shows an illustrated sun in light color mode and a moon with stars in dark color mode. +______________________________________________________________________ ------ ### List ### Normal list @@ -239,78 +239,86 @@ This site was built using [GitHub Pages](https://pages.github.com/). - John Adams - Thomas Jefferson -### Order list +### Ordered list 1. James Madison -2. James Monroe -3. John Quincy Adams +1. James Monroe +1. John Quincy Adams + +______________________________________________________________________ + +## TODO List + +- [ ] Task item + - [x] Completed Task item + - [x] Another Completed Task item +- [ ] Task item + - [ ] Task item +- [ ] Task item with code snippet `echo "hello world"` + +## Mixed List +1. Prepare + - Indented item + - Further indented + - [ ] Task + - [ ] [Linked File](./fixtures.md) +1. Done ### Nested Lists 1. First list item - - First nested list item - - list item - - list item - - Second nested list item - - list item - - list item -2. Second list item - - list item - - list item - - list item - ------ + - First nested list item + - list item + - list item + - Second nested list item + - list item + - list item +1. Second list item + - list item + - list item + - list item + +______________________________________________________________________ + ### Task lists - [x] #739 - [ ] https://github.com/octo-org/octo-repo/issues/740 - [ ] Add delight to the experience when all tasks are complete :tada: -- [ ] \(Optional) Open a followup issue +- [ ] (Optional) Open a followup issue @github/support What do you think about these updates? ------ +______________________________________________________________________ + ### emoji @octocat :+1: This PR looks great - it's ready to merge! :shipit: ------ -### Footnotes - -Here is a simple footnote[^1]. - -A footnote can also have multiple lines[^2]. +______________________________________________________________________ -You can also use words, to fit your writing style more closely[^note]. - -[^1]: My reference. -[^2]: Every new line should be prefixed with 2 spaces. - This allows you to have a footnote with multiple lines. -[^note]: - Named footnotes will still render with numbers instead of the text but allow easier identification and linking. - This footnote also has been made with a different syntax using 4 spaces for new lines. - ------ ### Hiding content with comments ------ +______________________________________________________________________ + ### Ignoring Markdown formatting Let's rename \*our-new-project\* to \*our-old-project\*. ------ +______________________________________________________________________ + ### Table | Left-aligned | Center-aligned | Right-aligned | -| :--- | :---: | ---: | -| git status | git status | git status | -| git diff | git diff | git diff | +| :----------- | :------------: | ------------: | +| git status | git status | git status | +| git diff | git diff | git diff | +______________________________________________________________________ ------ ### Diagrams Here is a simple flow chart: @@ -322,3 +330,5 @@ graph TD; B-->D; C-->D; ``` + +[external link]: https://github.com/czuli/github-markdown-example/tree/7326f19c94be992319394e5bfeaa07b30f858e46 diff --git a/tox.ini b/tox.ini index 996d368..5a39d27 100644 --- a/tox.ini +++ b/tox.ini @@ -1,20 +1,29 @@ [tox] -envlist = py{37,38,39,310} +envlist = + py{310}-cov + py{37}-recommended + py{310}-pre-commit + py{37,310}-hook isolated_build = True +skip_missing_interpreters = False -[testenv:py{37,38,39,310}] +[testenv:py{310}] extras = test commands = pytest {posargs} --ff -vv -[testenv:py{37,38,39,310}-cov] +[testenv:py{310}-cov] extras = test commands = pytest --cov={envsitepackagesdir}/mdformat_mkdocs {posargs} -[testenv:py{37,38,39,310}-pre-commit] +[testenv:py{37}-recommended] +extras = recommended,test +commands = pytest {posargs} --ff -vv + +[testenv:py{310}-pre-commit] extras = dev commands = pre-commit run {posargs:--all-files} -[testenv:py{37,38,39,310}-hook] +[testenv:py{37,310}-hook] extras = dev commands = pre-commit run --config .pre-commit-test.yaml {posargs:--all-files --verbose --show-diff-on-failure}