Georg Brandl avatar Georg Brandl committed 796d955

Closes #851: Recognize and warn about circular toctrees, instead of running into recursion errors.

Comments (0)

Files changed (2)

 
 * #862: Fix handling of ``-D`` and ``-A`` options on Python 3.
 
+* #851: Recognize and warn about circular toctrees, instead of running
+  into recursion errors.
+
 
 Release 1.1.2 (Nov 1, 2011) -- 1.1.1 is a silly version number anyway!
 ======================================================================

sphinx/environment.py

                             subnode['iscurrent'] = True
                             subnode = subnode.parent
 
-        def _entries_from_toctree(toctreenode, separate=False, subtree=False):
+        def _entries_from_toctree(toctreenode, parents,
+                                  separate=False, subtree=False):
             """Return TOC entries for a toctree node."""
             refs = [(e[0], str(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='',
                         # 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:
                         if not (toctreenode.get('hidden', False)
                                 and not includehidden):
                             i = toctreenode.parent.index(toctreenode) + 1
-                            for item in _entries_from_toctree(toctreenode,
-                                                              subtree=True):
+                            for item in _entries_from_toctree(
+                                    toctreenode, [refdoc] + parents,
+                                    subtree=True):
                                 toctreenode.parent.insert(i, item)
                                 i += 1
                             toctreenode.parent.remove(toctreenode)
         # NOTE: previously, this was separate=True, but that leads to artificial
         # separation when two or more toctree entries form a logical unit, so
         # separating mode is no longer used -- it's kept here for history's sake
-        tocentries = _entries_from_toctree(toctree, separate=False)
+        tocentries = _entries_from_toctree(toctree, [], separate=False)
         if not tocentries:
             return None
 
     def collect_relations(self):
         relations = {}
         getinc = self.toctree_includes.get
-        def collect(parents, docname, previous, next):
+        def collect(parents, parents_set, docname, previous, next):
+            # circular relationship?
+            if docname in parents_set:
+                # we will warn about this in resolve_toctree()
+                return
             includes = getinc(docname)
             # previous
             if not previous:
                 for subindex, args in enumerate(izip(includes,
                                                      [None] + includes,
                                                      includes[1:] + [None])):
-                    collect([(docname, subindex)] + parents, *args)
+                    collect([(docname, subindex)] + parents,
+                            parents_set.union([docname]), *args)
             relations[docname] = [parents[0][0], previous, next]
-        collect([(None, 0)], self.config.master_doc, None, None)
+        collect([(None, 0)], set(), self.config.master_doc, None, None)
         return relations
 
     def check_consistency(self):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.