30

I am using Sphinx to generate the documentation for a project of mine.

In this project, I describe a list of available commands in a yaml file which, once loaded, results in a dictionary in the form {command-name : command-description} for example:

commands = {"copy"  : "Copy the highlighted text in the clipboard",
            "paste" : "Paste the clipboard text to cursor location",
            ...}

What I would like to know, is if there is a method in sphinx to load the yaml file during the make html cycle, translate the python dictionary in some reStructuredText format (e.g. a definition list) and include in my html output.

I would expect my .rst file to look like:

Available commands
==================
The commands available in bla-bla-bla...

.. magic-directive-that-execute-python-code::
   :maybe python code or name of python file here:

and to be converted internally to:

Available commands
==================
The commands available in bla-bla-bla...

copy
  Copy the highlighted text in the clipboard

paste
  Paste the clipboard text to cursor location

before being translated to HTML.

7
  • This is not a proper answer so I put it as a comment. As far as I know, there's no way to parse a yaml file with sphinx directly, but I think you can make it using pyyaml and modifying you sphinx Makefile. Commented Aug 30, 2011 at 23:00
  • 1
    What's the point of writing YAML code? Why not just write the description in the Python module and use Sphinx's autodoc? Why make something that's more complicated than sphinx.pocoo.org/ext/autodoc.html? Commented Aug 30, 2011 at 23:29
  • @S.Lott - The basic idea is DNRY: commands are defined (and can be overridden by the user) in a yml file. The example above is simplified for making the question easier to understand, but the actual yml file contains indeed extra information for the parser like the number of parameters, the possible flags, the validation callback, etc... It simply seems stupid (and a potential source of bugs in the documentation) to repeat the same information in the yml file and the module docstring. Commented Aug 31, 2011 at 6:33
  • 1
    @Oscar - Appreciate you took the time to drop a comment, but pyyaml is not the problem here (obviously I'm using it already in the module that loads the yml file). The problem is that I don't know how I should modify the makefile... would you mind to articulate your suggestion a bit more? Thanks! Commented Aug 31, 2011 at 6:37
  • 1
    @mac: Why waste time writing code in YAML? If you want the users to be able to define (and override) commands, Python is perfectly acceptable. The Python syntax for a dictionary is no more complex than YAML, and you simply avoid this weird multi-language issue. Commented Aug 31, 2011 at 12:04

7 Answers 7

27

At the end I find a way to achieve what I wanted. Here's the how-to:

  1. Create a python script (let's call it generate-includes.py) that will generate the reStructuredText and save it in the myrst.inc file. (In my example, this would be the script loading and parsing the YAML, but this is irrelevant). Make sure this file is executable!!!
  2. Use the include directive in your main .rst document of your documentation, in the point where you want your dynamically-generated documentation to be inserted:

    .. include:: myrst.inc
    
  3. Modify the sphinx Makefile in order to generate the required .inc files at build time:

    myrst.inc:
        ./generate-includes.py
    
    html: myrst.inc
        ...(other stuff here)
    
  4. Build your documentation normally with make html.

Sign up to request clarification or add additional context in comments.

1 Comment

Works great. Althought readthedocs doesn't work anymore, but I guess that's inevitable for python-generated documentation...
19

An improvement based on Michael's code and the built-in include directive:

import sys
from os.path import basename

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO

from docutils.parsers.rst import Directive    
from docutils import nodes, statemachine

class ExecDirective(Directive):
    """Execute the specified python code and insert the output into the document"""
    has_content = True

    def run(self):
        oldStdout, sys.stdout = sys.stdout, StringIO()

        tab_width = self.options.get('tab-width', self.state.document.settings.tab_width)
        source = self.state_machine.input_lines.source(self.lineno - self.state_machine.input_offset - 1)

        try:
            exec('\n'.join(self.content))
            text = sys.stdout.getvalue()
            lines = statemachine.string2lines(text, tab_width, convert_whitespace=True)
            self.state_machine.insert_input(lines, source)
            return []
        except Exception:
            return [nodes.error(None, nodes.paragraph(text = "Unable to execute python code at %s:%d:" % (basename(source), self.lineno)), nodes.paragraph(text = str(sys.exc_info()[1])))]
        finally:
            sys.stdout = oldStdout

def setup(app):
    app.add_directive('exec', ExecDirective)

This one imports the output earlier so that it goes straight through the parser. It also works in Python 3.

1 Comment

With Sphinx v1.8.1, this code generates Extension error: ... cannot import name 'Directive' from 'sphinx.util.compact'. Changing the import to from docutils.parsers.rst import Directive fixes the issue.
9

I needed the same thing, so I threw together a new directive that seems to work (I know nothing about custom Sphinx directives, but it's worked so far):

import sys
from os.path import basename
from StringIO import StringIO

from sphinx.util.compat import Directive
from docutils import nodes

class ExecDirective(Directive):
    """Execute the specified python code and insert the output into the document"""
    has_content = True

    def run(self):
        oldStdout, sys.stdout = sys.stdout, StringIO()
        try:
            exec '\n'.join(self.content)
            return [nodes.paragraph(text = sys.stdout.getvalue())]
        except Exception, e:
            return [nodes.error(None, nodes.paragraph(text = "Unable to execute python code at %s:%d:" % (basename(self.src), self.srcline)), nodes.paragraph(text = str(e)))]
        finally:
            sys.stdout = oldStdout

def setup(app):
    app.add_directive('exec', ExecDirective)

It's used as follows:

.. exec::
   print "Python code!"
   print "This text will show up in the document"

1 Comment

With Sphinx v1.8.1, this code generates Extension error: ... cannot import name 'Directive' from 'sphinx.util.compact'. Changing the import to from docutils.parsers.rst import Directive fixes the issue.
4

Sphinx doesn't have anything built-in to do what you like. You can either create a custom directive to process your files or generate the reStructuredText in a separate step and include the resulting reStructuredText file using the include directive.

3 Comments

So good to know that .. magic-directive-that-execute-python-code:: needs to be something I code then. I tried to get a crack at it and although I manage to write a directive that insert my script-generated text I couldn't understand how I can make it parse. For example: nodes.paragraph('', '**text**') will output **text** rather than text. How can I tell sphinx to parse it with standard reStructuredText syntax?
In your Directive subclass you'll have handle_content() and handle_signature() methods. You can recursively call self.state.nested_parse() and it will handle the built-in styles properly. Checkout Creating reStructuredText Directives
Thank you devin_s (+1). As you may have noticed (see my own answer) I finally solved using the .. include:: directive. Will check out your solution too, though, asap!
3

I know this question is old, but maybe someone else will find it useful as well.

It sounds like you don't actually need to execute any python code, but you just need to reformat the contents of your file. In that case you might want to look at sphinx-jinja (https://pypi.python.org/pypi/sphinx-jinja).

You can load your YAML file in the conf.py:

jinja_contexts = yaml.load(yourFileHere)

Then you can use jinja templating to write out the contents and have them treated as reST input.

Comments

1

Sphinx does support custom extensions that would probably be the best way to do this http://sphinx.pocoo.org/ext/tutorial.html.

Comments

0

Not quite the answer you're after, but perhaps a close approximation: yaml2rst. It's a converter from YAML to RST. Doesn't do anything explicitly fancy with the YAML itself, but looks for comment lines (starts with #) and pulls them out into RST chunks (with the YAML going into code-blocks). Allows for a sort-of literate YAML.

Also, the syntax-highlighted YAML is quite readable (heck, it's YAML, not JSON!).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.