diff options
28 files changed, 241 insertions, 61 deletions
@@ -68,6 +68,14 @@ Bugs fixed * #1237: Fix footnotes not working in definition list in LaTeX * #2168: Fix raw directive does not work for text writer * #2171: Fix cannot linkcheck url with unicode +* #2182: LaTeX: support image file names with more than 1 dots +* #2189: Fix previous sibling link for first file in subdirectory uses last file, not intended previous from root toctree +* #2003: Fix decode error under python2 (only) when ``make linkcheck`` is run +* #2186: Fix LaTeX output of \mathbb in math +* #1480, #2188: LaTeX: Support math in section titles +* #2071: Fix same footnote in more than two section titles => LaTeX/PDF Bug +* #2040: Fix UnicodeDecodeError in sphinx-apidoc when author contains non-ascii characters +* #2193: Fix shutil.SameFileError if source directory and destination directory are same Release 1.3.3 (released Dec 2, 2015) diff --git a/doc/ext/viewcode.rst b/doc/ext/viewcode.rst index f2b6c9283..5bf8eb033 100644 --- a/doc/ext/viewcode.rst +++ b/doc/ext/viewcode.rst @@ -15,11 +15,7 @@ a highlighted version of the source code, and a link will be added to all object descriptions that leads to the source code of the described object. A link back from the source to the description will also be inserted. -There are currently no configuration values for this extension; you just need to -add ``'sphinx.ext.viewcode'`` to your :confval:`extensions` value for it to -work. - -There is also an additional config value: +There is an additional config value: .. confval:: viewcode_import diff --git a/sphinx/apidoc.py b/sphinx/apidoc.py index 805e862c9..8e2d88ef7 100644 --- a/sphinx/apidoc.py +++ b/sphinx/apidoc.py @@ -20,6 +20,7 @@ import os import sys import optparse from os import path +from six import binary_type from sphinx.util.osutil import walk from sphinx import __display_version__ @@ -369,6 +370,15 @@ Note: By default this script will not overwrite already created files.""") mastertoctree = text, language = 'en', ) + if isinstance(opts.header, binary_type): + d['project'] = d['project'].decode('utf-8') + if isinstance(opts.author, binary_type): + d['author'] = d['author'].decode('utf-8') + if isinstance(opts.version, binary_type): + d['version'] = d['version'].decode('utf-8') + if isinstance(opts.release, binary_type): + d['release'] = d['release'].decode('utf-8') + if not opts.dryrun: qs.generate(d, silent=True, overwrite=opts.force) elif not opts.notoc: diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 15b22d9e1..5dac89e2f 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -95,6 +95,17 @@ def check_anchor(f, hash): return parser.found +def get_content_charset(f): + content_type = f.headers.get('content-type') + if content_type: + params = (p.strip() for p in content_type.split(';')[1:]) + for param in params: + if param.startswith('charset='): + return param[8:] + + return None + + class CheckExternalLinksBuilder(Builder): """ Checks for broken external links. @@ -165,6 +176,8 @@ class CheckExternalLinksBuilder(Builder): encoding = 'utf-8' if hasattr(f.headers, 'get_content_charset'): encoding = f.headers.get_content_charset() or encoding + else: + encoding = get_content_charset(f) or encoding found = check_anchor(TextIOWrapper(f, encoding), unquote(hash)) f.close() diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index 7fa2fbfbd..ac3de5fc2 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -144,6 +144,10 @@ def main(argv): file=sys.stderr) return 1 outdir = abspath(args[1]) + if srcdir == outdir: + print('Error: source directory and destination directory are same.', + file=sys.stderr) + return 1 except IndexError: parser.print_help() return 1 diff --git a/sphinx/environment.py b/sphinx/environment.py index 58463cae8..399e51e5e 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -23,8 +23,8 @@ from os import path from glob import glob from itertools import groupby -from six import iteritems, itervalues, text_type, class_types, string_types -from six.moves import cPickle as pickle, zip +from six import iteritems, itervalues, text_type, class_types, string_types, next +from six.moves import cPickle as pickle from docutils import nodes from docutils.io import FileInput, NullOutput from docutils.core import Publisher @@ -1951,54 +1951,31 @@ class BuildEnvironment: for (key_, group) in groupby(newlist, keyfunc2)] def collect_relations(self): + traversed = set() + + def traverse_toctree(parent, docname): + # traverse toctree by pre-order + yield parent, docname + traversed.add(docname) + + for child in (self.toctree_includes.get(docname) or []): + for subparent, subdocname in traverse_toctree(docname, child): + if subdocname not in traversed: + yield subparent, subdocname + traversed.add(subdocname) + relations = {} - getinc = self.toctree_includes.get + docnames = traverse_toctree(None, self.config.master_doc) + prevdoc = None + parent, docname = next(docnames) + for nextparent, nextdoc in docnames: + relations[docname] = [parent, prevdoc, nextdoc] + prevdoc = docname + docname = nextdoc + parent = nextparent + + relations[docname] = [parent, prevdoc, None] - 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: - # if no previous sibling, go to parent - previous = parents[0][0] - else: - # else, go to previous sibling, or if it has children, to - # the last of its children, or if that has children, to the - # last of those, and so forth - while 1: - previncs = getinc(previous) - if previncs: - previous = previncs[-1] - else: - break - # next - if includes: - # if it has children, go to first of them - next = includes[0] - elif next: - # else, if next sibling, go to it - pass - else: - # else, go to the next sibling of the parent, if present, - # else the grandparent's sibling, if present, and so forth - for parname, parindex in parents: - parincs = getinc(parname) - if parincs and parindex + 1 < len(parincs): - next = parincs[parindex+1] - break - # else it will stay None - # same for children - if includes: - for subindex, args in enumerate(zip(includes, - [None] + includes, - includes[1:] + [None])): - collect([(docname, subindex)] + parents, - parents_set.union([docname]), *args) - relations[docname] = [parents[0][0], previous, next] - collect([(None, 0)], set(), self.config.master_doc, None, None) return relations def check_consistency(self): diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index 4327fba1c..3510d5006 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -56,6 +56,17 @@ def eq_role(role, rawtext, text, lineno, inliner, options={}, content=[]): return [node], [] +def is_in_section_title(node): + """Determine whether the node is in a section title""" + from sphinx.util.nodes import traverse_parent + + for ancestor in traverse_parent(node): + if isinstance(ancestor, nodes.title) and \ + isinstance(ancestor.parent, nodes.section): + return True + return False + + class MathDirective(Directive): has_content = True @@ -91,7 +102,12 @@ class MathDirective(Directive): def latex_visit_math(self, node): - self.body.append('\\(' + node['latex'] + '\\)') + if is_in_section_title(node): + protect = r'\protect' + else: + protect = '' + equation = protect + r'\(' + node['latex'] + protect + r'\)' + self.body.append(equation) raise nodes.SkipNode @@ -214,3 +230,4 @@ def setup_math(app, htmlinlinevisitors, htmldisplayvisitors): app.add_role('eq', eq_role) app.add_directive('math', MathDirective) app.connect('doctree-resolved', number_equations) + app.add_latex_package('amsfonts') diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 28410257e..a54175a2c 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1063,8 +1063,11 @@ class LaTeXTranslator(nodes.NodeVisitor): self.remember_multirowcol[self.table.col] = node.get('morecols') self.table.col += node.get('morecols') if isinstance(node.parent.parent, nodes.thead): - self.body.append('\\textsf{\\relax ') - context += '}' + if len(node) == 1 and isinstance(node[0], nodes.paragraph) and node.astext() == '': + pass + else: + self.body.append('\\textsf{\\relax ') + context += '}' while self.remember_multirow.get(self.table.col + 1, 0): self.table.col += 1 self.remember_multirow[self.table.col] -= 1 @@ -1661,7 +1664,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # if a footnote has been inserted once, it shouldn't be repeated # by the next reference if used: - if self.table or self.in_term: + if self.table or self.in_term or self.in_title: self.body.append('\\protect\\footnotemark[%s]' % num) else: self.body.append('\\footnotemark[%s]' % num) diff --git a/tests/root/markup.txt b/tests/root/markup.txt index 9e8c6bc86..b322b39fa 100644 --- a/tests/root/markup.txt +++ b/tests/root/markup.txt @@ -187,6 +187,15 @@ Tables | 2 | Empty cells: | | +----+----------------+----+ +.. table:: empty cell in table header + + ===== ====== + \ + ===== ====== + 1 2 + 3 4 + ===== ====== + Tables with multirow and multicol: .. only:: latex diff --git a/tests/root/math.txt b/tests/root/math.txt index 36b244943..aeba85f24 100644 --- a/tests/root/math.txt +++ b/tests/root/math.txt @@ -1,5 +1,5 @@ -Test math extensions -==================== +Test math extensions :math:`E = m c^2` +====================================== This is inline math: :math:`a^2 + b^2 = c^2`. @@ -19,4 +19,8 @@ This is inline math: :math:`a^2 + b^2 = c^2`. e^{ix} = \cos x + i\sin x +.. math:: + + n \in \mathbb N + Referencing equation :eq:`foo`. diff --git a/tests/roots/test-footnotes/index.rst b/tests/roots/test-footnotes/index.rst index c7fe38ff9..e8137da71 100644 --- a/tests/roots/test-footnotes/index.rst +++ b/tests/roots/test-footnotes/index.rst @@ -33,6 +33,9 @@ The section with a reference to [AuthorYear]_ .. [1] Second .. [#] Third +The section with a reference to [1]_ +===================================== + `URL in term <http://sphinx-doc.org/>`_ Description Description Description ... diff --git a/tests/roots/test-toctree-glob/bar/bar_1.rst b/tests/roots/test-toctree-glob/bar/bar_1.rst new file mode 100644 index 000000000..6229a1561 --- /dev/null +++ b/tests/roots/test-toctree-glob/bar/bar_1.rst @@ -0,0 +1,4 @@ +Bar-1 +===== + +bar diff --git a/tests/roots/test-toctree-glob/bar/bar_2.rst b/tests/roots/test-toctree-glob/bar/bar_2.rst new file mode 100644 index 000000000..ed7862100 --- /dev/null +++ b/tests/roots/test-toctree-glob/bar/bar_2.rst @@ -0,0 +1,4 @@ +Bar-2 +===== + +bar diff --git a/tests/roots/test-toctree-glob/bar/bar_3.rst b/tests/roots/test-toctree-glob/bar/bar_3.rst new file mode 100644 index 000000000..93c58d41f --- /dev/null +++ b/tests/roots/test-toctree-glob/bar/bar_3.rst @@ -0,0 +1,4 @@ +Bar-3 +===== + +bar diff --git a/tests/roots/test-toctree-glob/bar/bar_4/index.rst b/tests/roots/test-toctree-glob/bar/bar_4/index.rst new file mode 100644 index 000000000..4fae623ce --- /dev/null +++ b/tests/roots/test-toctree-glob/bar/bar_4/index.rst @@ -0,0 +1,4 @@ +Bar-4 +===== + +bar diff --git a/tests/roots/test-toctree-glob/bar/index.rst b/tests/roots/test-toctree-glob/bar/index.rst new file mode 100644 index 000000000..74a9ba942 --- /dev/null +++ b/tests/roots/test-toctree-glob/bar/index.rst @@ -0,0 +1,8 @@ +Bar +=== + +.. toctree:: + :glob: + + * + bar_4/index diff --git a/tests/roots/test-toctree-glob/baz.rst b/tests/roots/test-toctree-glob/baz.rst new file mode 100644 index 000000000..2c1bbbc72 --- /dev/null +++ b/tests/roots/test-toctree-glob/baz.rst @@ -0,0 +1,4 @@ +Baz +=== + +baz diff --git a/tests/roots/test-toctree-glob/conf.py b/tests/roots/test-toctree-glob/conf.py new file mode 100644 index 000000000..cf05c9b5c --- /dev/null +++ b/tests/roots/test-toctree-glob/conf.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +master_doc = 'index' +html_theme = 'classic' diff --git a/tests/roots/test-toctree-glob/foo.rst b/tests/roots/test-toctree-glob/foo.rst new file mode 100644 index 000000000..83f952239 --- /dev/null +++ b/tests/roots/test-toctree-glob/foo.rst @@ -0,0 +1,4 @@ +Foo +=== + +foo diff --git a/tests/roots/test-toctree-glob/index.rst b/tests/roots/test-toctree-glob/index.rst new file mode 100644 index 000000000..079cd6027 --- /dev/null +++ b/tests/roots/test-toctree-glob/index.rst @@ -0,0 +1,11 @@ +test-toctree-glob +================= + +.. toctree:: + :glob: + + foo + bar/index + bar/* + baz + qux/index diff --git a/tests/roots/test-toctree-glob/quux.rst b/tests/roots/test-toctree-glob/quux.rst new file mode 100644 index 000000000..340389d0a --- /dev/null +++ b/tests/roots/test-toctree-glob/quux.rst @@ -0,0 +1,4 @@ +Quux +==== + +quux diff --git a/tests/roots/test-toctree-glob/qux/index.rst b/tests/roots/test-toctree-glob/qux/index.rst new file mode 100644 index 000000000..ad0bee51f --- /dev/null +++ b/tests/roots/test-toctree-glob/qux/index.rst @@ -0,0 +1,8 @@ +Qux +=== + +.. toctree:: + :glob: + :hidden: + + * diff --git a/tests/roots/test-toctree-glob/qux/qux_1.rst b/tests/roots/test-toctree-glob/qux/qux_1.rst new file mode 100644 index 000000000..bac227b42 --- /dev/null +++ b/tests/roots/test-toctree-glob/qux/qux_1.rst @@ -0,0 +1,4 @@ +Qux-1 +===== + +qux diff --git a/tests/roots/test-toctree-glob/qux/qux_2.rst b/tests/roots/test-toctree-glob/qux/qux_2.rst new file mode 100644 index 000000000..bac227b42 --- /dev/null +++ b/tests/roots/test-toctree-glob/qux/qux_2.rst @@ -0,0 +1,4 @@ +Qux-1 +===== + +qux diff --git a/tests/test_apidoc.py b/tests/test_apidoc.py index 215b2e571..794b1a293 100644 --- a/tests/test_apidoc.py +++ b/tests/test_apidoc.py @@ -12,6 +12,7 @@ from __future__ import print_function import sys +from six import PY2 from sphinx import apidoc @@ -40,3 +41,43 @@ def test_simple(tempdir): assert_build() finally: sys.path.remove(codedir) + + +@with_tempdir +def test_multibyte_parameters(tempdir): + codedir = rootdir / 'root' + outdir = tempdir / 'out' + args = ['sphinx-apidoc', '-o', outdir, '-F', codedir, + '--doc-project', u'プロジェクト名'.encode('utf-8'), + '--doc-author', u'著者名'.encode('utf-8'), + '--doc-version', u'バージョン'.encode('utf-8'), + '--doc-release', u'リリース'.encode('utf-8')] + apidoc.main(args) + + assert (outdir / 'conf.py').isfile() + assert (outdir / 'autodoc_fodder.rst').isfile() + assert (outdir / 'index.rst').isfile() + + conf_py = (outdir / 'conf.py').text() + if PY2: + assert u"project = u'プロジェクト名'" in conf_py + assert u"author = u'著者名'" in conf_py + assert u"version = u'バージョン'" in conf_py + assert u"release = u'リリース'" in conf_py + else: + assert u"project = 'プロジェクト名'" in conf_py + assert u"author = '著者名'" in conf_py + assert u"version = 'バージョン'" in conf_py + assert u"release = 'リリース'" in conf_py + + @with_app('text', srcdir=outdir) + def assert_build(app, status, warning): + app.build() + print(status.getvalue()) + print(warning.getvalue()) + + sys.path.append(codedir) + try: + assert_build() + finally: + sys.path.remove(codedir) diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 5cdd3f592..774ed4273 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -90,7 +90,6 @@ def test_latex(app, status, warning): if p.returncode != 0: print(stdout) print(stderr) - del app.cleanup_trees[:] assert False, 'latex exited with return code %s' % p.returncode finally: os.chdir(cwd) @@ -335,6 +334,7 @@ def test_reference_in_caption(app, status, warning): assert '\\chapter{The section with a reference to {[}AuthorYear{]}}' in result assert '\\caption{The table title with a reference to {[}AuthorYear{]}}' in result assert '\\paragraph{The rubric title with a reference to {[}AuthorYear{]}}' in result + assert '\\chapter{The section with a reference to \\protect\\footnotemark[1]}' in result @with_app(buildername='latex', testroot='footnotes', diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py index 8050695d9..618d79015 100644 --- a/tests/test_build_texinfo.py +++ b/tests/test_build_texinfo.py @@ -58,7 +58,6 @@ def test_texinfo(app, status, warning): if retcode != 0: print(stdout) print(stderr) - del app.cleanup_trees[:] assert False, 'makeinfo exited with return code %s' % retcode finally: os.chdir(cwd) diff --git a/tests/test_toctree.py b/tests/test_toctree.py new file mode 100644 index 000000000..d91d92389 --- /dev/null +++ b/tests/test_toctree.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" + test_toctree + ~~~~~~~~~~~~ + + Test the HTML builder and check output against XPath. + + :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from util import with_app + + +@with_app(testroot='toctree-glob') +def test_relations(app, status, warning): + app.builder.build_all() + assert app.builder.relations['index'] == [None, None, 'foo'] + assert app.builder.relations['foo'] == ['index', 'index', 'bar/index'] + assert app.builder.relations['bar/index'] == ['index', 'foo', 'bar/bar_1'] + assert app.builder.relations['bar/bar_1'] == ['bar/index', 'bar/index', 'bar/bar_2'] + assert app.builder.relations['bar/bar_2'] == ['bar/index', 'bar/bar_1', 'bar/bar_3'] + assert app.builder.relations['bar/bar_3'] == ['bar/index', 'bar/bar_2', 'bar/bar_4/index'] + assert app.builder.relations['bar/bar_4/index'] == ['bar/index', 'bar/bar_3', 'baz'] + assert app.builder.relations['baz'] == ['index', 'bar/bar_4/index', 'qux/index'] + assert app.builder.relations['qux/index'] == ['index', 'baz', 'qux/qux_1'] + assert app.builder.relations['qux/qux_1'] == ['qux/index', 'qux/index', 'qux/qux_2'] + assert app.builder.relations['qux/qux_2'] == ['qux/index', 'qux/qux_1', None] + assert 'quux' not in app.builder.relations |