docutils / sphinx custom指令创建兄弟节而不是子节点

时间:2014-01-03 20:50:36

标签: python python-sphinx restructuredtext docutils

考虑一个带有此骨架的reStructuredText文档:

Main Title
==========

text text text text text

Subsection
----------

text text text text text

.. my-import-from:: file1
.. my-import-from:: file2

my-import-from指令由特定于文档的Sphinx扩展提供,该扩展应该读取作为其参数提供的文件,解析嵌入其中的reST,并将结果作为当前输入文件中的一部分注入。 (就像autodoc一样,但是对于不同的文件格式。)我现在的代码就像这样:

class MyImportFromDirective(Directive):
    required_arguments = 1
    def run(self):
        src, srcline = self.state_machine.get_source_and_line()
        doc_file = os.path.normpath(os.path.join(os.path.dirname(src),
                                                 self.arguments[0]))
        self.state.document.settings.record_dependencies.add(doc_file)
        doc_text  = ViewList()

        try:
            doc_text = extract_doc_from_file(doc_file)
        except EnvironmentError as e:
            raise self.error(e.filename + ": " + e.strerror) from e

        doc_section = nodes.section()
        doc_section.document = self.state.document

        # report line numbers within the nested parse correctly
        old_reporter = self.state.memo.reporter
        self.state.memo.reporter = AutodocReporter(doc_text,
                                                   self.state.memo.reporter)
        nested_parse_with_titles(self.state, doc_text, doc_section)
        self.state.memo.reporter = old_reporter

        if len(doc_section) == 1 and isinstance(doc_section[0], nodes.section):
            doc_section = doc_section[0]

        # If there was no title, synthesize one from the name of the file.
        if len(doc_section) == 0 or not isinstance(doc_section[0], nodes.title):
            doc_title = nodes.title()
            doc_title.append(make_title_text(doc_file))
            doc_section.insert(0, doc_title)

        return [doc_section]

这是有效的,除了,新部分被注入当前部分的,而不是兄弟。换句话说,上面的示例文档生成了这样的TOC树:

  
      
  • 主题   
        
    • 第   
          
      • File1中
      •   
      • 文件2
      •   
    •   
  •   

而不是所需的

  
      
  • 主题   
        
    •   
    • File1中
    •   
    • 文件2
    •   
  •   

我该如何解决这个问题? Docutils文档是......不适当的,特别是在控制截面深度方面。我尝试过的一个显而易见的事情是返回doc_section.children而不是[doc_section];从TOC树中完全删除File1File2(但确实使文档正文中的节标题为正确的嵌套级别。)

2 个答案:

答案 0 :(得分:1)

我不知道如何直接在自定义指令中执行此操作。但是,您可以使用自定义转换在解析后引发树中的File1File2节点。例如,请参阅docutils.transforms.frontmatter模块中的转换。

在Sphinx扩展程序中,使用Sphinx.add_transform方法注册自定义转换。

更新:您还可以通过在节点列表中返回docutils.nodes.pending类的一个或多个实例来直接在指令中注册转换。在这种情况下,请确保调用文档的note_pending方法(在您的指令中,您可以通过self.state_machine.document获取文档。)

答案 1 :(得分:1)

我认为不可能通过返回指令中的部分来做到这一点(没有按照Florian建议的那样做),因为它会被附加到“当前”部分。但是,您可以通过self.state.section添加该部分,如下所示(为简洁起见,处理删除的选项)

class FauxHeading(object):
    """
    A heading level that is not defined by a string. We need this to work with
    the mechanics of
    :py:meth:`docutils.parsers.rst.states.RSTState.check_subsection`.

    The important thing is that the length can vary, but it must be equal to
    any other instance of FauxHeading.
    """

    def __init__(self, length):
        self.length = length

    def __len__(self):
        return self.length

    def __eq__(self, other):
        return isinstance(other, FauxHeading)


class ParmDirective(Directive):

    required_arguments = 1
    optional_arguments = 0
    has_content = True
    option_spec = {
        'type':          directives.unchanged,
        'precision':     directives.nonnegative_int,
        'scale':         directives.nonnegative_int,
        'length':        directives.nonnegative_int}

    def run(self):
        variableName = self.arguments[0]
        lineno = self.state_machine.abs_line_number()
        secBody = None
        block_length = 0

        # added for some space
        lineBlock = nodes.line('', '', nodes.line_block())

        # parse the body of the directive
        if self.has_content and len(self.content):
            secBody = nodes.container()
            block_length += nested_parse_with_titles(
                self.state, self.content, secBody)

        # keeping track of the level seems to be required if we want to allow
        # nested content. Not sure why, but fits with the pattern in
        # :py:meth:`docutils.parsers.rst.states.RSTState.new_subsection`
        myLevel = self.state.memo.section_level
        self.state.section(
            variableName,
            '',
            FauxHeading(2 + len(self.options) + block_length),
            lineno,
            [lineBlock] if secBody is None else [lineBlock, secBody])
        self.state.memo.section_level = myLevel

        return []