Sphinx Extensions

Sphinx supports extending how it processes a group of reStructuredText files using an extension. Basically, an extension is a set of Python functions that get called at various points while Sphinx runs. You can do a lot of things in that code to modify what Sphinx will produce in the final output.

Most of the extensions I have used generate some kind of file that I include in the final output files. For instance, I use a lot of figures produced using LaTeX. My Sphinx extension lets me write a directive block in my reStructuredText file, and when Sphinx is run, the body of that block is wrapped up in a script that is sent to LaTeX for processing. The extension then includes the image file produced by that process in place of that directive block in the final output.

I have used this technique for many years, and not thought much about it.

Until recently!

External Processes

Much of the work I ask Sphinx to do involves launching an external tool to do something. Python can do this using the subprocess module. What occurred to me is that much of the real development work I do is managed by make and Makefiles in some development directory on my system.

All I really need for Sphinx to do is let me manage that external process to do what I want, then tell my directive` to include a final file in my document. Make`` already knows if that external processing is really needed, or if the final output is sitting there, ready to be included in my document output.

As an example of this idea, let’s set up an extension that will let us create several different kinds of images with LaTeX. This will give us a look at how you set up an extension, and manage the external process outside of SPhinx.

Basic Extension Structure

The first thing we need to do is create an interface between our extension code and Sphinx itself. This is done by “registering” the extension with Sphinx.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# -*- coding: utf-8 -*-
"""
    sphinx_ext.pylit
    ~~~~~~~~~~~~~~~~

    include LaTeX generated imags in Sphinx docs

    :copyright: 2017 by Roie R. Black
    :license: BSD, see ICENSE for details
"""
from docutils.parsers.rst import Directive, directives
from docutils import nodes, utils

import binascii

class pylit(nodes.General, nodes.Element):
    pass

class Pylit(Directive):
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {
         'wrapper': directives.unchanged,
    }
    has_content = True 
    
    def run(self):
        env = self.state.document.settings.env

        body = binascii.hexlify('\n'.join(self.content))
        if 'wrapper' in self.options:
            wrapper = self.options['wrapper']
        else: 
            wrapper = ""
        if hasattr(env,'pylit_root'):
            root = env.pylit_root
        else:
            root = '.'
        fout = open("pylit.raw","w")
        fout.write(body)
        fout.write(root)
        fout.write(wrapper)
        fout.close()
        node = pylit();
        return [node]

def latex_visit_pylit(self, node) :
    self.body.append('$' + node['latex'] + '$')
    raise nodeSkipNode

def html_visit_pylit(self, node):
    wrapper = node['wrapper']
    self.body.append('<h3>PyLiT</h3>')
    raise nodes.SkipNode

def setup(app):
    app.add_config_value('pylit_root', "", 'html')
    app.add_node(pylit,
        latex = (latex_visit_pylit, None),
        html = (html_visit_pylit, None))
    app.add_directive('pylit', Pylit)

    return {'version': '0.1'}

To get Sphinx to use this extension, set up the Sphinx project configuration file as follows:

conf.py
import os
sys.path.insert(0, os.path.abspath('.'))
...
extensions = [
    'sphinx_ext.sphinx-pylit',
    ...
]


PyLiT visitor