I recently discovered a nice way to integrate plots in sphinx documentation with the custom directive bokeh-plot. I thought it would be quite easy to create mine to add a simple blogging system. However the documentation is pretty rare on that topic. All my searches ended up at Tutorial: Writing a simple extension. So here are my finding about creating a custom directive BlogPostDirective to process something like:
.. blogpost:: :title: Migration to IPython 3.1 :keywords: ipython, migration, jupyter, jenkins, pandoc :date: 2015-04-16 :categories: ipython, documentation Any text this blog could contains and any RST tag:: ...
Step 1: create a custom node
Sphinx converts a RST files into a tree, each nodes contains some text and some information on how to process it. It also contains children. After being converted into HTML, the same structure appears. All nodes comes from docutils.nodes.
from docutils import nodes class blogpost_node(nodes.Structural, nodes.Element): pass
Step 2: create a custom directive
We define here the class which convert RST into a set of nodes. It contains static variables and overrides method run which produces a list of nodes such as blogpost_node.
from docutils.parsers.rst import Directive class BlogPostDirective(Directive): # defines the parameter the directive expects # directives.unchanged means you get the raw value from RST required_arguments = 0 optional_arguments = 0 final_argument_whitespace = True option_spec = {'date': directives.unchanged, 'title': directives.unchanged, 'keywords': directives.unchanged, 'categories': directives.unchanged, } has_content = True add_index = True def run(self): sett = self.state.document.settings language_code = sett.language_code env = self.state.document.settings.env # gives you access to the parameter stored # in the main configuration file (conf.py) config = env.config # gives you access to the options of the directive options = self.options # we create a section idb = nodes.make_id("blog-" + options["date"] + "-" + options["title"]) section = nodes.section(ids=[idb]) # we create a title and we add it to section section += nodes.title(options["title"]) # we create the content of the blog post # because it contains any kind of RST # we parse parse it with function nested_parse par = nodes.paragraph() self.state.nested_parse(content, self.content_offset, par) # we create a blogpost and we add the section node = blogpost_node() node += section node += par # we return the result return [ node ]
The important function is the method nested_parse which converts the raw RST into nodes for the documentation. Our method run just add a title before this conversion happens. You will discover others tricks in file sphinx_blog_extension.py.
Step 3: register the class and the nodes
This takes places in file conf.py.
from somewhere import BlogPostDirective def setup(app): app.add_node(blogpost_node) app.add_directive('blogpost', BlogPostDirective)
It is all done but there are some others tricks you can use.
Step 4: register a new variable in the documentation
Still in the file conf.py (everything can be placed there). The value of the new variable can be retrieved from method run in the new directive.
def setup(app): app.add_config_value('my_new_variable', 'default_value', 'env') my_new_variable = "another value"
Step 5: post process HTML
Imagine we now want to add HTML content before and after the blog was processed. We registered two functions Sphinx will call later during the process. For example, we add a link to something after the blog post content.
def visit_blogpost_node(self, node): pass def depart_blogpost_node(self, node): link = """<p><a class="reference internal" href="something.html" title="a title">a title</a></p>""" self.body.append(link) def setup(app): app.add_node(blogpost_node, html=(visit_blogpost_node, depart_blogpost_node))
Step 6: encapsulate a node into a div
Sphinx offers simple commands to insert the blog into a div (HTML).
def visit_blogpost_node(self, node): # this function adds "admonition" to the class name of tag div # it will look like a warning or a note self.visit_admonition(node) def depart_blogpost_node(self, node): self.depart_admonition(node)
Good luck if you start your own directive, you will probably need a couple of tries before getting it right. You can also try running your class using function publish_programmatically. That's what I did in the constructor of class BlogPost.
<-- --> |