Sophie

Sophie

distrib > Fedora > 14 > x86_64 > media > updates > by-pkgid > 2b39e69e45f9a0b2b3aece7339c56216 > files > 94

python-sphinx-doc-1.0.7-1.fc14.noarch.rpm



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    
    <title>Tutorial: Writing a simple extension &mdash; Sphinx v1.0.7 documentation</title>
    <link rel="stylesheet" href="../_static/sphinxdoc.css" type="text/css" />
    <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
    <script type="text/javascript">
      var DOCUMENTATION_OPTIONS = {
        URL_ROOT:    '../',
        VERSION:     '1.0.7',
        COLLAPSE_INDEX: false,
        FILE_SUFFIX: '.html',
        HAS_SOURCE:  true
      };
    </script>
    <script type="text/javascript" src="../_static/jquery.js"></script>
    <script type="text/javascript" src="../_static/underscore.js"></script>
    <script type="text/javascript" src="../_static/doctools.js"></script>
    <link rel="search" type="application/opensearchdescription+xml"
          title="Search within Sphinx v1.0.7 documentation"
          href="../_static/opensearch.xml"/>
    <link rel="top" title="Sphinx v1.0.7 documentation" href="../index.html" />
    <link rel="up" title="Sphinx Extensions" href="../extensions.html" />
    <link rel="next" title="Extension API" href="appapi.html" />
    <link rel="prev" title="Sphinx Extensions" href="../extensions.html" />
 
<style type="text/css">
  table.right { float: right; margin-left: 20px; }
  table.right td { border: 1px solid #ccc; }
</style>

  </head>
  <body>
<div style="background-color: white; text-align: left; padding: 10px 10px 15px 15px">
<img src="../_static/sphinx.png" alt="Sphinx logo" />
</div>

    <div class="related">
      <h3>Navigation</h3>
      <ul>
        <li class="right" style="margin-right: 10px">
          <a href="../genindex.html" title="General Index"
             accesskey="I">index</a></li>
        <li class="right" >
          <a href="../py-modindex.html" title="Python Module Index"
             >modules</a> |</li>
        <li class="right" >
          <a href="appapi.html" title="Extension API"
             accesskey="N">next</a> |</li>
        <li class="right" >
          <a href="../extensions.html" title="Sphinx Extensions"
             accesskey="P">previous</a> |</li>
        <li><a href="../index.html">Sphinx home</a>&nbsp;|&nbsp;</li>
        <li><a href="../contents.html">Documentation</a>
          &raquo;</li>

          <li><a href="../extensions.html" accesskey="U">Sphinx Extensions</a> &raquo;</li> 
      </ul>
    </div>
      <div class="sphinxsidebar">
        <div class="sphinxsidebarwrapper">
  <h3><a href="../contents.html">Table Of Contents</a></h3>
  <ul>
<li><a class="reference internal" href="#">Tutorial: Writing a simple extension</a><ul>
<li><a class="reference internal" href="#build-phases">Build Phases</a></li>
<li><a class="reference internal" href="#extension-design">Extension Design</a></li>
<li><a class="reference internal" href="#the-setup-function">The Setup Function</a></li>
<li><a class="reference internal" href="#the-node-classes">The Node Classes</a></li>
<li><a class="reference internal" href="#the-directive-classes">The Directive Classes</a></li>
<li><a class="reference internal" href="#the-event-handlers">The Event Handlers</a></li>
</ul>
</li>
</ul>

  <h4>Previous topic</h4>
  <p class="topless"><a href="../extensions.html"
                        title="previous chapter">Sphinx Extensions</a></p>
  <h4>Next topic</h4>
  <p class="topless"><a href="appapi.html"
                        title="next chapter">Extension API</a></p>
  <h3>This Page</h3>
  <ul class="this-page-menu">
    <li><a href="../_sources/ext/tutorial.txt"
           rel="nofollow">Show Source</a></li>
  </ul>
<div id="searchbox" style="display: none">
  <h3>Quick search</h3>
    <form class="search" action="../search.html" method="get">
      <input type="text" name="q" size="18" />
      <input type="submit" value="Go" />
      <input type="hidden" name="check_keywords" value="yes" />
      <input type="hidden" name="area" value="default" />
    </form>
    <p class="searchtip" style="font-size: 90%">
    Enter search terms or a module, class or function name.
    </p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
        </div>
      </div>

    <div class="document">
      <div class="documentwrapper">
        <div class="bodywrapper">
          <div class="body">
            
  <div class="section" id="tutorial-writing-a-simple-extension">
<span id="exttut"></span><h1>Tutorial: Writing a simple extension<a class="headerlink" href="#tutorial-writing-a-simple-extension" title="Permalink to this headline">¶</a></h1>
<p>This section is intended as a walkthrough for the creation of custom extensions.
It covers the basics of writing and activating an extensions, as well as
commonly used features of extensions.</p>
<p>As an example, we will cover a &#8220;todo&#8221; extension that adds capabilities to
include todo entries in the documentation, and collecting these in a central
place.  (A similar &#8220;todo&#8221; extension is distributed with Sphinx.)</p>
<div class="section" id="build-phases">
<h2>Build Phases<a class="headerlink" href="#build-phases" title="Permalink to this headline">¶</a></h2>
<p>One thing that is vital in order to understand extension mechanisms is the way
in which a Sphinx project is built: this works in several phases.</p>
<p><strong>Phase 0: Initialization</strong></p>
<blockquote>
<div>In this phase, almost nothing interesting for us happens.  The source
directory is searched for source files, and extensions are initialized.
Should a stored build environment exist, it is loaded, otherwise a new one is
created.</div></blockquote>
<p><strong>Phase 1: Reading</strong></p>
<blockquote>
<div><p>In Phase 1, all source files (and on subsequent builds, those that are new or
changed) are read and parsed.  This is the phase where directives and roles
are encountered by the docutils, and the corresponding functions are called.
The output of this phase is a <em>doctree</em> for each source files, that is a tree
of docutils nodes.  For document elements that aren&#8217;t fully known until all
existing files are read, temporary nodes are created.</p>
<p>During reading, the build environment is updated with all meta- and cross
reference data of the read documents, such as labels, the names of headings,
described Python objects and index entries.  This will later be used to
replace the temporary nodes.</p>
<p>The parsed doctrees are stored on the disk, because it is not possible to
hold all of them in memory.</p>
</div></blockquote>
<p><strong>Phase 2: Consistency checks</strong></p>
<blockquote>
<div>Some checking is done to ensure no surprises in the built documents.</div></blockquote>
<p><strong>Phase 3: Resolving</strong></p>
<blockquote>
<div>Now that the metadata and cross-reference data of all existing documents is
known, all temporary nodes are replaced by nodes that can be converted into
output.  For example, links are created for object references that exist, and
simple literal nodes are created for those that don&#8217;t.</div></blockquote>
<p><strong>Phase 4: Writing</strong></p>
<blockquote>
<div>This phase converts the resolved doctrees to the desired output format, such
as HTML or LaTeX.  This happens via a so-called docutils writer that visits
the individual nodes of each doctree and produces some output in the process.</div></blockquote>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">Some builders deviate from this general build plan, for example, the builder
that checks external links does not need anything more than the parsed
doctrees and therefore does not have phases 2&#8211;4.</p>
</div>
</div>
<div class="section" id="extension-design">
<h2>Extension Design<a class="headerlink" href="#extension-design" title="Permalink to this headline">¶</a></h2>
<p>We want the extension to add the following to Sphinx:</p>
<ul class="simple">
<li>A &#8220;todo&#8221; directive, containing some content that is marked with &#8220;TODO&#8221;, and
only shown in the output if a new config value is set.  (Todo entries should
not be in the output by default.)</li>
<li>A &#8220;todolist&#8221; directive that creates a list of all todo entries throughout the
documentation.</li>
</ul>
<p>For that, we will need to add the following elements to Sphinx:</p>
<ul class="simple">
<li>New directives, called <tt class="docutils literal"><span class="pre">todo</span></tt> and <tt class="docutils literal"><span class="pre">todolist</span></tt>.</li>
<li>New document tree nodes to represent these directives, conventionally also
called <tt class="docutils literal"><span class="pre">todo</span></tt> and <tt class="docutils literal"><span class="pre">todolist</span></tt>.  We wouldn&#8217;t need new nodes if the new
directives only produced some content representable by existing nodes.</li>
<li>A new config value <tt class="docutils literal"><span class="pre">todo_include_todos</span></tt> (config value names should start
with the extension name, in order to stay unique) that controls whether todo
entries make it into the output.</li>
<li>New event handlers: one for the <a class="reference internal" href="appapi.html#event-doctree-resolved"><tt class="xref std std-event docutils literal"><span class="pre">doctree-resolved</span></tt></a> event, to replace
the todo and todolist nodes, and one for <a class="reference internal" href="appapi.html#event-env-purge-doc"><tt class="xref std std-event docutils literal"><span class="pre">env-purge-doc</span></tt></a> (the reason
for that will be covered later).</li>
</ul>
</div>
<div class="section" id="the-setup-function">
<h2>The Setup Function<a class="headerlink" href="#the-setup-function" title="Permalink to this headline">¶</a></h2>
<p>The new elements are added in the extension&#8217;s setup function.  Let us create a
new Python module called <tt class="file docutils literal"><span class="pre">todo.py</span></tt> and add the setup function:</p>
<div class="highlight-python"><pre>def setup(app):
    app.add_config_value('todo_include_todos', False, False)

    app.add_node(todolist)
    app.add_node(todo,
                 html=(visit_todo_node, depart_todo_node),
                 latex=(visit_todo_node, depart_todo_node),
                 text=(visit_todo_node, depart_todo_node))

    app.add_directive('todo', TodoDirective)
    app.add_directive('todolist', TodolistDirective)
    app.connect('doctree-resolved', process_todo_nodes)
    app.connect('env-purge-doc', purge_todos)</pre>
</div>
<p>The calls in this function refer to classes and functions not yet written.  What
the individual calls do is the following:</p>
<ul>
<li><p class="first"><a class="reference internal" href="appapi.html#sphinx.application.Sphinx.add_config_value" title="sphinx.application.Sphinx.add_config_value"><tt class="xref py py-meth docutils literal"><span class="pre">add_config_value()</span></tt></a> lets Sphinx know that it should recognize the
new <em>config value</em> <tt class="docutils literal"><span class="pre">todo_include_todos</span></tt>, whose default value should be
<tt class="xref docutils literal"><span class="pre">False</span></tt> (this also tells Sphinx that it is a boolean value).</p>
<p>If the third argument was <tt class="xref docutils literal"><span class="pre">True</span></tt>, all documents would be re-read if the
config value changed its value.  This is needed for config values that
influence reading (build phase 1).</p>
</li>
<li><p class="first"><a class="reference internal" href="appapi.html#sphinx.application.Sphinx.add_node" title="sphinx.application.Sphinx.add_node"><tt class="xref py py-meth docutils literal"><span class="pre">add_node()</span></tt></a> adds a new <em>node class</em> to the build system.  It also
can specify visitor functions for each supported output format.  These visitor
functions are needed when the new nodes stay until phase 4 &#8211; since the
<tt class="docutils literal"><span class="pre">todolist</span></tt> node is always replaced in phase 3, it doesn&#8217;t need any.</p>
<p>We need to create the two node classes <tt class="docutils literal"><span class="pre">todo</span></tt> and <tt class="docutils literal"><span class="pre">todolist</span></tt> later.</p>
</li>
<li><p class="first"><a class="reference internal" href="appapi.html#sphinx.application.Sphinx.add_directive" title="sphinx.application.Sphinx.add_directive"><tt class="xref py py-meth docutils literal"><span class="pre">add_directive()</span></tt></a> adds a new <em>directive</em>, given by name and class.</p>
<p>The handler functions are created later.</p>
</li>
<li><p class="first">Finally, <a class="reference internal" href="appapi.html#sphinx.application.Sphinx.connect" title="sphinx.application.Sphinx.connect"><tt class="xref py py-meth docutils literal"><span class="pre">connect()</span></tt></a> adds an <em>event handler</em> to the event whose
name is given by the first argument.  The event handler function is called
with several arguments which are documented with the event.</p>
</li>
</ul>
</div>
<div class="section" id="the-node-classes">
<h2>The Node Classes<a class="headerlink" href="#the-node-classes" title="Permalink to this headline">¶</a></h2>
<p>Let&#8217;s start with the node classes:</p>
<div class="highlight-python"><pre>from docutils import nodes

class todo(nodes.Admonition, nodes.Element):
    pass

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

def visit_todo_node(self, node):
    self.visit_admonition(node)

def depart_todo_node(self, node):
    self.depart_admonition(node)</pre>
</div>
<p>Node classes usually don&#8217;t have to do anything except inherit from the standard
docutils classes defined in <tt class="xref py py-mod docutils literal"><span class="pre">docutils.nodes</span></tt>.  <tt class="docutils literal"><span class="pre">todo</span></tt> inherits from
<tt class="docutils literal"><span class="pre">Admonition</span></tt> because it should be handled like a note or warning, <tt class="docutils literal"><span class="pre">todolist</span></tt>
is just a &#8220;general&#8221; node.</p>
</div>
<div class="section" id="the-directive-classes">
<h2>The Directive Classes<a class="headerlink" href="#the-directive-classes" title="Permalink to this headline">¶</a></h2>
<p>A directive class is a class deriving usually from
<tt class="docutils literal"><span class="pre">docutils.parsers.rst.Directive</span></tt>.  Since the class-based directive interface
doesn&#8217;t exist yet in Docutils 0.4, Sphinx has another base class called
<tt class="docutils literal"><span class="pre">sphinx.util.compat.Directive</span></tt> that you can derive your directive from, and it
will work with both Docutils 0.4 and 0.5 upwards.  The directive interface is
covered in detail in the docutils documentation; the important thing is that the
class has a method <tt class="docutils literal"><span class="pre">run</span></tt> that returns a list of nodes.</p>
<p>The <tt class="docutils literal"><span class="pre">todolist</span></tt> directive is quite simple:</p>
<div class="highlight-python"><pre>from sphinx.util.compat import Directive

class TodolistDirective(Directive):

    def run(self):
        return [todolist('')]</pre>
</div>
<p>An instance of our <tt class="docutils literal"><span class="pre">todolist</span></tt> node class is created and returned.  The
todolist directive has neither content nor arguments that need to be handled.</p>
<p>The <tt class="docutils literal"><span class="pre">todo</span></tt> directive function looks like this:</p>
<div class="highlight-python"><pre>from sphinx.util.compat import make_admonition

class TodoDirective(Directive):

    # this enables content in the directive
    has_content = True

    def run(self):
        env = self.state.document.settings.env

        targetid = "todo-%d" % env.new_serialno('todo')
        targetnode = nodes.target('', '', ids=[targetid])

        ad = make_admonition(todo, self.name, [_('Todo')], self.options,
                             self.content, self.lineno, self.content_offset,
                             self.block_text, self.state, self.state_machine)

        if not hasattr(env, 'todo_all_todos'):
            env.todo_all_todos = []
        env.todo_all_todos.append({
            'docname': env.docname,
            'lineno': self.lineno,
            'todo': ad[0].deepcopy(),
            'target': targetnode,
        })

        return [targetnode] + ad</pre>
</div>
<p>Several important things are covered here. First, as you can see, you can refer
to the build environment instance using <tt class="docutils literal"><span class="pre">self.state.document.settings.env</span></tt>.</p>
<p>Then, to act as a link target (from the todolist), the todo directive needs to
return a target node in addition to the todo node.  The target ID (in HTML, this
will be the anchor name) is generated by using <tt class="docutils literal"><span class="pre">env.new_serialno</span></tt> which is
returns a new integer directive on each call and therefore leads to unique
target names.  The target node is instantiated without any text (the first two
arguments).</p>
<p>An admonition is created using a standard docutils function (wrapped in Sphinx
for docutils cross-version compatibility).  The first argument gives the node
class, in our case <tt class="docutils literal"><span class="pre">todo</span></tt>.  The third argument gives the admonition title (use
<tt class="docutils literal"><span class="pre">arguments</span></tt> here to let the user specify the title).  A list of nodes is
returned from <tt class="docutils literal"><span class="pre">make_admonition</span></tt>.</p>
<p>Then, the todo node is added to the environment.  This is needed to be able to
create a list of all todo entries throughout the documentation, in the place
where the author puts a <tt class="docutils literal"><span class="pre">todolist</span></tt> directive.  For this case, the environment
attribute <tt class="docutils literal"><span class="pre">todo_all_todos</span></tt> is used (again, the name should be unique, so it is
prefixed by the extension name).  It does not exist when a new environment is
created, so the directive must check and create it if necessary.  Various
information about the todo entry&#8217;s location are stored along with a copy of the
node.</p>
<p>In the last line, the nodes that should be put into the doctree are returned:
the target node and the admonition node.</p>
<p>The node structure that the directive returns looks like this:</p>
<div class="highlight-python"><pre>+--------------------+
| target node        |
+--------------------+
+--------------------+
| todo node          |
+--------------------+
  \__+--------------------+
     | admonition title   |
     +--------------------+
     | paragraph          |
     +--------------------+
     | ...                |
     +--------------------+</pre>
</div>
</div>
<div class="section" id="the-event-handlers">
<h2>The Event Handlers<a class="headerlink" href="#the-event-handlers" title="Permalink to this headline">¶</a></h2>
<p>Finally, let&#8217;s look at the event handlers.  First, the one for the
<a class="reference internal" href="appapi.html#event-env-purge-doc"><tt class="xref std std-event docutils literal"><span class="pre">env-purge-doc</span></tt></a> event:</p>
<div class="highlight-python"><pre>def purge_todos(app, env, docname):
    if not hasattr(env, 'todo_all_todos'):
        return
    env.todo_all_todos = [todo for todo in env.todo_all_todos
                          if todo['docname'] != docname]</pre>
</div>
<p>Since we store information from source files in the environment, which is
persistent, it may become out of date when the source file changes.  Therefore,
before each source file is read, the environment&#8217;s records of it are cleared,
and the <a class="reference internal" href="appapi.html#event-env-purge-doc"><tt class="xref std std-event docutils literal"><span class="pre">env-purge-doc</span></tt></a> event gives extensions a chance to do the same.
Here we clear out all todos whose docname matches the given one from the
<tt class="docutils literal"><span class="pre">todo_all_todos</span></tt> list.  If there are todos left in the document, they will be
added again during parsing.</p>
<p>The other handler belongs to the <a class="reference internal" href="appapi.html#event-doctree-resolved"><tt class="xref std std-event docutils literal"><span class="pre">doctree-resolved</span></tt></a> event.  This event is
emitted at the end of phase 3 and allows custom resolving to be done:</p>
<div class="highlight-python"><pre>def process_todo_nodes(app, doctree, fromdocname):
    if not app.config.todo_include_todos:
        for node in doctree.traverse(todo):
            node.parent.remove(node)

    # Replace all todolist nodes with a list of the collected todos.
    # Augment each todo with a backlink to the original location.
    env = app.builder.env

    for node in doctree.traverse(todolist):
        if not app.config.todo_include_todos:
            node.replace_self([])
            continue

        content = []

        for todo_info in env.todo_all_todos:
            para = nodes.paragraph()
            filename = env.doc2path(todo_info['docname'], base=None)
            description = (
                _('(The original entry is located in %s, line %d and can be found ') %
                (filename, todo_info['lineno']))
            para += nodes.Text(description, description)

            # Create a reference
            newnode = nodes.reference('', '')
            innernode = nodes.emphasis(_('here'), _('here'))
            newnode['refdocname'] = todo_info['docname']
            newnode['refuri'] = app.builder.get_relative_uri(
                fromdocname, todo_info['docname'])
            newnode['refuri'] += '#' + todo_info['target']['refid']
            newnode.append(innernode)
            para += newnode
            para += nodes.Text('.)', '.)')

            # Insert into the todolist
            content.append(todo_info['todo'])
            content.append(para)

        node.replace_self(content)</pre>
</div>
<p>It is a bit more involved.  If our new &#8220;todo_include_todos&#8221; config value is
false, all todo and todolist nodes are removed from the documents.</p>
<p>If not, todo nodes just stay where and how they are.  Todolist nodes are
replaced by a list of todo entries, complete with backlinks to the location
where they come from.  The list items are composed of the nodes from the todo
entry and docutils nodes created on the fly: a paragraph for each entry,
containing text that gives the location, and a link (reference node containing
an italic node) with the backreference.  The reference URI is built by
<tt class="docutils literal"><span class="pre">app.builder.get_relative_uri</span></tt> which creates a suitable URI depending on the
used builder, and appending the todo node&#8217;s (the target&#8217;s) ID as the anchor
name.</p>
</div>
</div>


          </div>
        </div>
      </div>
      <div class="clearer"></div>
    </div>
    <div class="related">
      <h3>Navigation</h3>
      <ul>
        <li class="right" style="margin-right: 10px">
          <a href="../genindex.html" title="General Index"
             >index</a></li>
        <li class="right" >
          <a href="../py-modindex.html" title="Python Module Index"
             >modules</a> |</li>
        <li class="right" >
          <a href="appapi.html" title="Extension API"
             >next</a> |</li>
        <li class="right" >
          <a href="../extensions.html" title="Sphinx Extensions"
             >previous</a> |</li>
        <li><a href="../index.html">Sphinx home</a>&nbsp;|&nbsp;</li>
        <li><a href="../contents.html">Documentation</a>
          &raquo;</li>

          <li><a href="../extensions.html" >Sphinx Extensions</a> &raquo;</li> 
      </ul>
    </div>
    <div class="footer">
        &copy; Copyright 2007-2011, Georg Brandl.
      Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.7.
    </div>
  </body>
</html>