Commits

Anonymous committed 62cda7e Merge

Comments (0)

Files changed (23)

 .*\.egg
 .*\.so
 .dir-locals.el
+^\.tox
 \.DS_Store$
 ^build/
 ^dist/
 e2723a4a64f07ee10ec24fef27f8bf79bb3d5b3b 0.6.7
 80dc1a872e49556dce681fdb2ea4be54ad847e6c 1.0b1
 b494009dccf19d0df894a4ce50754e866a099808 1.0b2
+dc883adf6424f1265e6b6d3c95c23e7c562e1206 1.0
-Release 1.0 (in development)
+Release 1.1 (in development)
 ============================
 
+
+Release 1.0.1 (in development)
+==============================
+
+* Fix building with the JSON builder.
+
+* Fix hyperrefs in object descriptions for LaTeX.
+
+
+Release 1.0 (Jul 23, 2010)
+==========================
+
 Incompatible changes
 --------------------
 
   - Added :confval:`html_show_copyright` config value.
   - Added :confval:`latex_show_pagerefs` and :confval:`latex_show_urls`
     config values.
+  - The behavior of :confval:`html_file_suffix` changed slightly: the
+    empty string now means "no suffix" instead of "default suffix", use
+    ``None`` for "default suffix".
 
 * New builders:
 
 check:
 	@$(PYTHON) utils/check_sources.py -i build -i dist -i sphinx/style/jquery.js \
 		-i sphinx/pycode/pgen2 -i sphinx/util/smartypants.py -i .ropeproject \
-		-i doc/_build -i ez_setup.py -i tests/path.py -i tests/coverage.py -i env .
+		-i doc/_build -i ez_setup.py -i tests/path.py -i tests/coverage.py \
+		-i env -i .tox .
 
 clean: clean-pyc clean-patchfiles
 
 
 help:
 	@echo "Please use \`make <target>' where <target> is one of"
-	@echo "  html      to make standalone HTML files"
-	@echo "  dirhtml   to make HTML files called index.html in directories"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files called index.html in directories"
 	@echo "  singlehtml to make one big HTML file"
-	@echo "  text      to make text files"
-	@echo "  man       to make manual pages"
-	@echo "  pickle    to make pickle files"
-	@echo "  json      to make json files"
-	@echo "  htmlhelp  to make HTML files and a HTML help project"
-	@echo "  qthelp    to make Qt help files and project"
-	@echo "  devhelp   to make Devhelp files and project"
-	@echo "  epub      to make an epub file"
-	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
-	@echo "  latexpdf  to make LaTeX files and run pdflatex"
-	@echo "  changes   to make an overview over all changed/added/deprecated items"
-	@echo "  linkcheck to check all external links for integrity"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make json files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make Qt help files and project"
+	@echo "  devhelp    to make Devhelp files and project"
+	@echo "  epub       to make an epub file"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run pdflatex"
+	@echo "  changes    to make an overview over all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
 
 clean:
 	-rm -rf _build/*
 
 .. confval:: html_file_suffix
 
-   If nonempty, this is the file name suffix for generated HTML files.  The
-   default is ``".html"``.
+   This is the file name suffix for generated HTML files.  The default is
+   ``".html"``.
 
    .. versionadded:: 0.4
 
         'License :: OSI Approved :: BSD License',
         'Operating System :: OS Independent',
         'Programming Language :: Python',
+        'Programming Language :: Python :: 2',
         'Topic :: Documentation',
         'Topic :: Text Processing',
         'Topic :: Utilities',

sphinx/__init__.py

 import sys
 from os import path
 
-__version__ = '1.0b2+'
-__released__ = '1.0b2'  # used when Sphinx builds its own docs
+__version__ = '1.1pre'
+__released__ = '1.1 (hg)'  # used when Sphinx builds its own docs
 
 package_dir = path.abspath(path.dirname(__file__))
 

sphinx/builders/epub.py

 """
 
 import os
+import re
 import codecs
+import zipfile
 from os import path
-import zipfile
-import re
 
 from docutils import nodes
 
+from sphinx import addnodes
 from sphinx.builders.html import StandaloneHTMLBuilder
 from sphinx.util.osutil import EEXIST
 from sphinx.util.smartypants import sphinx_smarty_pants as ssp
-from sphinx import addnodes
 
 
 # (Fragment) templates from which the metainfo files content.opf, toc.ncx,
         """
         for node in tree.traverse(nodes.reference):
             uri = node.get('refuri', '')
-            if ( uri.startswith('http:') or uri.startswith('https:') or \
-                    uri.startswith('ftp:') ) and uri not in node.astext():
+            if (uri.startswith('http:') or uri.startswith('https:') or
+                    uri.startswith('ftp:')) and uri not in node.astext():
                 uri = _link_target_template % {'uri': uri}
                 if uri:
                     idx = node.parent.index(node) + 1

sphinx/builders/html.py

 from docutils.readers.doctree import Reader as DoctreeReader
 
 from sphinx import package_dir, __version__
-from sphinx.util import copy_static_entry
+from sphinx.util import jsonimpl, copy_static_entry
 from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \
      movefile, ustrftime, copyfile
 from sphinx.util.nodes import inline_all_toctrees
 from sphinx.writers.html import HTMLWriter, HTMLTranslator, \
      SmartyPantsHTMLTranslator
 
-try:
-    import json
-except ImportError:
-    try:
-        import simplejson as json
-    except ImportError:
-        json = None
-
 #: the filename for the inventory of objects
 INVENTORY_FILENAME = 'objects.inv'
 #: the filename for the "last build" file (for serializing builders)
         self.init_templates()
         self.init_highlighter()
         self.init_translator_class()
-        if self.config.html_file_suffix:
+        if self.config.html_file_suffix is not None:
             self.out_suffix = self.config.html_file_suffix
 
         if self.config.html_link_suffix is not None:
     """
     A builder that dumps the generated HTML into JSON files.
     """
-    implementation = json
-    indexer_format = json
+    implementation = jsonimpl
+    indexer_format = jsonimpl
     name = 'json'
     out_suffix = '.fjson'
     globalcontext_filename = 'globalcontext.json'
     searchindex_filename = 'searchindex.json'
 
     def init(self):
-        if json is None:
+        if jsonimpl.json is None:
             raise SphinxError(
                 'The module simplejson (or json in Python >= 2.6) '
                 'is not available. The JSONHTMLBuilder builder will not work.')

sphinx/directives/code.py

             rel_fn = filename[1:]
         else:
             docdir = path.dirname(env.doc2path(env.docname, base=None))
-            rel_fn = path.normpath(path.join(docdir, filename))
+            rel_fn = path.join(docdir, filename)
         try:
             fn = path.join(env.srcdir, rel_fn)
         except UnicodeDecodeError:

sphinx/directives/other.py

         else:
             ret = [node]
         env = self.state.document.settings.env
-        env.versionchanges.setdefault(node['version'], []).append(
-            (node['type'], env.temp_data['docname'], self.lineno,
-             # XXX: python domain specific
-             env.temp_data.get('py:module'),
-             env.temp_data.get('object'),
-             node.astext()))
+        env.note_versionchange(node['type'], node['version'], node, self.lineno)
         return ret
 
 

sphinx/environment.py

     def note_dependency(self, filename):
         self.dependencies.setdefault(self.docname, set()).add(filename)
 
+    def note_versionchange(self, type, version, node, lineno):
+        self.versionchanges.setdefault(version, []).append(
+            (type, self.temp_data['docname'], lineno,
+             self.temp_data.get('py:module'),
+             self.temp_data.get('object'), node.astext()))
+
     # post-processing of read doctrees
 
     def filter_messages(self, doctree):

sphinx/ext/oldcmarkup.py

     app.add_role('cfunc', old_crole)
     app.add_role('cmacro', old_crole)
     app.add_role('ctype', old_crole)
+    app.add_role('cmember', old_crole)

sphinx/quickstart.py

 # base URL from which the finished HTML is served.
 #html_use_opensearch = ''
 
-# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = ''
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
 
 # Output file base name for HTML help builder.
 htmlhelp_basename = '%(project_fn)sdoc'
 _amp_re = re.compile(r'(?<!&)&(?![&\s])')
 
 def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
+    text = utils.unescape(text)
     if typ == 'menuselection':
-        text = utils.unescape(text).replace('-->', u'\N{TRIANGULAR BULLET}')
+        text = text.replace('-->', u'\N{TRIANGULAR BULLET}')
     spans = _amp_re.split(text)
 
     node = nodes.emphasis(rawtext=rawtext)

sphinx/util/docfields.py

             if is_typefield:
                 # filter out only inline nodes; others will result in invalid
                 # markup being written out
-                content = filter(lambda n: isinstance(n, nodes.Inline), content)
+                content = filter(
+                    lambda n: isinstance(n, nodes.Inline) or
+                              isinstance(n, nodes.Text),
+                    content)
                 if content:
                     types.setdefault(typename, {})[fieldarg] = content
                 continue

sphinx/util/jsonimpl.py

+# -*- coding: utf-8 -*-
+"""
+    sphinx.util.jsonimpl
+    ~~~~~~~~~~~~~~~~~~~~
+
+    JSON serializer implementation wrapper.
+
+    :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+import UserString
+
+try:
+    import json
+    JSONEncoder = json.JSONEncoder
+except ImportError:
+    try:
+        import simplejson as json
+        JSONEncoder = json.JSONEncoder
+    except ImportError:
+        json = None
+        JSONEncoder = object
+
+
+class SphinxJSONEncoder(JSONEncoder):
+    """JSONEncoder subclass that forces translation proxies."""
+    def default(self, obj):
+        if isinstance(obj, UserString.UserString):
+            return unicode(obj)
+        return JSONEncoder.default(self, obj)
+
+
+def dump(obj, fp, *args, **kwds):
+    kwds['cls'] = SphinxJSONEncoder
+    return json.dump(obj, fp, *args, **kwds)
+
+def dumps(obj, *args, **kwds):
+    kwds['cls'] = SphinxJSONEncoder
+    return json.dumps(obj, *args, **kwds)
+
+load = json.load
+loads = json.loads

sphinx/writers/latex.py

                '\\label{%s}' % self.idescape(id)
 
     def hyperlink(self, id):
-        return '\\hyperref[%s]{' % (self.idescape(id))
+        return '{\\hyperref[%s]{' % (self.idescape(id))
 
     def hyperpageref(self, id):
         return '\\autopageref*{%s}' % (self.idescape(id))
         def add_target(id):
             # indexing uses standard LaTeX index markup, so the targets
             # will be generated differently
-            if not id.startswith('index-'):
-                self.body.append(self.hypertarget(id))
+            if id.startswith('index-'):
+                return
+            # do not generate \phantomsection in \section{}
+            anchor = not self.in_title
+            self.body.append(self.hypertarget(id, anchor=anchor))
 
         # postpone the labels until after the sectioning command
         parindex = node.parent.index(node)
             id = self.curfilestack[-1] + ':' + uri[1:]
             self.body.append(self.hyperlink(id))
             if self.builder.config.latex_show_pagerefs:
-                self.context.append('} (%s)' % self.hyperpageref(id))
+                self.context.append('}} (%s)' % self.hyperpageref(id))
             else:
-                self.context.append('}')
+                self.context.append('}}')
         elif uri.startswith('%'):
             # references to documents or labels inside documents
             hashindex = uri.find('#')
             if len(node) and hasattr(node[0], 'attributes') and \
                    'std-term' in node[0].get('classes', []):
                 # don't add a pageref for glossary terms
-                self.context.append('}')
+                self.context.append('}}')
             else:
                 if self.builder.config.latex_show_pagerefs:
-                    self.context.append('} (%s)' % self.hyperpageref(id))
+                    self.context.append('}} (%s)' % self.hyperpageref(id))
                 else:
-                    self.context.append('}')
+                    self.context.append('}}')
         elif uri.startswith('@token'):
             if self.in_production_list:
                 self.body.append('\\token{')

tests/root/markup.txt

 
 *Generic inline markup*
 
-* :command:`command`
-* :dfn:`dfn`
-* :guilabel:`guilabel with &accelerator`
-* :kbd:`kbd`
-* :mailheader:`mailheader`
-* :makevar:`makevar`
-* :manpage:`manpage`
-* :mimetype:`mimetype`
-* :newsgroup:`newsgroup`
-* :program:`program`
-* :regexp:`regexp`
-* :menuselection:`File --> Close`
+Adding \n to test unescaping.
+
+* :command:`command\\n`
+* :dfn:`dfn\\n`
+* :guilabel:`guilabel with &accelerator and \\n`
+* :kbd:`kbd\\n`
+* :mailheader:`mailheader\\n`
+* :makevar:`makevar\\n`
+* :manpage:`manpage\\n`
+* :mimetype:`mimetype\\n`
+* :newsgroup:`newsgroup\\n`
+* :program:`program\\n`
+* :regexp:`regexp\\n`
+* :menuselection:`File --> Close\\n`
 * :menuselection:`&File --> &Print`
-* :file:`a/{varpart}/b`
-* :samp:`print {i}`
+* :file:`a/{varpart}/b\\n`
+* :samp:`print {i}\\n`
 
 *Linking inline markup*
 

tests/test_build.py

 def test_pickle(app):
     app.builder.build_all()
 
+@with_app(buildername='json')
+def test_json(app):
+    app.builder.build_all()
+
 @with_app(buildername='linkcheck')
 def test_linkcheck(app):
     app.builder.build_all()

tests/test_build_html.py

 """
 
 HTML_XPATH = {
-    'images.html': {
-        ".//img[@src='_images/img.png']": '',
-        ".//img[@src='_images/img1.png']": '',
-        ".//img[@src='_images/simg.png']": '',
-        ".//object[@data='_images/svgimg.svg']": '',
-        ".//embed[@src='_images/svgimg.svg']": '',
-    },
-    'subdir/images.html': {
-        ".//img[@src='../_images/img1.png']": '',
-        ".//img[@src='../_images/rimg.png']": '',
-    },
-    'subdir/includes.html': {
-        ".//a[@href='../_downloads/img.png']": '',
-        ".//img[@src='../_images/img.png']": '',
-        ".//p": 'This is an include file.',
-    },
-    'includes.html': {
-        ".//pre": u'Max Strauß',
-        ".//a[@href='_downloads/img.png']": '',
-        ".//a[@href='_downloads/img1.png']": '',
-        ".//pre": u'"quotes"',
-        ".//pre": u"'included'",
-    },
-    'autodoc.html': {
-        ".//dt[@id='test_autodoc.Class']": '',
-        ".//dt[@id='test_autodoc.function']/em": r'\*\*kwds',
-        ".//dd/p": r'Return spam\.',
-    },
-    'extapi.html': {
-        ".//strong": 'from function: Foo',
-        ".//strong": 'from class: Bar',
-    },
-    'markup.html': {
-        ".//title": 'set by title directive',
-        ".//p/em": 'Section author: Georg Brandl',
-        ".//p/em": 'Module author: Georg Brandl',
+    'images.html': [
+        (".//img[@src='_images/img.png']", ''),
+        (".//img[@src='_images/img1.png']", ''),
+        (".//img[@src='_images/simg.png']", ''),
+        (".//object[@data='_images/svgimg.svg']", ''),
+        (".//embed[@src='_images/svgimg.svg']", ''),
+    ],
+    'subdir/images.html': [
+        (".//img[@src='../_images/img1.png']", ''),
+        (".//img[@src='../_images/rimg.png']", ''),
+    ],
+    'subdir/includes.html': [
+        (".//a[@href='../_downloads/img.png']", ''),
+        (".//img[@src='../_images/img.png']", ''),
+        (".//p", 'This is an include file.'),
+    ],
+    'includes.html': [
+        (".//pre", u'Max Strauß'),
+        (".//a[@href='_downloads/img.png']", ''),
+        (".//a[@href='_downloads/img1.png']", ''),
+        (".//pre", u'"quotes"'),
+        (".//pre", u"'included'"),
+    ],
+    'autodoc.html': [
+        (".//dt[@id='test_autodoc.Class']", ''),
+        (".//dt[@id='test_autodoc.function']/em", r'\*\*kwds'),
+        (".//dd/p", r'Return spam\.'),
+    ],
+    'extapi.html': [
+        (".//strong", 'from function: Foo'),
+        (".//strong", 'from class: Bar'),
+    ],
+    'markup.html': [
+        (".//title", 'set by title directive'),
+        (".//p/em", 'Section author: Georg Brandl'),
+        (".//p/em", 'Module author: Georg Brandl'),
         # created by the meta directive
-        ".//meta[@name='author'][@content='Me']": '',
-        ".//meta[@name='keywords'][@content='docs, sphinx']": '',
+        (".//meta[@name='author'][@content='Me']", ''),
+        (".//meta[@name='keywords'][@content='docs, sphinx']", ''),
         # a label created by ``.. _label:``
-        ".//div[@id='label']": '',
+        (".//div[@id='label']", ''),
         # code with standard code blocks
-        ".//pre": '^some code$',
+        (".//pre", '^some code$'),
         # an option list
-        ".//span[@class='option']": '--help',
+        (".//span[@class='option']", '--help'),
         # admonitions
-        ".//p[@class='first admonition-title']": 'My Admonition',
-        ".//p[@class='last']": 'Note text.',
-        ".//p[@class='last']": 'Warning text.',
+        (".//p[@class='first admonition-title']", 'My Admonition'),
+        (".//p[@class='last']", 'Note text.'),
+        (".//p[@class='last']", 'Warning text.'),
         # inline markup
-        ".//li/strong": '^command$',
-        ".//li/strong": '^program$',
-        ".//li/em": '^dfn$',
-        ".//li/tt/span[@class='pre']": '^kbd$',
-        ".//li/em": u'File \N{TRIANGULAR BULLET} Close',
-        ".//li/tt/span[@class='pre']": '^a/$',
-        ".//li/tt/em/span[@class='pre']": '^varpart$',
-        ".//li/tt/em/span[@class='pre']": '^i$',
-        ".//a[@href='http://www.python.org/dev/peps/pep-0008']"
-            "[@class='pep reference external']/strong": 'PEP 8',
-        ".//a[@href='http://tools.ietf.org/html/rfc1.html']"
-            "[@class='rfc reference external']/strong": 'RFC 1',
-        ".//a[@href='objects.html#envvar-HOME']"
-            "[@class='reference internal']/tt/span[@class='pre']": 'HOME',
-        ".//a[@href='#with']"
-            "[@class='reference internal']/tt/span[@class='pre']": '^with$',
-        ".//a[@href='#grammar-token-try_stmt']"
-            "[@class='reference internal']/tt/span": '^statement$',
-        ".//a[@href='subdir/includes.html']"
-            "[@class='reference internal']/em": 'Including in subdir',
-        ".//a[@href='objects.html#cmdoption-python-c']"
-            "[@class='reference internal']/em": 'Python -c option',
+        (".//li/strong", r'^command\\n$'),
+        (".//li/strong", r'^program\\n$'),
+        (".//li/em", r'^dfn\\n$'),
+        (".//li/tt/span[@class='pre']", r'^kbd\\n$'),
+        (".//li/em", u'File \N{TRIANGULAR BULLET} Close'),
+        (".//li/tt/span[@class='pre']", '^a/$'),
+        (".//li/tt/em/span[@class='pre']", '^varpart$'),
+        (".//li/tt/em/span[@class='pre']", '^i$'),
+        (".//a[@href='http://www.python.org/dev/peps/pep-0008']"
+            "[@class='pep reference external']/strong", 'PEP 8'),
+        (".//a[@href='http://tools.ietf.org/html/rfc1.html']"
+            "[@class='rfc reference external']/strong", 'RFC 1'),
+        (".//a[@href='objects.html#envvar-HOME']"
+            "[@class='reference internal']/tt/span[@class='pre']", 'HOME'),
+        (".//a[@href='#with']"
+            "[@class='reference internal']/tt/span[@class='pre']", '^with$'),
+        (".//a[@href='#grammar-token-try_stmt']"
+            "[@class='reference internal']/tt/span", '^statement$'),
+        (".//a[@href='subdir/includes.html']"
+            "[@class='reference internal']/em", 'Including in subdir'),
+        (".//a[@href='objects.html#cmdoption-python-c']"
+            "[@class='reference internal']/em", 'Python -c option'),
         # abbreviations
-        ".//abbr[@title='abbreviation']": '^abbr$',
+        (".//abbr[@title='abbreviation']", '^abbr$'),
         # version stuff
-        ".//span[@class='versionmodified']": 'New in version 0.6',
+        (".//span[@class='versionmodified']", 'New in version 0.6'),
         # footnote reference
-        ".//a[@class='footnote-reference']": r'\[1\]',
+        (".//a[@class='footnote-reference']", r'\[1\]'),
         # created by reference lookup
-        ".//a[@href='contents.html#ref1']": '',
+        (".//a[@href='contents.html#ref1']", ''),
         # ``seealso`` directive
-        ".//div/p[@class='first admonition-title']": 'See also',
+        (".//div/p[@class='first admonition-title']", 'See also'),
         # a ``hlist`` directive
-        ".//table[@class='hlist']/tr/td/ul/li": '^This$',
+        (".//table[@class='hlist']/tr/td/ul/li", '^This$'),
         # a ``centered`` directive
-        ".//p[@class='centered']/strong": 'LICENSE',
+        (".//p[@class='centered']/strong", 'LICENSE'),
         # a glossary
-        ".//dl/dt[@id='term-boson']": 'boson',
+        (".//dl/dt[@id='term-boson']", 'boson'),
         # a production list
-        ".//pre/strong": 'try_stmt',
-        ".//pre/a[@href='#grammar-token-try1_stmt']/tt/span": 'try1_stmt',
+        (".//pre/strong", 'try_stmt'),
+        (".//pre/a[@href='#grammar-token-try1_stmt']/tt/span", 'try1_stmt'),
         # tests for ``only`` directive
-        ".//p": 'A global substitution.',
-        ".//p": 'In HTML.',
-        ".//p": 'In both.',
-        ".//p": 'Always present',
-    },
-    'objects.html': {
-        ".//dt[@id='mod.Cls.meth1']": '',
-        ".//dt[@id='errmod.Error']": '',
-        ".//a[@href='#mod.Cls'][@class='reference internal']": '',
-        ".//dl[@class='userdesc']": '',
-        ".//dt[@id='userdesc-myobj']": '',
-        ".//a[@href='#userdesc-myobj']": '',
+        (".//p", 'A global substitution.'),
+        (".//p", 'In HTML.'),
+        (".//p", 'In both.'),
+        (".//p", 'Always present'),
+    ],
+    'objects.html': [
+        (".//dt[@id='mod.Cls.meth1']", ''),
+        (".//dt[@id='errmod.Error']", ''),
+        (".//a[@href='#mod.Cls'][@class='reference internal']", ''),
+        (".//dl[@class='userdesc']", ''),
+        (".//dt[@id='userdesc-myobj']", ''),
+        (".//a[@href='#userdesc-myobj']", ''),
         # C references
-        ".//span[@class='pre']": 'CFunction()',
-        ".//a[@href='#Sphinx_DoSomething']": '',
-        ".//a[@href='#SphinxStruct.member']": '',
-        ".//a[@href='#SPHINX_USE_PYTHON']": '',
-        ".//a[@href='#SphinxType']": '',
-        ".//a[@href='#sphinx_global']": '',
+        (".//span[@class='pre']", 'CFunction()'),
+        (".//a[@href='#Sphinx_DoSomething']", ''),
+        (".//a[@href='#SphinxStruct.member']", ''),
+        (".//a[@href='#SPHINX_USE_PYTHON']", ''),
+        (".//a[@href='#SphinxType']", ''),
+        (".//a[@href='#sphinx_global']", ''),
         # reference from old C markup extension
-        ".//a[@href='#Sphinx_Func']": '',
+        (".//a[@href='#Sphinx_Func']", ''),
         # test global TOC created by toctree()
-        ".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='']":
-            'Testing object descriptions',
-        ".//li[@class='toctree-l1']/a[@href='markup.html']":
-            'Testing various markup',
+        (".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='']",
+            'Testing object descriptions'),
+        (".//li[@class='toctree-l1']/a[@href='markup.html']",
+            'Testing various markup'),
         # custom sidebar
-        ".//h4": 'Custom sidebar',
-    },
-    'contents.html': {
-        ".//meta[@name='hc'][@content='hcval']": '',
-        ".//meta[@name='hc_co'][@content='hcval_co']": '',
-        ".//meta[@name='testopt'][@content='testoverride']": '',
-        ".//td[@class='label']": r'\[Ref1\]',
-        ".//td[@class='label']": '',
-        ".//li[@class='toctree-l1']/a": 'Testing various markup',
-        ".//li[@class='toctree-l2']/a": 'Inline markup',
-        ".//title": 'Sphinx <Tests>',
-        ".//div[@class='footer']": 'Georg Brandl & Team',
-        ".//a[@href='http://python.org/']"
-            "[@class='reference external']": '',
-        ".//li/a[@href='genindex.html']/em": 'Index',
-        ".//li/a[@href='py-modindex.html']/em": 'Module Index',
-        ".//li/a[@href='search.html']/em": 'Search Page',
+        (".//h4", 'Custom sidebar'),
+    ],
+    'contents.html': [
+        (".//meta[@name='hc'][@content='hcval']", ''),
+        (".//meta[@name='hc_co'][@content='hcval_co']", ''),
+        (".//meta[@name='testopt'][@content='testoverride']", ''),
+        (".//td[@class='label']", r'\[Ref1\]'),
+        (".//td[@class='label']", ''),
+        (".//li[@class='toctree-l1']/a", 'Testing various markup'),
+        (".//li[@class='toctree-l2']/a", 'Inline markup'),
+        (".//title", 'Sphinx <Tests>'),
+        (".//div[@class='footer']", 'Georg Brandl & Team'),
+        (".//a[@href='http://python.org/']"
+            "[@class='reference external']", ''),
+        (".//li/a[@href='genindex.html']/em", 'Index'),
+        (".//li/a[@href='py-modindex.html']/em", 'Module Index'),
+        (".//li/a[@href='search.html']/em", 'Search Page'),
         # custom sidebar only for contents
-        ".//h4": 'Contents sidebar',
-    },
-    'bom.html': {
-        ".//title": " File with UTF-8 BOM",
-    },
-    'extensions.html': {
-        ".//a[@href='http://python.org/dev/']": "http://python.org/dev/",
-        ".//a[@href='http://bugs.python.org/issue1000']": "issue 1000",
-        ".//a[@href='http://bugs.python.org/issue1042']": "explicit caption",
-    },
-    '_static/statictmpl.html': {
-        ".//project": 'Sphinx <Tests>',
-    },
+        (".//h4", 'Contents sidebar'),
+    ],
+    'bom.html': [
+        (".//title", " File with UTF-8 BOM"),
+    ],
+    'extensions.html': [
+        (".//a[@href='http://python.org/dev/']", "http://python.org/dev/"),
+        (".//a[@href='http://bugs.python.org/issue1000']", "issue 1000"),
+        (".//a[@href='http://bugs.python.org/issue1042']", "explicit caption"),
+    ],
+    '_static/statictmpl.html': [
+        (".//project", 'Sphinx <Tests>'),
+    ],
 }
 
 if pygments:
-    HTML_XPATH['includes.html'].update({
-        ".//pre/span[@class='s']": u'üöä',
-        ".//div[@class='inc-pyobj1 highlight-text']//pre":
-            r'^class Foo:\n    pass\n\s*$',
-        ".//div[@class='inc-pyobj2 highlight-text']//pre":
-            r'^    def baz\(\):\n        pass\n\s*$',
-        ".//div[@class='inc-lines highlight-text']//pre":
-            r'^class Foo:\n    pass\nclass Bar:\n$',
-        ".//div[@class='inc-startend highlight-text']//pre":
-            ur'^foo = u"Including Unicode characters: üöä"\n$',
-        ".//div[@class='inc-preappend highlight-text']//pre":
-            r'(?m)^START CODE$',
-        ".//div[@class='inc-pyobj-dedent highlight-python']//span":
-            r'def',
-        ".//div[@class='inc-tab3 highlight-text']//pre":
-            r'-| |-',
-        ".//div[@class='inc-tab8 highlight-python']//pre":
-            r'-|      |-',
-    })
-    HTML_XPATH['subdir/includes.html'].update({
-        ".//pre/span": 'line 1',
-        ".//pre/span": 'line 2',
-    })
+    HTML_XPATH['includes.html'].extend([
+        (".//pre/span[@class='s']", u'üöä'),
+        (".//div[@class='inc-pyobj1 highlight-text']//pre",
+            r'^class Foo:\n    pass\n\s*$'),
+        (".//div[@class='inc-pyobj2 highlight-text']//pre",
+            r'^    def baz\(\):\n        pass\n\s*$'),
+        (".//div[@class='inc-lines highlight-text']//pre",
+            r'^class Foo:\n    pass\nclass Bar:\n$'),
+        (".//div[@class='inc-startend highlight-text']//pre",
+            ur'^foo = u"Including Unicode characters: üöä"\n$'),
+        (".//div[@class='inc-preappend highlight-text']//pre",
+            r'(?m)^START CODE$'),
+        (".//div[@class='inc-pyobj-dedent highlight-python']//span",
+            r'def'),
+        (".//div[@class='inc-tab3 highlight-text']//pre",
+            r'-| |-'),
+        (".//div[@class='inc-tab8 highlight-python']//pre",
+            r'-|      |-'),
+    ])
+    HTML_XPATH['subdir/includes.html'].extend([
+        (".//pre/span", 'line 1'),
+        (".//pre/span", 'line 2'),
+    ])
 
 class NslessParser(ET.XMLParser):
     """XMLParser that throws away namespaces in tag names."""
         parser = NslessParser()
         parser.entity.update(htmlentitydefs.entitydefs)
         etree = ET.parse(os.path.join(app.outdir, fname), parser)
-        for path, check in paths.iteritems():
+        for path, check in paths:
             yield check_xpath, etree, fname, path, check
 
     check_static_entries(app.builder.outdir)
+[tox]
+envlist=du06,du05
+
+[testenv]
+deps=nose
+commands=
+    nosetests
+    sphinx-build -W -b html -d {envtmpdir}/doctrees doc {envtmpdir}/html
+
+[testenv:du05]
+deps=docutils==0.5
+
+[testenv:du06]
+deps=docutils==0.6