Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Myst support #239

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
Changelog
=========

* Add support for MyST - Markedly structured text.

Thanks to Zebedee Nicholls in `PR #239 <https://github.com/adamchainz/blacken-docs/pull/239>`__.

1.16.0 (2023-08-16)
-------------------

Expand Down
25 changes: 25 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,28 @@ In PythonTeX blocks:
def hello():
print("hello world")
\end{pycode}

MyST - Markedly Structured Text
-------------------------------

In “python” blocks:

.. code-block:: markdown

```python
def hello():
print("hello world")
```

In notebook blocks, including cell metadata and cell magic:

.. code-block:: markdown

```{code-cell}python
---
tags: [some-tags]
---
%matplotlib inline

plt.plot([0, 1, 2], [2, 4, 6])
```
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ project_urls =
[options]
packages = find:
install_requires =
IPython
black>=22.1.0
tokenize-rt>=5.0.0 # required to call black format_cell
python_requires = >=3.8
include_package_data = True
package_dir =
Expand Down
39 changes: 37 additions & 2 deletions src/blacken_docs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@
r"(?P<after>^(?P=indent)```.*$)",
re.DOTALL | re.MULTILINE,
)

MYST_RE = re.compile(
r"(?P<before>"
r"^(?P<indent>\s*)```(\s*{code-cell})?\s*(?P<lang>\w+)?"
r"(?P<yaml>\n---\n.*?\n---)?"
r"\n)"
r"(?P<code>.*?)"
r"(?P<after>^(?P=indent)```\s*$)",
re.DOTALL | re.MULTILINE,
)
RST_PY_LANGS = frozenset(
(
"python",
"ipython",
"py",
"python3",
"ipython3",
"py3",
)
)

BLOCK_TYPES = "(code|code-block|sourcecode|ipython)"
DOCTEST_TYPES = "(testsetup|testcleanup|testcode)"
RST_RE = re.compile(
Expand Down Expand Up @@ -116,6 +137,19 @@ def _md_match(match: Match[str]) -> str:
code = textwrap.indent(code, match["indent"])
return f'{match["before"]}{code}{match["after"]}'

def _myst_match(match: Match[str]) -> str:
lang = match["lang"]
if lang is not None and lang not in RST_PY_LANGS:
return match[0]
trailing_ws_match = TRAILING_NL_RE.search(match["code"])
assert trailing_ws_match
trailing_ws = trailing_ws_match.group()
code = textwrap.dedent(match["code"])
with _collect_error(match):
code = black.format_cell(code, fast=False, mode=black_mode)
code = textwrap.indent(code, match["indent"])
return f'{match["before"]}{code.rstrip()}{trailing_ws}{match["after"]}'

def _rst_match(match: Match[str]) -> str:
lang = match["lang"]
if lang is not None and lang not in PYGMENTS_PY_LANGS:
Expand Down Expand Up @@ -220,6 +254,7 @@ def _latex_pycon_match(match: Match[str]) -> str:
_rst_literal_blocks_match,
src,
)
src = MYST_RE.sub(_myst_match, src)
src = LATEX_RE.sub(_latex_match, src)
src = LATEX_PYCON_RE.sub(_latex_pycon_match, src)
src = PYTHONTEX_RE.sub(_latex_match, src)
Expand Down Expand Up @@ -249,8 +284,8 @@ def format_file(
with open(filename, "w", encoding="UTF-8") as f:
f.write(new_contents)
return 1
else:
return 0

return 0


def main(argv: Sequence[str] | None = None) -> int:
Expand Down
176 changes: 176 additions & 0 deletions tests/test_blacken_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from textwrap import dedent

import black
import pytest
from black.const import DEFAULT_LINE_LENGTH

import blacken_docs
Expand Down Expand Up @@ -944,3 +945,178 @@ def test_format_src_rst_pycon_comment_before_promopt():
" # Comment about next line\n"
" >>> pass\n"
)


def test_format_src_myst_simple():
before = dedent(
"""\
```{code-cell}
print('hello world')
```
"""
)
after, _ = blacken_docs.format_str(before, BLACK_MODE)
assert after == dedent(
"""\
```{code-cell}
print("hello world")
```
"""
)


def test_format_src_myst_no_code_cell():
before = dedent(
"""\
```
---
tags: [some-tags]
slideshow:
slide_type: ''
---
print('hello world')
```
"""
)
after, _ = blacken_docs.format_str(before, BLACK_MODE)
assert after == dedent(
"""\
```
---
tags: [some-tags]
slideshow:
slide_type: ''
---
print("hello world")
```
"""
)


def test_format_src_myst_tags():
before = dedent(
"""\
```{code-cell}
---
tags: [some-tags]
---
print('hello world')
```
"""
)
after, _ = blacken_docs.format_str(before, BLACK_MODE)
assert after == dedent(
"""\
```{code-cell}
---
tags: [some-tags]
---
print("hello world")
```
"""
)


@pytest.mark.parametrize(
"language, supported",
(
("python", True),
("pythona", False),
("ipython", True),
("py", True),
("sage", False),
("python3", True),
("ipython3", True),
("py3", True),
("numpy", False),
),
)
def test_format_src_myst_tags_language(language, supported):
before = dedent(
"""\
```{code-cell}language
---
tags: [some-tags]
---
print('hello world')
```
"""
).replace("language", language)
after, _ = blacken_docs.format_str(before, BLACK_MODE)

if supported:
assert (
after
== dedent(
"""\
```{code-cell}language
---
tags: [some-tags]
---
print("hello world")
```
"""
).replace("language", language)
)
else:
assert after == before


def test_format_src_myst_function_def():
before = dedent(
"""\
```{code-cell}python
---
tags: [some-tags]
---
import copy

def deepcopy(inp: Dict) -> Dict:
return copy.deepcopy(
inp)
```
"""
)
after, _ = blacken_docs.format_str(before, BLACK_MODE)
assert after == dedent(
"""\
```{code-cell}python
---
tags: [some-tags]
---
import copy


def deepcopy(inp: Dict) -> Dict:
return copy.deepcopy(inp)
```
"""
)


def test_format_src_myst_magic_comand():
before = dedent(
"""\
```{code-cell}python
---
tags: [some-tags]
---
%matplotlib inline

plt.plot([0, 1,2], [2,4,6])
```
"""
)
after, errors = blacken_docs.format_str(before, BLACK_MODE)
assert after == dedent(
"""\
```{code-cell}python
---
tags: [some-tags]
---
%matplotlib inline

plt.plot([0, 1, 2], [2, 4, 6])
```
"""
)