Skip to content

Commit

Permalink
fix: Shift Markdown headings according to the current heading_level
Browse files Browse the repository at this point in the history
  • Loading branch information
oprypin committed Dec 12, 2020
1 parent 4956b88 commit 96d050e
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 13 deletions.
44 changes: 42 additions & 2 deletions src/mkdocstrings/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Dict, Optional, Sequence
from xml.etree.ElementTree import Element # noqa: S405 (we choose to trust the XML input)

from jinja2 import Environment, FileSystemLoader
from markdown import Markdown
from markdown.extensions import Extension
from markdown.treeprocessors import Treeprocessor
from markupsafe import Markup
from pymdownx.highlight import Highlight

Expand Down Expand Up @@ -125,20 +128,23 @@ def do_any(seq: Sequence, attribute: str = None) -> bool:
return any(_[attribute] for _ in seq)


def do_convert_markdown(md: Markdown, text: str) -> Markup:
def do_convert_markdown(md: Markdown, text: str, heading_level: int) -> Markup:
"""
Render Markdown text; for use inside templates.
Arguments:
md: A `markdown.Markdown` instance.
text: The text to convert.
heading_level: The base heading level to start all Markdown headings from.
Returns:
An HTML string.
"""
md.treeprocessors["mkdocstrings_headings"].shift_by = heading_level
try:
return Markup(md.convert(text))
finally:
md.treeprocessors["mkdocstrings_headings"].shift_by = 0
md.reset()


Expand Down Expand Up @@ -215,7 +221,7 @@ def update_env(self, md: Markdown, config: dict) -> None:
of [mkdocstrings.plugin.MkdocstringsPlugin.on_config][] to see what's in this dictionary.
"""
# Re-instantiate md: see https://github.com/tomchristie/mkautodoc/issues/14
md = Markdown(extensions=config["mdx"], extension_configs=config["mdx_configs"])
md = Markdown(extensions=config["mdx"] + [ShiftHeadingsExtension()], extension_configs=config["mdx_configs"])

self.env.filters["convert_markdown"] = functools.partial(do_convert_markdown, md)

Expand Down Expand Up @@ -361,3 +367,37 @@ def teardown(self):
for handler in self._handlers.values():
handler.collector.teardown()
self._handlers.clear()


class _HeadingShiftingTreeprocessor(Treeprocessor):
def __init__(self, md, shift_by: int):
super().__init__(md)
self.shift_by = shift_by

def run(self, root: Element):
if not self.shift_by:
return
for el in root.iter():
match = re.fullmatch(r"([Hh])([1-6])", el.tag)
if match:
level = int(match[2]) + self.shift_by
level = max(1, min(level, 6))
el.tag = f"{match[1]}{level}"


class ShiftHeadingsExtension(Extension):
"""Shift levels of all Markdown headings according to the configured base level."""

treeprocessor_priority = 12

def extendMarkdown(self, md: Markdown) -> None: # noqa: N802 (casing: parent method's name)
"""
Register the extension, with a treeprocessor under the name 'mkdocstrings_headings'.
Arguments:
md: A `markdown.Markdown` instance.
"""
md.registerExtension(self)
md.treeprocessors.register(
_HeadingShiftingTreeprocessor(md, 0), "mkdocstrings_headings", self.treeprocessor_priority
)
2 changes: 1 addition & 1 deletion src/mkdocstrings/templates/python/material/attributes.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<tr>
<td><code>{{ attribute.name }}</code></td>
<td><code>{{ attribute.annotation }}</code></td>
<td>{{ attribute.description|convert_markdown }}</td>
<td>{{ attribute.description|convert_markdown(heading_level) }}</td>
</tr>
{% endfor %}
</tbody>
Expand Down
2 changes: 1 addition & 1 deletion src/mkdocstrings/templates/python/material/docstring.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% if docstring_sections %}
{% for section in docstring_sections %}
{% if section.type == "markdown" %}
{{ section.value|convert_markdown }}
{{ section.value|convert_markdown(heading_level) }}
{% elif section.type == "attributes" %}
{% with attributes = section.value %}
{% include "attributes.html" with context %}
Expand Down
2 changes: 1 addition & 1 deletion src/mkdocstrings/templates/python/material/examples.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<p><strong>Examples:</strong></p>
{% for section_type, sub_section in examples %}
{% if section_type == "markdown" %}
{{ sub_section|convert_markdown }}
{{ sub_section|convert_markdown(heading_level) }}
{% elif section_type == "examples" %}
{{ sub_section|highlight(language="python", line_nums=False) }}
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion src/mkdocstrings/templates/python/material/exceptions.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{% for exception in exceptions %}
<tr>
<td><code>{{ exception.annotation }}</code></td>
<td>{{ exception.description|convert_markdown }}</td>
<td>{{ exception.description|convert_markdown(heading_level) }}</td>
</tr>
{% endfor %}
</tbody>
Expand Down
2 changes: 1 addition & 1 deletion src/mkdocstrings/templates/python/material/parameters.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<tr>
<td><code>{{ parameter.name }}</code></td>
<td><code>{{ parameter.annotation }}</code></td>
<td>{{ parameter.description|convert_markdown }}</td>
<td>{{ parameter.description|convert_markdown(heading_level) }}</td>
<td>{% if parameter.default %}<code>{{ parameter.default }}</code>{% else %}<em>required</em>{% endif %}</td>
</tr>
{% endfor %}
Expand Down
2 changes: 1 addition & 1 deletion src/mkdocstrings/templates/python/material/return.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<tbody>
<tr>
<td><code>{{ return.annotation }}</code></td>
<td>{{ return.description|convert_markdown }}</td>
<td>{{ return.description|convert_markdown(heading_level) }}</td>
</tr>
</tbody>
</table>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<td class="field-body">
<ul class="first simple">
{% for exception in exceptions %}
<li>{{ ("`" + exception.annotation + "` – " + exception.description)|convert_markdown }}</li>
<li>{{ ("`" + exception.annotation + "` – " + exception.description)|convert_markdown(heading_level) }}</li>
{% endfor %}
</ul>
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<td class="field-body">
<ul class="first simple">
{% for parameter in parameters %}
<li>{{ ("**" + parameter.name + "** (`" + parameter.annotation + "`) – " + parameter.description)|convert_markdown }}</li>
<li>{{ ("**" + parameter.name + "** (`" + parameter.annotation + "`) – " + parameter.description)|convert_markdown(heading_level) }}</li>
{% endfor %}
</ul>
</td>
Expand Down
2 changes: 1 addition & 1 deletion src/mkdocstrings/templates/python/readthedocs/return.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<th class="field-name">Returns:</th>
<td class="field-body">
<ul class="first simple">
<li>{{ ("`" + return.annotation + "` – " + return.description)|convert_markdown }}</li>
<li>{{ ("`" + return.annotation + "` – " + return.description)|convert_markdown(heading_level) }}</li>
</ul>
</td>
</tr>
Expand Down
8 changes: 8 additions & 0 deletions tests/fixtures/headings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
Foo
===
### Bar
###### Baz
"""
16 changes: 14 additions & 2 deletions tests/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,26 @@ def test_multiple_footnotes():
::: tests.fixtures.footnotes.func_c
[^aaa]: Top footnote
"""
)
""",
),
)
assert output.count("Footnote A") == 1
assert output.count("Footnote B") == 1
assert output.count("Top footnote") == 1


def test_markdown_heading_level():
"""Assert that Markdown headings' level doesn't exceed heading_level."""
config = dict(_DEFAULT_CONFIG)
config["mdx"].append(MkdocstringsExtension(config, Handlers(config)))
md = Markdown(extensions=config["mdx"])

output = md.convert("::: tests.fixtures.headings\n rendering:\n show_root_heading: true")
assert "<h3>Foo</h3>" in output
assert "<h5>Bar</h5>" in output
assert "<h6>Baz</h6>" in output


def test_reference_inside_autodoc():
"""Assert cross-reference Markdown extension works correctly."""
config = dict(_DEFAULT_CONFIG)
Expand Down

0 comments on commit 96d050e

Please sign in to comment.