zoukankan      html  css  js  c++  java
  • 【tools&tips】sphinx生成本页面的带链接目录

    【tools&tips】sphinx生成本页面的带链接目录

    sphinx生成本页目录的问题

    使用restructuredText书写博客时,每篇博客单独一个网页。如果想要使用sphinx为每个页面生成本页的目录,找了很久发现似乎sphinx并不支持。在网上也有类似的 问题

    下文就实际的两种情形来讨论解决方式。

    1. 单文档模式。在一个sphinx project里面,只由一个文档index组成。
    2. 多级文档模式。在一个sphinx project里面,存在多级。假设多级文档的总控文档为index。sphinx里还有子目录A/B/C。A/B/C/的主控文档分别为Aindex,Bindex,Cindex。

    一种解决方式

    sphinx v1.1.3之后的版本,如果给每个博客都新建一个工程的话,倒是可以间接的解决。假定博客文件为index.rst。在toctree中将文件自己包含在内。虽然会引起sphinx的warning警告,但是也可以暂时满足生成本页面的需求。

    目录
    ================================
    
    .. toctree::
    
       index
    

    但是这种方式并不解决根本问题。因为这种方式存在多个弊端:

    • 仅仅支持单文档模式。必须为每篇博客都新建一个工程。多级文档模式下会因为递归调用,导致整个工程无法生成HTML。
    • 可恶的warning提醒,红的扎眼。

    因此这种方案只是权宜之计。还得想其他办法。

    最终解决方式

    其实sphinx的 官方文档 中,提到了一种解决方式。

    The special entry name self stands for the document containing the toctree directive. This is useful if you want to generate a “sitemap” from the toctree.
    

    也就是说,sphinx已经考虑到了,使用关键字”self”来生成本网页的目录。

    目录
    ================================
    
    .. toctree::
    
       self
    

    但是在实际使用中会发现,self只能生成本网页的title,对于本网页的其他标题,如一级、二级标题,均不生成相应的链接。

    因此还是要从源码着手解决。大致了解了下sphinx的代码,从sphinx-1.2.1-py2.7.eggsphinxenviroment.py文件中找到了这样一个函数_entries_from_toctree。

    def _entries_from_toctree(toctreenode, parents,
                                      separate=False, subtree=False):
                """Return TOC entries for a toctree node."""
                refs = [(e[0], e[1]) for e in toctreenode['entries']]
                entries = []
                for (title, ref) in refs:
                    try:
                        refdoc = None
                        if url_re.match(ref):
                            reference = nodes.reference('', '', internal=False,
                                                        refuri=ref, anchorname='',
                                                        *[nodes.Text(title)])
                            para = addnodes.compact_paragraph('', '', reference)
                            item = nodes.list_item('', para)
                            toc = nodes.bullet_list('', item)
                        elif ref == 'self':
                            # 'self' refers to the document from which this
                            # toctree originates
                            ref = toctreenode['parent']
                            if not title:
                                title = clean_astext(self.titles[ref])
                            reference = nodes.reference('', '', internal=True,
                                                        refuri=ref,
                                                        anchorname='',
                                                        *[nodes.Text(title)])
                            para = addnodes.compact_paragraph('', '', reference)
                            item = nodes.list_item('', para)
                            # don't show subitems
                            toc = nodes.bullet_list('', item)
                        else:
                            if ref in parents:
                                self.warn(ref, 'circular toctree references '
                                          'detected, ignoring: %s <- %s' %
                                          (ref, ' <- '.join(parents)))
                                continue
                            refdoc = ref
                            toc = self.tocs[ref].deepcopy()
                            self.process_only_nodes(toc, builder, ref)
                            if title and toc.children and len(toc.children) == 1:
                                child = toc.children[0]
                                for refnode in child.traverse(nodes.reference):
                                    if refnode['refuri'] == ref and 
                                           not refnode['anchorname']:
                                        refnode.children = [nodes.Text(title)]
                         ...
    

    这里面有一行注释,很清晰的写出,当使用self的时候,”don’t show subitems”。

    好了,我们的任务明确了,那我们的任务就是要让他”show subitems”。

    现在我们来想,为什么在toctree里写index(假设文档就是index.rst)会引起递归调用。是因为在解析这篇文档时,将本文档当做主控文档,为本文档建立子文档时。遇到了index,那么他会认为index是一个子文档,于是给自己添加一个<toctree>index</toctree>。

    系统遇到<toctree>index</toctree>,对其进行解析时,打开index,把index作为主文档,再次添加一个<toctree>index</toctree>到<toctree>index</toctree>里,形成递归,且没有递归头。当递归到一定层数,sphinx会自动给出个跳出递归的错误。

    那么,现在,我们要做的其实就很简单了,就是让他在解析自身的时候,不进行递归。方法也很简单,就是遍历,删除掉其中的toctree节点。在这里,我使用了深度优先的递归搜索方式。

    ...
    elif ref == 'self':
        # 'self' refers to the document from which this
        # toctree originates
        ref = toctreenode['parent']
        if not title:
            title = clean_astext(self.titles[ref])
        reference = nodes.reference('', '', internal=True,
                                    refuri=ref,
                                    anchorname='',
                                    *[nodes.Text(title)])
        para = addnodes.compact_paragraph('', '', reference)
        item = nodes.list_item('', para)
        # don't show subitems
        toc = nodes.bullet_list('', item)
    
        # show subitems!!!
        if ref not in parents:
            toc = self.tocs[ref].deepcopy()
            def clean_toctree(toc):
                poplist = []
                if len(toc.children) > 0:
                    for index, child in enumerate(toc.children):
                        if clean_toctree(child):
                            poplist.append(index)
                else:
                    if toc.__class__ == addnodes.toctree:
                        return True
                for index, popindex in enumerate(poplist):
                    toc.pop(popindex - index)
            clean_toctree(toc)
        else:
            para = addnodes.compact_paragraph('', '')
            item = nodes.list_item('', para)
            toc = nodes.bullet_list('', item)
    
    else:
        ...
    

    这里我还做了一个判断,就是如果是多级文档的时候,Aindex中也写了self(Aindex中还有子标题a1,a2,a3)。

    那么在index作为主控文档生成目录的时候,因为Aindex的toctree中有self,会将a1,a2,a3,加入到目录。index再去从self之下继续读取Aindex正文的时候,会再次添加a1,a2,a3,加入到目录。那么就造成了a1,a2,a3的重复添加,在目录里很不不美观。

    因此在这里做了一个判断,

    if ref not in parents:
    

    这段代码就是用来判断这里的self是Aindex本身还是在总控文档index的引用。如果是总控文档的引用,那么就不添加子标题a1,a2,a3(因为总控文档还会读取文档的正文再次添加)。

    附: environment.py的补丁文件(Sphinx v1.2.1)

  • 相关阅读:
    Python基础学习笔记(一)
    前端之jQuery
    JavaScript概述
    前端CSS
    前端HTML
    WIN10下Mysql安装使用
    python 创建线程
    Python 创建进程
    python day24模块
    python常用模块2(序列化,pickle,shelve,json,configparser)
  • 原文地址:https://www.cnblogs.com/ToBeSmart/p/3678783.html
Copyright © 2011-2022 走看看