Anonymous avatar Anonymous committed 2db7eac Merge

Merge with birkenfeld/sphinx

Comments (0)

Files changed (23)

 * PR#36: Make the "bibliography to TOC" fix in LaTeX output specific to
   the document class.
 
+* #695: When the highlight language "python" is specified explicitly,
+  do not try to parse the code to recognize non-Python snippets.
+
+* #859: Fix exception under certain circumstances when not finding
+  appropriate objects to link to.
+
+* #860: Do not crash when encountering invalid doctest examples, just
+  emit a warning.
+
+* #864: Fix crash with some settings of :confval:`modindex_common_prefix`.
+
+* #862: Fix handling of ``-D`` and ``-A`` options on Python 3.
+
+* #851: Recognize and warn about circular toctrees, instead of running
+  into recursion errors.
+
+* #853: Restore compatibility with docutils trunk.
+
+* #852: Fix HtmlHelp index entry links again.
+
+* #854: Fix inheritance_diagram raising attribute errors on builtins.
+
+* #832: Fix crashes when putting comments or lone terms in a glossary.
+
+* #834, #818: Fix HTML help language/encoding mapping for all Sphinx
+  supported languages.
+
+* #844: Fix crashes when dealing with Unicode output in doctest extension.
+
+* #831: Provide ``--project`` flag in setup_command as advertised.
+
 
 Release 1.1.2 (Nov 1, 2011) -- 1.1.1 is a silly version number anyway!
 ======================================================================
      the format given in :confval:`today_fmt`.
 
    The default is no :confval:`today` and a :confval:`today_fmt` of ``'%B %d,
-   %Y'`` (or, if translation is enabled with :confval:`language`, am equivalent
+   %Y'`` (or, if translation is enabled with :confval:`language`, an equivalent
    %format for the selected locale).
 
 .. confval:: highlight_language
    theme.  These are theme-specific.  For the options understood by the builtin
    themes, see :ref:`this section <builtin-themes>`.
 
+   .. versionadded:: 1.2
+
 .. confval:: epub_title
 
    The title of the document.  It defaults to the :confval:`html_title` option
    to use this option.  The default value is ``False`` because the automatic
    conversion may lose information.
 
+   .. versionadded:: 1.2
+
 .. confval:: epub_max_image_width
 
    This option specifies the maximum width of images.  If it is set to a value
    value is ``0``.  You need the Python Image Library (PIL) installed to use
    this option.
 
+   .. versionadded:: 1.2
+
 
 .. _latex-options:
 
 standard for plain-text math notation and has the added advantage that no
 further translation is necessary when building LaTeX output.
 
+Keep in mind that when you put math markup in **Python docstrings** read by
+:mod:`autodoc <sphinx.ext.autodoc>`, you either have to double all backslashes,
+or use Python raw strings (``r"raw"``).
+
 :mod:`.mathbase` defines these new markup elements:
 
 .. rst:role:: math

sphinx/builders/changes.py

 
 import codecs
 from os import path
-from cgi import escape
 
 from sphinx import package_dir
 from sphinx.util import copy_static_entry
 from sphinx.builders import Builder
 from sphinx.util.osutil import ensuredir, os_path
 from sphinx.util.console import bold
+from sphinx.util.pycompat import htmlescape
 
 
 class ChangesBuilder(Builder):
                   '.. deprecated:: %s' % version]
 
         def hl(no, line):
-            line = '<a name="L%s"> </a>' % no + escape(line)
+            line = '<a name="L%s"> </a>' % no + htmlescape(line)
             for x in hltext:
                 if x in line:
                     line = '<span class="hl">%s</span>' % line
         self.info(bold('copying source files...'))
         for docname in self.env.all_docs:
             f = codecs.open(self.env.doc2path(docname), 'r', 'latin1')
-            lines = f.readlines()
+            try:
+                lines = f.readlines()
+            finally:
+                f.close()
             targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html'
             ensuredir(path.dirname(targetfn))
             f = codecs.open(targetfn, 'w', 'latin1')
                           self.outdir, self)
 
     def hl(self, text, version):
-        text = escape(text)
+        text = htmlescape(text)
         for directive in ['versionchanged', 'versionadded', 'deprecated']:
             text = text.replace('.. %s:: %s' % (directive, version),
                                 '<b>.. %s:: %s</b>' % (directive, version))

sphinx/builders/htmlhelp.py

 """
 
 import os
-import cgi
 import codecs
 from os import path
 
 
 from sphinx import addnodes
 from sphinx.builders.html import StandaloneHTMLBuilder
+from sphinx.util.pycompat import htmlescape
 
 
 # Project file (*.hhp) template.  'outname' is the file basename (like
 # See http://msdn.microsoft.com/en-us/library/ms930130.aspx for more.
 chm_locales = {
     # lang:   LCID,  encoding
-    'cs':    (0x405, 'iso8859_2'),
-    'de':    (0x407, 'iso8859_1'),
-    'en':    (0x409, 'iso8859_1'),
-    'es':    (0x40a, 'iso8859_1'),
-    'fi':    (0x40b, 'iso8859_1'),
-    'fr':    (0x40c, 'iso8859_1'),
-    'it':    (0x410, 'iso8859_1'),
+    'ca':    (0x403, 'cp1252'),
+    'cs':    (0x405, 'cp1250'),
+    'da':    (0x406, 'cp1252'),
+    'de':    (0x407, 'cp1252'),
+    'en':    (0x409, 'cp1252'),
+    'es':    (0x40a, 'cp1252'),
+    'et':    (0x425, 'cp1257'),
+    'fa':    (0x429, 'cp1256'),
+    'fi':    (0x40b, 'cp1252'),
+    'fr':    (0x40c, 'cp1252'),
+    'hr':    (0x41a, 'cp1250'),
+    'hu':    (0x40e, 'cp1250'),
+    'it':    (0x410, 'cp1252'),
     'ja':    (0x411, 'cp932'),
+    'ko':    (0x412, 'cp949'),
+    'lt':    (0x427, 'cp1257'),
     'lv':    (0x426, 'cp1257'),
-    'nl':    (0x413, 'iso8859_1'),
-    'pl':    (0x415, 'iso8859_2'),
-    'pt_BR': (0x416, 'iso8859_1'),
+    'nl':    (0x413, 'cp1252'),
+    'pl':    (0x415, 'cp1250'),
+    'pt_BR': (0x416, 'cp1252'),
     'ru':    (0x419, 'cp1251'),
-    'sl':    (0x424, 'iso8859_2'),
+    'sk':    (0x41b, 'cp1250'),
+    'sl':    (0x424, 'cp1250'),
+    'sv':    (0x41d, 'cp1252'),
+    'tr':    (0x41f, 'cp1254'),
     'uk_UA': (0x422, 'cp1251'),
     'zh_CN': (0x804, 'cp936'),
     'zh_TW': (0x404, 'cp950'),
                         write_toc(subnode, ullevel)
                 elif isinstance(node, nodes.reference):
                     link = node['refuri']
-                    title = cgi.escape(node.astext()).replace('"','&quot;')
+                    title = htmlescape(node.astext()).replace('"','&quot;')
                     f.write(object_sitemap % (title, link))
                 elif isinstance(node, nodes.bullet_list):
                     if ullevel != 0:
             def write_index(title, refs, subitems):
                 def write_param(name, value):
                     item = '    <param name="%s" value="%s">\n' % \
-                        (name, value[1])
+                        (name, value)
                     f.write(item)
-                title = cgi.escape(title)
+                title = htmlescape(title)
                 f.write('<LI> <OBJECT type="text/sitemap">\n')
                 write_param('Keyword', title)
                 if len(refs) == 0:
                     write_param('See Also', title)
                 elif len(refs) == 1:
-                    write_param('Local', refs[0])
+                    write_param('Local', refs[0][1])
                 else:
                     for i, ref in enumerate(refs):
                         # XXX: better title?
-                        write_param('Name', '[%d] %s' % (i, ref))
-                        write_param('Local', ref)
+                        write_param('Name', '[%d] %s' % (i, ref[1]))
+                        write_param('Local', ref[1])
                 f.write('</OBJECT>\n')
                 if subitems:
                     f.write('<UL> ')

sphinx/builders/qthelp.py

 import codecs
 import posixpath
 from os import path
-from cgi import escape
 
 from docutils import nodes
 
 from sphinx import addnodes
 from sphinx.builders.html import StandaloneHTMLBuilder
+from sphinx.util.pycompat import htmlescape
 
 
 _idpattern = re.compile(
                        fn.endswith('.html'):
                     filename = path.join(root, fn)[olen:]
                     projectfiles.append(file_template %
-                                        {'filename': escape(filename)})
+                                        {'filename': htmlescape(filename)})
         projectfiles = '\n'.join(projectfiles)
 
         # it seems that the "namespace" may not contain non-alphanumeric
         f = codecs.open(path.join(outdir, outname+'.qhp'), 'w', 'utf-8')
         try:
             f.write(project_template % {
-                'outname': escape(outname),
-                'title': escape(self.config.html_title),
-                'version': escape(self.config.version),
-                'project': escape(self.config.project),
-                'namespace': escape(nspace),
-                'masterdoc': escape(self.config.master_doc),
+                'outname': htmlescape(outname),
+                'title': htmlescape(self.config.html_title),
+                'version': htmlescape(self.config.version),
+                'project': htmlescape(self.config.project),
+                'namespace': htmlescape(nspace),
+                'masterdoc': htmlescape(self.config.master_doc),
                 'sections': sections,
                 'keywords': keywords,
                 'files': projectfiles})
         f = codecs.open(path.join(outdir, outname+'.qhcp'), 'w', 'utf-8')
         try:
             f.write(collection_template % {
-                'outname': escape(outname),
-                'title': escape(self.config.html_short_title),
-                'homepage': escape(homepage),
-                'startpage': escape(startpage)})
+                'outname': htmlescape(outname),
+                'title': htmlescape(self.config.html_short_title),
+                'homepage': htmlescape(homepage),
+                'startpage': htmlescape(startpage)})
         finally:
             f.close()
 
         if self.isdocnode(node):
             refnode = node.children[0][0]
             link = refnode['refuri']
-            title = escape(refnode.astext()).replace('"','&quot;')
+            title = htmlescape(refnode.astext()).replace('"','&quot;')
             item = '<section title="%(title)s" ref="%(ref)s">' % {
                                                                 'title': title,
                                                                 'ref': link}
                 parts.extend(self.write_toc(subnode, indentlevel))
         elif isinstance(node, nodes.reference):
             link = node['refuri']
-            title = escape(node.astext()).replace('"','&quot;')
+            title = htmlescape(node.astext()).replace('"','&quot;')
             item = section_template % {'title': title, 'ref': link}
             item = u' ' * 4 * indentlevel + item
             parts.append(item.encode('ascii', 'xmlcharrefreplace'))
     def build_keywords(self, title, refs, subitems):
         keywords = []
 
-        title = escape(title)
+        title = htmlescape(title)
 #        if len(refs) == 0: # XXX
 #            write_param('See Also', title)
         if len(refs) == 1:

sphinx/cmdline.py

 from sphinx.application import Sphinx
 from sphinx.util import Tee, format_exception_cut_frames, save_traceback
 from sphinx.util.console import red, nocolor, color_terminal
-from sphinx.util.pycompat import terminal_safe
+from sphinx.util.pycompat import terminal_safe, bytes
 
 
 def usage(argv, msg=None):
         allopts = set(opt[0] for opt in opts)
         srcdir = confdir = path.abspath(args[0])
         if not path.isdir(srcdir):
-            print >>sys.stderr, 'Error: Cannot find source directory.'
+            print >>sys.stderr, 'Error: Cannot find source directory `%s\'.' % (
+                                srcdir,)
             return 1
         if not path.isfile(path.join(srcdir, 'conf.py')) and \
                '-c' not in allopts and '-C' not in allopts:
             try:
                 val = int(val)
             except ValueError:
-                if likely_encoding:
+                if likely_encoding and isinstance(val, bytes):
                     try:
                         val = val.decode(likely_encoding)
                     except UnicodeError:
             try:
                 val = int(val)
             except ValueError:
-                if likely_encoding:
+                if likely_encoding and isinstance(val, bytes):
                     try:
                         val = val.decode(likely_encoding)
                     except UnicodeError:

sphinx/domains/python.py

                 # it's a submodule
                 if prev_modname == package:
                     # first submodule - make parent a group head
-                    entries[-1][1] = 1
+                    if entries:
+                        entries[-1][1] = 1
                 elif not prev_modname.startswith(package):
                     # submodule without parent in list, add dummy entry
                     entries.append([stripped + package, 1, '', '', '', '', ''])
         newname = None
         if searchmode == 1:
             objtypes = self.objtypes_for_role(type)
-            if modname and classname:
-                fullname = modname + '.' + classname + '.' + name
-                if fullname in objects and objects[fullname][1] in objtypes:
-                    newname = fullname
-            if not newname:
-                if modname and modname + '.' + name in objects and \
-                   objects[modname + '.' + name][1] in objtypes:
-                    newname = modname + '.' + name
-                elif name in objects and objects[name][1] in objtypes:
-                    newname = name
-                else:
-                    # "fuzzy" searching mode
-                    searchname = '.' + name
-                    matches = [(oname, objects[oname]) for oname in objects
-                               if oname.endswith(searchname)
-                               and objects[oname][1] in objtypes]
+            if objtypes is not None:
+                if modname and classname:
+                    fullname = modname + '.' + classname + '.' + name
+                    if fullname in objects and objects[fullname][1] in objtypes:
+                        newname = fullname
+                if not newname:
+                    if modname and modname + '.' + name in objects and \
+                       objects[modname + '.' + name][1] in objtypes:
+                        newname = modname + '.' + name
+                    elif name in objects and objects[name][1] in objtypes:
+                        newname = name
+                    else:
+                        # "fuzzy" searching mode
+                        searchname = '.' + name
+                        matches = [(oname, objects[oname]) for oname in objects
+                                   if oname.endswith(searchname)
+                                   and objects[oname][1] in objtypes]
         else:
             # NOTE: searching for exact match, object type is not considered
             if name in objects:

sphinx/domains/std.py

                 continue
             # unindented line -> a term
             if line and not line[0].isspace():
+                # enable comments
+                if line.startswith('.. '):
+                    continue
                 # first term of definition
                 if in_definition:
                     if not was_empty:
             term += system_messages
 
             defnode = nodes.definition()
-            self.state.nested_parse(definition, definition.items[0][1], defnode)
+            if definition:
+                self.state.nested_parse(definition, definition.items[0][1], defnode)
 
             items.append((termtexts,
                           nodes.definition_list_item('', term, defnode)))

sphinx/environment.py

                             subnode['iscurrent'] = True
                             subnode = subnode.parent
 
-        def _entries_from_toctree(toctreenode, separate=False, subtree=False):
+        def _entries_from_toctree(toctreenode, parents,
+                                  separate=False, subtree=False):
             """Return TOC entries for a toctree node."""
             refs = [(e[0], str(e[1])) for e in toctreenode['entries']]
             entries = []
             for (title, ref) in refs:
                 try:
+                    refdoc = None
                     if url_re.match(ref):
                         reference = nodes.reference('', '', internal=False,
                                                     refuri=ref, anchorname='',
                         # don't show subitems
                         toc = nodes.bullet_list('', item)
                     else:
+                        if ref in parents:
+                            self.warn(ref, 'circular toctree references '
+                                      'detected, ignoring: %s <- %s' %
+                                      (ref, ' <- '.join(parents)))
+                            continue
+                        refdoc = ref
                         toc = self.tocs[ref].deepcopy()
                         self.process_only_nodes(toc, builder, ref)
                         if title and toc.children and len(toc.children) == 1:
                         if not (toctreenode.get('hidden', False)
                                 and not includehidden):
                             i = toctreenode.parent.index(toctreenode) + 1
-                            for item in _entries_from_toctree(toctreenode,
-                                                              subtree=True):
+                            for item in _entries_from_toctree(
+                                    toctreenode, [refdoc] + parents,
+                                    subtree=True):
                                 toctreenode.parent.insert(i, item)
                                 i += 1
                             toctreenode.parent.remove(toctreenode)
         # NOTE: previously, this was separate=True, but that leads to artificial
         # separation when two or more toctree entries form a logical unit, so
         # separating mode is no longer used -- it's kept here for history's sake
-        tocentries = _entries_from_toctree(toctree, separate=False)
+        tocentries = _entries_from_toctree(toctree, [], separate=False)
         if not tocentries:
             return None
 
     def collect_relations(self):
         relations = {}
         getinc = self.toctree_includes.get
-        def collect(parents, docname, previous, next):
+        def collect(parents, parents_set, docname, previous, next):
+            # circular relationship?
+            if docname in parents_set:
+                # we will warn about this in resolve_toctree()
+                return
             includes = getinc(docname)
             # previous
             if not previous:
                 for subindex, args in enumerate(izip(includes,
                                                      [None] + includes,
                                                      includes[1:] + [None])):
-                    collect([(docname, subindex)] + parents, *args)
+                    collect([(docname, subindex)] + parents,
+                            parents_set.union([docname]), *args)
             relations[docname] = [parents[0][0], previous, next]
-        collect([(None, 0)], self.config.master_doc, None, None)
+        collect([(None, 0)], set(), self.config.master_doc, None, None)
         return relations
 
     def check_consistency(self):

sphinx/ext/doctest.py

 from docutils.parsers.rst import directives
 
 from sphinx.builders import Builder
+from sphinx.util import force_decode
 from sphinx.util.nodes import set_source_info
 from sphinx.util.compat import Directive
 from sphinx.util.console import bold
+from sphinx.util.pycompat import bytes
 
 blankline_re = re.compile(r'^\s*<BLANKLINE>', re.MULTILINE)
 doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE)
         self.info(text, nonl=True)
         if self.app.quiet:
             self.warn(text)
+        if isinstance(text, bytes):
+            text = force_decode(text, None)
         self.outfile.write(text)
 
     def get_target_uri(self, docname, typ=None):
         for code in group.tests:
             if len(code) == 1:
                 # ordinary doctests (code/output interleaved)
-                test = parser.get_doctest(code[0].code, {}, group.name,
-                                          filename, code[0].lineno)
+                try:
+                    test = parser.get_doctest(code[0].code, {}, group.name,
+                                              filename, code[0].lineno)
+                except Exception:
+                    self.warn('ignoring invalid doctest code: %r' % code[0].code,
+                              '%s:%s' % (filename, code[0].lineno))
+                    continue
                 if not test.examples:
                     continue
                 for example in test.examples:

sphinx/ext/graphviz.py

 def text_visit_graphviz(self, node):
     if 'alt' in node.attributes:
         self.add_text(_('[graph: %s]') % node['alt'])
-    self.add_text(_('[graph]'))
+    else:
+        self.add_text(_('[graph]'))
     raise nodes.SkipNode
 
 
 def man_visit_graphviz(self, node):
     if 'alt' in node.attributes:
-        self.body.append(_('[graph: %s]') % node['alt'] + '\n')
-    self.body.append(_('[graph]'))
+        self.body.append(_('[graph: %s]') % node['alt'])
+    else:
+        self.body.append(_('[graph]'))
     raise nodes.SkipNode
 
 

sphinx/ext/inheritance_diagram.py

 import re
 import sys
 import inspect
+import __builtin__
 try:
     from hashlib import md5
 except ImportError:
         displayed node names.
         """
         all_classes = {}
-        builtins = __builtins__.values()
+        builtins = vars(__builtin__).values()
 
         def recurse(cls):
             if not show_builtins and cls in builtins:

sphinx/highlighting.py

 """
 
 import sys
-import cgi
 import re
 import textwrap
 
     # parser is not available on Jython
     parser = None
 
+from sphinx.util.pycompat import htmlescape
 from sphinx.util.texescape import tex_hl_escape_map_new
 from sphinx.ext import doctest
 
 
     def unhighlighted(self, source):
         if self.dest == 'html':
-            return '<pre>' + cgi.escape(source) + '</pre>\n'
+            return '<pre>' + htmlescape(source) + '</pre>\n'
         else:
             # first, escape highlighting characters like Pygments does
             source = source.translate(escape_hl_chars)
         else:
             return True
 
-    def highlight_block(self, source, lang, warn=None, **kwargs):
+    def highlight_block(self, source, lang, warn=None, force=False, **kwargs):
         if not isinstance(source, unicode):
             source = source.decode()
         if not pygments:
             if source.startswith('>>>'):
                 # interactive session
                 lexer = lexers['pycon']
-            else:
+            elif not force:
                 # maybe Python -- try parsing it
                 if self.try_parse(source):
                     lexer = lexers['python']
                 else:
                     return self.unhighlighted(source)
+            else:
+                lexer = lexers['python']
         elif lang in ('python3', 'py3') and source.startswith('>>>'):
             # for py3, recognize interactive sessions, but do not try parsing...
             lexer = lexers['pycon3']

sphinx/setup_command.py

         self.fresh_env = self.all_files = False
         self.source_dir = self.build_dir = None
         self.builder = 'html'
+        self.project = ''
         self.version = ''
         self.release = ''
         self.today = ''
         else:
             status_stream = sys.stdout
         confoverrides = {}
+        if self.project:
+             confoverrides['project'] = self.project        
         if self.version:
              confoverrides['version'] = self.version
         if self.release:

sphinx/util/inspect.py

 inspect = __import__('inspect')
 
 from sphinx.util import force_decode
+from sphinx.util.pycompat import bytes
 
 
 if sys.version_info >= (2, 5):
         s = repr(object)
     except Exception:
         raise ValueError
-    return force_decode(s, None).replace('\n', ' ')
+    if isinstance(s, bytes):
+        return force_decode(s, None).replace('\n', ' ')
+    return s.replace('\n', ' ')

sphinx/util/nodes.py

         directive.state_machine.get_source_and_line(directive.lineno)
 
 def set_role_source_info(inliner, lineno, node):
+    try:
         node.source, node.line = \
             inliner.reporter.locator(lineno)
+    except AttributeError:
+        # docutils 0.9+
+        node.source, node.line = inliner.reporter.get_source_and_line(lineno)
 
 # monkey-patch Node.__contains__ to get consistent "in" operator behavior
 # across docutils versions

sphinx/util/pycompat.py

         return s.encode('ascii', 'backslashreplace')
 
 
+try:
+    from html import escape as htmlescape
+except ImportError:
+    from cgi import escape as htmlescape
+
 # ------------------------------------------------------------------------------
 # Missing builtins and itertools in Python < 2.6
 

sphinx/websupport/__init__.py

     :license: BSD, see LICENSE for details.
 """
 
-import cgi
 import sys
 import cPickle as pickle
 import posixpath
 from sphinx.application import Sphinx
 from sphinx.util.osutil import ensuredir
 from sphinx.util.jsonimpl import dumps as dump_json
+from sphinx.util.pycompat import htmlescape
 from sphinx.websupport import errors
 from sphinx.websupport.search import BaseSearch, SEARCH_ADAPTERS
 from sphinx.websupport.storage import StorageBackend
             ret = publish_parts(text, writer_name='html',
                                 settings_overrides=settings)['fragment']
         except Exception:
-            ret = cgi.escape(text)
+            ret = htmlescape(text)
         return ret

sphinx/websupport/storage/differ.py

 """
 
 import re
-from cgi import escape
 from difflib import Differ
 
+from sphinx.util.pycompat import htmlescape
+
 
 class CombinedHtmlDiff(object):
     """Create an HTML representation of the differences between two pieces
     highlight_regex = re.compile(r'([\+\-\^]+)')
 
     def __init__(self, source, proposal):
-        proposal = escape(proposal)
+        proposal = htmlescape(proposal)
 
         differ = Differ()
         self.diff = list(differ.compare(source.splitlines(1),

sphinx/writers/html.py

             self.permalink_text = self.permalink_text and u'\u00B6' or ''
         self.permalink_text = self.encode(self.permalink_text)
         self.secnumber_suffix = builder.config.html_secnumber_suffix
+        self.param_separator = ''
+        self._table_row_index = 0
 
     def visit_start_of_file(self, node):
         # only occurs in the single-file builder
         lang = self.highlightlang
         linenos = node.rawsource.count('\n') >= \
                   self.highlightlinenothreshold - 1
+        highlight_args = node.get('highlight_args', {})
         if node.has_key('language'):
             # code-block directives
             lang = node['language']
+            highlight_args['force'] = True
         if node.has_key('linenos'):
             linenos = node['linenos']
-        highlight_args = node.get('highlight_args', {})
         def warner(msg):
             self.builder.warn(msg, (self.builder.current_docname, node.line))
         highlighted = self.highlighter.highlight_block(

sphinx/writers/latex.py

         code = self.verbatim.rstrip('\n')
         lang = self.hlsettingstack[-1][0]
         linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
+        highlight_args = node.get('highlight_args', {})
         if 'language' in node:
             # code-block directives
             lang = node['language']
+            highlight_args['force'] = True
         if 'linenos' in node:
             linenos = node['linenos']
-        highlight_args = node.get('highlight_args', {})
         def warner(msg):
             self.builder.warn(msg, (self.curfilestack[-1], node.line))
         hlcode = self.highlighter.highlight_block(code, lang, warn=warner,

tests/test_build_html.py

             r'def'),
         (".//div[@class='inc-tab3 highlight-text']//pre",
             r'-| |-'),
-        (".//div[@class='inc-tab8 highlight-python']//pre",
+        (".//div[@class='inc-tab8 highlight-python']//pre/span",
             r'-|      |-'),
     ])
     HTML_XPATH['subdir/includes.html'].extend([
     for fname, paths in HTML_XPATH.iteritems():
         parser = NslessParser()
         parser.entity.update(htmlentitydefs.entitydefs)
-        etree = ET.parse(os.path.join(app.outdir, fname), parser)
+        fp = open(os.path.join(app.outdir, fname))
+        try:
+            etree = ET.parse(fp, parser)
+        finally:
+            fp.close()
         for path, check in paths:
             yield check_xpath, etree, fname, path, check
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.