Conditional toctree in Sphinx
Asked Answered
H

5

11

I want to do multiple versions of a documentation, which differ in the sections that are included. To achieve this I would usually use either the only directive or the ifconfig extension. However, I cannot use any of those in combination with the toctree directive.

What I basically want is something like this:

.. toctree::
   :maxdepth: 2

   intro
   strings
   datatypes
   numeric
   .. only:: university
      complex

Is there a way to do that?

Heliometer answered 21/2, 2013 at 12:7 Comment(1)
M
7

My previous answer fails if you have hierarchies of table of contents so I wrote a simple toctree-filt directive that is able to filter entries based on a prefix to the entry. For example, given a toctree-filt directive like

.. toctree-filt::
   :maxdepth: 1

   user-manual
   :internal:supervisor-api
   :draft:new-feature
   :erik:erik-maths
   api

and setting the exclusion list to ['draft','erik'] will result in an effective toctree that looks like

.. toctree-filt::
   :maxdepth: 1

   user-manual
   supervisor-api
   api

Add the following lines to your conf.py:

sys.path.append(os.path.abspath('../sphinx-ext/'))
extensions = ['toctree_filter']
toc_filter_exclude = ['draft','erik']

Put the following code in /sphinx_ext next to your /source directory:

import re
from sphinx.directives.other import TocTree


def setup(app):
    app.add_config_value('toc_filter_exclude', [], 'html')
    app.add_directive('toctree-filt', TocTreeFilt)
    return {'version': '1.0.0'}

class TocTreeFilt(TocTree):
    """
    Directive to notify Sphinx about the hierarchical structure of the docs,
    and to include a table-of-contents like tree in the current document. This
    version filters the entries based on a list of prefixes. We simply filter
    the content of the directive and call the super's version of run. The
    list of exclusions is stored in the **toc_filter_exclusion** list. Any
    table of content entry prefixed by one of these strings will be excluded.
    If `toc_filter_exclusion=['secret','draft']` then all toc entries of the
    form `:secret:ultra-api` or `:draft:new-features` will be excuded from
    the final table of contents. Entries without a prefix are always included.
    """
    hasPat = re.compile('^\s*:(.+):(.+)$')

    # Remove any entries in the content that we dont want and strip
    # out any filter prefixes that we want but obviously don't want the
    # prefix to mess up the file name.
    def filter_entries(self, entries):
        excl = self.state.document.settings.env.config.toc_filter_exclude
        filtered = []
        for e in entries:
            m = self.hasPat.match(e)
            if m != None:
                if not m.groups()[0] in excl:
                    filtered.append(m.groups()[1])
            else:
                filtered.append(e)
        return filtered

    def run(self):
        # Remove all TOC entries that should not be on display
        self.content = self.filter_entries(self.content)
        return super().run()

Now just change your existing toctree directives to toctree-filt and you are good to roll. Note that Sphinx will post errors because it will find files that are not included in the document. Not sure how to fix that.

Minx answered 6/10, 2017 at 7:1 Comment(2)
This answer works great and I upvoted it, but be aware that just because the table of contents gets filtered doesn't mean that the filtered pages don't get compiled into the build. I found this out by accident by using the search bar to search for a value that was in some of the excluded content and I was able to still bring up that content. If you really want to completely exclude the content, you can get around this by also using an .. only:: directive at the beginning of the files that you've excluded.Zendejas
Oooh! Thanks for noting this one!Minx
M
5

A very simple solution is to maintain two separate index files under different names. You can specify which index file to use by default in conf.py and override it for a special build using -D master_doc=alternate-index on the sphinx-build command line.

Minx answered 9/3, 2017 at 7:29 Comment(0)
S
4

My solution is to place the conditional content in a separate directory 'intern' and using a tag 'internal'.

In conf.py I added the lines

if tags.has('internal'):
    exclude_patters = ['_build']
else:
    exclude_patterns = ['_build', 'intern*']

Now when I pass the 'internal' flag on the command line I get all, otherwise everything except the contents in the intern directory.

The tag internal can be used in combination with only.

The ToC contains references to intern/somedoc and they are included or skipped as required. I do get a number of warnings about missing pages but those can be silenced.

Spoken answered 11/1, 2017 at 12:32 Comment(0)
T
3

As far as I know there is no way to do what you would like. I have been struggling with the same issue, see https://github.com/sphinx-doc/sphinx/issues/1717.

The reason is that Sphinx process all lines contained in a toctree node as pure text.

I see two alternatives:

  1. you can write your own toctree directive;
  2. you can extend the toctree including an option that contains the expression to be evaluated

    .. toctree:
       :condition: expression
    
       file1
    

and then you customize the doctree resolve event.

  1. you can use text substitutions on the raw text defining your own tags. you can do that implementing an event handler for the source-read event. For instance $$condition$$ could contain the condition to be evaluated, while $$$ the end of the block, i.e.

    .. toctree:
    
       file1
       $$mycondition$$
       file2
       $$$
    

Depending on mycondition, you can remove the following block lines.

Number 3 is quite simple, while to me number 2 is the most elegant.

Teufert answered 10/3, 2015 at 21:1 Comment(0)
B
0

You can exclude the file "complex" using the exclude_patterns config variable. The entry in the toctree will just be ignored, but this will print a warning during the build.

However, you can silence this warning by using the configuration variable suppress_warnings = ['toc.excluded'], that allows links to be broken in the case where the target file was explicitly excluded.

For this to work, the file must be individually excluded. If "complex" is a directory, then both the directory and the index file must be excluded.

Here is an example using a tag to control the inclusion of the entry:

index.rst

.. toctree::

   intro
   strings
   datatypes
   numeric
   complex

"complex" is a directory that contains "index.rst" and other files.

config.py

suppress_warnings = ['toc.excluded']
if not tags.has('university'):
    exclude_patterns = ['complex/index.rst', 'complex']
Bourbon answered 10/1 at 19:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.