如何使用Python编写程序来生成Sphinx文档的部分内容?

30

我正在使用Sphinx为我的项目生成文档。

在这个项目中,我用yaml文件描述了一系列可用的命令,一旦加载,就会得到一个形如{command-name : command-description}的字典,例如:

commands = {"copy"  : "Copy the highlighted text in the clipboard",
            "paste" : "Paste the clipboard text to cursor location",
            ...}
我想知道的是,是否有一种方法在sphinx的“make html”周期中加载yaml文件,将python字典翻译成某种reStructuredText格式(例如定义列表),并包含在我的html输出中。
我期望我的.rst文件看起来像这样:
Available commands
==================
The commands available in bla-bla-bla...

.. magic-directive-that-execute-python-code::
   :maybe python code or name of python file here:

并且需要在内部转换为:

Available commands
==================
The commands available in bla-bla-bla...

copy
  Copy the highlighted text in the clipboard

paste
  Paste the clipboard text to cursor location

在被翻译成HTML之前。


1
写YAML代码有什么意义呢?为什么不直接在Python模块中编写描述并使用Sphinx的自动文档呢?为什么要制作比http://sphinx.pocoo.org/ext/autodoc.html更复杂的东西呢? - S.Lott
1
@Oscar - 感谢您抽出时间留下评论,但是 pyyaml 不是问题所在(显然我已经在加载 yml 文件的模块中使用它了)。问题在于我不知道应该如何修改 makefile...您能否详细说明一下您的建议?谢谢! - mac
1
@mac:为什么要浪费时间在YAML代码上?如果你想让用户定义(并覆盖)命令,Python是完全可以接受的。Python字典的语法并不比YAML复杂,而且你可以避免这种奇怪的多语言问题。 - S.Lott
@S.Lott - 问题与使用YAML序列化原始信息无关。问题在于包含由Python“实时”生成的rst(在文档构建时)。我现在已经解决了,稍后会发布自己问题的答案... - mac
@mac: “包括使用Python“即时”生成的rst(在文档构建时)”似乎根本不是问题的一部分。您能否请更新问题以使其清晰明确。该问题似乎是关于使用YAML而不是Python代码,并将YAML记录为Python代码的方式。 - S.Lott
显示剩余2条评论
7个回答

27

最终我找到了一种方法来实现我想要的。以下是如何操作:

  1. 创建一个Python脚本(我们称其为generate-includes.py),用于生成reStructuredText并将其保存在myrst.inc文件中。(在我的示例中,这是加载和解析YAML的脚本,但这与问题无关)。确保该文件可执行!!!
  2. 在你的主文档的.rst文件中使用include指令,在你希望插入动态生成文档的位置处:

    .. include:: myrst.inc
    
  3. 修改sphinx Makefile以便在构建时生成所需的.inc文件:

  4. myrst.inc:
        ./generate-includes.py
    
    html: myrst.inc
        ...(other stuff here)
    
  5. 使用make html正常构建您的文档。


1
工作得非常好。虽然 readthedocs 不再起作用了,但我想这对于由 Python 生成的文档来说是不可避免的... - Mark

18
基于Michael的代码和内置include指令进行改进:
import sys
from os.path import basename

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO

from docutils.parsers.rst import Directive    
from docutils import nodes, statemachine

class ExecDirective(Directive):
    """Execute the specified python code and insert the output into the document"""
    has_content = True

    def run(self):
        oldStdout, sys.stdout = sys.stdout, StringIO()

        tab_width = self.options.get('tab-width', self.state.document.settings.tab_width)
        source = self.state_machine.input_lines.source(self.lineno - self.state_machine.input_offset - 1)

        try:
            exec('\n'.join(self.content))
            text = sys.stdout.getvalue()
            lines = statemachine.string2lines(text, tab_width, convert_whitespace=True)
            self.state_machine.insert_input(lines, source)
            return []
        except Exception:
            return [nodes.error(None, nodes.paragraph(text = "Unable to execute python code at %s:%d:" % (basename(source), self.lineno)), nodes.paragraph(text = str(sys.exc_info()[1])))]
        finally:
            sys.stdout = oldStdout

def setup(app):
    app.add_directive('exec', ExecDirective)

这个脚本之前导入了输出,以便它可以直接通过解析器处理。它还可以在Python 3中使用。


2
使用Sphinx v1.8.1版本时,此代码会生成“扩展错误:...无法从'sphinx.util.compact'导入名称'Directive'”。将导入更改为“from docutils.parsers.rst import Directive”即可解决此问题。 - AJNeufeld

9

我也有同样的需求,所以我编写了一个新的指令,似乎可以正常工作(虽然我对自定义 Sphinx 指令一无所知):

import sys
from os.path import basename
from StringIO import StringIO

from sphinx.util.compat import Directive
from docutils import nodes

class ExecDirective(Directive):
    """Execute the specified python code and insert the output into the document"""
    has_content = True

    def run(self):
        oldStdout, sys.stdout = sys.stdout, StringIO()
        try:
            exec '\n'.join(self.content)
            return [nodes.paragraph(text = sys.stdout.getvalue())]
        except Exception, e:
            return [nodes.error(None, nodes.paragraph(text = "Unable to execute python code at %s:%d:" % (basename(self.src), self.srcline)), nodes.paragraph(text = str(e)))]
        finally:
            sys.stdout = oldStdout

def setup(app):
    app.add_directive('exec', ExecDirective)

以下是使用方法:

如下所示:

.. exec::
   print "Python code!"
   print "This text will show up in the document"

1
使用Sphinx v1.8.1版本时,此代码会生成“扩展错误:...无法从'sphinx.util.compact'导入名称'Directive'”。将导入更改为“from docutils.parsers.rst import Directive”即可解决此问题。 - AJNeufeld

4

Sphinx没有内置任何功能来实现您想要的内容。您可以创建自定义指令来处理文件,或者在单独的步骤中生成reStructuredText,并使用include指令包含生成的reStructuredText文件。


很高兴知道 .. magic-directive-that-execute-python-code:: 需要我编写。我试图尝试并编写了一个指令,可以插入我的脚本生成的文本,但我不知道如何使其解析。例如:nodes.paragraph('', '**text**') 将输出 **text** 而不是 **text**。我该如何告诉 Sphinx 使用标准的 reStructuredText 语法进行解析? - mac
在您的指令子类中,您将拥有handle_content()和handle_signature()方法。您可以递归调用self.state.nested_parse(),它将正确处理内置样式。请查看创建reStructuredText指令 - devin_s
感谢devin_s (+1)。正如您可能已经注意到的(请参见我的答案),我最终使用了.. include::指令来解决问题。不过,我会尽快查看您的解决方案! - mac

3
我知道这个问题很老,但也许其他人也会发现它有用。
听起来你实际上并不需要执行任何python代码,而是需要重新格式化你的文件内容。在这种情况下,你可能想看看sphinx-jinja (https://pypi.python.org/pypi/sphinx-jinja)。
你可以在conf.py中加载你的YAML文件:
jinja_contexts = yaml.load(yourFileHere)

然后您可以使用jinja模板将内容写出,并将它们视为reST输入进行处理。


1

0

虽然不完全是你想要的答案,但或许是一个接近的近似值:yaml2rst。这是一个从YAML到RST的转换器。它并没有对YAML本身进行任何明确的花哨处理,但会查找注释行(以#开头),并将它们提取为RST块(YAML进入code-block)。允许一种类似文学化的YAML。

此外,语法高亮的YAML非常易读(该死,它是YAML,而不是JSON!)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接