Merge with birkenfeld/sphinx

This commit is contained in:
Roland Meister 2012-02-28 22:09:53 +01:00
commit c20c83140a
23 changed files with 200 additions and 86 deletions

31
CHANGES
View File

@ -19,6 +19,37 @@ Release 1.1.3 (in development)
* PR#36: Make the "bibliography to TOC" fix in LaTeX output specific to * PR#36: Make the "bibliography to TOC" fix in LaTeX output specific to
the document class. 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! Release 1.1.2 (Nov 1, 2011) -- 1.1.1 is a silly version number anyway!
====================================================================== ======================================================================

View File

@ -277,7 +277,7 @@ Project information
the format given in :confval:`today_fmt`. the format given in :confval:`today_fmt`.
The default is no :confval:`today` and a :confval:`today_fmt` of ``'%B %d, 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). %format for the selected locale).
.. confval:: highlight_language .. confval:: highlight_language
@ -791,6 +791,8 @@ the `Dublin Core metadata <http://dublincore.org/>`_.
theme. These are theme-specific. For the options understood by the builtin theme. These are theme-specific. For the options understood by the builtin
themes, see :ref:`this section <builtin-themes>`. themes, see :ref:`this section <builtin-themes>`.
.. versionadded:: 1.2
.. confval:: epub_title .. confval:: epub_title
The title of the document. It defaults to the :confval:`html_title` option The title of the document. It defaults to the :confval:`html_title` option
@ -912,6 +914,8 @@ the `Dublin Core metadata <http://dublincore.org/>`_.
to use this option. The default value is ``False`` because the automatic to use this option. The default value is ``False`` because the automatic
conversion may lose information. conversion may lose information.
.. versionadded:: 1.2
.. confval:: epub_max_image_width .. confval:: epub_max_image_width
This option specifies the maximum width of images. If it is set to a value This option specifies the maximum width of images. If it is set to a value
@ -920,6 +924,8 @@ the `Dublin Core metadata <http://dublincore.org/>`_.
value is ``0``. You need the Python Image Library (PIL) installed to use value is ``0``. You need the Python Image Library (PIL) installed to use
this option. this option.
.. versionadded:: 1.2
.. _latex-options: .. _latex-options:

View File

@ -24,6 +24,10 @@ The input language for mathematics is LaTeX markup. This is the de-facto
standard for plain-text math notation and has the added advantage that no standard for plain-text math notation and has the added advantage that no
further translation is necessary when building LaTeX output. 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: :mod:`.mathbase` defines these new markup elements:
.. rst:role:: math .. rst:role:: math

View File

@ -11,7 +11,6 @@
import codecs import codecs
from os import path from os import path
from cgi import escape
from sphinx import package_dir from sphinx import package_dir
from sphinx.util import copy_static_entry from sphinx.util import copy_static_entry
@ -20,6 +19,7 @@ from sphinx.theming import Theme
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.util.osutil import ensuredir, os_path from sphinx.util.osutil import ensuredir, os_path
from sphinx.util.console import bold from sphinx.util.console import bold
from sphinx.util.pycompat import htmlescape
class ChangesBuilder(Builder): class ChangesBuilder(Builder):
@ -115,7 +115,7 @@ class ChangesBuilder(Builder):
'.. deprecated:: %s' % version] '.. deprecated:: %s' % version]
def hl(no, line): 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: for x in hltext:
if x in line: if x in line:
line = '<span class="hl">%s</span>' % line line = '<span class="hl">%s</span>' % line
@ -125,7 +125,10 @@ class ChangesBuilder(Builder):
self.info(bold('copying source files...')) self.info(bold('copying source files...'))
for docname in self.env.all_docs: for docname in self.env.all_docs:
f = codecs.open(self.env.doc2path(docname), 'r', 'latin1') 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' targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html'
ensuredir(path.dirname(targetfn)) ensuredir(path.dirname(targetfn))
f = codecs.open(targetfn, 'w', 'latin1') f = codecs.open(targetfn, 'w', 'latin1')
@ -148,7 +151,7 @@ class ChangesBuilder(Builder):
self.outdir, self) self.outdir, self)
def hl(self, text, version): def hl(self, text, version):
text = escape(text) text = htmlescape(text)
for directive in ['versionchanged', 'versionadded', 'deprecated']: for directive in ['versionchanged', 'versionadded', 'deprecated']:
text = text.replace('.. %s:: %s' % (directive, version), text = text.replace('.. %s:: %s' % (directive, version),
'<b>.. %s:: %s</b>' % (directive, version)) '<b>.. %s:: %s</b>' % (directive, version))

View File

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

View File

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

View File

@ -22,7 +22,7 @@ from sphinx.errors import SphinxError
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.util import Tee, format_exception_cut_frames, save_traceback from sphinx.util import Tee, format_exception_cut_frames, save_traceback
from sphinx.util.console import red, nocolor, color_terminal 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): def usage(argv, msg=None):
@ -67,7 +67,8 @@ def main(argv):
allopts = set(opt[0] for opt in opts) allopts = set(opt[0] for opt in opts)
srcdir = confdir = path.abspath(args[0]) srcdir = confdir = path.abspath(args[0])
if not path.isdir(srcdir): 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 return 1
if not path.isfile(path.join(srcdir, 'conf.py')) and \ if not path.isfile(path.join(srcdir, 'conf.py')) and \
'-c' not in allopts and '-C' not in allopts: '-c' not in allopts and '-C' not in allopts:
@ -137,7 +138,7 @@ def main(argv):
try: try:
val = int(val) val = int(val)
except ValueError: except ValueError:
if likely_encoding: if likely_encoding and isinstance(val, bytes):
try: try:
val = val.decode(likely_encoding) val = val.decode(likely_encoding)
except UnicodeError: except UnicodeError:
@ -153,7 +154,7 @@ def main(argv):
try: try:
val = int(val) val = int(val)
except ValueError: except ValueError:
if likely_encoding: if likely_encoding and isinstance(val, bytes):
try: try:
val = val.decode(likely_encoding) val = val.decode(likely_encoding)
except UnicodeError: except UnicodeError:

View File

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

View File

@ -245,6 +245,9 @@ class Glossary(Directive):
continue continue
# unindented line -> a term # unindented line -> a term
if line and not line[0].isspace(): if line and not line[0].isspace():
# enable comments
if line.startswith('.. '):
continue
# first term of definition # first term of definition
if in_definition: if in_definition:
if not was_empty: if not was_empty:
@ -315,7 +318,8 @@ class Glossary(Directive):
term += system_messages term += system_messages
defnode = nodes.definition() 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, items.append((termtexts,
nodes.definition_list_item('', term, defnode))) nodes.definition_list_item('', term, defnode)))

View File

@ -1313,12 +1313,14 @@ class BuildEnvironment:
subnode['iscurrent'] = True subnode['iscurrent'] = True
subnode = subnode.parent 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.""" """Return TOC entries for a toctree node."""
refs = [(e[0], str(e[1])) for e in toctreenode['entries']] refs = [(e[0], str(e[1])) for e in toctreenode['entries']]
entries = [] entries = []
for (title, ref) in refs: for (title, ref) in refs:
try: try:
refdoc = None
if url_re.match(ref): if url_re.match(ref):
reference = nodes.reference('', '', internal=False, reference = nodes.reference('', '', internal=False,
refuri=ref, anchorname='', refuri=ref, anchorname='',
@ -1341,6 +1343,12 @@ class BuildEnvironment:
# don't show subitems # don't show subitems
toc = nodes.bullet_list('', item) toc = nodes.bullet_list('', item)
else: 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() toc = self.tocs[ref].deepcopy()
self.process_only_nodes(toc, builder, ref) self.process_only_nodes(toc, builder, ref)
if title and toc.children and len(toc.children) == 1: if title and toc.children and len(toc.children) == 1:
@ -1376,8 +1384,9 @@ class BuildEnvironment:
if not (toctreenode.get('hidden', False) if not (toctreenode.get('hidden', False)
and not includehidden): and not includehidden):
i = toctreenode.parent.index(toctreenode) + 1 i = toctreenode.parent.index(toctreenode) + 1
for item in _entries_from_toctree(toctreenode, for item in _entries_from_toctree(
subtree=True): toctreenode, [refdoc] + parents,
subtree=True):
toctreenode.parent.insert(i, item) toctreenode.parent.insert(i, item)
i += 1 i += 1
toctreenode.parent.remove(toctreenode) toctreenode.parent.remove(toctreenode)
@ -1398,7 +1407,7 @@ class BuildEnvironment:
# NOTE: previously, this was separate=True, but that leads to artificial # NOTE: previously, this was separate=True, but that leads to artificial
# separation when two or more toctree entries form a logical unit, so # 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 # 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: if not tocentries:
return None return None
@ -1686,7 +1695,11 @@ class BuildEnvironment:
def collect_relations(self): def collect_relations(self):
relations = {} relations = {}
getinc = self.toctree_includes.get 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) includes = getinc(docname)
# previous # previous
if not previous: if not previous:
@ -1723,9 +1736,10 @@ class BuildEnvironment:
for subindex, args in enumerate(izip(includes, for subindex, args in enumerate(izip(includes,
[None] + includes, [None] + includes,
includes[1:] + [None])): includes[1:] + [None])):
collect([(docname, subindex)] + parents, *args) collect([(docname, subindex)] + parents,
parents_set.union([docname]), *args)
relations[docname] = [parents[0][0], previous, next] 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 return relations
def check_consistency(self): def check_consistency(self):

View File

@ -23,9 +23,11 @@ from docutils import nodes
from docutils.parsers.rst import directives from docutils.parsers.rst import directives
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.util import force_decode
from sphinx.util.nodes import set_source_info from sphinx.util.nodes import set_source_info
from sphinx.util.compat import Directive from sphinx.util.compat import Directive
from sphinx.util.console import bold from sphinx.util.console import bold
from sphinx.util.pycompat import bytes
blankline_re = re.compile(r'^\s*<BLANKLINE>', re.MULTILINE) blankline_re = re.compile(r'^\s*<BLANKLINE>', re.MULTILINE)
doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE) doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE)
@ -231,6 +233,8 @@ Results of doctest builder run on %s
self.info(text, nonl=True) self.info(text, nonl=True)
if self.app.quiet: if self.app.quiet:
self.warn(text) self.warn(text)
if isinstance(text, bytes):
text = force_decode(text, None)
self.outfile.write(text) self.outfile.write(text)
def get_target_uri(self, docname, typ=None): def get_target_uri(self, docname, typ=None):
@ -375,8 +379,13 @@ Doctest summary
for code in group.tests: for code in group.tests:
if len(code) == 1: if len(code) == 1:
# ordinary doctests (code/output interleaved) # ordinary doctests (code/output interleaved)
test = parser.get_doctest(code[0].code, {}, group.name, try:
filename, code[0].lineno) 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: if not test.examples:
continue continue
for example in test.examples: for example in test.examples:

View File

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

View File

@ -39,6 +39,7 @@ r"""
import re import re
import sys import sys
import inspect import inspect
import __builtin__
try: try:
from hashlib import md5 from hashlib import md5
except ImportError: except ImportError:
@ -142,7 +143,7 @@ class InheritanceGraph(object):
displayed node names. displayed node names.
""" """
all_classes = {} all_classes = {}
builtins = __builtins__.values() builtins = vars(__builtin__).values()
def recurse(cls): def recurse(cls):
if not show_builtins and cls in builtins: if not show_builtins and cls in builtins:

View File

@ -10,7 +10,6 @@
""" """
import sys import sys
import cgi
import re import re
import textwrap import textwrap
@ -20,6 +19,7 @@ except ImportError:
# parser is not available on Jython # parser is not available on Jython
parser = None parser = None
from sphinx.util.pycompat import htmlescape
from sphinx.util.texescape import tex_hl_escape_map_new from sphinx.util.texescape import tex_hl_escape_map_new
from sphinx.ext import doctest from sphinx.ext import doctest
@ -105,7 +105,7 @@ class PygmentsBridge(object):
def unhighlighted(self, source): def unhighlighted(self, source):
if self.dest == 'html': if self.dest == 'html':
return '<pre>' + cgi.escape(source) + '</pre>\n' return '<pre>' + htmlescape(source) + '</pre>\n'
else: else:
# first, escape highlighting characters like Pygments does # first, escape highlighting characters like Pygments does
source = source.translate(escape_hl_chars) source = source.translate(escape_hl_chars)
@ -153,7 +153,7 @@ class PygmentsBridge(object):
else: else:
return True 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): if not isinstance(source, unicode):
source = source.decode() source = source.decode()
if not pygments: if not pygments:
@ -164,12 +164,14 @@ class PygmentsBridge(object):
if source.startswith('>>>'): if source.startswith('>>>'):
# interactive session # interactive session
lexer = lexers['pycon'] lexer = lexers['pycon']
else: elif not force:
# maybe Python -- try parsing it # maybe Python -- try parsing it
if self.try_parse(source): if self.try_parse(source):
lexer = lexers['python'] lexer = lexers['python']
else: else:
return self.unhighlighted(source) return self.unhighlighted(source)
else:
lexer = lexers['python']
elif lang in ('python3', 'py3') and source.startswith('>>>'): elif lang in ('python3', 'py3') and source.startswith('>>>'):
# for py3, recognize interactive sessions, but do not try parsing... # for py3, recognize interactive sessions, but do not try parsing...
lexer = lexers['pycon3'] lexer = lexers['pycon3']

View File

@ -81,6 +81,7 @@ class BuildDoc(Command):
self.fresh_env = self.all_files = False self.fresh_env = self.all_files = False
self.source_dir = self.build_dir = None self.source_dir = self.build_dir = None
self.builder = 'html' self.builder = 'html'
self.project = ''
self.version = '' self.version = ''
self.release = '' self.release = ''
self.today = '' self.today = ''
@ -125,6 +126,8 @@ class BuildDoc(Command):
else: else:
status_stream = sys.stdout status_stream = sys.stdout
confoverrides = {} confoverrides = {}
if self.project:
confoverrides['project'] = self.project
if self.version: if self.version:
confoverrides['version'] = self.version confoverrides['version'] = self.version
if self.release: if self.release:

View File

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

View File

@ -179,8 +179,12 @@ def set_source_info(directive, node):
directive.state_machine.get_source_and_line(directive.lineno) directive.state_machine.get_source_and_line(directive.lineno)
def set_role_source_info(inliner, lineno, node): def set_role_source_info(inliner, lineno, node):
try:
node.source, node.line = \ node.source, node.line = \
inliner.reporter.locator(lineno) 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 # monkey-patch Node.__contains__ to get consistent "in" operator behavior
# across docutils versions # across docutils versions

View File

@ -64,6 +64,11 @@ else:
return s.encode('ascii', 'backslashreplace') 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 # Missing builtins and itertools in Python < 2.6

View File

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

View File

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

View File

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

View File

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

View File

@ -258,7 +258,7 @@ if pygments:
r'def'), r'def'),
(".//div[@class='inc-tab3 highlight-text']//pre", (".//div[@class='inc-tab3 highlight-text']//pre",
r'-| |-'), r'-| |-'),
(".//div[@class='inc-tab8 highlight-python']//pre", (".//div[@class='inc-tab8 highlight-python']//pre/span",
r'-| |-'), r'-| |-'),
]) ])
HTML_XPATH['subdir/includes.html'].extend([ HTML_XPATH['subdir/includes.html'].extend([
@ -328,7 +328,11 @@ def test_html(app):
for fname, paths in HTML_XPATH.iteritems(): for fname, paths in HTML_XPATH.iteritems():
parser = NslessParser() parser = NslessParser()
parser.entity.update(htmlentitydefs.entitydefs) 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: for path, check in paths:
yield check_xpath, etree, fname, path, check yield check_xpath, etree, fname, path, check