简体   繁体   English

强制 Sphinx 在 Python 文档字符串而不是 reStructuredText 中解释 Markdown

[英]Force Sphinx to interpret Markdown in Python docstrings instead of reStructuredText

I'm using Sphinx to document a python project.我正在使用 Sphinx 来记录一个 python 项目。 I would like to use Markdown in my docstrings to format them.我想在我的文档字符串中使用 Markdown 来格式化它们。 Even if I use the recommonmark extension, it only covers the .md files written manually, not the docstrings.即使我使用recommonmark扩展名,它也只涵盖手动编写的.md文件,而不是文档字符串。

I use autodoc , napoleon and recommonmark in my extensions.我在我的扩展中使用autodocnapoleonrecommonmark

How can I make sphinx parse markdown in my docstrings?如何在我的文档字符串中进行 sphinx 解析降价

Sphinx's autodoc extension emits an event named autodoc-process-docstring every time it processes a doc-string. 每次处理文档字符串时,Sphinx的autodoc扩展都会发出一个名为autodoc-process-docstring的事件。 You can hook into that mechanism to convert the syntax from Markdown to reStructuredText. 您可以挂钩该机制将语法从Markdown转换为reStructuredText。

I do not know why recommonmark does not offer that functionality out of the box. 我不知道为什么recommonmark不能提供开箱即用的功能。 It should be an easy feature to add. 这应该是一个简单的功能添加。 Personally, I use m2r for the conversion in my projects. 就个人而言,我在我的项目中使用m2r进行转换。 Because it's fast — much faster than pandoc , for example. 因为它很快 - pandocpandoc快得多。 Speed is important as the conversion happens on the fly and handles each doc-string individually. 速度很重要,因为转换会动态发生并单独处理每个文档字符串。 Other than that, any Markdown-to-reST converter would do. 除此之外,任何Markdown-to-reST转换器都可以。

Install m2r and add the following to Sphinx's configuration file conf.py : 安装m2r并将以下内容添加到Sphinx的配置文件conf.py

import m2r

def docstring(app, what, name, obj, options, lines):
    md  = '\n'.join(lines)
    rst = m2r.convert(md)
    lines.clear()
    for line in rst.splitlines():
        lines.append(line)

def setup(app):
    app.connect('autodoc-process-docstring', docstring)

[ Edited to add… ] [已编辑添加... ]

Just like above, but with commonmark : 就像上面一样,但有commonmark

import commonmark

def docstring(app, what, name, obj, options, lines):
    md  = '\n'.join(lines)
    ast = commonmark.Parser().parse(md)
    rst = commonmark.ReStructuredTextRenderer().render(ast)
    lines.clear()
    for line in rst.splitlines():
        lines.append(line)

def setup(app):
    app.connect('autodoc-process-docstring', docstring)

This uses the same Markdown parser as the Sphinx extension recommonmark and is as fast as m2r , which means next to no effect on build time compared to the native reStructuredText. 这使用与Sphinx扩展recommonmark相同的Markdown解析器,并且与m2r一样快,这意味着与本机reStructuredText相比,接下来对构建时间没有影响。

Building on @john-hennig answer, the following will keep the restructured text fields like: :py:attr: , :py:class: etc. .基于@john-hennig 的回答,以下内容将保留重组后的文本字段,例如: :py:attr::py:class:等。 This allows you to reference other classes, etc.这允许您引用其他类等。

import re
import commonmark

py_attr_re = re.compile(r"\:py\:\w+\:(``[^:`]+``)")

def docstring(app, what, name, obj, options, lines):
    md  = '\n'.join(lines)
    ast = commonmark.Parser().parse(md)
    rst = commonmark.ReStructuredTextRenderer().render(ast)
    lines.clear()
    lines += rst.splitlines()

    for i, line in enumerate(lines):
        while True:
            match = py_attr_re.search(line)
            if match is None:
                break 

            start, end = match.span(1)
            line_start = line[:start]
            line_end = line[end:]
            line_modify = line[start:end]
            line = line_start + line_modify[1:-1] + line_end
        lines[i] = line

def setup(app):
    app.connect('autodoc-process-docstring', docstring)

I had to extend the accepted answer by john-hen to allow multi-line descriptions of Args: entries to be considered a single parameter:我不得不扩展 john-hen 接受的答案,以允许Args:条目的多行描述被视为单个参数:

def docstring(app, what, name, obj, options, lines):
  wrapped = []
  literal = False
  for line in lines:
    if line.strip().startswith(r'```'):
      literal = not literal
    if not literal:
      line = ' '.join(x.rstrip() for x in line.split('\n'))
    indent = len(line) - len(line.lstrip())
    if indent and not literal:
      wrapped.append(' ' + line.lstrip())
    else:
      wrapped.append('\n' + line.strip())
  ast = commonmark.Parser().parse(''.join(wrapped))
  rst = commonmark.ReStructuredTextRenderer().render(ast)
  lines.clear()
  lines += rst.splitlines()

def setup(app):
  app.connect('autodoc-process-docstring', docstring)

The current @john-hennig is great, but seems to be failing for multi-line Args: in python style.当前的@john-hennig 很棒,但似乎在多行Args:失败了Args: python 风格。 Here was my fix:这是我的修复:


def docstring(app, what, name, obj, options, lines):
    md = "\n".join(lines)
    ast = commonmark.Parser().parse(md)
    rst = commonmark.ReStructuredTextRenderer().render(ast)

    lines.clear()
    lines += _normalize_docstring_lines(rst.splitlines())


def _normalize_docstring_lines(lines: list[str]) -> list[str]:
    """Fix an issue with multi-line args which are incorrectly parsed.

    ```
    Args:
        x: My multi-line description which fit on multiple lines
          and continue in this line.
    ```

    Is parsed as (missing indentation):

    ```
    :param x: My multi-line description which fit on multiple lines
    and continue in this line.
    ```

    Instead of:

    ```
    :param x: My multi-line description which fit on multiple lines
        and continue in this line.
    ```

    """
    is_param_field = False

    new_lines = []
    for l in lines:
        if l.lstrip().startswith(":param"):
            is_param_field = True
        elif is_param_field:
            if not l.strip():  # Blank line reset param
                is_param_field = False
            else:  # Restore indentation
                l = "    " + l.lstrip()
        new_lines.append(l)
    return new_lines


def setup(app):
    app.connect("autodoc-process-docstring", docstring)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM