diff --git a/.gitignore b/.gitignore index 298a9c370..be28908ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pyc *.egg *.so +*.swp .dir-locals.el .ropeproject/ diff --git a/.travis.yml b/.travis.yml index 3f8b7ee3a..da5a99185 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - "2.7" - "3.3" - "3.4" + - "3.5" - "pypy" env: - DOCUTILS=0.11 @@ -13,4 +14,7 @@ install: - pip install docutils==$DOCUTILS - pip install -r test-reqs.txt before_script: flake8 -script: make test +script: + + - if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then make test-async; fi + - if [[ $TRAVIS_PYTHON_VERSION != '3.5' ]]; then make test; fi diff --git a/AUTHORS b/AUTHORS index c823072bb..6c28b1681 100644 --- a/AUTHORS +++ b/AUTHORS @@ -51,13 +51,16 @@ Other contributors, listed alphabetically, are: * Stefan Seefeld -- toctree improvements * Shibukawa Yoshiki -- pluggable search API and Japanese search * Antonio Valentino -- qthelp builder +* Filip Vavera -- napoleon todo directive * Pauli Virtanen -- autodoc improvements, autosummary extension * Stefan van der Walt -- autosummary extension * Thomas Waldmann -- apidoc module fixes * John Waltman -- Texinfo builder * Barry Warsaw -- setup command improvements * Sebastian Wiesner -- image handling, distutils support +* Michael Wilson -- Intersphinx HTTP basic auth support * Joel Wurtz -- cellspanning support in LaTeX +* Hong Xu -- svg support in imgmath extension and various bug fixes Many thanks for all contributions! diff --git a/CHANGES b/CHANGES index 7b637443f..9ae850d74 100644 --- a/CHANGES +++ b/CHANGES @@ -4,17 +4,162 @@ Release 1.4 (in development) Incompatible changes -------------------- +* Drop ``PorterStemmer`` package support. Use ``PyStemmer`` instead of ``PorterStemmer`` + to accelerate stemming. +* sphinx_rtd_theme has become optional. Please install it manually. + Refs #2087, #2086, #1845 and #2097. Thanks to Victor Zverovich. +* #2231: Use DUrole instead of DUspan for custom roles in LaTeX writer. It enables to take + title of roles as an argument of custom macros. +* #2022: 'Thumbs.db' and '.DS_Store' are added to `exclude_patterns` default values in + conf.py that will be provided on sphinx-quickstart. +* #2027, #2208: The ``html_title`` accepts string values only. And The None value cannot be + accepted. +* ``sphinx.ext.graphviz``: show graph image in inline by default +* #2060, #2224: The ``manpage`` role now generate ``sphinx.addnodes.manpage`` node instead + of ``sphinx.addnodes.literal_emphasis`` node. + Features added -------------- +* #2092: add todo directive support in napoleon package. * #1962: when adding directives, roles or nodes from an extension, warn if such an element is already present (built-in or added by another extension). -* #1935: Make "numfig_format" overridable in latex_elements. * #1909: Add "doc" references to Intersphinx inventories. -* C++ type alias support (e.g., ``.. type:: T = int``) +* C++ type alias support (e.g., ``.. type:: T = int``). * C++ template support for classes, functions, type aliases, and variables (#1729, #1314). * C++, added new scope management directives ``namespace-push`` and ``namespace-pop``. * #1970: Keyboard shortcuts to navigate Next and Previous topics +* Intersphinx: Added support for fetching Intersphinx inventories with URLs + using HTTP basic auth. +* C++, added support for template parameter in function info field lists. +* C++, added support for pointers to member (function). +* #2113: Allow ``:class:`` option to code-block directive. +* #2192: Imgmath (pngmath with svg support). +* #2200: Support XeTeX and LuaTeX for the LaTeX builder. +* #1906: Use xcolor over color for \fcolorbox where available for LaTeX output. +* #2216: Texinputs makefile improvements. +* #2170: Support for Chinese language search index. +* #2214: Add sphinx.ext.githubpages to publish the docs on GitHub Pages +* #1030: Make page reference names for latex_show_pagerefs translatable +* #2162: Add Sphinx.add_source_parser() to add source_suffix and source_parsers from extension +* #2207: Add sphinx.parsers.Parser class; a base class for new parsers +* #656: Add ``graphviz_dot`` option to graphviz directives to switch the ``dot`` command + +Bugs fixed +---------- + +* #1913: C++, fix assert bug for enumerators in next-to-global and global scope. +* C++, fix parsing of 'signed char' and 'unsigned char' as types. +* C++, add missing support for 'friend' functions. +* C++, add missing support for virtual base classes (thanks to Rapptz). +* C++, add support for final classes. +* C++, fix parsing of types prefixed with 'enum'. +* #2023: Dutch search support uses Danish stemming info. +* C++, add support for user-defined literals. +* #1804: Now html output wraps overflowed long-line-text in the sidebar. Thanks to + Hassen ben tanfous. +* #2183: Fix porterstemmer causes ``make json`` to fail. +* #1899: Ensure list is sent to OptParse. +* #2164: Fix wrong check for pdftex inside sphinx.sty (for graphicx package option). +* #2165, #2218: Remove faulty and non-need conditional from sphinx.sty. +* Fix broken LaTeX code is generated if unknown language is given + +Documentation +------------- + +* #1757: Fix for usage of :confval:`html_last_updated_fmt`. Thanks to Ralf Hemmecke. + + +Release 1.3.5 (in development) +============================== + +Bugs fixed +---------- + +* Fix line numbers was not shown on warnings in LaTeX and texinfo builders +* Fix filenames were not shown on warnings of citations +* Fix line numbers was not shown on warnings in LaTeX and texinfo builders +* Fix line numbers was not shown on warnings of indecies +* #2026: Fix LaTeX builder rais error if parsed-literal includes links +* #2243: Ignore strange docstring types for classes, do not crash +* #2247: Fix #2205 breaks make html for definition list with classifiers + that contains regular-expression like string +* #1565: Show warning if Pygments throws an ErrorToken + +Release 1.3.4 (released Jan 12, 2016) +===================================== + +Bugs fixed +---------- + +* #2134: Fix figure caption with reference causes latex build error +* #2094: Fix rubric with reference not working in Latex +* #2147: Fix literalinclude code in latex does not break in pages +* #1833: Fix email addresses is showed again if latex_show_urls is not None +* #2176: sphinx.ext.graphviz: use instead of to embed svg +* #967: Fix SVG inheritance diagram is not hyperlinked (clickable) +* #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 +* #2178: Fix unparseable C++ cross-reference when referencing a function with :cpp:any: +* #2206: Fix Sphinx latex doc build failed due to a footnotes +* #2201: Fix wrong table caption for tables with over 30 rows +* #2213: Set
in the classic theme to fit with

+* #1815: Fix linkcheck does not raise an exception if warniserror set to true and link is + broken +* #2197: Fix slightly cryptic error message for missing index.rst file +* #1894: Unlisted phony targets in quickstart Makefile +* #2125: Fix unifies behavior of collapsed fields (``GroupedField`` and ``TypedField``) +* #1408: Check latex_logo validity before copying +* #771: Fix latex output doesn't set tocdepth +* #1820: On Windows, console coloring is broken with colorama version 0.3.3. + Now sphinx use colorama>=0.3.5 to avoid this problem. +* #2072: Fix footnotes in chapter-titles do not appear in PDF output +* #1580: Fix paragraphs in longtable don't work in Latex output +* #1366: Fix centered image not centered in latex +* #1860: Fix man page using ``:samp:`` with braces - font doesn't reset +* #1610: Sphinx crashes in japanese indexing in some systems +* Fix Sphinx crashes if mecab initialization failed +* #2160: Fix broken TOC of PDFs if section includes an image +* #2172: Fix dysfunctional admonition \py@lightbox in sphinx.sty. Thanks to jfbu. +* #2198,#2205: ``make gettext`` generate broken msgid for definition lists. +* #2062: Escape characters in doctests are treated incorrectly with Python 2. +* #2225: Fix if the option does not begin with dash, linking is not performed +* #2226: Fix math is not HTML-encoded when :nowrap: is given (jsmath, mathjax) +* #1601, #2220: 'any' role breaks extended domains behavior. Affected extensions doesn't + support resolve_any_xref and resolve_xref returns problematic node instead of None. + sphinxcontrib-httpdomain is one of them. +* #2229: Fix no warning is given for unknown options + +Release 1.3.3 (released Dec 2, 2015) +==================================== + +Bugs fixed +---------- + +* #2177: Fix parallel hangs +* #2012: Fix exception occurred if ``numfig_format`` is invalid +* #2142: Provide non-minified JS code in ``sphinx/search/non-minified-js/*.js`` for + source distribution on PyPI. +* #2148: Error while building devhelp target with non-ASCII document. + + +Release 1.3.2 (released Nov 29, 2015) +===================================== + +Features added +-------------- + +* #1935: Make "numfig_format" overridable in latex_elements. Bugs fixed ---------- @@ -23,9 +168,8 @@ Bugs fixed * Add a "default.css" stylesheet (which imports "classic.css") for compatibility. * #1788: graphviz extension raises exception when caption option is present. * #1789: ``:pyobject:`` option of ``literalinclude`` directive includes following - lines after class definitions. -* #1790: ``literalinclude`` strips empty lines at the head and tail. -* #1913: C++, fix assert bug for enumerators in next-to-global and global scope. + lines after class definitions +* #1790: ``literalinclude`` strips empty lines at the head and tail * #1802: load plugin themes automatically when theme.conf use it as 'inherit'. Thanks to Takayuki Hirai. * #1794: custom theme extended from alabaster or sphinx_rtd_theme can't find base theme. @@ -34,7 +178,7 @@ Bugs fixed * #1823: '.' as for sphinx-apidoc cause an unfriendly error. Now '.' is converted to absolute path automatically. * Fix a crash when setting up extensions which do not support metadata. -* #1784: Provide non-minified JS code in ``sphinx/search/*.py`` +* #1784: Provide non-minified JS code in ``sphinx/search/non-minified-js/*.js`` * #1822, #1892: Fix regression for #1061. autosummary can't generate doc for imported members since sphinx-1.3b3. Thanks to Eric Larson. * #1793, #1819: "see also" misses a linebreak in text output. Thanks to Takayuki Hirai. @@ -49,7 +193,6 @@ Bugs fixed * #1923: Use babel features only if the babel latex element is nonempty. * #1942: Fix a KeyError in websupport. * #1903: Fix strange id generation for glossary terms. -* #1796, On py3, automated .mo building cause UnicodeDecodeError * ``make text`` will crush if a definition list item has more than 1 classifiers as: ``term : classifier1 : classifier2``. * #1855: make gettext generates broken po file for definition lists with classifier. @@ -65,15 +208,24 @@ Bugs fixed * #1994: More supporting non-standard parser (like recommonmark parser) for Translation and WebSupport feature. Now node.rawsource is fall backed to node.astext() during docutils transforming. -* C++, fix parsing of 'signed char' and 'unsigned char' as types. -* C++, add missing support for 'friend' functions. -* C++, add missing support for virtual base classes (thanks to Rapptz). -* C++, add support for final classes. -* C++, fix parsing of types prefixed with 'enum'. -* #2023: Dutch search support uses Danish stemming info - -Documentation -------------- +* #1989: "make blahblah" on Windows indicate help messages for sphinx-build every time. + It was caused by wrong make.bat that generated by Sphinx-1.3.0/1.3.1. +* On Py2 environment, conf.py that is generated by sphinx-quickstart should have u prefixed + config value for 'version' and 'release'. +* #2102: On Windows + Py3, using ``|today|`` and non-ASCII date format will raise + UnicodeEncodeError. +* #1974: UnboundLocalError: local variable 'domain' referenced before assignment when + using `any` role and `sphinx.ext.intersphinx` in same time. +* #2121: multiple words search doesn't find pages when words across on the page title and + the page content. +* #1884, #1885: plug-in html themes cannot inherit another plug-in theme. Thanks to + Suzumizaki. +* #1818: `sphinx.ext.todo` directive generates broken html class attribute as + 'admonition-' when :confval:`language` is specified with non-ASCII linguistic area like + 'ru' or 'ja'. To fix this, now ``todo`` directive can use ``:class:`` option. +* #2140: Fix footnotes in table has broken in LaTeX +* #2127: MecabBinder for html searching feature doesn't work with Python 3. + Thanks to Tomoko Uchida. Release 1.3.1 (released Mar 17, 2015) @@ -550,7 +702,7 @@ Bugs fixed if they contain uppercase letters. * #923: Take the entire LaTeX document into account when caching pngmath-generated images. This rebuilds them correctly when - `pngmath_latex_preamble` changes. + ``pngmath_latex_preamble`` changes. * #901: Emit a warning when using docutils' new "math" markup without a Sphinx math extension active. * #845: In code blocks, when the selected lexer fails, display line numbers @@ -879,13 +1031,12 @@ Features added * Command-line interfaces: - PR#75: Added ``--follow-links`` option to sphinx-apidoc. - - #869: sphinx-build now has the option :option:`-T` for printing the full + - #869: sphinx-build now has the option ``-T`` for printing the full traceback after an unhandled exception. - - sphinx-build now supports the standard :option:`--help` and - :option:`--version` options. + - sphinx-build now supports the standard ``--help`` and ``--version`` options. - sphinx-build now provides more specific error messages when called with invalid options or arguments. - - sphinx-build now has a verbose option :option:`-v` which can be repeated for + - sphinx-build now has a verbose option ``-v`` which can be repeated for greater effect. A single occurrence provides a slightly more verbose output than normal. Two or more occurrences of this option provides more detailed output which may be useful for debugging. @@ -907,7 +1058,7 @@ Features added stemming routines. Saves about 20 seconds when building the Python documentation. - PR#108: Add experimental support for parallel building with a new - :option:`-j` option. + :option:`sphinx-build -j` option. Documentation ------------- @@ -1169,7 +1320,7 @@ Features added indicators. - #367: Added automatic exclusion of hidden members in inheritance diagrams, and an option to selectively enable it. - - Added `pngmath_add_tooltips`. + - Added ``pngmath_add_tooltips``. - The math extension displaymath directives now support ``name`` in addition to ``label`` for giving the equation label, for compatibility with Docutils. @@ -1497,7 +1648,7 @@ Features added * General: - Added a "nitpicky" mode that emits warnings for all missing - references. It is activated by the :option:`-n` command-line switch + references. It is activated by the :option:`sphinx-build -n` command-line switch or the `nitpicky` config value. - Added ``latexpdf`` target in quickstart Makefile. diff --git a/LICENSE b/LICENSE index 4944ef5a1..8f9e7f1ac 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ License for Sphinx ================== -Copyright (c) 2007-2015 by the Sphinx team (see AUTHORS file). +Copyright (c) 2007-2016 by the Sphinx team (see AUTHORS file). All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/MANIFEST.in b/MANIFEST.in index 4cafcdca6..bb0be4958 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,6 +16,7 @@ include sphinx-apidoc.py recursive-include sphinx/texinputs * recursive-include sphinx/themes * recursive-include sphinx/locale * +recursive-include sphinx/search/non-minified-js *.js recursive-include sphinx/ext/autosummary/templates * recursive-include tests * recursive-include utils * diff --git a/Makefile b/Makefile index 629140242..02854840e 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ DONT_CHECK = -i build -i dist -i sphinx/style/jquery.js \ -i .ropeproject -i doc/_build -i tests/path.py \ -i tests/coverage.py -i env -i utils/convert.py \ -i tests/typing_test_data.py \ + -i tests/test_autodoc_py35.py \ + -i tests/build \ -i sphinx/search/da.py \ -i sphinx/search/de.py \ -i sphinx/search/en.py \ @@ -55,6 +57,9 @@ reindent: @$(PYTHON) utils/reindent.py -r -n . test: + @cd tests; $(PYTHON) run.py -I py35 -d -m '^[tT]est' $(TEST) + +test-async: @cd tests; $(PYTHON) run.py -d -m '^[tT]est' $(TEST) covertest: diff --git a/doc/_templates/index.html b/doc/_templates/index.html index ccef89a78..4c23114db 100644 --- a/doc/_templates/index.html +++ b/doc/_templates/index.html @@ -62,11 +62,11 @@

{%trans%} - You can also download PDF versions of the Sphinx documentation: - a version generated from + You can also download PDF/EPUB versions of the Sphinx documentation: + a PDF version generated from the LaTeX Sphinx produces, and - a version generated - by rst2pdf.{%endtrans%} + a EPUB version. + {%endtrans%}

{%trans%}Examples{%endtrans%}

diff --git a/doc/_templates/indexsidebar.html b/doc/_templates/indexsidebar.html index 56094809f..b6f12f031 100644 --- a/doc/_templates/indexsidebar.html +++ b/doc/_templates/indexsidebar.html @@ -14,15 +14,7 @@

{%trans%}Current version: {{ version }}{%endtrans%}

{%trans%}Get Sphinx from the Python Package Index, or install it with:{%endtrans%}

-{% if version.split('b')|length > 1 %} -
pip install Sphinx=={{ version }}
-

{%trans%}Stable version docs -are also available.{%endtrans%}

-{% else %}
pip install -U Sphinx
-

{%trans%}Latest development version docs -are also available.{%endtrans%}

-{% endif %} {% endif %}

{%trans%}Questions? Suggestions?{%endtrans%}

diff --git a/doc/_themes/sphinx13/layout.html b/doc/_themes/sphinx13/layout.html index 0e6294cc4..bf28cd089 100644 --- a/doc/_themes/sphinx13/layout.html +++ b/doc/_themes/sphinx13/layout.html @@ -4,7 +4,7 @@ Sphinx layout template for the sphinxdoc theme. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "basic/layout.html" %} diff --git a/doc/_themes/sphinx13/static/sphinx13.css b/doc/_themes/sphinx13/static/sphinx13.css index 222432d5c..8b3ebdf3a 100644 --- a/doc/_themes/sphinx13/static/sphinx13.css +++ b/doc/_themes/sphinx13/static/sphinx13.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- sphinx13 theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/doc/builders.rst b/doc/builders.rst index 4fbd5bae6..93e01e4b7 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -23,6 +23,8 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. class:: DirectoryHTMLBuilder @@ -36,6 +38,8 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. versionadded:: 0.6 @@ -48,6 +52,8 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. versionadded:: 1.0 @@ -61,6 +67,8 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. module:: sphinx.builders.qthelp @@ -72,6 +80,8 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. _Qt help: http://qt-project.org/doc/qt-4.8/qthelp-framework.html @@ -96,6 +106,8 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. versionadded:: 1.3 @@ -109,6 +121,8 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. module:: sphinx.builders.epub @@ -122,6 +136,8 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. module:: sphinx.builders.latex @@ -145,6 +161,8 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types Note that a direct PDF builder using ReportLab is available in `rst2pdf @@ -162,6 +180,8 @@ for details. .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. versionadded:: 0.4 @@ -175,6 +195,8 @@ for details. .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. versionadded:: 1.0 @@ -196,6 +218,8 @@ for details. .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. versionadded:: 1.1 @@ -261,6 +285,8 @@ for details. The old name ``web`` still works as well. + .. autoattribute:: format + .. autoattribute:: supported_image_types The file suffix is ``.fpickle``. The global context is called @@ -276,6 +302,8 @@ for details. .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types The file suffix is ``.fjson``. The global context is called @@ -293,6 +321,8 @@ for details. .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. versionadded:: 1.1 @@ -307,6 +337,8 @@ for details. .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. module:: sphinx.builders.linkcheck @@ -318,6 +350,8 @@ for details. .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. module:: sphinx.builders.xml @@ -329,6 +363,8 @@ for details. .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. versionadded:: 1.2 @@ -343,6 +379,8 @@ for details. .. autoattribute:: name + .. autoattribute:: format + .. autoattribute:: supported_image_types .. versionadded:: 1.2 diff --git a/doc/conf.py b/doc/conf.py index 22ab60972..753ffab4c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -15,7 +15,7 @@ templates_path = ['_templates'] exclude_patterns = ['_build'] project = 'Sphinx' -copyright = '2007-2015, Georg Brandl and the Sphinx team' +copyright = '2007-2016, Georg Brandl and the Sphinx team' version = sphinx.__released__ release = version show_authors = True diff --git a/doc/config.rst b/doc/config.rst index f3ebd7f6f..542c36245 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -96,8 +96,11 @@ General configuration If given, a dictionary of parser classes for different source suffices. The keys are the suffix, the values can be either a class or a string giving a - fully-qualified name of a parser class. Files with a suffix that is not in - the dictionary will be parsed with the default reStructuredText parser. + fully-qualified name of a parser class. The parser class can be either + ``docutils.parsers.Parser`` or :class:`sphinx.parsers.Parser`. Files with a + suffix that is not in the dictionary will be parsed with the default + reStructuredText parser. + For example:: @@ -234,7 +237,7 @@ General configuration If true, Sphinx will warn about *all* references where the target cannot be found. Default is ``False``. You can activate this mode temporarily using - the :option:`-n` command-line switch. + the :option:`-n ` command-line switch. .. versionadded:: 1.0 @@ -453,7 +456,7 @@ documentation on :ref:`intl` for details. this path are searched by the standard :mod:`gettext` module. Internal messages are fetched from a text domain of ``sphinx``; so if you - add the directory :file:`./locale` to this settting, the message catalogs + add the directory :file:`./locale` to this setting, the message catalogs (compiled from ``.po`` format using :program:`msgfmt`) must be in :file:`./locale/{language}/LC_MESSAGES/sphinx.mo`. The text domain of individual documents depends on :confval:`gettext_compact`. @@ -565,8 +568,7 @@ that use Sphinx's HTMLWriter class. The "title" for HTML documentation generated with Sphinx's own templates. This is appended to the ```` tag of individual pages, and used in the navigation bar as the "topmost" element. It defaults to :samp:`'{<project>} - v{<revision>} documentation'` (with the values coming from the config - values). + v{<revision>} documentation'`. .. confval:: html_short_title @@ -580,7 +582,7 @@ that use Sphinx's HTMLWriter class. A dictionary of values to pass into the template engine's context for all pages. Single values can also be put in this dictionary using the - :option:`-A` command-line option of ``sphinx-build``. + :option:`-A <sphinx-build -A>` command-line option of ``sphinx-build``. .. versionadded:: 0.5 @@ -637,9 +639,10 @@ that use Sphinx's HTMLWriter class. .. confval:: html_last_updated_fmt - If this is not the empty string, a 'Last updated on:' timestamp is inserted - at every page bottom, using the given :func:`strftime` format. Default is - ``'%b %d, %Y'`` (or a locale-dependent equivalent). + If this is not None, a 'Last updated on:' timestamp is inserted + at every page bottom, using the given :func:`strftime` format. + The empty string is equivalent to ``'%b %d, %Y'`` (or a + locale-dependent equivalent). .. confval:: html_use_smartypants @@ -873,6 +876,7 @@ that use Sphinx's HTMLWriter class. * ``es`` -- Spanish * ``sv`` -- Swedish * ``tr`` -- Turkish + * ``zh`` -- Chinese .. admonition:: Accelerating build speed @@ -907,6 +911,12 @@ that use Sphinx's HTMLWriter class. .. versionadded:: 1.1 + + The Chinese support has these options: + + * ``dict`` -- the ``jieba`` dictionary path if want to use + custom dictionary. + .. confval:: html_search_scorer The name of a JavaScript file (relative to the configuration directory) that @@ -1431,6 +1441,8 @@ These options influence LaTeX output. 'floated' into the next page but may be preceded by any other text. If you don't like this behavior, use 'H' which will disable floating and position figures strictly in the order they appear in the source. + + .. versionadded:: 1.3 ``'footer'`` Additional footer content (before the indices), default empty. diff --git a/doc/domains.rst b/doc/domains.rst index 1ebcd3b7d..803a4343c 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -538,7 +538,7 @@ a visibility statement (``public``, ``private`` or ``protected``). .. cpp:class:: template<typename T, std::size_t N> std::array - or with a line beak:: + or with a line break:: .. cpp:class:: template<typename T, std::size_t N> \ std::array @@ -594,7 +594,7 @@ a visibility statement (``public``, ``private`` or ``protected``). .. rst:directive:: .. cpp:member:: (member) variable declaration .. cpp:var:: (member) variable declaration - Describe a varible or member variable, e.g.,:: + Describe a variable or member variable, e.g.,:: .. cpp:member:: std::string MyClass::myMember @@ -671,7 +671,7 @@ a visibility statement (``public``, ``private`` or ``protected``). Namespacing ~~~~~~~~~~~~~~~~~ -Declarations in the C++ doamin are as default placed in global scope. +Declarations in the C++ domain are as default placed in global scope. The current scope can be changed using three namespace directives. They manage a stack declarations where ``cpp:namespace`` resets the stack and changes a given scope. @@ -755,6 +755,7 @@ Info field lists The C++ directives support the following info fields (see also :ref:`info-field-lists`): * `param`, `parameter`, `arg`, `argument`: Description of a parameter. +* `tparam`: Description of a template parameter. * `returns`, `return`: Description of a return value. * `throws`, `throw`, `exception`: Description of a possibly thrown exception. @@ -764,7 +765,7 @@ The C++ directives support the following info fields (see also :ref:`info-field- Cross-referencing ~~~~~~~~~~~~~~~~~ -These roles link to the given object types: +These roles link to the given declaration types: .. rst:role:: cpp:any cpp:class @@ -775,19 +776,19 @@ These roles link to the given object types: cpp:enum cpp:enumerator - Reference a C++ object by name. The name must be properly qualified relative to the - position of the link. + Reference a C++ declaration by name (see below for details). + The name must be properly qualified relative to the position of the link. - .. note:: +.. admonition:: Note on References with Templates Parameters/Arguments - Sphinx's syntax to give references a custom title can interfere with - linking to template classes, if nothing follows the closing angle - bracket, i.e. if the link looks like this: ``:cpp:class:`MyClass<T>```. - This is interpreted as a link to ``T`` with a title of ``MyClass``. - In this case, please escape the opening angle bracket with a backslash, - like this: ``:cpp:class:`MyClass\<T>```. + Sphinx's syntax to give references a custom title can interfere with + linking to template classes, if nothing follows the closing angle + bracket, i.e. if the link looks like this: ``:cpp:class:`MyClass<int>```. + This is interpreted as a link to ``int`` with a title of ``MyClass``. + In this case, please escape the opening angle bracket with a backslash, + like this: ``:cpp:class:`MyClass\<int>```. -.. admonition:: Note on References +.. admonition:: Note on References to Overloaded Functions It is currently impossible to link to a specific version of an overloaded method. Currently the C++ domain is the first domain @@ -796,6 +797,80 @@ These roles link to the given object types: specific overload. Currently Sphinx will link to the first overloaded version of the method / function. +Declarations without template parameters and template arguments +................................................................. + +For linking to non-templated declarations the name must be a nested name, +e.g., ``f`` or ``MyClass::f``. + +Templated declarations +...................... + +Assume the following declarations. + +.. cpp:class:: Wrapper + + .. cpp:class:: template<typename TOuter> \ + Outer + + .. cpp:class:: template<typename TInner> \ + Inner + +In general the reference must include the template paraemter declarations, e.g., +``template\<typename TOuter> Wrapper::Outer`` (:cpp:class:`template\<typename TOuter> Wrapper::Outer`). +Currently the lookup only succeed if the template parameter identifiers are equal strings. That is, +``template\<typename UOuter> Wrapper::Outer`` will not work. + +The inner template class can not be directly referenced, unless the current namespace +is changed or the following shorthand is used. +If a template parameter list is omitted, then the lookup will assume either a template or a non-template, +but not a partial template specialisation. +This means the following references work. + +- ``Wrapper::Outer`` (:cpp:class:`Wrapper::Outer`) +- ``Wrapper::Outer::Inner`` (:cpp:class:`Wrapper::Outer::Inner`) +- ``template\<typename TInner> Wrapper::Outer::Inner`` (:cpp:class:`template\<typename TInner> Wrapper::Outer::Inner`) + +(Full) Template Specialisations +................................ + +Assume the following declarations. + +.. cpp:class:: template<typename TOuter> \ + Outer + + .. cpp:class:: template<typename TInner> \ + Inner + +.. cpp:class:: template<> \ + Outer<int> + + .. cpp:class:: template<typename TInner> \ + Inner + + .. cpp:class:: template<> \ + Inner<bool> + +In general the reference must include a template parameter list for each template argument list. +The full specialisation above can therefore be referenced with ``template\<> Outer\<int>`` (:cpp:class:`template\<> Outer\<int>`) +and ``template\<> template\<> Outer\<int>::Inner\<bool>`` (:cpp:class:`template\<> template\<> Outer\<int>::Inner\<bool>`). +As a shorthand the empty template parameter list can be omitted, e.g., ``Outer\<int>`` (:cpp:class:`Outer\<int>`) +and ``Outer\<int>::Inner\<bool>`` (:cpp:class:`Outer\<int>::Inner\<bool>`). + + +Partial Template Specialisations +................................. + +Assume the following declaration. + +.. cpp:class:: template<typename T> \ + Outer<T*> + +References to partial specialisations must always include the template parameter lists, e.g., +``template\<typename T> Outer\<T*>`` (:cpp:class:`template\<typename T> Outer\<T*>`). +Currently the lookup only succeed if the template parameter identifiers are equal strings. + + The Standard Domain ------------------- diff --git a/doc/ext/example_google.py b/doc/ext/example_google.py index 337b06d4f..81a312cfd 100644 --- a/doc/ext/example_google.py +++ b/doc/ext/example_google.py @@ -24,6 +24,10 @@ Attributes: one convention to document module level variables and be consistent with it. +Todo: + * For module TODOs + * You have to also use ``sphinx.ext.todo`` extension + .. _Google Python Style Guide: http://google.github.io/styleguide/pyguide.html @@ -237,16 +241,17 @@ class ExampleClass(object): return True def __special__(self): - """By default special members with docstrings are included. + """By default special members with docstrings are not included. Special members are any methods or attributes that start with and end with a double underscore. Any special member with a docstring - will be included in the output. + will be included in the output, if + ``napoleon_include_special_with_doc`` is set to True. - This behavior can be disabled by changing the following setting in + This behavior can be enabled by changing the following setting in Sphinx's conf.py:: - napoleon_include_special_with_doc = False + napoleon_include_special_with_doc = True """ pass diff --git a/doc/ext/example_numpy.py b/doc/ext/example_numpy.py index 68bc9d289..bb91cac82 100644 --- a/doc/ext/example_numpy.py +++ b/doc/ext/example_numpy.py @@ -286,16 +286,17 @@ class ExampleClass(object): return True def __special__(self): - """By default special members with docstrings are included. + """By default special members with docstrings are not included. Special members are any methods or attributes that start with and end with a double underscore. Any special member with a docstring - will be included in the output. + will be included in the output, if + ``napoleon_include_special_with_doc`` is set to True. - This behavior can be disabled by changing the following setting in + This behavior can be enabled by changing the following setting in Sphinx's conf.py:: - napoleon_include_special_with_doc = False + napoleon_include_special_with_doc = True """ pass diff --git a/doc/ext/githubpages.rst b/doc/ext/githubpages.rst new file mode 100644 index 000000000..0cd76a2c9 --- /dev/null +++ b/doc/ext/githubpages.rst @@ -0,0 +1,12 @@ +.. highlight:: rest + +:mod:`sphinx.ext.githubpages` -- Publish HTML docs in GitHub Pages +================================================================== + +.. module:: sphinx.ext.githubpages + :synopsis: Publish HTML docs in GitHub Pages + +.. versionadded:: 1.4 + +This extension creates ``.nojekyll`` file on generated HTML directory to publish +the document on GitHub Pages. diff --git a/doc/ext/graphviz.rst b/doc/ext/graphviz.rst index d5a5969f4..ca34fa281 100644 --- a/doc/ext/graphviz.rst +++ b/doc/ext/graphviz.rst @@ -87,6 +87,15 @@ It adds these directives: caption to the diagram. Naturally, diagrams marked as "inline" cannot have a caption. +.. deprecated:: 1.4 + ``inline`` option is deprecated. + All three directives generate inline node by default. If ``caption`` is given, + these generate block node instead. + +.. versionchanged:: 1.4 + All three directives support a ``graphviz_dot`` option that can be switch the + ``dot`` command within the directive. + There are also these new config values: .. confval:: graphviz_dot @@ -97,8 +106,8 @@ There are also these new config values: Since this setting is not portable from system to system, it is normally not useful to set it in ``conf.py``; rather, giving it on the - :program:`sphinx-build` command line via the :option:`-D` option should be - preferable, like this:: + :program:`sphinx-build` command line via the :option:`-D <sphinx-build -D>` + option should be preferable, like this:: sphinx-build -b html -D graphviz_dot=C:\graphviz\bin\dot.exe . _build/html @@ -111,7 +120,18 @@ There are also these new config values: .. confval:: graphviz_output_format The output format for Graphviz when building HTML files. This must be either - ``'png'`` or ``'svg'``; the default is ``'png'``. + ``'png'`` or ``'svg'``; the default is ``'png'``. If ``'svg'`` is used, in + order to make the URL links work properly, an appropriate ``target`` + attribute must be set, such as ``"_top"`` and ``"_blank"``. For example, the + link in the following graph should work in the svg output: :: + + .. graphviz:: + + digraph example { + a [label="sphinx", href="http://sphinx-doc.org", target="_top"]; + b [label="other"]; + a -> b; + } .. versionadded:: 1.0 Previously, output always was PNG. diff --git a/doc/ext/math.rst b/doc/ext/math.rst index 4c1546109..081027b92 100644 --- a/doc/ext/math.rst +++ b/doc/ext/math.rst @@ -4,7 +4,7 @@ Math support in Sphinx ====================== .. module:: sphinx.ext.mathbase - :synopsis: Common math support for pngmath and mathjax / jsmath. + :synopsis: Common math support for imgmath and mathjax / jsmath. .. versionadded:: 0.5 @@ -17,7 +17,7 @@ support extensions should, if possible, reuse that support too. .. note:: :mod:`.mathbase` is not meant to be added to the :confval:`extensions` config - value, instead, use either :mod:`sphinx.ext.pngmath` or + value, instead, use either :mod:`sphinx.ext.imgmath` or :mod:`sphinx.ext.mathjax` as described below. The input language for mathematics is LaTeX markup. This is the de-facto @@ -96,20 +96,27 @@ or use Python raw strings (``r"raw"``). beautiful mathematical formulas. -:mod:`sphinx.ext.pngmath` -- Render math as PNG images ------------------------------------------------------- +:mod:`sphinx.ext.imgmath` -- Render math as images +-------------------------------------------------- -.. module:: sphinx.ext.pngmath - :synopsis: Render math as PNG images. +.. module:: sphinx.ext.imgmath + :synopsis: Render math as PNG or SVG images. -This extension renders math via LaTeX and dvipng_ into PNG images. This of -course means that the computer where the docs are built must have both programs -available. +.. versionadded:: 1.4 + +This extension renders math via LaTeX and dvipng_ or dvisvgm_ into PNG or SVG +images. This of course means that the computer where the docs are built must +have both programs available. There are various config values you can set to influence how the images are built: -.. confval:: pngmath_latex +.. confval:: imgmath_image_format + + The output image format. The default is ``'png'``. It should be either + ``'png'`` or ``'svg'``. + +.. confval:: imgmath_latex The command name with which to invoke LaTeX. The default is ``'latex'``; you may need to set this to a full path if ``latex`` is not in the executable @@ -117,45 +124,54 @@ built: Since this setting is not portable from system to system, it is normally not useful to set it in ``conf.py``; rather, giving it on the - :program:`sphinx-build` command line via the :option:`-D` option should be - preferable, like this:: + :program:`sphinx-build` command line via the :option:`-D <sphinx-build -D>` + option should be preferable, like this:: - sphinx-build -b html -D pngmath_latex=C:\tex\latex.exe . _build/html + sphinx-build -b html -D imgmath_latex=C:\tex\latex.exe . _build/html - .. versionchanged:: 0.5.1 - This value should only contain the path to the latex executable, not - further arguments; use :confval:`pngmath_latex_args` for that purpose. + This value should only contain the path to the latex executable, not further + arguments; use :confval:`imgmath_latex_args` for that purpose. -.. confval:: pngmath_dvipng +.. confval:: imgmath_dvipng The command name with which to invoke ``dvipng``. The default is ``'dvipng'``; you may need to set this to a full path if ``dvipng`` is not in - the executable search path. + the executable search path. This option is only used when + ``imgmath_image_format`` is set to ``'png'``. -.. confval:: pngmath_latex_args +.. confval:: imgmath_dvisvgm + + The command name with which to invoke ``dvisvgm``. The default is + ``'dvisvgm'``; you may need to set this to a full path if ``dvisvgm`` is not + in the executable search path. This option is only used when + ``imgmath_image_format`` is ``'svg'``. + +.. confval:: imgmath_latex_args Additional arguments to give to latex, as a list. The default is an empty list. - .. versionadded:: 0.5.1 - -.. confval:: pngmath_latex_preamble +.. confval:: imgmath_latex_preamble Additional LaTeX code to put into the preamble of the short LaTeX files that are used to translate the math snippets. This is empty by default. Use it e.g. to add more packages whose commands you want to use in the math. -.. confval:: pngmath_dvipng_args +.. confval:: imgmath_dvipng_args Additional arguments to give to dvipng, as a list. The default value is ``['-gamma', '1.5', '-D', '110', '-bg', 'Transparent']`` which makes the image a bit darker and larger then it is by default, and produces PNGs with a - transparent background. + transparent background. This option is used only when + ``imgmath_image_format`` is ``'png'``. - .. versionchanged:: 1.2 - Now includes ``-bg Transparent`` by default. +.. confval:: imgmath_dvisvgm_args -.. confval:: pngmath_use_preview + Additional arguments to give to dvisvgm, as a list. The default value is + ``['--no-fonts']``. This option is used only when ``imgmath_image_format`` + is ``'svg'``. + +.. confval:: imgmath_use_preview ``dvipng`` has the ability to determine the "depth" of the rendered text: for example, when typesetting a fraction inline, the baseline of surrounding text @@ -165,14 +181,20 @@ built: ``vertical-align`` style that correctly aligns the baselines. Unfortunately, this only works when the `preview-latex package`_ is - installed. Therefore, the default for this option is ``False``. + installed. Therefore, the default for this option is ``False``. -.. confval:: pngmath_add_tooltips + Currently this option is only used when ``imgmath_image_format`` is + ``'png'``. + +.. confval:: imgmath_add_tooltips Default: ``True``. If false, do not add the LaTeX code as an "alt" attribute for math images. - .. versionadded:: 1.1 +.. confval:: imgmath_font_size + + The font size (in ``pt``) of the displayed math. The default value is + ``12``. It must be a positive integer. :mod:`sphinx.ext.mathjax` -- Render math via JavaScript @@ -235,6 +257,7 @@ package jsMath_. It provides this config value: .. _dvipng: http://savannah.nongnu.org/projects/dvipng/ +.. _dvisvgm: http://dvisvgm.bplaced.net/ .. _MathJax: http://www.mathjax.org/ .. _jsMath: http://www.math.union.edu/~dpvc/jsmath/ .. _preview-latex package: http://www.gnu.org/software/auctex/preview-latex.html diff --git a/doc/ext/napoleon.rst b/doc/ext/napoleon.rst index 7bd74aabd..fed6187f3 100644 --- a/doc/ext/napoleon.rst +++ b/doc/ext/napoleon.rst @@ -110,6 +110,7 @@ All of the following section headers are supported: * ``Raises`` * ``References`` * ``See Also`` + * ``Todo`` * ``Warning`` * ``Warnings`` *(alias of Warning)* * ``Warns`` diff --git a/doc/ext/todo.rst b/doc/ext/todo.rst index c0d94ba1d..8ab6eb37a 100644 --- a/doc/ext/todo.rst +++ b/doc/ext/todo.rst @@ -16,6 +16,10 @@ There are two additional directives when using this extension: It will only show up in the output if :confval:`todo_include_todos` is ``True``. + .. versionadded:: 1.3.2 + This directive supports an ``class`` option that determines the class attribute + for HTML output. If not given, the class defaults to ``admonition-todo``. + .. rst:directive:: todolist 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/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index c2ee4cc86..d16f3a597 100644 --- a/doc/extdev/appapi.rst +++ b/doc/extdev/appapi.rst @@ -155,7 +155,7 @@ package. add_directive('literalinclude', literalinclude_directive, content = 0, arguments = (1, 0, 0), linenos = directives.flag, - language = direcitves.unchanged, + language = directives.unchanged, encoding = directives.encoding) .. versionchanged:: 0.6 @@ -336,6 +336,12 @@ package. .. versionadded:: 1.1 +.. method:: Sphinx.add_source_parser(name, suffix, parser) + + Register a parser class for specified *suffix*. + + .. versionadded:: 1.4 + .. method:: Sphinx.require_sphinx(version) Compare *version* (which must be a ``major.minor`` version string, diff --git a/doc/extdev/envapi.rst b/doc/extdev/envapi.rst index 84ad3e0d9..729725fc5 100644 --- a/doc/extdev/envapi.rst +++ b/doc/extdev/envapi.rst @@ -17,7 +17,11 @@ Build environment API .. attribute:: srcdir - Source directory (the directory containing ``conf.py``). + Source directory. + + .. attribute:: confdir + + Directory containing ``conf.py``. .. attribute:: doctreedir diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 5144c5f8b..b27db4b2d 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -52,4 +52,5 @@ APIs used for writing extensions builderapi markupapi domainapi + parserapi nodes diff --git a/doc/extdev/parserapi.rst b/doc/extdev/parserapi.rst new file mode 100644 index 000000000..7008a15a8 --- /dev/null +++ b/doc/extdev/parserapi.rst @@ -0,0 +1,8 @@ +.. _parser-api: + +Parser API +========== + +.. module:: sphinx.parsers + +.. autoclass:: Parser diff --git a/doc/extensions.rst b/doc/extensions.rst index 8347f4965..47632e593 100644 --- a/doc/extensions.rst +++ b/doc/extensions.rst @@ -32,6 +32,7 @@ These extensions are built in and can be activated by respective entries in the ext/viewcode ext/linkcode ext/napoleon + ext/githubpages Third-party extensions diff --git a/doc/intl.rst b/doc/intl.rst index a358c436a..bd4f9e889 100644 --- a/doc/intl.rst +++ b/doc/intl.rst @@ -63,7 +63,7 @@ be translated you need to follow these instructions: msgfmt "usage.po" -o "locale/es/LC_MESSAGES/usage.mo" * Set :confval:`locale_dirs` to ``["locale/"]``. -* Set :confval:`language` to ``es`` (also possible via :option:`-D`). +* Set :confval:`language` to ``es`` (also possible via :option:`-D <sphinx-build -D>`). * Run your desired build. diff --git a/doc/invocation.rst b/doc/invocation.rst index 1d0c613c3..8fe01bf8d 100644 --- a/doc/invocation.rst +++ b/doc/invocation.rst @@ -100,9 +100,9 @@ Extension options Enable `sphinx.ext.coverage` extension. -.. option:: --ext-pngmath +.. option:: --ext-imgmath - Enable `sphinx.ext.pngmath` extension. + Enable `sphinx.ext.imgmath` extension. .. option:: --ext-mathjax diff --git a/doc/markup/inline.rst b/doc/markup/inline.rst index 10db17d79..0e825b66a 100644 --- a/doc/markup/inline.rst +++ b/doc/markup/inline.rst @@ -242,9 +242,8 @@ objects: .. rst:role:: option - A command-line option to an executable program. The leading hyphen(s) must - be included. This generates a link to a :rst:dir:`option` directive, if it - exists. + A command-line option to an executable program. This generates a link to + a :rst:dir:`option` directive, if it exists. The following role creates a cross-reference to a term in a diff --git a/doc/rest.rst b/doc/rest.rst index c6a4ada04..9fca31b6e 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -223,8 +223,10 @@ as long as the text:: ================= Normally, there are no heading levels assigned to certain characters as the -structure is determined from the succession of headings. However, for the -Python documentation, this convention is used which you may follow: +structure is determined from the succession of headings. However, this +convention is used in `Python's Style Guide for documentating +<https://docs.python.org/devguide/documenting.html#style-guide>`_ which you may +follow: * ``#`` with overline, for parts * ``*`` with overline, for chapters diff --git a/doc/theming.rst b/doc/theming.rst index 13e672b69..551b02c34 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -81,8 +81,6 @@ that has to return the directory with themes in it:: Builtin themes -------------- -.. cssclass:: right - +--------------------+--------------------+ | **Theme overview** | | +--------------------+--------------------+ diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 95bd61ebc..385746f72 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -122,8 +122,9 @@ this:: $ sphinx-build -b html sourcedir builddir where *sourcedir* is the :term:`source directory`, and *builddir* is the -directory in which you want to place the built documentation. The :option:`-b` -option selects a builder; in this example Sphinx will build HTML files. +directory in which you want to place the built documentation. +The :option:`-b <sphinx-build -b>` option selects a builder; in this example +Sphinx will build HTML files. |more| See :ref:`invocation` for all options that :program:`sphinx-build` supports. diff --git a/doc/web/quickstart.rst b/doc/web/quickstart.rst index 996942db6..1611774c1 100644 --- a/doc/web/quickstart.rst +++ b/doc/web/quickstart.rst @@ -135,7 +135,7 @@ add this data to the ``COMMENT_OPTIONS`` that are used in the template. .. note:: - This only works works if your documentation is served from your + This only works if your documentation is served from your document root. If it is served from another directory, you will need to prefix the url route with that directory, and give the `docroot` keyword argument when creating the web support object:: diff --git a/setup.py b/setup.py index cc977f50f..6d1ae2de4 100644 --- a/setup.py +++ b/setup.py @@ -53,12 +53,11 @@ requires = [ 'snowballstemmer>=1.1', 'babel>=1.3,!=2.0', 'alabaster>=0.7,<0.8', - 'sphinx_rtd_theme>=0.1,<2.0', ] extras_require = { # Environment Marker works for wheel 0.24 or later ':sys_platform=="win32"': [ - 'colorama', + 'colorama>=0.3.5', ], 'websupport': [ 'sqlalchemy>=0.9', @@ -73,7 +72,7 @@ extras_require = { # for sdist installation with pip-1.5.6 if sys.platform == 'win32': - requires.append('colorama') + requires.append('colorama>=0.3.5') # Provide a "compile_catalog" command that also creates the translated # JavaScript files if Babel is available. diff --git a/sphinx-apidoc.py b/sphinx-apidoc.py index 7752fc0d1..56394ea96 100755 --- a/sphinx-apidoc.py +++ b/sphinx-apidoc.py @@ -4,7 +4,7 @@ Sphinx - Python documentation toolchain ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx-autogen.py b/sphinx-autogen.py index ea085573f..bcb630855 100755 --- a/sphinx-autogen.py +++ b/sphinx-autogen.py @@ -4,7 +4,7 @@ Sphinx - Python documentation toolchain ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx-build.py b/sphinx-build.py index 7c539d643..ab7919dbb 100755 --- a/sphinx-build.py +++ b/sphinx-build.py @@ -4,7 +4,7 @@ Sphinx - Python documentation toolchain ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx-quickstart.py b/sphinx-quickstart.py index 89ad8f891..fcc594132 100755 --- a/sphinx-quickstart.py +++ b/sphinx-quickstart.py @@ -4,7 +4,7 @@ Sphinx - Python documentation toolchain ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 1abbd23aa..71401fab8 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -5,7 +5,7 @@ The Sphinx documentation toolchain. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/__main__.py b/sphinx/__main__.py index cd19db393..42022bf50 100644 --- a/sphinx/__main__.py +++ b/sphinx/__main__.py @@ -5,7 +5,7 @@ The Sphinx documentation toolchain. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import sys diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index b30bd40ae..3ff5772ca 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -5,7 +5,7 @@ Additional docutils nodes. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -212,6 +212,10 @@ class termsep(nodes.Structural, nodes.Element): """Separates two terms within a <term> node.""" +class manpage(nodes.Inline, nodes.TextElement): + """Node for references to manpages.""" + + # make the new nodes known to docutils; needed because the HTML writer will # choke at some point if these are not added nodes._add_node_class_names(k for k in globals().keys() diff --git a/sphinx/apidoc.py b/sphinx/apidoc.py index 805e862c9..58724fd5a 100644 --- a/sphinx/apidoc.py +++ b/sphinx/apidoc.py @@ -11,7 +11,7 @@ Copyright 2008 Société des arts technologiques (SAT), http://www.sat.qc.ca/ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -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/application.py b/sphinx/application.py index db456e3f7..3842b13fa 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -7,7 +7,7 @@ Gracefully adapted from the TextPress system by Armin. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -77,6 +77,7 @@ class Sphinx(object): self.next_listener_id = 0 self._extensions = {} self._extension_metadata = {} + self._additional_source_parsers = {} self._listeners = {} self._setting_up_extension = ['?'] self.domains = BUILTIN_DOMAINS.copy() @@ -185,6 +186,8 @@ class Sphinx(object): self._init_i18n() # check all configuration values for permissible types self.config.check_types(self.warn) + # set up source_parsers + self._init_source_parsers() # set up the build environment self._init_env(freshenv) # set up the builder @@ -211,6 +214,13 @@ class Sphinx(object): else: self.info('not available for built-in messages') + def _init_source_parsers(self): + for suffix, parser in iteritems(self._additional_source_parsers): + if suffix not in self.config.source_suffix: + self.config.source_suffix.append(suffix) + if suffix not in self.config.source_parsers: + self.config.source_parsers[suffix] = parser + def _init_env(self, freshenv): if freshenv: self.env = BuildEnvironment(self.srcdir, self.doctreedir, @@ -535,13 +545,14 @@ class Sphinx(object): builder.name, self.builderclasses[builder.name].__module__)) self.builderclasses[builder.name] = builder - def add_config_value(self, name, default, rebuild): - self.debug('[app] adding config value: %r', (name, default, rebuild)) + def add_config_value(self, name, default, rebuild, types=()): + self.debug('[app] adding config value: %r', + (name, default, rebuild) + ((types,) if types else ())) if name in self.config.values: raise ExtensionError('Config value %r already present' % name) if rebuild in (False, True): rebuild = rebuild and 'env' or '' - self.config.values[name] = (default, rebuild) + self.config.values[name] = (default, rebuild, types) def add_event(self, name): self.debug('[app] adding event: %r', name) @@ -751,6 +762,10 @@ class Sphinx(object): assert issubclass(cls, SearchLanguage) languages[cls.lang] = cls + def add_source_parser(self, suffix, parser): + self.debug('[app] adding search source_parser: %r, %r', (suffix, parser)) + self._additional_source_parsers[suffix] = parser + class TemplateBridge(object): """ diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index eebd6af64..30c1f73d9 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -5,7 +5,7 @@ Builder superclass for all builders. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -14,9 +14,8 @@ from os import path try: import multiprocessing - import threading except ImportError: - multiprocessing = threading = None + multiprocessing = None from docutils import nodes diff --git a/sphinx/builders/applehelp.py b/sphinx/builders/applehelp.py index c98962633..53a0c99ad 100644 --- a/sphinx/builders/applehelp.py +++ b/sphinx/builders/applehelp.py @@ -5,7 +5,7 @@ Build Apple help books. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index 4fb98ba12..c077b7dd2 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -5,7 +5,7 @@ Changelog builder. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/devhelp.py b/sphinx/builders/devhelp.py index e6199e621..5ebaf2dc5 100644 --- a/sphinx/builders/devhelp.py +++ b/sphinx/builders/devhelp.py @@ -7,7 +7,7 @@ .. _Devhelp: http://live.gnome.org/devhelp - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import absolute_import @@ -91,7 +91,7 @@ class DevhelpBuilder(StandaloneHTMLBuilder): write_toc(subnode, item) elif isinstance(node, nodes.reference): parent.attrib['link'] = node['refuri'] - parent.attrib['name'] = node.astext().encode('utf-8') + parent.attrib['name'] = node.astext() def istoctree(node): return isinstance(node, addnodes.compact_paragraph) and \ @@ -129,6 +129,6 @@ class DevhelpBuilder(StandaloneHTMLBuilder): # Dump the XML file f = comp_open(path.join(outdir, outname + '.devhelp'), 'w') try: - tree.write(f) + tree.write(f, 'utf-8') finally: f.close() diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 2a80642dd..a269b12bc 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -6,7 +6,7 @@ Build epub files. Originally derived from qthelp.py. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index ec6a18805..ef4992a26 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -5,7 +5,7 @@ The MessageCatalogBuilder class. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 79fd06902..01c593b72 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -5,7 +5,7 @@ Several HTML builders. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py index 9a21fb15b..2f06cb0e1 100644 --- a/sphinx/builders/htmlhelp.py +++ b/sphinx/builders/htmlhelp.py @@ -6,7 +6,7 @@ Build HTML help support files. Parts adapted from Python's Doc/tools/prechm.py. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex.py index 1eef58d66..d50e53a61 100644 --- a/sphinx/builders/latex.py +++ b/sphinx/builders/latex.py @@ -5,7 +5,7 @@ LaTeX builder. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -20,6 +20,7 @@ from docutils.frontend import OptionParser from sphinx import package_dir, addnodes from sphinx.util import texescape +from sphinx.errors import SphinxError from sphinx.locale import _ from sphinx.builders import Builder from sphinx.environment import NoUri @@ -94,9 +95,18 @@ class LaTeXBuilder(Builder): destination_path=path.join(self.outdir, targetname), encoding='utf-8') self.info("processing " + targetname + "... ", nonl=1) + toctrees = self.env.get_doctree(docname).traverse(addnodes.toctree) + if toctrees: + if toctrees[0].get('maxdepth'): + tocdepth = int(toctrees[0].get('maxdepth')) + else: + tocdepth = None + else: + tocdepth = None doctree = self.assemble_doctree( docname, toctree_only, appendices=((docclass != 'howto') and self.config.latex_appendices or [])) + doctree['tocdepth'] = tocdepth self.post_process_images(doctree) self.info("writing... ", nonl=1) doctree.settings = docsettings @@ -191,6 +201,9 @@ class LaTeXBuilder(Builder): # the logo is handled differently if self.config.latex_logo: logobase = path.basename(self.config.latex_logo) - copyfile(path.join(self.confdir, self.config.latex_logo), - path.join(self.outdir, logobase)) + logotarget = path.join(self.outdir, logobase) + if not path.isfile(path.join(self.confdir, self.config.latex_logo)): + raise SphinxError('logo file %r does not exist' % self.config.latex_logo) + elif not path.isfile(logotarget): + copyfile(path.join(self.confdir, self.config.latex_logo), logotarget) self.info('done') diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index b05c5b2e0..5904d659b 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -5,7 +5,7 @@ The CheckExternalLinksBuilder class. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -17,7 +17,7 @@ from os import path from six.moves import queue from six.moves.urllib.request import build_opener, Request, HTTPRedirectHandler -from six.moves.urllib.parse import unquote, urlsplit, quote +from six.moves.urllib.parse import unquote from six.moves.urllib.error import HTTPError from six.moves.html_parser import HTMLParser from docutils import nodes @@ -33,6 +33,7 @@ except ImportError: pass from sphinx.builders import Builder +from sphinx.util import encode_uri from sphinx.util.console import purple, red, darkgreen, darkgray, \ darkred, turquoise from sphinx.util.pycompat import TextIOWrapper @@ -94,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. @@ -153,15 +165,7 @@ class CheckExternalLinksBuilder(Builder): try: req_url.encode('ascii') except UnicodeError: - split = urlsplit(req_url) - req_url = (split[0].encode() + '://' + # scheme - split[1].encode('idna') + # netloc - quote(split[2].encode('utf-8'))) # path - if split[3]: # query - req_url += '?' + quote(split[3].encode('utf-8')) - # go back to Unicode strings which is required by Python 3 - # (but now all parts are pure ascii) - req_url = req_url.decode('ascii') + req_url = encode_uri(req_url) # need to actually check the URI try: @@ -172,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() @@ -237,11 +243,12 @@ class CheckExternalLinksBuilder(Builder): elif status == 'working': self.info(darkgreen('ok ') + uri + info) elif status == 'broken': - self.info(red('broken ') + uri + red(' - ' + info)) self.write_entry('broken', docname, lineno, uri + ': ' + info) - if self.app.quiet: + if self.app.quiet or self.app.warningiserror: self.warn('broken link: %s' % uri, '%s:%s' % (self.env.doc2path(docname), lineno)) + else: + self.info(red('broken ') + uri + red(' - ' + info)) elif status == 'redirected': text, color = { 301: ('permanently', darkred), diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index 2af853dd8..a2e75f9f7 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -5,7 +5,7 @@ Manual pages builder. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/qthelp.py b/sphinx/builders/qthelp.py index 825f5651f..f6cfa84c5 100644 --- a/sphinx/builders/qthelp.py +++ b/sphinx/builders/qthelp.py @@ -5,7 +5,7 @@ Build input files for the Qt collection generator. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py index 7528c0777..dec278c86 100644 --- a/sphinx/builders/texinfo.py +++ b/sphinx/builders/texinfo.py @@ -5,7 +5,7 @@ Texinfo builder. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py index 9e624d0d5..85da4a1a2 100644 --- a/sphinx/builders/text.py +++ b/sphinx/builders/text.py @@ -5,7 +5,7 @@ Plain-text Sphinx builder. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/websupport.py b/sphinx/builders/websupport.py index 66af0ab80..843b0899e 100644 --- a/sphinx/builders/websupport.py +++ b/sphinx/builders/websupport.py @@ -5,7 +5,7 @@ Builder for the web support package. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/xml.py b/sphinx/builders/xml.py index 18d8cbfb5..91cb273f5 100644 --- a/sphinx/builders/xml.py +++ b/sphinx/builders/xml.py @@ -5,7 +5,7 @@ Docutils-native XML and pseudo-XML builders. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index e411836c3..f5b141e8c 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -5,7 +5,7 @@ sphinx-build command-line handling. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -120,7 +120,7 @@ def main(argv): # parse options try: - opts, args = parser.parse_args(argv[1:]) + opts, args = parser.parse_args(list(argv[1:])) except SystemExit as err: return err.code @@ -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/config.py b/sphinx/config.py index 090875a79..c33a685f2 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -5,7 +5,7 @@ Build configuration file handling. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -18,7 +18,7 @@ from six import PY3, iteritems, string_types, binary_type, integer_types from sphinx.errors import ConfigError from sphinx.locale import l_ from sphinx.util.osutil import make_filename, cd -from sphinx.util.pycompat import execfile_ +from sphinx.util.pycompat import execfile_, NoneType nonascii_re = re.compile(br'[\x80-\xff]') @@ -27,10 +27,8 @@ if PY3: CONFIG_SYNTAX_ERROR += "\nDid you change the syntax from 2.x to 3.x?" CONFIG_EXIT_ERROR = "The configuration file (or one of the modules it imports) " \ "called sys.exit()" - -IGNORE_CONFIG_TYPE_CHECKS = ( - 'html_domain_indices', 'latex_domain_indices', 'texinfo_domain_indices' -) +CONFIG_TYPE_WARNING = "The config value `{name}' has type `{current.__name__}', " \ + "defaults to `{default.__name__}.'" class Config(object): @@ -50,9 +48,10 @@ class Config(object): version = ('', 'env'), release = ('', 'env'), today = ('', 'env'), - today_fmt = (None, 'env'), # the real default is locale-dependent + # the real default is locale-dependent + today_fmt = (None, 'env', [str]), - language = (None, 'env'), + language = (None, 'env', [str]), locale_dirs = ([], 'env'), master_doc = ('contents', 'env'), @@ -60,23 +59,23 @@ class Config(object): source_encoding = ('utf-8-sig', 'env'), source_parsers = ({}, 'env'), exclude_patterns = ([], 'env'), - default_role = (None, 'env'), + default_role = (None, 'env', [str]), add_function_parentheses = (True, 'env'), add_module_names = (True, 'env'), trim_footnote_reference_space = (False, 'env'), show_authors = (False, 'env'), - pygments_style = (None, 'html'), + pygments_style = (None, 'html', [str]), highlight_language = ('python', 'env'), highlight_options = ({}, 'env'), templates_path = ([], 'html'), - template_bridge = (None, 'html'), + template_bridge = (None, 'html', [str]), keep_warnings = (False, 'env'), modindex_common_prefix = ([], 'html'), - rst_epilog = (None, 'env'), - rst_prolog = (None, 'env'), + rst_epilog = (None, 'env', [str]), + rst_prolog = (None, 'env', [str]), trim_doctest_flags = (True, 'env'), - primary_domain = ('py', 'env'), - needs_sphinx = (None, None), + primary_domain = ('py', 'env', [NoneType]), + needs_sphinx = (None, None, [str]), needs_extensions = ({}, None), nitpicky = (False, 'env'), nitpick_ignore = ([], 'html'), @@ -93,36 +92,36 @@ class Config(object): html_theme_options = ({}, 'html'), html_title = (lambda self: l_('%s %s documentation') % (self.project, self.release), - 'html'), + 'html', [str]), html_short_title = (lambda self: self.html_title, 'html'), - html_style = (None, 'html'), - html_logo = (None, 'html'), - html_favicon = (None, 'html'), + html_style = (None, 'html', [str]), + html_logo = (None, 'html', [str]), + html_favicon = (None, 'html', [str]), html_static_path = ([], 'html'), html_extra_path = ([], 'html'), # the real default is locale-dependent - html_last_updated_fmt = (None, 'html'), + html_last_updated_fmt = (None, 'html', [str]), html_use_smartypants = (True, 'html'), - html_translator_class = (None, 'html'), + html_translator_class = (None, 'html', [str]), html_sidebars = ({}, 'html'), html_additional_pages = ({}, 'html'), html_use_modindex = (True, 'html'), # deprecated - html_domain_indices = (True, 'html'), + html_domain_indices = (True, 'html', [list]), html_add_permalinks = (u'\u00B6', 'html'), html_use_index = (True, 'html'), html_split_index = (False, 'html'), html_copy_source = (True, 'html'), html_show_sourcelink = (True, 'html'), html_use_opensearch = ('', 'html'), - html_file_suffix = (None, 'html'), - html_link_suffix = (None, 'html'), + html_file_suffix = (None, 'html', [str]), + html_link_suffix = (None, 'html', [str]), html_show_copyright = (True, 'html'), html_show_sphinx = (True, 'html'), html_context = ({}, 'html'), html_output_encoding = ('utf-8', 'html'), html_compact_lists = (True, 'html'), html_secnumber_suffix = ('. ', 'html'), - html_search_language = (None, 'html'), + html_search_language = (None, 'html', [str]), html_search_options = ({}, 'html'), html_search_scorer = ('', None), html_scaled_image_link = (True, 'html'), @@ -139,17 +138,17 @@ class Config(object): # Apple help options applehelp_bundle_name = (lambda self: make_filename(self.project), 'applehelp'), - applehelp_bundle_id = (None, 'applehelp'), + applehelp_bundle_id = (None, 'applehelp', [str]), applehelp_dev_region = ('en-us', 'applehelp'), applehelp_bundle_version = ('1', 'applehelp'), - applehelp_icon = (None, 'applehelp'), + applehelp_icon = (None, 'applehelp', [str]), applehelp_kb_product = (lambda self: '%s-%s' % (make_filename(self.project), self.release), 'applehelp'), - applehelp_kb_url = (None, 'applehelp'), - applehelp_remote_url = (None, 'applehelp'), - applehelp_index_anchors = (False, 'applehelp'), - applehelp_min_term_length = (None, 'applehelp'), + applehelp_kb_url = (None, 'applehelp', [str]), + applehelp_remote_url = (None, 'applehelp', [str]), + applehelp_index_anchors = (False, 'applehelp', [str]), + applehelp_min_term_length = (None, 'applehelp', [str]), applehelp_stopwords = (lambda self: self.language or 'en', 'applehelp'), applehelp_locale = (lambda self: self.language or 'en', 'applehelp'), applehelp_title = (lambda self: self.project + ' Help', 'applehelp'), @@ -196,11 +195,11 @@ class Config(object): self.project, '', 'manual')], None), - latex_logo = (None, None), + latex_logo = (None, None, [str]), latex_appendices = ([], None), latex_use_parts = (False, None), latex_use_modindex = (True, None), # deprecated - latex_domain_indices = (True, None), + latex_domain_indices = (True, None, [list]), latex_show_urls = ('no', None), latex_show_pagerefs = (False, None), # paper_size and font_size are still separate values @@ -236,13 +235,13 @@ class Config(object): None), texinfo_appendices = ([], None), texinfo_elements = ({}, None), - texinfo_domain_indices = (True, None), + texinfo_domain_indices = (True, None, [list]), texinfo_show_urls = ('footnote', None), texinfo_no_detailmenu = (False, None), # linkcheck options linkcheck_ignore = ([], None), - linkcheck_timeout = (None, None), + linkcheck_timeout = (None, None, [int]), linkcheck_workers = (5, None), linkcheck_anchors = (True, None), @@ -292,25 +291,30 @@ class Config(object): # NB. since config values might use l_() we have to wait with calling # this method until i18n is initialized for name in self._raw_config: - if name in IGNORE_CONFIG_TYPE_CHECKS: - continue # for a while, ignore multiple types config value. see #1781 - if name not in Config.config_values: + if name not in self.values: continue # we don't know a default value - default, dummy_rebuild = Config.config_values[name] + settings = self.values[name] + default, dummy_rebuild = settings[:2] + permitted = settings[2] if len(settings) == 3 else () + if hasattr(default, '__call__'): default = default(self) # could invoke l_() - if default is None: - continue + if default is None and not permitted: + continue # neither inferrable nor expliclitly permitted types current = self[name] if type(current) is type(default): continue + if type(current) in permitted: + continue + common_bases = (set(type(current).__bases__ + (type(current),)) & set(type(default).__bases__)) common_bases.discard(object) if common_bases: continue # at least we share a non-trivial base class - warn("the config value %r has type `%s', defaults to `%s.'" % - (name, type(current).__name__, type(default).__name__)) + + warn(CONFIG_TYPE_WARNING.format( + name=name, current=type(current), default=type(default))) def check_unicode(self, warn): # check all string values for non-ASCII characters in bytestrings, diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 2200cdb6c..36ca3d34d 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -5,7 +5,7 @@ Handlers for additional ReST directives. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index f31ba7a99..fac8f6419 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -3,7 +3,7 @@ sphinx.directives.code ~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -93,6 +93,7 @@ class CodeBlock(Directive): 'lineno-start': int, 'emphasize-lines': directives.unchanged_required, 'caption': directives.unchanged_required, + 'class': directives.class_option, 'name': directives.unchanged, } @@ -119,6 +120,7 @@ class CodeBlock(Directive): literal['language'] = self.arguments[0] literal['linenos'] = 'linenos' in self.options or \ 'lineno-start' in self.options + literal['classes'] += self.options.get('class', []) extra_args = literal['highlight_args'] = {} if hl_lines is not None: extra_args['hl_lines'] = hl_lines @@ -165,6 +167,7 @@ class LiteralInclude(Directive): 'append': directives.unchanged_required, 'emphasize-lines': directives.unchanged_required, 'caption': directives.unchanged, + 'class': directives.class_option, 'name': directives.unchanged, 'diff': directives.unchanged_required, } @@ -322,6 +325,7 @@ class LiteralInclude(Directive): retnode['linenos'] = 'linenos' in self.options or \ 'lineno-start' in self.options or \ 'lineno-match' in self.options + retnode['classes'] += self.options.get('class', []) extra_args = retnode['highlight_args'] = {} if hl_lines is not None: extra_args['hl_lines'] = hl_lines diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 794dbda5e..51294570c 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -3,7 +3,7 @@ sphinx.directives.other ~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 5d530ae5b..e4d397efe 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -6,7 +6,7 @@ Support for domains, which are groupings of description directives and roles describing e.g. constructs of one programming language. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 6b71f882d..23ed04d05 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -5,7 +5,7 @@ The C language domain. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 552b32252..5a7bddc84 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -5,7 +5,7 @@ The C++ language domain. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -151,7 +151,7 @@ from sphinx.util.docfields import Field, GroupedField | "& attribute-specifier-seq[opt] | "&&" attribute-specifier-seq[opt] | "::"[opt] nested-name-specifier "*" attribute-specifier-seq[opt] - cv-qualifier-seq[opt] # TOOD: not implemented + cv-qualifier-seq[opt] # function_object must use a parameters-and-qualifiers, the others may # use it (e.g., function poitners) parameters-and-qualifiers -> @@ -754,7 +754,7 @@ class ASTTemplateDeclarationPrefix(ASTBase): def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) for t in self.templates: - templateNode = nodes.line() + templateNode = addnodes.desc_signature() t.describe_signature(templateNode, 'lastIsName', env, symbol) signode += templateNode @@ -821,6 +821,31 @@ class ASTOperatorType(ASTBase): signode += addnodes.desc_addname(identifier, identifier) +class ASTOperatorLiteral(ASTBase): + def __init__(self, identifier): + self.identifier = identifier + + def is_operator(self): + return True + + def get_id_v1(self): + raise NoOldIdError() + + def get_id_v2(self): + return u'li' + self.identifier.get_id_v2() + + def __unicode__(self): + return u'operator""' + text_type(self.identifier) + + def describe_signature(self, signode, mode, env, prefix, symbol): + _verify_description_mode(mode) + identifier = text_type(self) + if mode == 'lastIsName': + signode += addnodes.desc_name(identifier, identifier) + else: + signode += addnodes.desc_addname(identifier, identifier) + + class ASTTemplateArgConstant(ASTBase): def __init__(self, value): self.value = value @@ -915,8 +940,6 @@ class ASTNestedName(ASTBase): assert len(names) > 0 self.names = names self.rooted = rooted - for i in range(len(names) - 1): - assert not names[i].is_operator() @property def name(self): @@ -1379,9 +1402,14 @@ class ASTDeclaratorPtr(ASTBase): def __unicode__(self): res = ['*'] if self.volatile: - res.append('volatile ') + res.append('volatile') if self.const: - res.append('const ') + if self.volatile: + res.append(' ') + res.append('const') + if self.const or self.volatile: + if self.next.require_space_after_declSpecs: + res.append(' ') res.append(text_type(self.next)) return u''.join(res) @@ -1436,6 +1464,18 @@ class ASTDeclaratorPtr(ASTBase): def describe_signature(self, signode, mode, env, symbol): _verify_description_mode(mode) signode += nodes.Text("*") + + def _add_anno(signode, text): + signode += addnodes.desc_annotation(text, text) + if self.volatile: + _add_anno(signode, 'volatile') + if self.const: + if self.volatile: + signode += nodes.Text(' ') + _add_anno(signode, 'const') + if self.const or self.volatile: + if self.next.require_space_after_declSpecs: + signode += nodes.Text(' ') self.next.describe_signature(signode, mode, env, symbol) @@ -1548,6 +1588,94 @@ class ASTDeclaratorParamPack(ASTBase): self.next.describe_signature(signode, mode, env, symbol) +class ASTDeclaratorMemPtr(ASTBase): + def __init__(self, className, const, volatile, next): + assert className + assert next + self.className = className + self.const = const + self.volatile = volatile + self.next = next + + @property + def name(self): + return self.next.name + + def require_space_after_declSpecs(self): + return True + + def __unicode__(self): + res = [] + res.append(text_type(self.className)) + res.append('::*') + if self.volatile: + res.append(' volatile') + if self.const: + res.append(' const') + if self.next.require_space_after_declSpecs(): + res.append(' ') + res.append(text_type(self.next)) + return ''.join(res) + + # Id v1 ------------------------------------------------------------------ + + def get_modifiers_id_v1(self): + raise NoOldIdError() + + def get_param_id_v1(self): # only the parameters (if any) + raise NoOldIdError() + + def get_ptr_suffix_id_v1(self): + raise NoOldIdError() + + # Id v2 ------------------------------------------------------------------ + + def get_modifiers_id_v2(self): + return self.next.get_modifiers_id_v2() + + def get_param_id_v2(self): # only the parameters (if any) + return self.next.get_param_id_v2() + + def get_ptr_suffix_id_v2(self): + raise NotImplementedError() + return self.next.get_ptr_suffix_id_v2() + u'Dp' + + def get_type_id_v2(self, returnTypeId): + # ReturnType name::* next, so we are part of the return type of next + nextReturnTypeId = '' + if self.volatile: + nextReturnTypeId += 'V' + if self.const: + nextReturnTypeId += 'K' + nextReturnTypeId += 'M' + nextReturnTypeId += self.className.get_id_v2() + nextReturnTypeId += returnTypeId + return self.next.get_type_id_v2(nextReturnTypeId) + + # ------------------------------------------------------------------------ + + def is_function_type(self): + return self.next.is_function_type() + + def describe_signature(self, signode, mode, env, symbol): + _verify_description_mode(mode) + self.className.describe_signature(signode, mode, env, symbol) + signode += nodes.Text('::*') + + def _add_anno(signode, text): + signode += addnodes.desc_annotation(text, text) + if self.volatile: + _add_anno(signode, 'volatile') + if self.const: + if self.volatile: + signode += nodes.Text(' ') + _add_anno(signode, 'const') + if self.next.require_space_after_declSpecs(): + if self.volatile or self.const: + signode += nodes.Text(' ') + self.next.describe_signature(signode, mode, env, symbol) + + class ASTDeclaratorParen(ASTBase): def __init__(self, inner, next): assert inner @@ -1990,7 +2118,17 @@ class ASTDeclaration(ASTBase): self.declaration = declaration self.symbol = None - self.declarationScope = None # set by Symbol.add_declaration + # set by CPPObject._add_enumerator_to_parent + self.enumeratorScopedSymbol = None + + def clone(self): + if self.templatePrefix: + templatePrefixClone = self.templatePrefix.clone() + else: + templatePrefixClone = None + return ASTDeclaration(self.objectType, self.visibility, + templatePrefixClone, + self.declaration.clone()) @property def name(self): @@ -1999,9 +2137,13 @@ class ASTDeclaration(ASTBase): def get_id_v1(self): if self.templatePrefix: raise NoOldIdError() + if self.objectType == 'enumerator' and self.enumeratorScopedSymbol: + return self.enumeratorScopedSymbol.declaration.get_id_v1() return self.declaration.get_id_v1(self.objectType, self.symbol) def get_id_v2(self, prefixed=True): + if self.objectType == 'enumerator' and self.enumeratorScopedSymbol: + return self.enumeratorScopedSymbol.declaration.get_id_v2(prefixed) if prefixed: res = [_id_prefix_v2] else: @@ -2026,36 +2168,40 @@ class ASTDeclaration(ASTBase): def describe_signature(self, signode, mode, env): _verify_description_mode(mode) + # the caller of the domain added a desc_signature node + # let's pop it so we can add templates before that + parentNode = signode.parent + mainDeclNode = signode + parentNode.pop() + assert self.symbol - if not self.declarationScope: - raise NotImplementedError("hmm, a bug? %s" % text_type(self)) - assert self.declarationScope - if self.visibility and self.visibility != "public": - signode += addnodes.desc_annotation(self.visibility + " ", - self.visibility + " ") if self.templatePrefix: - self.templatePrefix.describe_signature(signode, mode, env, + self.templatePrefix.describe_signature(parentNode, mode, env, symbol=self.symbol) + if self.visibility and self.visibility != "public": + mainDeclNode += addnodes.desc_annotation(self.visibility + " ", + self.visibility + " ") if self.objectType == 'type': - signode += addnodes.desc_annotation('type ', 'type ') + mainDeclNode += addnodes.desc_annotation('type ', 'type ') elif self.objectType == 'member': pass elif self.objectType == 'function': pass elif self.objectType == 'class': - signode += addnodes.desc_annotation('class ', 'class ') + mainDeclNode += addnodes.desc_annotation('class ', 'class ') elif self.objectType == 'enum': prefix = 'enum ' if self.scoped: prefix += self.scoped prefix += ' ' - signode += addnodes.desc_annotation(prefix, prefix) + mainDeclNode += addnodes.desc_annotation(prefix, prefix) elif self.objectType == 'enumerator': - signode += addnodes.desc_annotation('enumerator ', 'enumerator ') + mainDeclNode += addnodes.desc_annotation('enumerator ', 'enumerator ') else: assert False - self.declaration.describe_signature(signode, mode, env, + self.declaration.describe_signature(mainDeclNode, mode, env, symbol=self.symbol) + parentNode += mainDeclNode class ASTNamespace(ASTBase): @@ -2065,23 +2211,30 @@ class ASTNamespace(ASTBase): class Symbol(object): - def __init__(self, parent, identifier, - templateParams, templateArgs, declaration): - if not parent: + def _assert_invariants(self): + if not self.parent: # parent == None means global scope, so declaration means a parent - assert not identifier - assert not templateParams - assert not templateArgs - assert not declaration + assert not self.identifier + assert not self.templateParams + assert not self.templateArgs + assert not self.declaration + assert not self.docname else: - if not identifier: + if not self.identifier: # in case it's an operator - assert declaration + assert self.declaration + if self.declaration: + assert self.docname + + def __init__(self, parent, identifier, + templateParams, templateArgs, declaration, docname): self.parent = parent self.identifier = identifier self.templateParams = templateParams # template<templateParams> self.templateArgs = templateArgs # identifier<templateArgs> self.declaration = declaration + self.docname = docname + self._assert_invariants() self.children = [] if self.parent: @@ -2094,17 +2247,46 @@ class Symbol(object): for p in self.templateParams.params: if not p.get_identifier(): continue - decl = ASTDeclaration('templateParam', None, None, p) + # only add a declaration if we our selfs from a declaration + if declaration: + decl = ASTDeclaration('templateParam', None, None, p) + else: + decl = None nne = ASTNestedNameElement(p.get_identifier(), None) nn = ASTNestedName([nne], rooted=False) - self._add_symbols(nn, [], decl) + self._add_symbols(nn, [], decl, docname) + + def _fill_empty(self, declaration, docname): + self._assert_invariants() + assert not self.declaration + assert not self.docname + assert declaration + assert docname + self.declaration = declaration + self.declaration.symbol = self + self.docname = docname + self._assert_invariants() + + def clear_doc(self, docname): + newChildren = [] + for sChild in self.children: + sChild.clear_doc(docname) + if sChild.declaration and sChild.docname == docname: + sChild.declaration = None + sChild.docname = None + # Just remove operators, because there is no identification if + # they got removed. + # Don't remove other symbols because they may be used in namespace + # directives. + if sChild.identifier or sChild.declaration: + newChildren.append(sChild) + self.children = newChildren def get_all_symbols(self): - # assumed to be in post-order + yield self for sChild in self.children: for s in sChild.get_all_symbols(): yield s - yield self def get_lookup_key(self): if not self.parent: @@ -2117,17 +2299,13 @@ class Symbol(object): s = s.parent symbols.reverse() key = [] - for s in symbols[:-1]: - assert s.identifier - nne = ASTNestedNameElement(s.identifier, s.templateArgs) + for s in symbols: + if s.identifier: + nne = ASTNestedNameElement(s.identifier, s.templateArgs) + else: + assert s.declaration + nne = s.declaration.name.names[-1] key.append((nne, s.templateParams)) - s = symbols[-1] - if s.identifier: - nne = ASTNestedNameElement(s.identifier, s.templateArgs) - else: - assert self.declaration - nne = s.declaration.name.names[-1] - key.append((nne, s.templateParams)) return key def get_full_nested_name(self): @@ -2137,36 +2315,48 @@ class Symbol(object): return ASTNestedName(names, rooted=False) def _find_named_symbol(self, identifier, templateParams, - templateArgs, operator): + templateArgs, operator, + templateShorthand, matchSelf): assert (identifier is None) != (operator is None) - for s in self.children: + + def matches(s): if s.identifier != identifier: - continue + return False if not s.identifier: if not s.declaration: - continue + return False assert operator name = s.declaration.name.names[-1] if not name.is_operator(): - continue + return False if text_type(name) != text_type(operator): - continue + return False if (s.templateParams is None) != (templateParams is None): - continue - if s.templateParams: + if templateParams is not None: + # we query with params, they must match params + return False + if not templateShorthand: + # we don't query with params, and we do care about them + return False + if templateParams: # TODO: do better comparison if text_type(s.templateParams) != text_type(templateParams): - continue + return False if (s.templateArgs is None) != (templateArgs is None): - continue + return False if s.templateArgs: # TODO: do better comparison if text_type(s.templateArgs) != text_type(templateArgs): - continue - return s + return False + return True + if matchSelf and matches(self): + return self + for s in self.children: + if matches(s): + return s return None - def _add_symbols(self, nestedName, templateDecls, declaration): + def _add_symbols(self, nestedName, templateDecls, declaration, docname): # This condition should be checked at the parser level. # Each template argument list must have a template parameter list. # But to declare a template there must be an additional template parameter list. @@ -2177,101 +2367,148 @@ class Symbol(object): if nestedName.rooted: while parentSymbol.parent: parentSymbol = parentSymbol.parent - declarationScope = parentSymbol names = nestedName.names iTemplateDecl = 0 - for iName in range(len(names)): - name = names[iName] - if iName + 1 == len(names): - if name.is_operator(): - identifier = None - templateArgs = None - operator = name - else: - identifier = name.identifier - templateArgs = name.templateArgs - operator = None - if iTemplateDecl < len(templateDecls): - if iTemplateDecl + 1 != len(templateDecls): - print(text_type(templateDecls)) - print(text_type(nestedName)) - assert iTemplateDecl + 1 == len(templateDecls) - templateParams = templateDecls[iTemplateDecl] - else: - assert iTemplateDecl == len(templateDecls) - templateParams = None - symbol = parentSymbol._find_named_symbol(identifier, - templateParams, - templateArgs, - operator) - if symbol: - if not declaration: - # good, just a scope creation - return symbol - if not symbol.declaration: - # If someone first opened the scope, and then later - # declares it, e.g, - # .. namespace:: Test - # .. namespace:: nullptr - # .. class:: Test - symbol.declaration = declaration - declaration.symbol = symbol - declaration.declarationScope = declarationScope - return symbol - # it may simply be a functin overload - # TODO: it could be a duplicate but let's just insert anyway - # the id generation will warn about it - symbol = Symbol(parent=parentSymbol, identifier=identifier, - templateParams=templateParams, - templateArgs=templateArgs, - declaration=declaration) - declaration.declarationScope = declarationScope - else: - symbol = Symbol(parent=parentSymbol, identifier=identifier, - templateParams=templateParams, - templateArgs=templateArgs, - declaration=declaration) - if declaration: - declaration.declarationScope = declarationScope - return symbol + for name in names[:-1]: + # there shouldn't be anything inside an operator + # (other than template parameters, which are not added this way, right?) + assert not name.is_operator() + identifier = name.identifier + templateArgs = name.templateArgs + if templateArgs: + assert iTemplateDecl < len(templateDecls) + templateParams = templateDecls[iTemplateDecl] + iTemplateDecl += 1 else: - # there shouldn't be anything inside an operator - assert not name.is_operator() - identifier = name.identifier - templateArgs = name.templateArgs - if templateArgs: - assert iTemplateDecl < len(templateDecls) - templateParams = templateDecls[iTemplateDecl] - iTemplateDecl += 1 - else: - templateParams = None - symbol = parentSymbol._find_named_symbol(identifier, - templateParams, - templateArgs, - operator=None) - if symbol is None: - symbol = Symbol(parent=parentSymbol, identifier=identifier, - templateParams=templateParams, - templateArgs=templateArgs, declaration=None) + templateParams = None + symbol = parentSymbol._find_named_symbol(identifier, + templateParams, + templateArgs, + operator=None, + templateShorthand=False, + matchSelf=False) + if symbol is None: + symbol = Symbol(parent=parentSymbol, identifier=identifier, + templateParams=templateParams, + templateArgs=templateArgs, declaration=None, + docname=None) parentSymbol = symbol - assert False # should have returned in the loop + name = names[-1] + if name.is_operator(): + identifier = None + templateArgs = None + operator = name + else: + identifier = name.identifier + templateArgs = name.templateArgs + operator = None + if iTemplateDecl < len(templateDecls): + if iTemplateDecl + 1 != len(templateDecls): + print(text_type(templateDecls)) + print(text_type(nestedName)) + assert iTemplateDecl + 1 == len(templateDecls) + templateParams = templateDecls[iTemplateDecl] + else: + assert iTemplateDecl == len(templateDecls) + templateParams = None + symbol = parentSymbol._find_named_symbol(identifier, + templateParams, + templateArgs, + operator, + templateShorthand=False, + matchSelf=False) + if symbol: + if not declaration: + # good, just a scope creation + return symbol + if not symbol.declaration: + # If someone first opened the scope, and then later + # declares it, e.g, + # .. namespace:: Test + # .. namespace:: nullptr + # .. class:: Test + symbol._fill_empty(declaration, docname) + return symbol + # it may simply be a functin overload + # TODO: it could be a duplicate but let's just insert anyway + # the id generation will warn about it + symbol = Symbol(parent=parentSymbol, identifier=identifier, + templateParams=templateParams, + templateArgs=templateArgs, + declaration=declaration, + docname=docname) + else: + symbol = Symbol(parent=parentSymbol, identifier=identifier, + templateParams=templateParams, + templateArgs=templateArgs, + declaration=declaration, + docname=docname) + return symbol + + def merge_with(self, other, docnames, env): + assert other is not None + for otherChild in other.children: + if not otherChild.identifier: + if not otherChild.declaration: + print("Problem in symbol tree merging") + print("OtherChild.dump:") + print(otherChild.dump(0)) + print("Other.dump:") + print(other.dump(0)) + assert otherChild.declaration + operator = otherChild.declaration.name.names[-1] + assert operator.is_operator() + else: + operator = None + ourChild = self._find_named_symbol(otherChild.identifier, + otherChild.templateParams, + otherChild.templateArgs, + operator, + templateShorthand=False, + matchSelf=False) + if ourChild is None: + # TODO: hmm, should we prune by docnames? + self.children.append(otherChild) + otherChild.parent = self + otherChild._assert_invariants() + continue + if otherChild.declaration and otherChild.docname in docnames: + if not ourChild.declaration: + ourChild._fill_empty(otherChild.declaration, otherChild.docname) + elif ourChild.docname != otherChild.docname: + name = text_type(ourChild.declaration) + msg = "Duplicate declaration, also defined in '%s'.\n" + msg += "Declaration is '%s'." + msg = msg % (ourChild.docname, name) + env.warn(otherChild.docname, msg) + else: + # Both have declarations, and in the same docname. + # This can apparently happen, it should be safe to + # just ignore it, right? + pass + ourChild.merge_with(otherChild, docnames, env) def add_name(self, nestedName, templatePrefix=None): if templatePrefix: templateDecls = templatePrefix.templates else: templateDecls = [] - return self._add_symbols(nestedName, templateDecls, declaration=None) + return self._add_symbols(nestedName, templateDecls, + declaration=None, docname=None) - def add_declaration(self, declaration): + def add_declaration(self, declaration, docname): + assert declaration + assert docname nestedName = declaration.name if declaration.templatePrefix: templateDecls = declaration.templatePrefix.templates else: templateDecls = [] - return self._add_symbols(nestedName, templateDecls, declaration) + return self._add_symbols(nestedName, templateDecls, declaration, docname) - def find_identifier(self, identifier): + def find_identifier(self, identifier, matchSelf): + if matchSelf and self.identifier and self.identifier == identifier: + return self for s in self.children: if s.identifier and s.identifier == identifier: return s @@ -2289,12 +2526,16 @@ class Symbol(object): templateArgs = name.templateArgs operator = None s = s._find_named_symbol(identifier, templateParams, - templateArgs, operator) + templateArgs, operator, + templateShorthand=False, + matchSelf=True) if not s: return None return s - def find_name(self, nestedName, templateDecls, specific_specialisation): + def find_name(self, nestedName, templateDecls, templateShorthand, matchSelf): + # templateShorthand: missing template parameter lists for templates is ok + # TODO: unify this with the _add_symbols # This condition should be checked at the parser level. assert len(templateDecls) <= nestedName.num_templates() + 1 @@ -2308,7 +2549,8 @@ class Symbol(object): firstName = names[0] if not firstName.is_operator(): while parentSymbol.parent: - if parentSymbol.find_identifier(firstName.identifier): + if parentSymbol.find_identifier(firstName.identifier, + matchSelf=matchSelf): break parentSymbol = parentSymbol.parent @@ -2333,12 +2575,12 @@ class Symbol(object): symbol = parentSymbol._find_named_symbol(identifier, templateParams, templateArgs, - operator) + operator, + templateShorthand=templateShorthand, + matchSelf=matchSelf) if symbol: return symbol else: - # TODO: search for version without template args, - # if not specific_specialisation return None else: # there shouldn't be anything inside an operator @@ -2353,29 +2595,42 @@ class Symbol(object): symbol = parentSymbol._find_named_symbol(identifier, templateParams, templateArgs, - operator=None) + operator=None, + templateShorthand=templateShorthand, + matchSelf=matchSelf) if symbol is None: # TODO: maybe search without template args return None parentSymbol = symbol assert False # should have returned in the loop - def dump(self, indent): + def to_string(self, indent): res = ['\t'*indent] - if self.identifier: + if not self.parent: + res.append('::') + else: if self.templateParams: res.append(text_type(self.templateParams)) res.append('\n') res.append('\t'*indent) - res.append(text_type(self.identifier)) + if self.identifier: + res.append(text_type(self.identifier)) + else: + res.append(text_type(self.declaration)) if self.templateArgs: res.append(text_type(self.templateArgs)) if self.declaration: res.append(": ") res.append(text_type(self.declaration)) - else: - res.append('::') + if self.docname: + res.append('\t(') + res.append(self.docname) + res.append(')') res.append('\n') + return ''.join(res) + + def dump(self, indent): + res = [self.to_string(indent)] for c in self.children: res.append(c.dump(indent + 1)) return ''.join(res) @@ -2400,6 +2655,30 @@ class DefinitionParser(object): self.warnEnv = warnEnv + def _make_multi_error(self, errors, header): + if len(errors) == 1: + return DefinitionError(header + '\n' + errors[0][0].description) + result = [header, '\n'] + for e in errors: + if len(e[1]) > 0: + ident = ' ' + result.append(e[1]) + result.append(':\n') + for line in e[0].description.split('\n'): + if len(line) == 0: + continue + result.append(ident) + result.append(line) + result.append('\n') + else: + result.append(e[0].description) + return DefinitionError(''.join(result)) + + def status(self, msg): + # for debugging + indicator = '-' * self.pos + '^' + print("%s\n%s\n%s" % (msg, self.definition, indicator)) + def fail(self, msg): indicator = '-' * self.pos + '^' raise DefinitionError( @@ -2467,8 +2746,7 @@ class DefinitionParser(object): def assert_end(self): self.skip_ws() if not self.eof: - self.fail('expected end of definition, got %r' % - self.definition[self.pos:]) + self.fail('Expected end of definition.') def _parse_expression(self, end): # Stupidly "parse" an expression. @@ -2515,6 +2793,14 @@ class DefinitionParser(object): op += '[]' return ASTOperatorBuildIn(op) + # user-defined literal? + if self.skip_string('""'): + self.skip_ws() + if not self.match(_identifier_re): + self.fail("Expected user-defined literal suffix.") + identifier = ASTIdentifier(self.matched_text) + return ASTOperatorLiteral(identifier) + # oh well, looks like a cast operator definition. # In that case, eat another type. type = self._parse_type(named=False, outer="operatorCast") @@ -2524,6 +2810,7 @@ class DefinitionParser(object): self.skip_ws() if not self.skip_string('<'): return None + prevErrors = [] templateArgs = [] while 1: pos = self.pos @@ -2540,7 +2827,7 @@ class DefinitionParser(object): self.fail('Expected ">" or "," in template argument list.') templateArgs.append(type) except DefinitionError as e: - errorType = e.description + prevErrors.append((e, "If type argument")) self.pos = pos try: value = self._parse_expression(end=[',', '>']) @@ -2553,18 +2840,16 @@ class DefinitionParser(object): self.fail('Expected ">" or "," in template argument list.') templateArgs.append(ASTTemplateArgConstant(value)) except DefinitionError as e: - errorExpr = e.description - msg = "Error in parsing template argument list. " \ - "Error if type argument:\n%s\n" \ - "Error if non-type argument:\n%s" \ - % (errorType, errorExpr) - self.fail(msg) + self.pos = pos + prevErrors.append((e, "If non-type argument")) + header = "Error in parsing template argument list." + raise self._make_multi_error(prevErrors, header) if parsedEnd: assert not parsedComma break return ASTTemplateArgs(templateArgs) - def _parse_nested_name(self): + def _parse_nested_name(self, memberPointer=False): names = [] self.skip_ws() @@ -2580,6 +2865,8 @@ class DefinitionParser(object): names.append(op) else: if not self.match(_identifier_re): + if memberPointer and len(names) > 0: + break self.fail("Expected identifier in nested name.") identifier = self.matched_text # make sure there isn't a keyword @@ -2592,6 +2879,8 @@ class DefinitionParser(object): self.skip_ws() if not self.skip_string('::'): + if memberPointer: + self.fail("Expected '::' in pointer to member (function).") break return ASTNestedName(names, rooted) @@ -2660,7 +2949,7 @@ class DefinitionParser(object): 'parameters_and_qualifiers.') break if paramMode == 'function': - arg = self._parse_type_with_init(outer=None, named='maybe') + arg = self._parse_type_with_init(outer=None, named='single') else: arg = self._parse_type(named=False) # TODO: parse default parameters # TODO: didn't we just do that? @@ -2676,7 +2965,10 @@ class DefinitionParser(object): 'Expecting "," or ")" in parameters_and_qualifiers, ' 'got "%s".' % self.current_char) - if paramMode != 'function': + # TODO: why did we have this bail-out? + # does it hurt to parse the extra stuff? + # it's needed for pointer to member functions + if paramMode != 'function' and False: return ASTParametersQualifiers( args, None, None, None, None, None, None, None) @@ -2817,26 +3109,27 @@ class DefinitionParser(object): def _parse_declarator_name_param_qual(self, named, paramMode, typed): # now we should parse the name, and then suffixes if named == 'maybe': + pos = self.pos try: declId = self._parse_nested_name() except DefinitionError: + self.pos = pos declId = None elif named == 'single': if self.match(_identifier_re): identifier = ASTIdentifier(self.matched_text) nne = ASTNestedNameElement(identifier, None) declId = ASTNestedName([nne], rooted=False) + # if it's a member pointer, we may have '::', which should be an error + self.skip_ws() + if self.current_char == ':': + self.fail("Unexpected ':' after identifier.") else: declId = None elif named: declId = self._parse_nested_name() else: declId = None - self.skip_ws() - if typed and declId: - if self.skip_string("*"): - self.fail("Member pointers not implemented.") - arrayOps = [] while 1: self.skip_ws() @@ -2857,6 +3150,7 @@ class DefinitionParser(object): if paramMode not in ('type', 'function', 'operatorCast'): raise Exception( "Internal error, unknown paramMode '%s'." % paramMode) + prevErrors = [] self.skip_ws() if typed and self.skip_string('*'): self.skip_ws() @@ -2875,13 +3169,39 @@ class DefinitionParser(object): next = self._parse_declerator(named, paramMode, typed) return ASTDeclaratorPtr(next=next, volatile=volatile, const=const) # TODO: shouldn't we parse an R-value ref here first? - elif typed and self.skip_string("&"): + if typed and self.skip_string("&"): next = self._parse_declerator(named, paramMode, typed) return ASTDeclaratorRef(next=next) - elif typed and self.skip_string("..."): + if typed and self.skip_string("..."): next = self._parse_declerator(named, paramMode, False) return ASTDeclaratorParamPack(next=next) - elif typed and self.current_char == '(': # note: peeking, not skipping + if typed: # pointer to member + pos = self.pos + try: + name = self._parse_nested_name(memberPointer=True) + self.skip_ws() + if not self.skip_string('*'): + self.fail("Expected '*' in pointer to member declarator.") + self.skip_ws() + except DefinitionError as e: + self.pos = pos + prevErrors.append((e, "If pointer to member declarator")) + else: + volatile = False + const = False + while 1: + if not volatile: + volatile = self.skip_word_and_ws('volatile') + if volatile: + continue + if not const: + const = self.skip_word_and_ws('const') + if const: + continue + break + next = self._parse_declerator(named, paramMode, typed) + return ASTDeclaratorMemPtr(name, const, volatile, next=next) + if typed and self.current_char == '(': # note: peeking, not skipping if paramMode == "operatorCast": # TODO: we should be able to parse cast operators which return # function pointers. For now, just hax it and ignore. @@ -2889,14 +3209,15 @@ class DefinitionParser(object): paramQual=None) # maybe this is the beginning of params and quals,try that first, # otherwise assume it's noptr->declarator > ( ptr-declarator ) - startPos = self.pos + pos = self.pos try: # assume this is params and quals res = self._parse_declarator_name_param_qual(named, paramMode, typed) return res except DefinitionError as exParamQual: - self.pos = startPos + prevErrors.append((exParamQual, "If declId, parameters, and qualifiers")) + self.pos = pos try: assert self.current_char == '(' self.skip_string('(') @@ -2911,13 +3232,18 @@ class DefinitionParser(object): typed=typed) return ASTDeclaratorParen(inner=inner, next=next) except DefinitionError as exNoPtrParen: - raise DefinitionError( - "If declId, parameters, and qualifiers {\n%s\n" - "} else If parenthesis in noptr-declarator {\n%s\n}" - % (exParamQual, exNoPtrParen)) - else: - return self._parse_declarator_name_param_qual(named, paramMode, - typed) + self.pos = pos + prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator")) + header = "Error in declarator" + raise self._make_multi_error(prevErrors, header) + pos = self.pos + try: + return self._parse_declarator_name_param_qual(named, paramMode, typed) + except DefinitionError as e: + self.pos = pos + prevErrors.append((e, "If declarator-id")) + header = "Error in declarator or parameters and qualifiers" + raise self._make_multi_error(prevErrors, header) def _parse_initializer(self, outer=None): self.skip_ws() @@ -2954,6 +3280,7 @@ class DefinitionParser(object): # We allow type objects to just be a name. # Some functions don't have normal return types: constructors, # destrutors, cast operators + prevErrors = [] startPos = self.pos # first try without the type try: @@ -2962,37 +3289,49 @@ class DefinitionParser(object): typed=False) self.assert_end() except DefinitionError as exUntyped: + if outer == 'type': + desc = "If just a name" + elif outer == 'function': + desc = "If the function has no return type" + else: + assert False + prevErrors.append((exUntyped, desc)) self.pos = startPos try: declSpecs = self._parse_decl_specs(outer=outer) decl = self._parse_declerator(named=True, paramMode=outer) except DefinitionError as exTyped: + self.pos = startPos + if outer == 'type': + desc = "If typedef-like declaration" + elif outer == 'function': + desc = "If the function has a return type" + else: + assert False + prevErrors.append((exTyped, desc)) # Retain the else branch for easier debugging. # TODO: it would be nice to save the previous stacktrace # and output it here. if True: if outer == 'type': - desc = ('Type must be either just a name or a ' - 'typedef-like declaration.\n' - 'Just a name error: %s\n' - 'Typedef-like expression error: %s') + header = "Type must be either just a name or a " + header += "typedef-like declaration." elif outer == 'function': - desc = ('Error when parsing function declaration:\n' - 'If no return type {\n%s\n' - '} else if return type {\n%s\n}') + header = "Error when parsing function declaration." else: assert False - raise DefinitionError( - desc % (exUntyped.description, exTyped.description)) + raise self._make_multi_error(prevErrors, header) else: # For testing purposes. # do it again to get the proper traceback (how do you # relieable save a traceback when an exception is # constructed?) + pass self.pos = startPos - declSpecs = self._parse_decl_specs(outer=outer, typed=False) + typed = True + declSpecs = self._parse_decl_specs(outer=outer, typed=typed) decl = self._parse_declerator(named=True, paramMode=outer, - typed=False) + typed=typed) else: paramMode = 'type' if outer == 'member': # i.e., member @@ -3078,7 +3417,7 @@ class DefinitionParser(object): if not self.skip_string("<"): self.fail("Expected '<' after 'template'") while 1: - extraError = '' + prevErrors = [] self.skip_ws() if self.skip_word('template'): # declare a tenplate template parameter @@ -3124,19 +3463,20 @@ class DefinitionParser(object): param = self._parse_type_with_init('maybe', 'templateParam') templateParams.append(ASTTemplateParamNonType(param)) except DefinitionError as e: + prevErrors.append((e, "If non-type template parameter")) self.pos = pos - extraError = "Error if non-type template parameter: %s" - extraError = extraError % e.description self.skip_ws() if self.skip_string('>'): return ASTTemplateParams(templateParams) elif self.skip_string(','): continue else: - msg = 'Expected "=", ",", or ">" in template parameter list.' - if len(extraError) > 0: - msg += '\n%s' % extraError - self.fail(msg) + header = "Error in template parameter list." + try: + self.fail('Expected "=", ",", or ">".') + except DefinitionError as e: + prevErrors.append((e, "")) + raise self._make_multi_error(prevErrors, header) def _parse_template_declaration_prefix(self): templates = [] @@ -3151,7 +3491,8 @@ class DefinitionParser(object): else: return ASTTemplateDeclarationPrefix(templates) - def _check_template_consistency(self, nestedName, templatePrefix): + def _check_template_consistency(self, nestedName, templatePrefix, + fullSpecShorthand): numArgs = nestedName.num_templates() if not templatePrefix: numParams = 0 @@ -3163,15 +3504,16 @@ class DefinitionParser(object): % (numArgs, numParams)) if numArgs > numParams: numExtra = numArgs - numParams - msg = "Too many template argument lists compared to parameter" \ - " lists. Argument lists: %d, Parameter lists: %d," \ - " Extra empty parameters lists prepended: %d." \ - % (numArgs, numParams, numExtra) - msg += " Declaration:\n\t" - if templatePrefix: - msg += "%s\n\t" % text_type(templatePrefix) - msg += text_type(nestedName) - self.warn(msg) + if not fullSpecShorthand: + msg = "Too many template argument lists compared to parameter" \ + " lists. Argument lists: %d, Parameter lists: %d," \ + " Extra empty parameters lists prepended: %d." \ + % (numArgs, numParams, numExtra) + msg += " Declaration:\n\t" + if templatePrefix: + msg += "%s\n\t" % text_type(templatePrefix) + msg += text_type(nestedName) + self.warn(msg) newTemplates = [] for i in range(numExtra): @@ -3197,25 +3539,23 @@ class DefinitionParser(object): templatePrefix = self._parse_template_declaration_prefix() if objectType == 'type': - error = None + prevErrors = [] pos = self.pos try: if not templatePrefix: declaration = self._parse_type(named=True, outer='type') except DefinitionError as e: - error = e.description + prevErrors.append((e, "If typedef-like declaration")) self.pos = pos + pos = self.pos try: if not declaration: declaration = self._parse_type_using() except DefinitionError as e: - if error: - msg = "Error if typedef:\n%s\n" \ - "Error if type alias or template alias:\n%s" \ - % (error, e.description) - raise DefinitionError(msg) - else: - raise e + self.pos = pos + prevErrors.append((e, "If type alias or template alias")) + header = "Error in type declaration." + raise self._make_multi_error(prevErrors, header) elif objectType == 'member': declaration = self._parse_type_with_init(named=True, outer='member') elif objectType == 'function': @@ -3229,14 +3569,16 @@ class DefinitionParser(object): else: assert False templatePrefix = self._check_template_consistency(declaration.name, - templatePrefix) + templatePrefix, + fullSpecShorthand=False) return ASTDeclaration(objectType, visibility, templatePrefix, declaration) def parse_namespace_object(self): templatePrefix = self._parse_template_declaration_prefix() name = self._parse_nested_name() - templatePrefix = self._check_template_consistency(name, templatePrefix) + templatePrefix = self._check_template_consistency(name, templatePrefix, + fullSpecShorthand=False) res = ASTNamespace(name, templatePrefix) res.objectType = 'namespace' return res @@ -3244,6 +3586,8 @@ class DefinitionParser(object): def parse_xref_object(self): templatePrefix = self._parse_template_declaration_prefix() name = self._parse_nested_name() + templatePrefix = self._check_template_consistency(name, templatePrefix, + fullSpecShorthand=True) res = ASTNamespace(name, templatePrefix) res.objectType = 'xref' return res @@ -3261,6 +3605,9 @@ class CPPObject(ObjectDescription): GroupedField('parameter', label=l_('Parameters'), names=('param', 'parameter', 'arg', 'argument'), can_collapse=True), + GroupedField('template parameter', label=l_('Template Parameters'), + names=('tparam', 'template parameter'), + can_collapse=True), GroupedField('exceptions', label=l_('Throws'), rolename='cpp:class', names=('throws', 'throw', 'exception'), can_collapse=True), @@ -3269,7 +3616,7 @@ class CPPObject(ObjectDescription): ] def warn(self, msg): - self.state_machine.reporter.warning(msg, lineno=self.lineno) + self.state_machine.reporter.warning(msg, line=self.lineno) def _add_enumerator_to_parent(self, ast): assert ast.objectType == 'enumerator' @@ -3301,13 +3648,16 @@ class CPPObject(ObjectDescription): return targetSymbol = parentSymbol.parent - s = targetSymbol.find_identifier(symbol.identifier) + s = targetSymbol.find_identifier(symbol.identifier, matchSelf=False) if s is not None: # something is already declared with that name return + declClone = symbol.declaration.clone() + declClone.enumeratorScopedSymbol = symbol Symbol(parent=targetSymbol, identifier=symbol.identifier, templateParams=None, templateArgs=None, - declaration=symbol.declaration.clone()) + declaration=declClone, + docname=self.env.docname) def add_target_and_index(self, ast, sig, signode): # general note: name must be lstrip(':')'ed, to remove "::" @@ -3368,9 +3718,8 @@ class CPPObject(ObjectDescription): symbol = parentSymbol.add_name(name) self.env.ref_context['cpp:lastSymbol'] = symbol raise ValueError - symbol = parentSymbol.add_declaration(ast) + symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) self.env.ref_context['cpp:lastSymbol'] = symbol - symbol.docname = self.env.docname if ast.objectType == 'enumerator': self._add_enumerator_to_parent(ast) @@ -3486,7 +3835,7 @@ class CPPNamespaceObject(Directive): option_spec = {} def warn(self, msg): - self.state_machine.reporter.warning(msg, lineno=self.lineno) + self.state_machine.reporter.warning(msg, line=self.lineno) def run(self): env = self.state.document.settings.env @@ -3500,8 +3849,7 @@ class CPPNamespaceObject(Directive): ast = parser.parse_namespace_object() parser.assert_end() except DefinitionError as e: - self.state_machine.reporter.warning(e.description, - line=self.lineno) + self.warn(e.description) name = _make_phony_error_name() ast = ASTNamespace(name, None) symbol = rootSymbol.add_name(ast.nestedName, ast.templatePrefix) @@ -3519,7 +3867,7 @@ class CPPNamespacePushObject(Directive): option_spec = {} def warn(self, msg): - self.state_machine.reporter.warning(msg, lineno=self.lineno) + self.state_machine.reporter.warning(msg, line=self.lineno) def run(self): env = self.state.document.settings.env @@ -3530,8 +3878,7 @@ class CPPNamespacePushObject(Directive): ast = parser.parse_namespace_object() parser.assert_end() except DefinitionError as e: - self.state_machine.reporter.warning(e.description, - line=self.lineno) + self.warn(e.description) name = _make_phony_error_name() ast = ASTNamespace(name, None) oldParent = env.ref_context.get('cpp:parentSymbol', None) @@ -3553,7 +3900,7 @@ class CPPNamespacePopObject(Directive): option_spec = {} def warn(self, msg): - self.state_machine.reporter.warning(msg, lineno=self.lineno) + self.state_machine.reporter.warning(msg, line=self.lineno) def run(self): env = self.state.document.settings.env @@ -3577,6 +3924,9 @@ class CPPXRefRole(XRefRole): parent = env.ref_context.get('cpp:parentSymbol', None) if parent: refnode['cpp:parentKey'] = parent.get_lookup_key() + if refnode['reftype'] == 'any': + # Remove parentheses from the target (not from title) + title, target = self._fix_parens(env, True, title, target) # TODO: should this really be here? if not has_explicit_title: target = target.lstrip('~') # only has a meaning for the title @@ -3628,37 +3978,36 @@ class CPPDomain(Domain): 'enumerator': CPPXRefRole() } initial_data = { - 'rootSymbol': Symbol(None, None, None, None, None), + 'rootSymbol': Symbol(None, None, None, None, None, None), 'names': {} # full name for indexing -> docname } def clear_doc(self, docname): rootSymbol = self.data['rootSymbol'] - for symbol in rootSymbol.get_all_symbols(): - if not symbol.declaration: - continue - try: - sDocname = symbol.docname - except AttributeError: - # it's a template parameter - # the symbols are yielded in post-order, so this should be fine - assert symbol.parent - sDocname = symbol.parent.docname - if sDocname != docname: - continue - symbol.declaration = None - symbol.docname = None + rootSymbol.clear_doc(docname) for name, nDocname in list(self.data['names'].items()): if nDocname == docname: del self.data['names'][name] def process_doc(self, env, docname, document): # just for debugging + # print(docname) # print(self.data['rootSymbol'].dump(0)) pass - # def merge_domaindata(self, docnames, otherdata): - # # TODO: merge rootSymbol + def merge_domaindata(self, docnames, otherdata): + self.data['rootSymbol'].merge_with(otherdata['rootSymbol'], + docnames, self.env) + ourNames = self.data['names'] + for name, docname in otherdata['names'].items(): + if docname in docnames: + if name in ourNames: + msg = "Duplicate declaration, also defined in '%s'.\n" + msg += "Name of declaration is '%s'." + msg = msg % (ourNames[name], name) + self.env.warn(docname, msg) + else: + ourNames[name] = docname def _resolve_xref_inner(self, env, fromdocname, builder, target, node, contnode, emitWarnings=True): @@ -3671,10 +4020,10 @@ class CPPDomain(Domain): try: ast = parser.parse_xref_object() parser.skip_ws() - if not parser.eof: - raise DefinitionError('') - except DefinitionError: - warner.warn('Unparseable C++ cross-reference: %r' % target) + parser.assert_end() + except DefinitionError as e: + warner.warn('Unparseable C++ cross-reference: %r\n%s' + % (target, str(e.description))) return None, None parentKey = node.get("cpp:parentKey", None) rootSymbol = self.data['rootSymbol'] @@ -3693,18 +4042,23 @@ class CPPDomain(Domain): else: templateDecls = [] s = parentSymbol.find_name(name, templateDecls, - specific_specialisation=False) + templateShorthand=True, + matchSelf=True) if s is None or s.declaration is None: return None, None declaration = s.declaration fullNestedName = s.get_full_nested_name() name = text_type(fullNestedName).lstrip(':') - try: - docname = s.docname - except AttributeError: - # it's a template parameter - assert s.parent - docname = s.parent.docname + docname = s.docname + assert docname + if declaration.objectType == 'function': + title = name + if title.endswith('()'): + title = title[:-2] # remove parentheses + if env.config.add_function_parentheses: + title += '()' + contnode.pop(0) + contnode.insert(0, nodes.Text(title)) return make_refnode(builder, fromdocname, docname, declaration.get_newest_id(), contnode, name ), declaration.objectType @@ -3726,13 +4080,11 @@ class CPPDomain(Domain): def get_objects(self): rootSymbol = self.data['rootSymbol'] for symbol in rootSymbol.get_all_symbols(): - if not symbol.declaration: + if symbol.declaration is None: continue + assert symbol.docname name = text_type(symbol.get_full_nested_name()).lstrip(':') objectType = symbol.declaration.objectType - try: - docname = symbol.docname - except AttributeError: - continue + docname = symbol.docname newestId = symbol.declaration.get_newest_id() yield (name, name, objectType, docname, newestId, 1) diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index 843629b3b..f7e2703e4 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -5,7 +5,7 @@ The JavaScript domain. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index b64e797b0..ba5998084 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -5,7 +5,7 @@ The Python domain. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index b47961469..f0ad66ddc 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -5,7 +5,7 @@ The reStructuredText domain. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index bcadc3a11..371b39b80 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -5,7 +5,7 @@ The standard domain. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -210,10 +210,6 @@ class Program(Directive): class OptionXRefRole(XRefRole): def process_link(self, env, refnode, has_explicit_title, title, target): - # validate content - if not re.match(r'(.+ )?[-/+\w]', target): - env.warn_node('Malformed :option: %r, does not contain option ' - 'marker - or -- or / or +' % target, refnode) refnode['std:program'] = env.ref_context.get('std:program') return title, target @@ -457,7 +453,7 @@ class StandardDomain(Domain): 'productionlist': ProductionList, } roles = { - 'option': OptionXRefRole(), + 'option': OptionXRefRole(warn_dangling=True), 'envvar': EnvVarXRefRole(), # links to tokens in grammar productions 'token': XRefRole(), @@ -495,6 +491,7 @@ class StandardDomain(Domain): 'the label must precede a section header)', 'numref': 'undefined label: %(target)s', 'keyword': 'unknown keyword: %(target)s', + 'option': 'unknown option: %(target)s', } def clear_doc(self, docname): @@ -633,7 +630,8 @@ class StandardDomain(Domain): return None if env.config.numfig is False: - env.warn(fromdocname, 'numfig is disabled. :numref: is ignored.') + env.warn(fromdocname, 'numfig is disabled. :numref: is ignored.', + lineno=node.line) return contnode try: @@ -648,7 +646,13 @@ class StandardDomain(Domain): if target == fully_normalize_name(title): title = env.config.numfig_format.get(figtype, '') - newtitle = title % '.'.join(map(str, fignumber)) + try: + newtitle = title % '.'.join(map(str, fignumber)) + except TypeError: + env.warn(fromdocname, 'invalid numfig_format: %s' % title, + lineno=node.line) + return None + return self.build_reference_node(fromdocname, builder, docname, labelid, newtitle, 'numref', nodeclass=addnodes.number_reference, @@ -661,22 +665,23 @@ class StandardDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) elif typ == 'option': + progname = node.get('std:program') target = target.strip() - # most obvious thing: we are a flag option without program - if target.startswith(('-', '/', '+')): - progname = node.get('std:program') - elif re.search(r'[-/+]', target): - try: - progname, target = re.split(r' (?=-|--|/|\+)', target, 1) - except ValueError: - return None - progname = ws_re.sub('-', progname.strip()) - else: - progname = None - docname, labelid = self.data['progoptions'].get((progname, target), - ('', '')) + docname, labelid = self.data['progoptions'].get((progname, target), ('', '')) if not docname: - return None + commands = [] + while ws_re.search(target): + subcommand, target = ws_re.split(target, 1) + commands.append(subcommand) + progname = "-".join(commands) + + docname, labelid = self.data['progoptions'].get((progname, target), + ('', '')) + if docname: + break + else: + return None + return make_refnode(builder, fromdocname, docname, labelid, contnode) else: diff --git a/sphinx/environment.py b/sphinx/environment.py index 58463cae8..f919c0048 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -5,7 +5,7 @@ Global creation environment. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -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 @@ -38,7 +38,7 @@ from docutils.frontend import OptionParser from sphinx import addnodes from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ - FilenameUniqDict, get_figtype, import_object + FilenameUniqDict, get_figtype, import_object, split_index_msg from sphinx.util.nodes import clean_astext, make_refnode, WarningStream, is_translatable from sphinx.util.osutil import SEP, getcwd, fs_encoding from sphinx.util.i18n import find_catalog_files @@ -105,13 +105,16 @@ class SphinxStandaloneReader(standalone.Reader): DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, AutoNumbering, SortIds, RemoveTranslatableInline] - def __init__(self, parsers={}, *args, **kwargs): + def __init__(self, app, parsers={}, *args, **kwargs): standalone.Reader.__init__(self, *args, **kwargs) self.parser_map = {} for suffix, parser_class in parsers.items(): if isinstance(parser_class, string_types): parser_class = import_object(parser_class, 'source parser') - self.parser_map[suffix] = parser_class() + parser = parser_class() + if hasattr(parser, 'set_application'): + parser.set_application(app) + self.parser_map[suffix] = parser def read(self, source, parser, settings): self.source = source @@ -618,8 +621,8 @@ class BuildEnvironment: self._read_serial(docnames, app) if config.master_doc not in self.all_docs: - self.warn(None, 'master file %s not found' % - self.doc2path(config.master_doc)) + raise SphinxError('master file %s not found' % + self.doc2path(config.master_doc)) self.app = None @@ -776,7 +779,7 @@ class BuildEnvironment: codecs.register_error('sphinx', self.warn_and_replace) # publish manually - reader = SphinxStandaloneReader(parsers=self.config.source_parsers) + reader = SphinxStandaloneReader(self.app, parsers=self.config.source_parsers) pub = Publisher(reader=reader, writer=SphinxDummyWriter(), destination_class=NullOutput) @@ -1123,7 +1126,14 @@ class BuildEnvironment: def note_indexentries_from(self, docname, document): entries = self.indexentries[docname] = [] for node in document.traverse(addnodes.index): - entries.extend(node['entries']) + try: + for type, value, tid, main in node['entries']: + split_index_msg(type, value) + except ValueError as exc: + self.warn_node(exc, node) + node.parent.remove(node) + else: + entries.extend(node['entries']) def note_citations_from(self, docname, document): for node in document.traverse(nodes.citation): @@ -1656,7 +1666,7 @@ class BuildEnvironment: for role in domain.roles: res = domain.resolve_xref(self, refdoc, builder, role, target, node, contnode) - if res: + if res and isinstance(res[0], nodes.Element): results.append(('%s:%s' % (domain.name, role), res)) # now, see how many matches we got... if not results: @@ -1951,54 +1961,31 @@ class BuildEnvironment: for (key_, group) in groupby(newlist, keyfunc2)] def collect_relations(self): - relations = {} - getinc = self.toctree_includes.get + 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 = {} + 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/errors.py b/sphinx/errors.py index 6761e87f6..8d695c190 100644 --- a/sphinx/errors.py +++ b/sphinx/errors.py @@ -6,7 +6,7 @@ Contains SphinxError and a few subclasses (in an extra module to avoid circular import problems). - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/__init__.py b/sphinx/ext/__init__.py index 62c6416c3..776250c4d 100644 --- a/sphinx/ext/__init__.py +++ b/sphinx/ext/__init__.py @@ -5,6 +5,6 @@ Contains Sphinx features not activated by default. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index a6a2a11a3..fc2e237b6 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -7,7 +7,7 @@ the doctree, thus avoiding duplication between docstrings and documentation for those who like elaborate docstrings. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -17,8 +17,8 @@ import inspect import traceback from types import FunctionType, BuiltinFunctionType, MethodType -from six import iteritems, itervalues, text_type, class_types, string_types, \ - StringIO +from six import iterkeys, iteritems, itervalues, text_type, class_types, \ + string_types, StringIO from docutils import nodes from docutils.utils import assemble_option_dict from docutils.statemachine import ViewList @@ -720,7 +720,7 @@ class Documenter(object): # __dict__ contains only the members directly defined in # the class (but get them via getattr anyway, to e.g. get # unbound method objects instead of function objects); - # using keys() because apparently there are objects for which + # using list(iterkeys()) because apparently there are objects for which # __dict__ changes while getting attributes try: obj_dict = self.get_attr(self.object, '__dict__') @@ -728,7 +728,7 @@ class Documenter(object): members = [] else: members = [(mname, self.get_attr(self.object, mname, None)) - for mname in obj_dict.keys()] + for mname in list(iterkeys(obj_dict))] membernames = set(m[0] for m in members) # add instance attributes from the analyzer for aname in analyzed_member_names: @@ -1292,9 +1292,11 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): docstrings.append(initdocstring) doc = [] for docstring in docstrings: - if not isinstance(docstring, text_type): - docstring = force_decode(docstring, encoding) - doc.append(prepare_docstring(docstring)) + if isinstance(docstring, text_type): + doc.append(prepare_docstring(docstring, ignore)) + elif isinstance(docstring, str): # this will not trigger on Py3 + doc.append(prepare_docstring(force_decode(docstring, encoding), + ignore)) return doc def add_content(self, more_content, no_docstring=False): diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index a53b125d5..9ef48ca88 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -49,7 +49,7 @@ resolved to a Python object, and otherwise it becomes simple emphasis. This can be used as the default role to make links 'smart'. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -583,5 +583,5 @@ def setup(app): app.add_role('autolink', autolink_role) app.connect('doctree-read', process_autosummary_toc) app.connect('builder-inited', process_generate_options) - app.add_config_value('autosummary_generate', [], True) + app.add_config_value('autosummary_generate', [], True, [bool]) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 2cacadfd7..8495da7b4 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -14,7 +14,7 @@ generate: sphinx-autogen -o source/generated source/*.rst - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index c564aeedd..78281bb85 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -6,7 +6,7 @@ Check Python modules and C API for coverage. Mostly written by Josip Dzolonga for the Google Highly Open Participation contest. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index e22024d42..0f5241a19 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -6,7 +6,7 @@ Mimic doctest by automatically executing code snippets and checking their results. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import absolute_import @@ -45,14 +45,6 @@ else: return text -class _SpoofOutSphinx(doctest._SpoofOut): - # override: convert console encoding to unicode - if PY2: - def getvalue(self): - result = doctest._SpoofOut.getvalue(self) - return result.decode('string_escape') - - # set up the necessary directives class TestDirective(Directive): @@ -184,11 +176,6 @@ class TestCode(object): class SphinxDocTestRunner(doctest.DocTestRunner): - def __init__(self, *args, **kw): - doctest.DocTestRunner.__init__(self, *args, **kw) - # Override a fake output target for capturing doctest output. - self._fakeout = _SpoofOutSphinx() - def summarize(self, out, verbose=None): string_io = StringIO() old_stdout = sys.stdout @@ -262,9 +249,10 @@ Results of doctest builder run on %s self.outfile.write(text) def _warn_out(self, text): - self.info(text, nonl=True) - if self.app.quiet: + if self.app.quiet or self.app.warningiserror: self.warn(text) + else: + self.info(text, nonl=True) if isinstance(text, binary_type): text = force_decode(text, None) self.outfile.write(text) diff --git a/sphinx/ext/extlinks.py b/sphinx/ext/extlinks.py index 1f93fc944..8bd8017a9 100644 --- a/sphinx/ext/extlinks.py +++ b/sphinx/ext/extlinks.py @@ -20,7 +20,7 @@ You can also give an explicit caption, e.g. :exmpl:`Foo <foo>`. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/githubpages.py b/sphinx/ext/githubpages.py new file mode 100644 index 000000000..1d5c4c55d --- /dev/null +++ b/sphinx/ext/githubpages.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.githubpages + ~~~~~~~~~~~~~~~~~~~~~~ + + To publish HTML docs at GitHub Pages, create .nojekyll file. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import os +import sphinx + + +def create_nojekyll(app, env): + if app.builder.format == 'html': + path = os.path.join(app.builder.outdir, '.nojekyll') + open(path, 'wt').close() + + +def setup(app): + app.connect('env-updated', create_nojekyll) + return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 3970edc07..44a47d5e1 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -6,7 +6,7 @@ Allow graphviz-formatted graphs to be included in Sphinx-generated documents inline. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -36,7 +36,7 @@ class GraphvizError(SphinxError): category = 'Graphviz error' -class graphviz(nodes.General, nodes.Element): +class graphviz(nodes.General, nodes.Inline, nodes.Element): pass @@ -66,6 +66,7 @@ class Graphviz(Directive): 'alt': directives.unchanged, 'inline': directives.flag, 'caption': directives.unchanged, + 'graphviz_dot': directives.unchanged, } def run(self): @@ -96,13 +97,16 @@ class Graphviz(Directive): line=self.lineno)] node = graphviz() node['code'] = dotcode - node['options'] = [] + node['options'] = {} + if 'graphviz_dot' in self.options: + node['options']['graphviz_dot'] = self.options['graphviz_dot'] if 'alt' in self.options: node['alt'] = self.options['alt'] - node['inline'] = 'inline' in self.options + if 'inline' in self.options: + node['inline'] = True caption = self.options.get('caption') - if caption and not node['inline']: + if caption: node = figure_wrapper(self, node, caption) return [node] @@ -120,19 +124,23 @@ class GraphvizSimple(Directive): 'alt': directives.unchanged, 'inline': directives.flag, 'caption': directives.unchanged, + 'graphviz_dot': directives.unchanged, } def run(self): node = graphviz() node['code'] = '%s %s {\n%s\n}\n' % \ (self.name, self.arguments[0], '\n'.join(self.content)) - node['options'] = [] + node['options'] = {} + if 'graphviz_dot' in self.options: + node['options']['graphviz_dot'] = self.options['graphviz_dot'] if 'alt' in self.options: node['alt'] = self.options['alt'] - node['inline'] = 'inline' in self.options + if 'inline' in self.options: + node['inline'] = True caption = self.options.get('caption') - if caption and not node['inline']: + if caption: node = figure_wrapper(self, node, caption) return [node] @@ -140,8 +148,8 @@ class GraphvizSimple(Directive): def render_dot(self, code, options, format, prefix='graphviz'): """Render graphviz code into a PNG or PDF output file.""" - hashkey = (code + str(options) + - str(self.builder.config.graphviz_dot) + + graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot) + hashkey = (code + str(options) + str(graphviz_dot) + str(self.builder.config.graphviz_dot_args)).encode('utf-8') fname = '%s-%s.%s' % (prefix, sha1(hashkey).hexdigest(), format) @@ -151,8 +159,8 @@ def render_dot(self, code, options, format, prefix='graphviz'): if path.isfile(outfn): return relfn, outfn - if hasattr(self.builder, '_graphviz_warned_dot') or \ - hasattr(self.builder, '_graphviz_warned_ps2pdf'): + if (hasattr(self.builder, '_graphviz_warned_dot') and + self.builder._graphviz_warned_dot.get(graphviz_dot)): return None, None ensuredir(path.dirname(outfn)) @@ -161,9 +169,8 @@ def render_dot(self, code, options, format, prefix='graphviz'): if isinstance(code, text_type): code = code.encode('utf-8') - dot_args = [self.builder.config.graphviz_dot] + dot_args = [graphviz_dot] dot_args.extend(self.builder.config.graphviz_dot_args) - dot_args.extend(options) dot_args.extend(['-T' + format, '-o' + outfn]) if format == 'png': dot_args.extend(['-Tcmapx', '-o%s.map' % outfn]) @@ -173,9 +180,10 @@ def render_dot(self, code, options, format, prefix='graphviz'): if err.errno != ENOENT: # No such file or directory raise self.builder.warn('dot command %r cannot be run (needed for graphviz ' - 'output), check the graphviz_dot setting' % - self.builder.config.graphviz_dot) - self.builder._graphviz_warned_dot = True + 'output), check the graphviz_dot setting' % graphviz_dot) + if not hasattr(self.builder, '_graphviz_warned_dot'): + self.builder._graphviz_warned_dot = {} + self.builder._graphviz_warned_dot[graphviz_dot] = True return None, None try: # Graphviz may close standard input when an error occurs, @@ -197,6 +205,15 @@ def render_dot(self, code, options, format, prefix='graphviz'): return relfn, outfn +def warn_for_deprecated_option(self, node): + if hasattr(self.builder, '_graphviz_warned_inline'): + return + + if 'inline' in node: + self.builder.warn(':inline: option for graphviz is deprecated since version 1.4.0.') + self.builder._graphviz_warned_inline = True + + def render_dot_html(self, node, code, options, prefix='graphviz', imgcls=None, alt=None): format = self.builder.config.graphviz_output_format @@ -209,13 +226,6 @@ def render_dot_html(self, node, code, options, prefix='graphviz', self.builder.warn('dot code %r: ' % code + str(exc)) raise nodes.SkipNode - inline = node.get('inline', False) - if inline: - wrapper = 'span' - else: - wrapper = 'p' - - self.body.append(self.starttag(node, wrapper, CLASS='graphviz')) if fname is None: self.body.append(self.encode(code)) else: @@ -223,7 +233,8 @@ def render_dot_html(self, node, code, options, prefix='graphviz', alt = node.get('alt', self.encode(code).strip()) imgcss = imgcls and 'class="%s"' % imgcls or '' if format == 'svg': - svgtag = '<img src="%s" alt="%s" %s/>\n' % (fname, alt, imgcss) + svgtag = '''<object data="%s" type="image/svg+xml"> + <p class="warning">%s</p></object>\n''' % (fname, alt) self.body.append(svgtag) else: mapfile = open(outfn + '.map', 'rb') @@ -242,11 +253,11 @@ def render_dot_html(self, node, code, options, prefix='graphviz', (fname, alt, mapname, imgcss)) self.body.extend([item.decode('utf-8') for item in imgmap]) - self.body.append('</%s>\n' % wrapper) raise nodes.SkipNode def html_visit_graphviz(self, node): + warn_for_deprecated_option(self, node) render_dot_html(self, node, node['code'], node['options']) @@ -257,8 +268,8 @@ def render_dot_latex(self, node, code, options, prefix='graphviz'): self.builder.warn('dot code %r: ' % code + str(exc)) raise nodes.SkipNode - inline = node.get('inline', False) - if inline: + is_inline = self.is_inline(node) + if is_inline: para_separator = '' else: para_separator = '\n' @@ -270,6 +281,7 @@ def render_dot_latex(self, node, code, options, prefix='graphviz'): def latex_visit_graphviz(self, node): + warn_for_deprecated_option(self, node) render_dot_latex(self, node, node['code'], node['options']) @@ -285,10 +297,12 @@ def render_dot_texinfo(self, node, code, options, prefix='graphviz'): def texinfo_visit_graphviz(self, node): + warn_for_deprecated_option(self, node) render_dot_texinfo(self, node, node['code'], node['options']) def text_visit_graphviz(self, node): + warn_for_deprecated_option(self, node) if 'alt' in node.attributes: self.add_text(_('[graph: %s]') % node['alt']) else: @@ -297,6 +311,7 @@ def text_visit_graphviz(self, node): def man_visit_graphviz(self, node): + warn_for_deprecated_option(self, node) if 'alt' in node.attributes: self.body.append(_('[graph: %s]') % node['alt']) else: diff --git a/sphinx/ext/ifconfig.py b/sphinx/ext/ifconfig.py index 3c77566ef..74580fb4a 100644 --- a/sphinx/ext/ifconfig.py +++ b/sphinx/ext/ifconfig.py @@ -16,7 +16,7 @@ namespace of the project configuration (that is, all variables from ``conf.py`` are available.) - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py new file mode 100644 index 000000000..461cba17e --- /dev/null +++ b/sphinx/ext/imgmath.py @@ -0,0 +1,284 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.imgmath + ~~~~~~~~~~~~~~~~~~ + + Render math in HTML via dvipng or dvisvgm. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import codecs +import shutil +import tempfile +import posixpath +from os import path +from subprocess import Popen, PIPE +from hashlib import sha1 + +from six import text_type +from docutils import nodes + +import sphinx +from sphinx.errors import SphinxError +from sphinx.util.png import read_png_depth, write_png_depth +from sphinx.util.osutil import ensuredir, ENOENT, cd +from sphinx.util.pycompat import sys_encoding +from sphinx.ext.mathbase import setup_math as mathbase_setup, wrap_displaymath + + +class MathExtError(SphinxError): + category = 'Math extension error' + + def __init__(self, msg, stderr=None, stdout=None): + if stderr: + msg += '\n[stderr]\n' + stderr.decode(sys_encoding, 'replace') + if stdout: + msg += '\n[stdout]\n' + stdout.decode(sys_encoding, 'replace') + SphinxError.__init__(self, msg) + + +DOC_HEAD = r''' +\documentclass[12pt]{article} +\usepackage[utf8x]{inputenc} +\usepackage{amsmath} +\usepackage{amsthm} +\usepackage{amssymb} +\usepackage{amsfonts} +\usepackage{anyfontsize} +\usepackage{bm} +\pagestyle{empty} +''' + +DOC_BODY = r''' +\begin{document} +\fontsize{%d}{%d}\selectfont %s +\end{document} +''' + +DOC_BODY_PREVIEW = r''' +\usepackage[active]{preview} +\begin{document} +\begin{preview} +\fontsize{%s}{%s}\selectfont %s +\end{preview} +\end{document} +''' + +depth_re = re.compile(br'\[\d+ depth=(-?\d+)\]') + + +def render_math(self, math): + """Render the LaTeX math expression *math* using latex and dvipng or + dvisvgm. + + Return the filename relative to the built document and the "depth", + that is, the distance of image bottom and baseline in pixels, if the + option to use preview_latex is switched on. + + Error handling may seem strange, but follows a pattern: if LaTeX or dvipng + (dvisvgm) aren't available, only a warning is generated (since that enables + people on machines without these programs to at least build the rest of the + docs successfully). If the programs are there, however, they may not fail + since that indicates a problem in the math source. + """ + image_format = self.builder.config.imgmath_image_format + if image_format not in ('png', 'svg'): + raise MathExtError( + 'imgmath_image_format must be either "png" or "svg"') + + font_size = self.builder.config.imgmath_font_size + use_preview = self.builder.config.imgmath_use_preview + latex = DOC_HEAD + self.builder.config.imgmath_latex_preamble + latex += (use_preview and DOC_BODY_PREVIEW or DOC_BODY) % ( + font_size, int(round(font_size * 1.2)), math) + + shasum = "%s.%s" % (sha1(latex.encode('utf-8')).hexdigest(), image_format) + relfn = posixpath.join(self.builder.imgpath, 'math', shasum) + outfn = path.join(self.builder.outdir, self.builder.imagedir, 'math', shasum) + if path.isfile(outfn): + depth = read_png_depth(outfn) + return relfn, depth + + # if latex or dvipng (dvisvgm) has failed once, don't bother to try again + if hasattr(self.builder, '_imgmath_warned_latex') or \ + hasattr(self.builder, '_imgmath_warned_image_translator'): + return None, None + + # use only one tempdir per build -- the use of a directory is cleaner + # than using temporary files, since we can clean up everything at once + # just removing the whole directory (see cleanup_tempdir) + if not hasattr(self.builder, '_imgmath_tempdir'): + tempdir = self.builder._imgmath_tempdir = tempfile.mkdtemp() + else: + tempdir = self.builder._imgmath_tempdir + + tf = codecs.open(path.join(tempdir, 'math.tex'), 'w', 'utf-8') + tf.write(latex) + tf.close() + + # build latex command; old versions of latex don't have the + # --output-directory option, so we have to manually chdir to the + # temp dir to run it. + ltx_args = [self.builder.config.imgmath_latex, '--interaction=nonstopmode'] + # add custom args from the config file + ltx_args.extend(self.builder.config.imgmath_latex_args) + ltx_args.append('math.tex') + + with cd(tempdir): + try: + p = Popen(ltx_args, stdout=PIPE, stderr=PIPE) + except OSError as err: + if err.errno != ENOENT: # No such file or directory + raise + self.builder.warn('LaTeX command %r cannot be run (needed for math ' + 'display), check the imgmath_latex setting' % + self.builder.config.imgmath_latex) + self.builder._imgmath_warned_latex = True + return None, None + + stdout, stderr = p.communicate() + if p.returncode != 0: + raise MathExtError('latex exited with error', stderr, stdout) + + ensuredir(path.dirname(outfn)) + if image_format == 'png': + image_translator = 'dvipng' + image_translator_executable = self.builder.config.imgmath_dvipng + # use some standard dvipng arguments + image_translator_args = [self.builder.config.imgmath_dvipng] + image_translator_args += ['-o', outfn, '-T', 'tight', '-z9'] + # add custom ones from config value + image_translator_args.extend(self.builder.config.imgmath_dvipng_args) + if use_preview: + image_translator_args.append('--depth') + elif image_format == 'svg': + image_translator = 'dvisvgm' + image_translator_executable = self.builder.config.imgmath_dvisvgm + # use some standard dvisvgm arguments + image_translator_args = [self.builder.config.imgmath_dvisvgm] + image_translator_args += ['-o', outfn] + # add custom ones from config value + image_translator_args.extend(self.builder.config.imgmath_dvisvgm_args) + # last, the input file name + image_translator_args.append(path.join(tempdir, 'math.dvi')) + else: + raise MathExtError( + 'imgmath_image_format must be either "png" or "svg"') + + # last, the input file name + image_translator_args.append(path.join(tempdir, 'math.dvi')) + + try: + p = Popen(image_translator_args, stdout=PIPE, stderr=PIPE) + except OSError as err: + if err.errno != ENOENT: # No such file or directory + raise + self.builder.warn('%s command %r cannot be run (needed for math ' + 'display), check the imgmath_%s setting' % + image_translator, image_translator_executable, + image_translator) + self.builder._imgmath_warned_image_translator = True + return None, None + + stdout, stderr = p.communicate() + if p.returncode != 0: + raise MathExtError('%s exited with error', + image_translator, stderr, stdout) + depth = None + if use_preview and image_format == 'png': # depth is only useful for png + for line in stdout.splitlines(): + m = depth_re.match(line) + if m: + depth = int(m.group(1)) + write_png_depth(outfn, depth) + break + + return relfn, depth + + +def cleanup_tempdir(app, exc): + if exc: + return + if not hasattr(app.builder, '_imgmath_tempdir'): + return + try: + shutil.rmtree(app.builder._mathpng_tempdir) + except Exception: + pass + + +def get_tooltip(self, node): + if self.builder.config.imgmath_add_tooltips: + return ' alt="%s"' % self.encode(node['latex']).strip() + return '' + + +def html_visit_math(self, node): + try: + fname, depth = render_math(self, '$'+node['latex']+'$') + except MathExtError as exc: + msg = text_type(exc) + sm = nodes.system_message(msg, type='WARNING', level=2, + backrefs=[], source=node['latex']) + sm.walkabout(self) + self.builder.warn('display latex %r: ' % node['latex'] + msg) + raise nodes.SkipNode + if fname is None: + # something failed -- use text-only as a bad substitute + self.body.append('<span class="math">%s</span>' % + self.encode(node['latex']).strip()) + else: + c = ('<img class="math" src="%s"' % fname) + get_tooltip(self, node) + if depth is not None: + c += ' style="vertical-align: %dpx"' % (-depth) + self.body.append(c + '/>') + raise nodes.SkipNode + + +def html_visit_displaymath(self, node): + if node['nowrap']: + latex = node['latex'] + else: + latex = wrap_displaymath(node['latex'], None) + try: + fname, depth = render_math(self, latex) + except MathExtError as exc: + sm = nodes.system_message(str(exc), type='WARNING', level=2, + backrefs=[], source=node['latex']) + sm.walkabout(self) + self.builder.warn('inline latex %r: ' % node['latex'] + str(exc)) + raise nodes.SkipNode + self.body.append(self.starttag(node, 'div', CLASS='math')) + self.body.append('<p>') + if node['number']: + self.body.append('<span class="eqno">(%s)</span>' % node['number']) + if fname is None: + # something failed -- use text-only as a bad substitute + self.body.append('<span class="math">%s</span></p>\n</div>' % + self.encode(node['latex']).strip()) + else: + self.body.append(('<img src="%s"' % fname) + get_tooltip(self, node) + + '/></p>\n</div>') + raise nodes.SkipNode + + +def setup(app): + mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None)) + app.add_config_value('imgmath_image_format', 'png', 'html') + app.add_config_value('imgmath_dvipng', 'dvipng', 'html') + app.add_config_value('imgmath_dvisvgm', 'dvisvgm', 'html') + app.add_config_value('imgmath_latex', 'latex', 'html') + app.add_config_value('imgmath_use_preview', False, 'html') + app.add_config_value('imgmath_dvipng_args', + ['-gamma', '1.5', '-D', '110', '-bg', 'Transparent'], + 'html') + app.add_config_value('imgmath_dvisvgm_args', ['--no-fonts'], 'html') + app.add_config_value('imgmath_latex_args', [], 'html') + app.add_config_value('imgmath_latex_preamble', '', 'html') + app.add_config_value('imgmath_add_tooltips', True, 'html') + app.add_config_value('imgmath_font_size', 12, 'html') + app.connect('build-finished', cleanup_tempdir) + return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index f618aaf15..4ac2bd2fd 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -32,7 +32,7 @@ r""" The graph is inserted as a PNG+image map into HTML and a PDF in LaTeX. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -227,10 +227,10 @@ class InheritanceGraph(object): } def _format_node_attrs(self, attrs): - return ','.join(['%s=%s' % x for x in attrs.items()]) + return ','.join(['%s=%s' % x for x in sorted(attrs.items())]) def _format_graph_attrs(self, attrs): - return ''.join(['%s=%s;\n' % x for x in attrs.items()]) + return ''.join(['%s=%s;\n' % x for x in sorted(attrs.items())]) def generate_dot(self, name, urls={}, env=None, graph_attrs={}, node_attrs={}, edge_attrs={}): @@ -264,6 +264,7 @@ class InheritanceGraph(object): this_node_attrs = n_attrs.copy() if fullname in urls: this_node_attrs['URL'] = '"%s"' % urls[fullname] + this_node_attrs['target'] = '"_top"' if tooltip: this_node_attrs['tooltip'] = tooltip res.append(' "%s" [%s];\n' % @@ -348,12 +349,20 @@ def html_visit_inheritance_diagram(self, node): name = 'inheritance%s' % graph_hash # Create a mapping from fully-qualified class names to URLs. + graphviz_output_format = self.builder.env.config.graphviz_output_format.upper() + current_filename = self.builder.current_docname + self.builder.out_suffix urls = {} for child in node: if child.get('refuri') is not None: - urls[child['reftitle']] = child.get('refuri') + if graphviz_output_format == 'SVG': + urls[child['reftitle']] = "../" + child.get('refuri') + else: + urls[child['reftitle']] = child.get('refuri') elif child.get('refid') is not None: - urls[child['reftitle']] = '#' + child.get('refid') + if graphviz_output_format == 'SVG': + urls[child['reftitle']] = '../' + current_filename + '#' + child.get('refid') + else: + urls[child['reftitle']] = '#' + child.get('refid') dotcode = graph.generate_dot(name, urls, env=self.builder.env) render_dot_html(self, node, dotcode, [], 'inheritance', 'inheritance', diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index a2bb9401d..9fe2f765c 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -20,7 +20,7 @@ also be specified individually, e.g. if the docs should be buildable without Internet access. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -34,7 +34,7 @@ from os import path import re from six import iteritems -from six.moves.urllib import request +from six.moves.urllib import parse, request from docutils import nodes from docutils.utils import relative_path @@ -43,14 +43,14 @@ from sphinx.locale import _ from sphinx.builders.html import INVENTORY_FILENAME -handlers = [request.ProxyHandler(), request.HTTPRedirectHandler(), - request.HTTPHandler()] +default_handlers = [request.ProxyHandler(), request.HTTPRedirectHandler(), + request.HTTPHandler()] try: - handlers.append(request.HTTPSHandler) + default_handlers.append(request.HTTPSHandler) except AttributeError: pass -request.install_opener(request.build_opener(*handlers)) +default_opener = request.build_opener(*default_handlers) UTF8StreamReader = codecs.lookup('utf-8')[2] @@ -124,15 +124,109 @@ def read_inventory_v2(f, uri, join, bufsize=16*1024): return invdata +def _strip_basic_auth(url): + """Returns *url* with basic auth credentials removed. Also returns the + basic auth username and password if they're present in *url*. + + E.g.: https://user:pass@example.com => https://example.com + + *url* need not include basic auth credentials. + + :param url: url which may or may not contain basic auth credentials + :type url: ``str`` + + :return: 3-``tuple`` of: + + * (``str``) -- *url* with any basic auth creds removed + * (``str`` or ``NoneType``) -- basic auth username or ``None`` if basic + auth username not given + * (``str`` or ``NoneType``) -- basic auth password or ``None`` if basic + auth password not given + + :rtype: ``tuple`` + """ + url_parts = parse.urlsplit(url) + username = url_parts.username + password = url_parts.password + frags = list(url_parts) + # swap out "user[:pass]@hostname" for "hostname" + frags[1] = url_parts.hostname + url = parse.urlunsplit(frags) + return (url, username, password) + + +def _read_from_url(url): + """Reads data from *url* with an HTTP *GET*. + + This function supports fetching from resources which use basic HTTP auth as + laid out by RFC1738 § 3.1. See § 5 for grammar definitions for URLs. + + .. seealso: + + https://www.ietf.org/rfc/rfc1738.txt + + :param url: URL of an HTTP resource + :type url: ``str`` + + :return: data read from resource described by *url* + :rtype: ``file``-like object + """ + url, username, password = _strip_basic_auth(url) + if username is not None and password is not None: + # case: url contains basic auth creds + password_mgr = request.HTTPPasswordMgrWithDefaultRealm() + password_mgr.add_password(None, url, username, password) + handler = request.HTTPBasicAuthHandler(password_mgr) + opener = request.build_opener(default_handlers + [handler]) + else: + opener = default_opener + + return opener.open(url) + + +def _get_safe_url(url): + """Gets version of *url* with basic auth passwords obscured. This function + returns results suitable for printing and logging. + + E.g.: https://user:12345@example.com => https://user:********@example.com + + .. note:: + + The number of astrisks is invariant in the length of the basic auth + password, so minimal information is leaked. + + :param url: a url + :type url: ``str`` + + :return: *url* with password obscured + :rtype: ``str`` + """ + safe_url = url + url, username, _ = _strip_basic_auth(url) + if username is not None: + # case: url contained basic auth creds; obscure password + url_parts = parse.urlsplit(url) + safe_netloc = '{0}@{1}'.format(username, url_parts.hostname) + # replace original netloc w/ obscured version + frags = list(url_parts) + frags[1] = safe_netloc + safe_url = parse.urlunsplit(frags) + + return safe_url + + def fetch_inventory(app, uri, inv): """Fetch, parse and return an intersphinx inventory file.""" # both *uri* (base URI of the links to generate) and *inv* (actual # location of the inventory file) can be local or remote URIs - localuri = uri.find('://') == -1 + localuri = '://' not in uri + if not localuri: + # case: inv URI points to remote resource; strip any existing auth + uri, _, _ = _strip_basic_auth(uri) join = localuri and path.join or posixpath.join try: - if inv.find('://') != -1: - f = request.urlopen(inv) + if '://' in inv: + f = _read_from_url(inv) else: f = open(path.join(app.srcdir, inv), 'rb') except Exception as err: @@ -194,7 +288,9 @@ def load_mappings(app): # files; remote ones only if the cache time is expired if '://' not in inv or uri not in cache \ or cache[uri][1] < cache_time: - app.info('loading intersphinx inventory from %s...' % inv) + safe_inv_url = _get_safe_url(inv) + app.info( + 'loading intersphinx inventory from %s...' % safe_inv_url) invdata = fetch_inventory(app, uri, inv) if invdata: cache[uri] = (name, now, invdata) @@ -230,6 +326,7 @@ def missing_reference(app, env, node, contnode): objtypes = ['%s:%s' % (domain.name, objtype) for domain in env.domains.values() for objtype in domain.object_types] + domain = None elif node['reftype'] == 'doc': domain = 'std' # special case objtypes = ['std:doc'] diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py index 2cc661430..ff77c2d1b 100644 --- a/sphinx/ext/jsmath.py +++ b/sphinx/ext/jsmath.py @@ -6,7 +6,7 @@ Set up everything for use of JSMath to display math in HTML via JavaScript. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -26,7 +26,7 @@ def html_visit_math(self, node): def html_visit_displaymath(self, node): if node['nowrap']: self.body.append(self.starttag(node, 'div', CLASS='math')) - self.body.append(node['latex']) + self.body.append(self.encode(node['latex'])) self.body.append('</div>') raise nodes.SkipNode for i, part in enumerate(node['latex'].split('\n\n')): diff --git a/sphinx/ext/linkcode.py b/sphinx/ext/linkcode.py index b0ee5e43e..63bd38727 100644 --- a/sphinx/ext/linkcode.py +++ b/sphinx/ext/linkcode.py @@ -5,7 +5,7 @@ Add external links to module code in Python object descriptions. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index 4327fba1c..5673fcf25 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -5,7 +5,7 @@ Set up math support in source files and LaTeX/text output. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -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/ext/mathjax.py b/sphinx/ext/mathjax.py index d512db465..511c4f3dd 100644 --- a/sphinx/ext/mathjax.py +++ b/sphinx/ext/mathjax.py @@ -7,7 +7,7 @@ Sphinx's HTML writer -- requires the MathJax JavaScript library on your webserver/computer. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -30,7 +30,7 @@ def html_visit_displaymath(self, node): self.body.append(self.starttag(node, 'div', CLASS='math')) if node['nowrap']: self.body.append(self.builder.config.mathjax_display[0] + - node['latex'] + + self.encode(node['latex']) + self.builder.config.mathjax_display[1]) self.body.append('</div>') raise nodes.SkipNode diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py index 59c1213c6..85c8acec8 100644 --- a/sphinx/ext/napoleon/__init__.py +++ b/sphinx/ext/napoleon/__init__.py @@ -5,7 +5,7 @@ Support for NumPy and Google style docstrings. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -34,7 +34,7 @@ class Config(object): napoleon_google_docstring = True napoleon_numpy_docstring = True napoleon_include_private_with_doc = False - napoleon_include_special_with_doc = True + napoleon_include_special_with_doc = False napoleon_use_admonition_for_examples = False napoleon_use_admonition_for_notes = False napoleon_use_admonition_for_references = False @@ -43,7 +43,7 @@ class Config(object): napoleon_use_rtype = True .. _Google style: - http://google-styleguide.googlecode.com/svn/trunk/pyguide.html + http://google.github.io/styleguide/pyguide.html .. _NumPy style: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt @@ -71,7 +71,7 @@ class Config(object): # This will NOT be included in the docs pass - napoleon_include_special_with_doc : bool, defaults to True + napoleon_include_special_with_doc : bool, defaults to False True to include special members (like ``__membername__``) with docstrings in the documentation. False to fall back to Sphinx's default behavior. @@ -209,7 +209,7 @@ class Config(object): 'napoleon_google_docstring': (True, 'env'), 'napoleon_numpy_docstring': (True, 'env'), 'napoleon_include_private_with_doc': (False, 'env'), - 'napoleon_include_special_with_doc': (True, 'env'), + 'napoleon_include_special_with_doc': (False, 'env'), 'napoleon_use_admonition_for_examples': (False, 'env'), 'napoleon_use_admonition_for_notes': (False, 'env'), 'napoleon_use_admonition_for_references': (False, 'env'), @@ -239,12 +239,12 @@ def setup(app): See Also -------- - The Sphinx documentation on `Extensions`_, the `Extension Tutorial`_, and - the `Extension API`_. + `The Sphinx documentation on Extensions + <http://sphinx-doc.org/extensions.html>`_ - .. _Extensions: http://sphinx-doc.org/extensions.html - .. _Extension Tutorial: http://sphinx-doc.org/ext/tutorial.html - .. _Extension API: http://sphinx-doc.org/ext/appapi.html + `The Extension Tutorial <http://sphinx-doc.org/extdev/tutorial.html>`_ + + `The Extension API <http://sphinx-doc.org/extdev/appapi.html>`_ """ from sphinx.application import Sphinx diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 4c2348507..27ea1617f 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -7,7 +7,7 @@ Classes for docstring parsing and formatting. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -15,7 +15,7 @@ import collections import inspect import re -from six import string_types +from six import string_types, u from six.moves import range from sphinx.ext.napoleon.iterators import modify_iter @@ -144,6 +144,7 @@ class GoogleDocstring(UnicodeMixin): 'raises': self._parse_raises_section, 'references': self._parse_references_section, 'see also': self._parse_see_also_section, + 'todo': self._parse_todo_section, 'warning': self._parse_warning_section, 'warnings': self._parse_warning_section, 'warns': self._parse_warns_section, @@ -161,7 +162,7 @@ class GoogleDocstring(UnicodeMixin): Unicode version of the docstring. """ - return u'\n'.join(self.lines()) + return u('\n').join(self.lines()) def lines(self): """Return the parsed lines of the docstring in reStructuredText format. @@ -211,10 +212,7 @@ class GoogleDocstring(UnicodeMixin): _name = match.group(1) _type = match.group(2) - if _name[:2] == '**': - _name = r'\*\*'+_name[2:] - elif _name[:1] == '*': - _name = r'\*'+_name[1:] + _name = self._escape_args_and_kwargs(_name) if prefer_type and not _type: _type, _name = _name, _type @@ -296,6 +294,14 @@ class GoogleDocstring(UnicodeMixin): min_indent = self._get_min_indent(lines) return [line[min_indent:] for line in lines] + def _escape_args_and_kwargs(self, name): + if name[:2] == '**': + return r'\*\*' + name[2:] + elif name[:1] == '*': + return r'\*' + name[1:] + else: + return name + def _format_admonition(self, admonition, lines): lines = self._strip_empty(lines) if len(lines) == 1: @@ -606,6 +612,10 @@ class GoogleDocstring(UnicodeMixin): lines = self._consume_to_next_section() return self._format_admonition('seealso', lines) + def _parse_todo_section(self, section): + lines = self._consume_to_next_section() + return self._format_admonition('todo', lines) + def _parse_warning_section(self, section): lines = self._consume_to_next_section() return self._format_admonition('warning', lines) @@ -766,6 +776,7 @@ class NumpyDocstring(GoogleDocstring): else: _name, _type = line, '' _name, _type = _name.strip(), _type.strip() + _name = self._escape_args_and_kwargs(_name) if prefer_type and not _type: _type, _name = _name, _type indent = self._get_indent(line) diff --git a/sphinx/ext/napoleon/iterators.py b/sphinx/ext/napoleon/iterators.py index f59a3f088..f66d67f2c 100644 --- a/sphinx/ext/napoleon/iterators.py +++ b/sphinx/ext/napoleon/iterators.py @@ -7,7 +7,7 @@ A collection of helpful iterators. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/pngmath.py b/sphinx/ext/pngmath.py index 8ef860f80..74ab29666 100644 --- a/sphinx/ext/pngmath.py +++ b/sphinx/ext/pngmath.py @@ -3,9 +3,10 @@ sphinx.ext.pngmath ~~~~~~~~~~~~~~~~~~ - Render math in HTML via dvipng. + Render math in HTML via dvipng. This extension has been deprecated; please + use sphinx.ext.imgmath instead. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -236,6 +237,7 @@ def html_visit_displaymath(self, node): def setup(app): + app.warn('sphinx.ext.pngmath has been deprecated. Please use sphinx.ext.imgmath instead.') mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None)) app.add_config_value('pngmath_dvipng', 'dvipng', 'html') app.add_config_value('pngmath_latex', 'latex', 'html') diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 64359c9b0..1f8f6d166 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -8,17 +8,19 @@ all todos of your project and lists them along with a backlink to the original location. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from docutils import nodes +from docutils.parsers.rst import directives import sphinx from sphinx.locale import _ from sphinx.environment import NoUri from sphinx.util.nodes import set_source_info -from sphinx.util.compat import Directive, make_admonition +from docutils.parsers.rst import Directive +from docutils.parsers.rst.directives.admonitions import BaseAdmonition class todo_node(nodes.Admonition, nodes.Element): @@ -29,27 +31,35 @@ class todolist(nodes.General, nodes.Element): pass -class Todo(Directive): +class Todo(BaseAdmonition): """ A todo entry, displayed (if configured) in the form of an admonition. """ + node_class = todo_node has_content = True required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} + option_spec = { + 'class': directives.class_option, + } def run(self): + if not self.options.get('class'): + self.options['class'] = ['admonition-todo'] + + (todo,) = super(Todo, self).run() + if isinstance(todo, nodes.system_message): + return [todo] + + todo.insert(0, nodes.title(text=_('Todo'))) + set_source_info(self, todo) + env = self.state.document.settings.env targetid = 'index-%s' % env.new_serialno('index') targetnode = nodes.target('', '', ids=[targetid]) - - ad = make_admonition(todo_node, self.name, [_('Todo')], self.options, - self.content, self.lineno, self.content_offset, - self.block_text, self.state, self.state_machine) - set_source_info(self, ad[0]) - return [targetnode] + ad + return [targetnode, todo] def process_todos(app, doctree): @@ -165,6 +175,7 @@ def merge_info(app, env, docnames, other): def visit_todo_node(self, node): self.visit_admonition(node) + # self.visit_admonition(node, 'todo') def depart_todo_node(self, node): diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index e6af4f550..d64cc88f7 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -5,7 +5,7 @@ Add links to module code in Python object descriptions. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 6f22993c2..eb008dc4e 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -5,7 +5,7 @@ Highlight code blocks using Pygments. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -183,9 +183,13 @@ class PygmentsBridge(object): formatter = self.get_formatter(**kwargs) try: hlsource = highlight(source, lexer, formatter) - except ErrorToken: + except ErrorToken as exc: # this is most probably not the selected language, # so let it pass unhighlighted + if warn: + warn('Could not parse literal_block as "%s". highlighting skipped.' % lang) + else: + raise exc hlsource = highlight(source, lexers['none'], formatter) if self.dest == 'html': return hlsource diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index c8a7165fc..f3ce87a8c 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -5,7 +5,7 @@ Glue code for the jinja2 templating engine. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index 444ad5d0c..f7a375dec 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -5,7 +5,7 @@ Locale utilities. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/make_mode.py b/sphinx/make_mode.py index 7d239f51c..67ba9e1e1 100644 --- a/sphinx/make_mode.py +++ b/sphinx/make_mode.py @@ -11,7 +11,7 @@ This is in its own module so that importing it is fast. It should not import the main Sphinx modules (like sphinx.applications, sphinx.builders). - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function diff --git a/sphinx/parsers.py b/sphinx/parsers.py new file mode 100644 index 000000000..926de9f1c --- /dev/null +++ b/sphinx/parsers.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" + sphinx.parsers + ~~~~~~~~~~~~~~ + + A Base class for additional parsers. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import docutils.parsers + + +class Parser(docutils.parsers.Parser): + """ + A base class of source parsers. The additonal parsers should inherits this class instead + of ``docutils.parsers.Parser``. Compared with ``docutils.parsers.Parser``, this class + improves accessibility to Sphinx APIs. + + The subclasses can access following objects and functions: + + self.app + The application object (:class:`sphinx.application.Sphinx`) + self.config + The config object (:class:`sphinx.config.Config`) + self.env + The environment object (:class:`sphinx.environment.BuildEnvironment`) + self.warn() + Emit a warning. (Same as :meth:`sphinx.application.Sphinx.warn()`) + self.info() + Emit a informational message. (Same as :meth:`sphinx.application.Sphinx.info()`) + """ + + def set_application(self, app): + """set_application will be called from Sphinx to set app and other instance variables + + :param sphinx.application.Sphinx app: Sphinx application object + """ + self.app = app + self.config = app.config + self.env = app.env + self.warn = app.warn + self.info = app.info diff --git a/sphinx/pycode/Grammar-py3.txt b/sphinx/pycode/Grammar-py3.txt index 083b5f91e..d05b758ee 100644 --- a/sphinx/pycode/Grammar-py3.txt +++ b/sphinx/pycode/Grammar-py3.txt @@ -1,4 +1,5 @@ -# Grammar for Python 3.x (with at least x <= 4) +# Grammar for Python 3.x (with at least x <= 5) + # IMPORTANT: when copying over a new Grammar file, make sure file_input # is the first nonterminal in the file! @@ -14,8 +15,11 @@ eval_input: testlist NEWLINE* ENDMARKER decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE decorators: decorator+ -decorated: decorators (classdef | funcdef) +decorated: decorators (classdef | funcdef | async_funcdef) + +async_funcdef: ASYNC funcdef funcdef: 'def' NAME parameters ['->' test] ':' suite + parameters: '(' [typedargslist] ')' typedargslist: (tfpdef ['=' test] (',' tfpdef ['=' test])* [',' ['*' [tfpdef] (',' tfpdef ['=' test])* [',' '**' tfpdef] | '**' tfpdef]] @@ -33,7 +37,7 @@ small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt | expr_stmt: testlist_star_expr (augassign (yield_expr|testlist) | ('=' (yield_expr|testlist_star_expr))*) testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [','] -augassign: ('+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | +augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=') # For normal assignments, additional restrictions enforced by the interpreter del_stmt: 'del' exprlist @@ -58,7 +62,8 @@ global_stmt: 'global' NAME (',' NAME)* nonlocal_stmt: 'nonlocal' NAME (',' NAME)* assert_stmt: 'assert' test [',' test] -compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated +compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt +async_stmt: ASYNC (funcdef | with_stmt | for_stmt) if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] while_stmt: 'while' test ':' suite ['else' ':' suite] for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite] @@ -82,7 +87,7 @@ and_test: not_test ('and' not_test)* not_test: 'not' not_test | comparison comparison: expr (comp_op expr)* # <> isn't actually a valid comparison operator in Python. It's here for the -# sake of a __future__ import described in PEP 401 +# sake of a __future__ import described in PEP 401 (which really works :-) comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not' star_expr: '*' expr expr: xor_expr ('|' xor_expr)* @@ -90,9 +95,9 @@ xor_expr: and_expr ('^' and_expr)* and_expr: shift_expr ('&' shift_expr)* shift_expr: arith_expr (('<<'|'>>') arith_expr)* arith_expr: term (('+'|'-') term)* -term: factor (('*'|'/'|'%'|'//') factor)* +term: factor (('*'|'@'|'/'|'%'|'//') factor)* factor: ('+'|'-'|'~') factor | power -power: atom trailer* ['**' factor] +power: [AWAIT] atom trailer* ['**' factor] atom: ('(' [yield_expr|testlist_comp] ')' | '[' [testlist_comp] ']' | '{' [dictorsetmaker] '}' | @@ -104,17 +109,29 @@ subscript: test | [test] ':' [test] [sliceop] sliceop: ':' [test] exprlist: (expr|star_expr) (',' (expr|star_expr))* [','] testlist: test (',' test)* [','] -dictorsetmaker: ( (test ':' test (comp_for | (',' test ':' test)* [','])) | - (test (comp_for | (',' test)* [','])) ) +dictorsetmaker: ( ((test ':' test | '**' expr) + (comp_for | (',' (test ':' test | '**' expr))* [','])) | + ((test | star_expr) + (comp_for | (',' (test | star_expr))* [','])) ) classdef: 'class' NAME ['(' [arglist] ')'] ':' suite -arglist: (argument ',')* (argument [','] - |'*' test (',' argument)* [',' '**' test] - |'**' test) +arglist: argument (',' argument)* [','] + # The reason that keywords are test nodes instead of NAME is that using NAME # results in an ambiguity. ast.c makes sure it's a NAME. -argument: test [comp_for] | test '=' test # Really [keyword '='] test +# "test '=' test" is really "keyword '=' test", but we have no such token. +# These need to be in a single rule to avoid grammar that is ambiguous +# to our LL(1) parser. Even though 'test' includes '*expr' in star_expr, +# we explicitly match '*' here, too, to give it proper precedence. +# Illegal combinations and orderings are blocked in ast.c: +# multiple (test comp_for) arguements are blocked; keyword unpackings +# that precede iterable unpackings are blocked; etc. +argument: ( test [comp_for] | + test '=' test | + '**' test | + '*' test ) + comp_iter: comp_for | comp_if comp_for: 'for' exprlist 'in' or_test [comp_iter] comp_if: 'if' test_nocond [comp_iter] diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 57707e9d0..8e8ecf706 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -5,7 +5,7 @@ Utilities parsing and analyzing Python code. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function diff --git a/sphinx/pycode/nodes.py b/sphinx/pycode/nodes.py index 3b244db7f..ee40f3c0d 100644 --- a/sphinx/pycode/nodes.py +++ b/sphinx/pycode/nodes.py @@ -5,7 +5,7 @@ Parse tree node implementations. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/pycode/pgen2/grammar.py b/sphinx/pycode/pgen2/grammar.py index 91874fa23..42e6d72ee 100644 --- a/sphinx/pycode/pgen2/grammar.py +++ b/sphinx/pycode/pgen2/grammar.py @@ -139,6 +139,7 @@ opmap_raw = """ { LBRACE } RBRACE @ AT +@= ATEQUAL == EQEQUAL != NOTEQUAL <> NOTEQUAL diff --git a/sphinx/pycode/pgen2/token.py b/sphinx/pycode/pgen2/token.py index 55bf5e8d1..73718d166 100755 --- a/sphinx/pycode/pgen2/token.py +++ b/sphinx/pycode/pgen2/token.py @@ -57,13 +57,16 @@ DOUBLESTAREQUAL = 47 DOUBLESLASH = 48 DOUBLESLASHEQUAL = 49 AT = 50 -OP = 51 -COMMENT = 52 -NL = 53 -RARROW = 54 -ERRORTOKEN = 55 -ELLIPSIS = 56 -N_TOKENS = 57 +ATEQUAL = 51 +RARROW = 52 +ELLIPSIS = 53 +OP = 54 +AWAIT = 55 +ASYNC = 56 +COMMENT = 57 +NL = 58 +ERRORTOKEN = 59 +N_TOKENS = 60 NT_OFFSET = 256 #--end constants-- diff --git a/sphinx/pycode/pgen2/tokenize.py b/sphinx/pycode/pgen2/tokenize.py index d62535050..c7013bf91 100644 --- a/sphinx/pycode/pgen2/tokenize.py +++ b/sphinx/pycode/pgen2/tokenize.py @@ -360,6 +360,16 @@ def generate_tokens(readline): spos, epos, pos = (lnum, start), (lnum, end), end token, initial = line[start:end], line[start] + if end < max: + next_pseudomatch = pseudoprog.match(line, end) + if next_pseudomatch: + n_start, n_end = next_pseudomatch.span(1) + n_token = line[n_start:n_end] + else: + n_token = None + else: + n_token = None + if initial in numchars or ( initial == '.' and token not in ('.', '...') ): # ordinary number @@ -396,6 +406,10 @@ def generate_tokens(readline): break else: # ordinary string yield (STRING, token, spos, epos, line) + elif token == 'await' and n_token: + yield (AWAIT, token, spos, epos, line) + elif token == 'async' and n_token in ('def', 'for', 'with'): + yield (ASYNC, token, spos, epos, line) elif initial in namechars: # ordinary name yield (NAME, token, spos, epos, line) elif token in ('...',): # ordinary name diff --git a/sphinx/pygments_styles.py b/sphinx/pygments_styles.py index ff2263e1c..469947149 100644 --- a/sphinx/pygments_styles.py +++ b/sphinx/pygments_styles.py @@ -5,7 +5,7 @@ Sphinx theme specific highlighting styles. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index c8c46fabf..c69681d1f 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -5,7 +5,7 @@ Quickly setup documentation source to work with Sphinx. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -60,7 +60,7 @@ DEFAULT_VALUE = { } EXTENSIONS = ('autodoc', 'doctest', 'intersphinx', 'todo', 'coverage', - 'pngmath', 'mathjax', 'ifconfig', 'viewcode') + 'imgmath', 'mathjax', 'ifconfig', 'viewcode', 'githubpages') PROMPT_PREFIX = '> ' @@ -127,9 +127,9 @@ author = u'%(author_str)s' # built documents. # # The short X.Y version. -version = '%(version_str)s' +version = u'%(version_str)s' # The full version, including alpha/beta/rc tags. -release = '%(release_str)s' +release = u'%(release_str)s' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -190,9 +190,9 @@ html_theme = 'alabaster' # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] -# The name for this set of Sphinx documents. If None, it defaults to -# "<project> v<release> documentation". -#html_title = None +# The name for this set of Sphinx documents. +# "<project> v<release> documentation" by default. +#html_title = u'%(project_str)s v%(release_str)s' # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None @@ -216,9 +216,10 @@ html_static_path = ['%(dot)sstatic'] # directly to the root of the documentation. #html_extra_path = [] -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%%b %%d, %%Y' +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%%b %%d, %%Y'. +#html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. @@ -260,11 +261,12 @@ html_static_path = ['%(dot)sstatic'] # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that @@ -294,8 +296,8 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, '%(project_fn)s.tex', u'%(project_doc_texescaped_str)s', - u'%(author_texescaped_str)s', 'manual'), + (master_doc, '%(project_fn)s.tex', u'%(project_doc_texescaped_str)s', + u'%(author_texescaped_str)s', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -338,9 +340,9 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, '%(project_fn)s', u'%(project_doc_str)s', - author, '%(project_fn)s', 'One line description of project.', - 'Miscellaneous'), + (master_doc, '%(project_fn)s', u'%(project_doc_str)s', + author, '%(project_fn)s', 'One line description of project.', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. @@ -369,10 +371,10 @@ epub_copyright = copyright # The basename for the epub file. It defaults to the project name. #epub_basename = project -# The HTML theme for the epub output. Since the default themes are not optimized -# for small screen space, using the same theme for HTML and epub output is -# usually not wise. This defaults to 'epub', a theme designed to save visual -# space. +# The HTML theme for the epub output. Since the default themes are not +# optimized for small screen space, using the same theme for HTML and epub +# output is usually not wise. This defaults to 'epub', a theme designed to save +# visual space. #epub_theme = 'epub' # The language of the text. It defaults to the language option @@ -487,9 +489,7 @@ $(SPHINXOPTS) %(rsrcdir)s # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) %(rsrcdir)s -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp \ -epub latex latexpdf text man changes linkcheck doctest coverage gettext - +.PHONY: help help: \t@echo "Please use \\`make <target>' where <target> is one of" \t@echo " html to make standalone HTML files" @@ -518,40 +518,48 @@ help: (if enabled)" \t@echo " coverage to run coverage check of the documentation (if enabled)" +.PHONY: clean clean: \trm -rf $(BUILDDIR)/* +.PHONY: html html: \t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html \t@echo \t@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." +.PHONY: dirhtml dirhtml: \t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml \t@echo \t@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." +.PHONY: singlehtml singlehtml: \t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml \t@echo \t@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." +.PHONY: pickle pickle: \t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle \t@echo \t@echo "Build finished; now you can process the pickle files." +.PHONY: json json: \t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json \t@echo \t@echo "Build finished; now you can process the JSON files." +.PHONY: htmlhelp htmlhelp: \t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp \t@echo \t@echo "Build finished; now you can run HTML Help Workshop with the" \\ \t ".hhp project file in $(BUILDDIR)/htmlhelp." +.PHONY: qthelp qthelp: \t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp \t@echo @@ -561,6 +569,7 @@ qthelp: \t@echo "To view the help file:" \t@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/%(project_fn)s.qhc" +.PHONY: applehelp applehelp: \t$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp \t@echo @@ -569,6 +578,7 @@ applehelp: \t "~/Library/Documentation/Help or install it in your application" \\ \t "bundle." +.PHONY: devhelp devhelp: \t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp \t@echo @@ -579,11 +589,13 @@ devhelp: $$HOME/.local/share/devhelp/%(project_fn)s" \t@echo "# devhelp" +.PHONY: epub epub: \t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub \t@echo \t@echo "Build finished. The epub file is in $(BUILDDIR)/epub." +.PHONY: latex latex: \t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex \t@echo @@ -591,28 +603,33 @@ latex: \t@echo "Run \\`make' in that directory to run these through (pdf)latex" \\ \t "(use \\`make latexpdf' here to do that automatically)." +.PHONY: latexpdf latexpdf: \t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex \t@echo "Running LaTeX files through pdflatex..." \t$(MAKE) -C $(BUILDDIR)/latex all-pdf \t@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." +.PHONY: latexpdfja latexpdfja: \t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex \t@echo "Running LaTeX files through platex and dvipdfmx..." \t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja \t@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." +.PHONY: text text: \t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text \t@echo \t@echo "Build finished. The text files are in $(BUILDDIR)/text." +.PHONY: man man: \t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man \t@echo \t@echo "Build finished. The manual pages are in $(BUILDDIR)/man." +.PHONY: texinfo texinfo: \t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo \t@echo @@ -620,43 +637,51 @@ texinfo: \t@echo "Run \\`make' in that directory to run these through makeinfo" \\ \t "(use \\`make info' here to do that automatically)." +.PHONY: info info: \t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo \t@echo "Running Texinfo files through makeinfo..." \tmake -C $(BUILDDIR)/texinfo info \t@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." +.PHONY: gettext gettext: \t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale \t@echo \t@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." +.PHONY: changes changes: \t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes \t@echo \t@echo "The overview file is in $(BUILDDIR)/changes." +.PHONY: linkcheck linkcheck: \t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck \t@echo \t@echo "Link check complete; look for any errors in the above output " \\ \t "or in $(BUILDDIR)/linkcheck/output.txt." +.PHONY: doctest doctest: \t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest \t@echo "Testing of doctests in the sources finished, look at the " \\ \t "results in $(BUILDDIR)/doctest/output.txt." +.PHONY: coverage coverage: \t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage \t@echo "Testing of coverage in the sources finished, look at the " \\ \t "results in $(BUILDDIR)/coverage/python.txt." +.PHONY: xml xml: \t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml \t@echo \t@echo "Build finished. The XML files are in $(BUILDDIR)/xml." +.PHONY: pseudoxml pseudoxml: \t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml \t@echo @@ -715,7 +740,7 @@ if "%%1" == "clean" ( REM Check if sphinx-build is available and fallback to Python version if any -%%SPHINXBUILD%% 2> nul +%%SPHINXBUILD%% 1>NUL 2>NUL if errorlevel 9009 goto sphinx_python goto sphinx_ok @@ -978,7 +1003,7 @@ set SPHINXPROJ=%(project_fn)s if "%%1" == "" goto help -%%SPHINXBUILD%% 2> nul +%%SPHINXBUILD%% >NUL 2>NUL if errorlevel 9009 ( \techo. \techo.The 'sphinx-build' command was not found. Make sure you have Sphinx @@ -1254,22 +1279,25 @@ Please indicate if you want to use one of the following Sphinx extensions:''') if 'ext_coverage' not in d: do_prompt(d, 'ext_coverage', 'coverage: checks for documentation ' 'coverage (y/n)', 'n', boolean) - if 'ext_pngmath' not in d: - do_prompt(d, 'ext_pngmath', 'pngmath: include math, rendered ' - 'as PNG images (y/n)', 'n', boolean) + if 'ext_imgmath' not in d: + do_prompt(d, 'ext_imgmath', 'imgmath: include math, rendered ' + 'as PNG or SVG images (y/n)', 'n', boolean) if 'ext_mathjax' not in d: do_prompt(d, 'ext_mathjax', 'mathjax: include math, rendered in the ' 'browser by MathJax (y/n)', 'n', boolean) - if d['ext_pngmath'] and d['ext_mathjax']: - print('''Note: pngmath and mathjax cannot be enabled at the same time. -pngmath has been deselected.''') - d['ext_pngmath'] = False + if d['ext_imgmath'] and d['ext_mathjax']: + print('''Note: imgmath and mathjax cannot be enabled at the same time. +imgmath has been deselected.''') + d['ext_imgmath'] = False if 'ext_ifconfig' not in d: do_prompt(d, 'ext_ifconfig', 'ifconfig: conditional inclusion of ' 'content based on config values (y/n)', 'n', boolean) if 'ext_viewcode' not in d: do_prompt(d, 'ext_viewcode', 'viewcode: include links to the source ' 'code of documented Python objects (y/n)', 'n', boolean) + if 'ext_githubpages' not in d: + do_prompt(d, 'ext_githubpages', 'githubpages: create .nojekyll file ' + 'to publish the document on GitHub pages (y/n)', 'n', boolean) if 'no_makefile' in d: d['makefile'] = False @@ -1336,7 +1364,11 @@ def generate(d, overwrite=True, silent=False): d['exclude_patterns'] = '' else: builddir = path.join(srcdir, d['dot'] + 'build') - d['exclude_patterns'] = repr(d['dot'] + 'build') + exclude_patterns = map(repr, [ + d['dot'] + 'build', + 'Thumbs.db', '.DS_Store', + ]) + d['exclude_patterns'] = ', '.join(exclude_patterns) mkdir_p(builddir) mkdir_p(path.join(srcdir, d['dot'] + 'templates')) mkdir_p(path.join(srcdir, d['dot'] + 'static')) diff --git a/sphinx/roles.py b/sphinx/roles.py index 96e098569..59d69a1f1 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -5,7 +5,7 @@ Handlers for additional ReST roles. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -29,7 +29,7 @@ generic_docroles = { 'kbd': nodes.literal, 'mailheader': addnodes.literal_emphasis, 'makevar': addnodes.literal_strong, - 'manpage': addnodes.literal_emphasis, + 'manpage': addnodes.manpage, 'mimetype': addnodes.literal_emphasis, 'newsgroup': addnodes.literal_emphasis, 'program': addnodes.literal_strong, # XXX should be an x-ref diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index 050ab2d7f..75d03f19d 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -5,7 +5,7 @@ Create a full-text search index for offline search. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re @@ -135,6 +135,7 @@ languages = { 'ru': 'sphinx.search.ru.SearchRussian', 'sv': 'sphinx.search.sv.SearchSwedish', 'tr': 'sphinx.search.tr.SearchTurkish', + 'zh': 'sphinx.search.zh.SearchChinese', } diff --git a/sphinx/search/de.py b/sphinx/search/de.py index 89cbe3de5..35c6263c4 100644 --- a/sphinx/search/de.py +++ b/sphinx/search/de.py @@ -311,4 +311,5 @@ class SearchGerman(SearchLanguage): self.stemmer = snowballstemmer.stemmer('german') def stem(self, word): + word = word.lower() return self.stemmer.stemWord(word) diff --git a/sphinx/search/en.py b/sphinx/search/en.py index de77ae294..9501cddeb 100644 --- a/sphinx/search/en.py +++ b/sphinx/search/en.py @@ -5,25 +5,18 @@ English search language: includes the JS porter stemmer. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from sphinx.search import SearchLanguage try: - # http://bitbucket.org/methane/porterstemmer/ - from porterstemmer import Stemmer as CStemmer - CSTEMMER = True - PYSTEMMER = False + from Stemmer import Stemmer as PyStemmer + PYSTEMMER = True except ImportError: - CSTEMMER = False - try: - from Stemmer import Stemmer as PyStemmer - PYSTEMMER = True - except ImportError: - from sphinx.util.stemmer import PorterStemmer - PYSTEMMER = False + from sphinx.util.stemmer import PorterStemmer + PYSTEMMER = False english_stopwords = set(""" a and are as at @@ -231,11 +224,7 @@ class SearchEnglish(SearchLanguage): stopwords = english_stopwords def init(self, options): - if CSTEMMER: - class Stemmer(CStemmer): - def stem(self, word): - return self(word.lower()) - elif PYSTEMMER: + if PYSTEMMER: class Stemmer(object): def __init__(self): self.stemmer = PyStemmer('porter') diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py index 3c9ee88fd..6e9ead616 100644 --- a/sphinx/search/ja.py +++ b/sphinx/search/ja.py @@ -5,7 +5,7 @@ Japanese search language: includes routine to split words. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -21,7 +21,7 @@ import os import re import sys -from six import iteritems +from six import iteritems, PY3 try: import MeCab @@ -29,6 +29,7 @@ try: except ImportError: native_module = False +from sphinx.errors import SphinxError from sphinx.search import SearchLanguage @@ -43,13 +44,16 @@ class MecabBinder(object): self.dict_encode = options.get('dic_enc', 'utf-8') def split(self, input): - input2 = input.encode(self.dict_encode) + input2 = input if PY3 else input.encode(self.dict_encode) if native_module: result = self.native.parse(input2) else: result = self.ctypes_libmecab.mecab_sparse_tostr( self.ctypes_mecab, input.encode(self.dict_encode)) - return result.decode(self.dict_encode).split(' ') + if PY3: + return result.split(' ') + else: + return result.decode(self.dict_encode).split(' ') def init_native(self, options): param = '-Owakati' @@ -83,9 +87,16 @@ class MecabBinder(object): if dict: param += ' -d %s' % dict + fs_enc = sys.getfilesystemencoding() or sys.getdefaultencoding() + self.ctypes_libmecab = ctypes.CDLL(libpath) + self.ctypes_libmecab.mecab_new2.argtypes = (ctypes.c_char_p,) + self.ctypes_libmecab.mecab_new2.restype = ctypes.c_void_p + self.ctypes_libmecab.mecab_sparse_tostr.argtypes = (ctypes.c_void_p, ctypes.c_char_p) self.ctypes_libmecab.mecab_sparse_tostr.restype = ctypes.c_char_p - self.ctypes_mecab = self.ctypes_libmecab.mecab_new2(param) + self.ctypes_mecab = self.ctypes_libmecab.mecab_new2(param.encode(fs_enc)) + if self.ctypes_mecab is None: + raise SphinxError('mecab initialization failed') def __del__(self): if self.ctypes_libmecab: diff --git a/sphinx/search/zh.py b/sphinx/search/zh.py new file mode 100644 index 000000000..92f66e971 --- /dev/null +++ b/sphinx/search/zh.py @@ -0,0 +1,276 @@ +# -*- coding: utf-8 -*- +""" + sphinx.search.zh + ~~~~~~~~~~~~~~~~ + + Chinese search language: includes routine to split words. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import os +import re + +from sphinx.search import SearchLanguage + +try: + from Stemmer import Stemmer as PyStemmer + PYSTEMMER = True +except ImportError: + from sphinx.util.stemmer import PorterStemmer + PYSTEMMER = False + +try: + import jieba + JIEBA = True +except ImportError: + JIEBA = False + +english_stopwords = set(""" +a and are as at +be but by +for +if in into is it +near no not +of on or +such +that the their then there these they this to +was will with +""".split()) + +js_porter_stemmer = """ +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|\ +ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|\ +iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} +""" + + +class SearchChinese(SearchLanguage): + """ + Chinese search implementation + """ + + lang = 'zh' + language_name = 'Chinese' + js_stemmer_code = js_porter_stemmer + stopwords = english_stopwords + latin1_letters = re.compile(r'\w+(?u)[\u0000-\u00ff]') + + def init(self, options): + if JIEBA: + dict_path = options.get('dict') + if dict_path and os.path.isfile(dict_path): + jieba.set_dictionary(dict_path) + + if PYSTEMMER: + class Stemmer(object): + def __init__(self): + self.stemmer = PyStemmer('porter') + + def stem(self, word): + return self.stemmer.stemWord(word) + else: + class Stemmer(PorterStemmer): + """All those porter stemmer implementations look hideous; + make at least the stem method nicer. + """ + def stem(self, word): + word = word.lower() + return PorterStemmer.stem(self, word, 0, len(word) - 1) + + self.stemmer = Stemmer() + + def split(self, input): + chinese = [] + if JIEBA: + chinese = list(jieba.cut_for_search(input)) + + latin1 = self.latin1_letters.findall(input) + return chinese + latin1 + + def word_filter(self, stemmed_word): + return len(stemmed_word) > 1 + + def stem(self, word): + return self.stemmer.stem(word) diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py index 1a942c5da..128c1415c 100644 --- a/sphinx/setup_command.py +++ b/sphinx/setup_command.py @@ -8,7 +8,7 @@ :author: Sebastian Wiesner :contact: basti.wiesner@gmx.net - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function diff --git a/sphinx/texinputs/Makefile b/sphinx/texinputs/Makefile index 5e6030c0d..d748006cc 100644 --- a/sphinx/texinputs/Makefile +++ b/sphinx/texinputs/Makefile @@ -8,6 +8,8 @@ ALLDVI = $(addsuffix .dvi,$(ALLDOCS)) ARCHIVEPRREFIX = # Additional LaTeX options LATEXOPTS = +# format: pdf or dvi +FMT = pdf LATEX = latex PDFLATEX = pdflatex @@ -41,9 +43,15 @@ tar: all-$(FMT) tar cf $(ARCHIVEPREFIX)docs-$(FMT).tar $(ARCHIVEPREFIX)docs-$(FMT) rm -r $(ARCHIVEPREFIX)docs-$(FMT) +gz: tar + gzip -9 < $(ARCHIVEPREFIX)docs-$(FMT).tar > $(ARCHIVEPREFIX)docs-$(FMT).tar.gz + bz2: tar bzip2 -9 -k $(ARCHIVEPREFIX)docs-$(FMT).tar +xz: tar + xz -9 -k $(ARCHIVEPREFIX)docs-$(FMT).tar + # The number of LaTeX runs is quite conservative, but I don't expect it # to get run often, so the little extra time won't hurt. %.dvi: %.tex @@ -63,8 +71,8 @@ bz2: tar $(PDFLATEX) $(LATEXOPTS) '$<' clean: - rm -f *.dvi *.log *.ind *.aux *.toc *.syn *.idx *.out *.ilg *.pla + rm -f *.log *.ind *.aux *.toc *.syn *.idx *.out *.ilg *.pla *.ps *.tar *.tar.gz *.tar.bz2 *.tar.xz $(ALLPDF) $(ALLDVI) -.PHONY: all all-pdf all-dvi all-ps clean +.PHONY: all all-pdf all-dvi all-ps clean zip tar gz bz2 xz .PHONY: all-pdf-ja diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 210f6ea1d..f94d5c934 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -19,7 +19,13 @@ \RequirePackage{makeidx} \RequirePackage{framed} \RequirePackage{ifthen} -\RequirePackage{color} +%The xcolor package draws better fcolorboxes +%around verbatim code +\IfFileExists{xcolor.sty}{ + \RequirePackage[xcdraw]{xcolor} +}{ + \RequirePackage{color} +} % For highlighted code. \RequirePackage{fancyvrb} % For table captions. @@ -52,18 +58,12 @@ %\renewcommand{\paperwidth}{7in} % O'Reilly ``Programmming Python'' % use pdfoutput for pTeX and dvipdfmx +% when pTeX (\kanjiskip is defined), set pdfoutput to evade \include{pdfcolor} \ifx\kanjiskip\undefined\else - \ifx\Gin@driver{dvipdfmx.def}\undefined\else - \newcount\pdfoutput\pdfoutput=0 - \fi + \newcount\pdfoutput\pdfoutput=0 \fi -% For graphicx, check if we are compiling under latex or pdflatex. -\ifx\pdftexversion\undefined - \usepackage{graphicx} -\else - \usepackage[pdftex]{graphicx} -\fi +\RequirePackage{graphicx} % for PDF output, use colors and maximal compression \newif\ifsphinxpdfoutput\sphinxpdfoutputfalse @@ -270,16 +270,15 @@ \fbox{\TheSbox} } -\newcommand{\py@lightbox}{{% - \setlength\parskip{0pt}\par - \noindent\rule[0ex]{\linewidth}{0.5pt}% - \par\noindent\vspace{-0.5ex}% - }} -\newcommand{\py@endlightbox}{{% - \setlength{\parskip}{0pt}% - \par\noindent\rule[0.5ex]{\linewidth}{0.5pt}% - \par\vspace{-0.5ex}% - }} +\newcommand{\py@lightbox}{% + \par\allowbreak + \noindent\rule{\linewidth}{0.5pt}\par\nobreak + {\parskip\z@skip\noindent}% + } +\newcommand{\py@endlightbox}{% + \par\nobreak + {\parskip\z@skip\noindent\rule[.4\baselineskip]{\linewidth}{0.5pt}}\par + } % Some are quite plain: \newcommand{\py@noticestart@note}{\py@lightbox} @@ -445,15 +444,18 @@ \RequirePackage{hypcap} % From docutils.writers.latex2e -\providecommand{\DUspan}[2]{% - {% group ("span") to limit the scope of styling commands - \@for\node@class@name:=#1\do{% - \ifcsname docutilsrole\node@class@name\endcsname% - \csname docutilsrole\node@class@name\endcsname% +% inline markup (custom roles) +% \DUrole{#1}{#2} tries \DUrole#1{#2} +\providecommand*{\DUrole}[2]{% + \ifcsname DUrole#1\endcsname% + \csname DUrole#1\endcsname{#2}% + \else% backwards compatibility: try \docutilsrole#1{#2} + \ifcsname docutilsrole#1\endcsname% + \csname docutilsrole#1\endcsname{#2}% + \else% + #2% \fi% - }% - {#2}% node content - }% close "span" + \fi% } \providecommand*{\DUprovidelength}[2]{ @@ -525,11 +527,18 @@ \fi % Define literal-block environment -\RequirePackage{float} -\floatstyle{plaintop} +\RequirePackage{newfloat} +\DeclareFloatingEnvironment{literal-block} \ifx\thechapter\undefined - \newfloat{literal-block}{htbp}{loc}[section] + \SetupFloatingEnvironment{literal-block}{within=section,placement=h} \else - \newfloat{literal-block}{htbp}{loc}[chapter] + \SetupFloatingEnvironment{literal-block}{within=chapter,placement=h} \fi -\floatname{literal-block}{List} +\SetupFloatingEnvironment{literal-block}{name=List} +% control caption around literal-block +\RequirePackage{capt-of} +\RequirePackage{needspace} +% if the left page space is less than \literalblockneedsapce, insert page-break +\newcommand{\literalblockneedspace}{5\baselineskip} +% margin before the caption of literal-block +\newcommand{\literalblockcaptiontopvspace}{0.5\baselineskip} diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html index 6a80a207f..9a24b834f 100644 --- a/sphinx/themes/agogo/layout.html +++ b/sphinx/themes/agogo/layout.html @@ -5,7 +5,7 @@ Sphinx layout template for the agogo theme, originally written by Andi Albrecht. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "basic/layout.html" %} diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t index 87801d685..26df9d6d5 100644 --- a/sphinx/themes/agogo/static/agogo.css_t +++ b/sphinx/themes/agogo/static/agogo.css_t @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- agogo theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/basic/defindex.html b/sphinx/themes/basic/defindex.html index 3c1240ebd..020f7e396 100644 --- a/sphinx/themes/basic/defindex.html +++ b/sphinx/themes/basic/defindex.html @@ -4,7 +4,7 @@ Default template for the "index" page. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "layout.html" %} diff --git a/sphinx/themes/basic/domainindex.html b/sphinx/themes/basic/domainindex.html index 2ca1111e1..33f1b8a2a 100644 --- a/sphinx/themes/basic/domainindex.html +++ b/sphinx/themes/basic/domainindex.html @@ -4,7 +4,7 @@ Template for domain indices (module index, ...). - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "layout.html" %} diff --git a/sphinx/themes/basic/genindex-single.html b/sphinx/themes/basic/genindex-single.html index ddc988381..a771c587b 100644 --- a/sphinx/themes/basic/genindex-single.html +++ b/sphinx/themes/basic/genindex-single.html @@ -4,7 +4,7 @@ Template for a "single" page of a split index. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {% macro indexentries(firstname, links) %} diff --git a/sphinx/themes/basic/genindex-split.html b/sphinx/themes/basic/genindex-split.html index ca8db18b0..f6545dea0 100644 --- a/sphinx/themes/basic/genindex-split.html +++ b/sphinx/themes/basic/genindex-split.html @@ -4,7 +4,7 @@ Template for a "split" index overview page. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "layout.html" %} diff --git a/sphinx/themes/basic/genindex.html b/sphinx/themes/basic/genindex.html index 18e29b5e3..69c4ec564 100644 --- a/sphinx/themes/basic/genindex.html +++ b/sphinx/themes/basic/genindex.html @@ -4,7 +4,7 @@ Template for an "all-in-one" index. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {% macro indexentries(firstname, links) %} diff --git a/sphinx/themes/basic/globaltoc.html b/sphinx/themes/basic/globaltoc.html index 9f0296a93..b16bb9f51 100644 --- a/sphinx/themes/basic/globaltoc.html +++ b/sphinx/themes/basic/globaltoc.html @@ -4,7 +4,7 @@ Sphinx sidebar template: global table of contents. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} <h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3> diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index 4c0ebd658..2576d564e 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -4,7 +4,7 @@ Master layout template for Sphinx themes. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- block doctype -%} diff --git a/sphinx/themes/basic/localtoc.html b/sphinx/themes/basic/localtoc.html index 02e7cf38e..82c2599c3 100644 --- a/sphinx/themes/basic/localtoc.html +++ b/sphinx/themes/basic/localtoc.html @@ -4,7 +4,7 @@ Sphinx sidebar template: local table of contents. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- if display_toc %} diff --git a/sphinx/themes/basic/page.html b/sphinx/themes/basic/page.html index e8be92e09..e30385338 100644 --- a/sphinx/themes/basic/page.html +++ b/sphinx/themes/basic/page.html @@ -4,7 +4,7 @@ Master template for simple pages. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "layout.html" %} diff --git a/sphinx/themes/basic/relations.html b/sphinx/themes/basic/relations.html index 907b38be1..e3faec04a 100644 --- a/sphinx/themes/basic/relations.html +++ b/sphinx/themes/basic/relations.html @@ -4,7 +4,7 @@ Sphinx sidebar template: relation links. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- if prev %} diff --git a/sphinx/themes/basic/search.html b/sphinx/themes/basic/search.html index d4a0b7d77..4e7f383ba 100644 --- a/sphinx/themes/basic/search.html +++ b/sphinx/themes/basic/search.html @@ -4,7 +4,7 @@ Template for the search page. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "layout.html" %} diff --git a/sphinx/themes/basic/searchbox.html b/sphinx/themes/basic/searchbox.html index 4e128f4c1..c2727fb91 100644 --- a/sphinx/themes/basic/searchbox.html +++ b/sphinx/themes/basic/searchbox.html @@ -4,7 +4,7 @@ Sphinx sidebar template: quick search box. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- if pagename != "search" and builder != "singlehtml" %} diff --git a/sphinx/themes/basic/searchresults.html b/sphinx/themes/basic/searchresults.html index dccd20b6e..2d7537aec 100644 --- a/sphinx/themes/basic/searchresults.html +++ b/sphinx/themes/basic/searchresults.html @@ -4,7 +4,7 @@ Template for the body of the search results page. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} <h1 id="search-documentation">{{ _('Search') }}</h1> diff --git a/sphinx/themes/basic/sourcelink.html b/sphinx/themes/basic/sourcelink.html index a104b8923..fdeb49da6 100644 --- a/sphinx/themes/basic/sourcelink.html +++ b/sphinx/themes/basic/sourcelink.html @@ -4,7 +4,7 @@ Sphinx sidebar template: "show source" link. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- if show_source and has_source and sourcename %} diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index d2afc4a3d..1f365c689 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -52,6 +52,8 @@ div.sphinxsidebar { width: {{ theme_sidebarwidth|toint }}px; margin-left: -100%; font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; } div.sphinxsidebar ul { diff --git a/sphinx/themes/basic/static/doctools.js_t b/sphinx/themes/basic/static/doctools.js_t index bf2856286..8d09bd906 100644 --- a/sphinx/themes/basic/static/doctools.js_t +++ b/sphinx/themes/basic/static/doctools.js_t @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for all documentation. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/basic/static/searchtools.js_t b/sphinx/themes/basic/static/searchtools.js_t index 1ab708ea0..8a150b272 100644 --- a/sphinx/themes/basic/static/searchtools.js_t +++ b/sphinx/themes/basic/static/searchtools.js_t @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for the full-text search. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -195,8 +195,7 @@ var Search = { } // lookup as search terms in fulltext - results = results.concat(this.performTermsSearch(searchterms, excluded, terms, Scorer.term)) - .concat(this.performTermsSearch(searchterms, excluded, titleterms, Scorer.title)); + results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms)); // let the scorer override scores with a custom scoring function if (Scorer.score) { @@ -360,23 +359,47 @@ var Search = { /** * search for full-text terms in the index */ - performTermsSearch : function(searchterms, excluded, terms, score) { + performTermsSearch : function(searchterms, excluded, terms, titleterms) { var filenames = this._index.filenames; var titles = this._index.titles; - var i, j, file, files; + var i, j, file; var fileMap = {}; + var scoreMap = {}; var results = []; // perform the search on the required terms for (i = 0; i < searchterms.length; i++) { var word = searchterms[i]; + var files = []; + var _o = [ + {files: terms[word], score: Scorer.term}, + {files: titleterms[word], score: Scorer.title} + ]; + // no match but word was a required one - if ((files = terms[word]) === undefined) + if ($u.every(_o, function(o){return o.files === undefined;})) { break; - if (files.length === undefined) { - files = [files]; } + // found search word in contents + $u.each(_o, function(o) { + var _files = o.files; + if (_files === undefined) + return + + if (_files.length === undefined) + _files = [_files]; + files = files.concat(_files); + + // set score for the word in each file to Scorer.term + for (j = 0; j < _files.length; j++) { + file = _files[j]; + if (!(file in scoreMap)) + scoreMap[file] = {} + scoreMap[file][word] = o.score; + } + }); + // create the mapping for (j = 0; j < files.length; j++) { file = files[j]; @@ -398,7 +421,9 @@ var Search = { // ensure that none of the excluded terms is in the search result for (i = 0; i < excluded.length; i++) { if (terms[excluded[i]] == file || - $u.contains(terms[excluded[i]] || [], file)) { + titleterms[excluded[i]] == file || + $u.contains(terms[excluded[i]] || [], file) || + $u.contains(titleterms[excluded[i]] || [], file)) { valid = false; break; } @@ -406,6 +431,9 @@ var Search = { // if we have still a valid result we can add it to the result list if (valid) { + // select one (max) score for the file. + // for better ranking, we should calculate ranking by using words statistics like basic tf-idf... + var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]})); results.push([filenames[file], titles[file], '', null, score]); } } diff --git a/sphinx/themes/basic/static/websupport.js b/sphinx/themes/basic/static/websupport.js index ef3a5714c..98e7f40b6 100644 --- a/sphinx/themes/basic/static/websupport.js +++ b/sphinx/themes/basic/static/websupport.js @@ -4,7 +4,7 @@ * * sphinx.websupport utilities for all documentation. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/classic/layout.html b/sphinx/themes/classic/layout.html index 149cd86b7..934d62c0d 100644 --- a/sphinx/themes/classic/layout.html +++ b/sphinx/themes/classic/layout.html @@ -4,7 +4,7 @@ Sphinx layout template for the default theme. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "basic/layout.html" %} diff --git a/sphinx/themes/classic/static/classic.css_t b/sphinx/themes/classic/static/classic.css_t index 15d072954..93673b01f 100644 --- a/sphinx/themes/classic/static/classic.css_t +++ b/sphinx/themes/classic/static/classic.css_t @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- default theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -223,7 +223,7 @@ a.headerlink:hover { color: white; } -div.body p, div.body dd, div.body li { +div.body p, div.body dd, div.body li, div.body blockquote { text-align: justify; line-height: 130%; } diff --git a/sphinx/themes/classic/static/sidebar.js_t b/sphinx/themes/classic/static/sidebar.js_t index 5b38e532e..cecef37ff 100644 --- a/sphinx/themes/classic/static/sidebar.js_t +++ b/sphinx/themes/classic/static/sidebar.js_t @@ -16,7 +16,7 @@ * Once the browser is closed the cookie is deleted and the position * reset to the default (expanded). * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/epub/epub-cover.html b/sphinx/themes/epub/epub-cover.html index 86de4d488..d933c2548 100644 --- a/sphinx/themes/epub/epub-cover.html +++ b/sphinx/themes/epub/epub-cover.html @@ -4,7 +4,7 @@ Sample template for the html cover page. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "layout.html" %} diff --git a/sphinx/themes/epub/layout.html b/sphinx/themes/epub/layout.html index 83706cf5f..bdbcec21d 100644 --- a/sphinx/themes/epub/layout.html +++ b/sphinx/themes/epub/layout.html @@ -4,7 +4,7 @@ Sphinx layout template for the epub theme. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "basic/layout.html" %} diff --git a/sphinx/themes/epub/static/epub.css b/sphinx/themes/epub/static/epub.css index d4cc62afa..84db952c4 100644 --- a/sphinx/themes/epub/static/epub.css +++ b/sphinx/themes/epub/static/epub.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- epub theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/haiku/layout.html b/sphinx/themes/haiku/layout.html index 0f4c96f28..f89d859cf 100644 --- a/sphinx/themes/haiku/layout.html +++ b/sphinx/themes/haiku/layout.html @@ -4,7 +4,7 @@ Sphinx layout template for the haiku theme. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "basic/layout.html" %} diff --git a/sphinx/themes/haiku/static/haiku.css_t b/sphinx/themes/haiku/static/haiku.css_t index e2f32368c..0c20fd5c1 100644 --- a/sphinx/themes/haiku/static/haiku.css_t +++ b/sphinx/themes/haiku/static/haiku.css_t @@ -16,7 +16,7 @@ * Braden Ewing <brewin@gmail.com> * Humdinger <humdingerb@gmail.com> * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/nature/static/nature.css_t b/sphinx/themes/nature/static/nature.css_t index 391de6e79..4406956b4 100644 --- a/sphinx/themes/nature/static/nature.css_t +++ b/sphinx/themes/nature/static/nature.css_t @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- nature theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/pyramid/static/epub.css b/sphinx/themes/pyramid/static/epub.css index 7c6e67806..6fdf47704 100644 --- a/sphinx/themes/pyramid/static/epub.css +++ b/sphinx/themes/pyramid/static/epub.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- default theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/pyramid/static/pyramid.css_t b/sphinx/themes/pyramid/static/pyramid.css_t index 80252d209..d40da6661 100644 --- a/sphinx/themes/pyramid/static/pyramid.css_t +++ b/sphinx/themes/pyramid/static/pyramid.css_t @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- pylons theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/scrolls/layout.html b/sphinx/themes/scrolls/layout.html index 7120750a9..d07fec556 100644 --- a/sphinx/themes/scrolls/layout.html +++ b/sphinx/themes/scrolls/layout.html @@ -5,7 +5,7 @@ Sphinx layout template for the scrolls theme, originally written by Armin Ronacher. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "basic/layout.html" %} diff --git a/sphinx/themes/scrolls/static/scrolls.css_t b/sphinx/themes/scrolls/static/scrolls.css_t index a6833cf7b..83752c9d1 100644 --- a/sphinx/themes/scrolls/static/scrolls.css_t +++ b/sphinx/themes/scrolls/static/scrolls.css_t @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- scrolls theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/sphinxdoc/layout.html b/sphinx/themes/sphinxdoc/layout.html index 54c03fd55..ea05440eb 100644 --- a/sphinx/themes/sphinxdoc/layout.html +++ b/sphinx/themes/sphinxdoc/layout.html @@ -4,7 +4,7 @@ Sphinx layout template for the sphinxdoc theme. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "basic/layout.html" %} diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t index 712eec085..c47a03d4c 100644 --- a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +++ b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t @@ -5,7 +5,7 @@ * Sphinx stylesheet -- sphinxdoc theme. Originally created by * Armin Ronacher for Werkzeug. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/traditional/static/traditional.css_t b/sphinx/themes/traditional/static/traditional.css_t index ba96ca6e8..b98de2268 100644 --- a/sphinx/themes/traditional/static/traditional.css_t +++ b/sphinx/themes/traditional/static/traditional.css_t @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- traditional docs.python.org theme. * - * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/theming.py b/sphinx/theming.py index 49bd707d0..7c53479ca 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -5,7 +5,7 @@ Theming support for HTML builders. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -27,7 +27,6 @@ from sphinx import package_dir from sphinx.errors import ThemeError import alabaster -import sphinx_rtd_theme NODEFAULT = object() THEMECONF = 'theme.conf' @@ -71,7 +70,13 @@ class Theme(object): @classmethod def load_extra_theme(cls, name): - if name in ('alabaster', 'sphinx_rtd_theme'): + themes = ['alabaster'] + try: + import sphinx_rtd_theme + themes.append('sphinx_rtd_theme') + except ImportError: + pass + if name in themes: if name == 'alabaster': themedir = alabaster.get_path() # alabaster theme also requires 'alabaster' extension, it will be loaded @@ -97,8 +102,12 @@ class Theme(object): if name not in self.themes: self.load_extra_theme(name) if name not in self.themes: - raise ThemeError('no theme named %r found ' - '(missing theme.conf?)' % name) + if name == 'sphinx_rtd_theme': + raise ThemeError('sphinx_rtd_theme is no longer a hard dependency ' + 'since version 1.4.0. Please install it manually.') + else: + raise ThemeError('no theme named %r found ' + '(missing theme.conf?)' % name) self.name = name # Do not warn yet -- to be compatible with old Sphinxes, people *have* @@ -136,9 +145,8 @@ class Theme(object): except configparser.NoOptionError: raise ThemeError('theme %r doesn\'t have "inherit" setting' % name) - if inherit in ['alabaster', 'sphinx_rtd_theme']: - # include 'alabaster' or 'sphinx_themes' automatically #1794 - self.load_extra_theme(inherit) + # load inherited theme automatically #1794, #1884, #1885 + self.load_extra_theme(inherit) if inherit == 'none': self.base = None diff --git a/sphinx/transforms.py b/sphinx/transforms.py index d4ad4316e..49c0aba1e 100644 --- a/sphinx/transforms.py +++ b/sphinx/transforms.py @@ -5,7 +5,7 @@ Docutils transforms used by Sphinx when reading documents. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -156,6 +156,7 @@ class CitationReferences(Transform): refnode = addnodes.pending_xref(cittext, reftype='citation', reftarget=cittext, refwarn=True, ids=citnode["ids"]) + refnode.source = citnode.source or citnode.parent.source refnode.line = citnode.line or citnode.parent.line refnode += nodes.Text('[' + cittext + ']') citnode.parent.replace(citnode, refnode) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index e23539fdc..5e5c8ac9e 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -5,7 +5,7 @@ Utility functions for Sphinx. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -23,6 +23,7 @@ from collections import deque from six import iteritems, text_type, binary_type from six.moves import range +from six.moves.urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode import docutils from docutils.utils import relative_path @@ -420,23 +421,21 @@ def split_into(n, type, value): def split_index_msg(type, value): # new entry types must be listed in directives/other.py! - result = [] - try: - if type == 'single': - try: - result = split_into(2, 'single', value) - except ValueError: - result = split_into(1, 'single', value) - elif type == 'pair': - result = split_into(2, 'pair', value) - elif type == 'triple': - result = split_into(3, 'triple', value) - elif type == 'see': - result = split_into(2, 'see', value) - elif type == 'seealso': - result = split_into(2, 'see', value) - except ValueError: - pass + if type == 'single': + try: + result = split_into(2, 'single', value) + except ValueError: + result = split_into(1, 'single', value) + elif type == 'pair': + result = split_into(2, 'pair', value) + elif type == 'triple': + result = split_into(3, 'triple', value) + elif type == 'see': + result = split_into(2, 'see', value) + elif type == 'seealso': + result = split_into(2, 'see', value) + else: + raise ValueError('invalid %s index entry %r' % (type, value)) return result @@ -523,3 +522,13 @@ def import_object(objname, source=None): raise ExtensionError('Could not find %s' % objname + (source and ' (needed for %s)' % source or ''), err) + + +def encode_uri(uri): + split = list(urlsplit(uri)) + split[1] = split[1].encode('idna').decode('ascii') + split[2] = quote_plus(split[2].encode('utf-8'), '/').decode('ascii') + query = list((q, quote_plus(v.encode('utf-8'))) + for (q, v) in parse_qsl(split[3])) + split[3] = urlencode(query).decode('ascii') + return urlunsplit(split) diff --git a/sphinx/util/compat.py b/sphinx/util/compat.py index 2203634aa..5329cb668 100644 --- a/sphinx/util/compat.py +++ b/sphinx/util/compat.py @@ -5,7 +5,7 @@ Stuff for docutils compatibility. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import warnings diff --git a/sphinx/util/console.py b/sphinx/util/console.py index acba52a9f..7974ebb2b 100644 --- a/sphinx/util/console.py +++ b/sphinx/util/console.py @@ -5,7 +5,7 @@ Format colored console output. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index d503ac58b..f4eb703ce 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -6,7 +6,7 @@ "Doc fields" are reST field lists in object descriptions that will be domain-specifically transformed to a more appealing presentation. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -106,8 +106,6 @@ class GroupedField(Field): def make_field(self, types, domain, items): fieldname = nodes.field_name('', self.label) listnode = self.list_type() - if len(items) == 1 and self.can_collapse: - return Field.make_field(self, types, domain, items[0]) for fieldarg, content in items: par = nodes.paragraph() par += self.make_xref(self.rolename, domain, fieldarg, @@ -115,6 +113,9 @@ class GroupedField(Field): par += nodes.Text(' -- ') par += content listnode += nodes.list_item('', par) + if len(items) == 1 and self.can_collapse: + fieldbody = nodes.field_body('', listnode[0][0]) + return nodes.field('', fieldname, fieldbody) fieldbody = nodes.field_body('', listnode) return nodes.field('', fieldname, fieldbody) diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py index 22c3696ab..fba9bf490 100644 --- a/sphinx/util/docstrings.py +++ b/sphinx/util/docstrings.py @@ -5,7 +5,7 @@ Utilities for docstring processing. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index a72e138c5..47d981773 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -5,7 +5,7 @@ Builder superclass for all builders. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import gettext diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 42b79ab75..5dc2008c8 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -5,7 +5,7 @@ Helpers for inspecting Python modules. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/jsdump.py b/sphinx/util/jsdump.py index 362f67942..fd553bd40 100644 --- a/sphinx/util/jsdump.py +++ b/sphinx/util/jsdump.py @@ -6,7 +6,7 @@ This module implements a simple JavaScript serializer. Uses the basestring encode function from simplejson by Bob Ippolito. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/jsonimpl.py b/sphinx/util/jsonimpl.py index 38e1b13cb..215dfe44d 100644 --- a/sphinx/util/jsonimpl.py +++ b/sphinx/util/jsonimpl.py @@ -5,7 +5,7 @@ JSON serializer implementation wrapper. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/matching.py b/sphinx/util/matching.py index b3bbdcd14..91fda6378 100644 --- a/sphinx/util/matching.py +++ b/sphinx/util/matching.py @@ -5,7 +5,7 @@ Pattern-matching utility functions for Sphinx. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 2b0fff81d..f8d2d0a93 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -5,7 +5,7 @@ Docutils node-related utility functions for Sphinx. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -47,8 +47,10 @@ def apply_source_workaround(node): node.line = definition_list_item.line - 1 node.rawsource = node.astext() # set 'classifier1' (or 'classifier2') if isinstance(node, nodes.term): - # overwrite: ``term : classifier1 : classifier2`` -> ``term text`` - node.rawsource = node.astext() + # strip classifier from rawsource of term + for classifier in reversed(node.parent.traverse(nodes.classifier)): + node.rawsource = re.sub( + '\s*:\s*%s' % re.escape(classifier.astext()), '', node.rawsource) # workaround: recommonmark-0.2.0 doesn't set rawsource attribute if not node.rawsource: @@ -290,9 +292,13 @@ def set_role_source_info(inliner, lineno, node): node.source, node.line = inliner.reporter.get_source_and_line(lineno) -# monkey-patch Element.copy to copy the rawsource +# monkey-patch Element.copy to copy the rawsource and line def _new_copy(self): - return self.__class__(self.rawsource, **self.attributes) + newnode = self.__class__(self.rawsource, **self.attributes) + if isinstance(self, nodes.Element): + newnode.source = self.source + newnode.line = self.line + return newnode nodes.Element.copy = _new_copy diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 5bfbcabc4..e1d29a9d3 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -5,7 +5,7 @@ Operating system-related utility functions for Sphinx. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -166,8 +166,14 @@ def ustrftime(format, *args): # given by LC_TIME; if that is available, use it enc = locale.getlocale(locale.LC_TIME)[1] or 'utf-8' return time.strftime(text_type(format).encode(enc), *args).decode(enc) - else: - return time.strftime(format, *args) + else: # Py3 + # On Windows, time.strftime() and Unicode characters will raise UnicodeEncodeError. + # http://bugs.python.org/issue8304 + try: + return time.strftime(format, *args) + except UnicodeEncodeError: + r = time.strftime(format.encode('unicode-escape').decode(), *args) + return r.encode().decode('unicode-escape') def safe_relpath(path, start=None): diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py index 1d1e0a098..bace0b5fd 100644 --- a/sphinx/util/parallel.py +++ b/sphinx/util/parallel.py @@ -5,20 +5,21 @@ Parallel building utilities. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os +import time import traceback +from math import sqrt try: import multiprocessing - import threading except ImportError: - multiprocessing = threading = None + multiprocessing = None -from six.moves import queue +from six import iteritems from sphinx.errors import SphinxParallelError @@ -49,17 +50,20 @@ class ParallelTasks(object): def __init__(self, nproc): self.nproc = nproc - # list of threads to join when waiting for completion - self._taskid = 0 - self._threads = {} - self._nthreads = 0 - # queue of result objects to process - self.result_queue = queue.Queue() - self._nprocessed = 0 - # maps tasks to result functions + # (optional) function performed by each task on the result of main task self._result_funcs = {} - # allow only "nproc" worker processes at once - self._semaphore = threading.Semaphore(self.nproc) + # task arguments + self._args = {} + # list of subprocesses (both started and waiting) + self._procs = {} + # list of receiving pipe connections of running subprocesses + self._precvs = {} + # list of receiving pipe connections of waiting subprocesses + self._precvsWaiting = {} + # number of working subprocesses + self._pworking = 0 + # task number of each subprocess + self._taskid = 0 def _process(self, pipe, func, arg): try: @@ -71,61 +75,48 @@ class ParallelTasks(object): except BaseException as err: pipe.send((True, (err, traceback.format_exc()))) - def _process_thread(self, tid, func, arg): - precv, psend = multiprocessing.Pipe(False) - proc = multiprocessing.Process(target=self._process, - args=(psend, func, arg)) - proc.start() - result = precv.recv() - self.result_queue.put((tid, arg) + result) - proc.join() - self._semaphore.release() - def add_task(self, task_func, arg=None, result_func=None): tid = self._taskid self._taskid += 1 - self._semaphore.acquire() - thread = threading.Thread(target=self._process_thread, - args=(tid, task_func, arg)) - thread.setDaemon(True) - thread.start() - self._nthreads += 1 - self._threads[tid] = thread - self._result_funcs[tid] = result_func or (lambda *x: None) - # try processing results already in parallel - try: - tid, arg, exc, result = self.result_queue.get(False) - except queue.Empty: - pass - else: - del self._threads[tid] - if exc: - raise SphinxParallelError(*result) - result_func = self._result_funcs.pop(tid)(arg, result) - if result_func: - result_func(result) - self._nprocessed += 1 + self._result_funcs[tid] = result_func or (lambda arg: None) + self._args[tid] = arg + precv, psend = multiprocessing.Pipe(False) + proc = multiprocessing.Process(target=self._process, + args=(psend, task_func, arg)) + self._procs[tid] = proc + self._precvsWaiting[tid] = precv + self._join_one() def join(self): - while self._nprocessed < self._nthreads: - tid, arg, exc, result = self.result_queue.get() - del self._threads[tid] - if exc: - raise SphinxParallelError(*result) - result_func = self._result_funcs.pop(tid)(arg, result) - if result_func: - result_func(result) - self._nprocessed += 1 + while self._pworking: + self._join_one() - # there shouldn't be any threads left... - for t in self._threads.values(): - t.join() + def _join_one(self): + for tid, pipe in iteritems(self._precvs): + if pipe.poll(): + exc, result = pipe.recv() + if exc: + raise SphinxParallelError(*result) + self._result_funcs.pop(tid)(self._args.pop(tid), result) + self._procs[tid].join() + self._pworking -= 1 + break + else: + time.sleep(0.02) + while self._precvsWaiting and self._pworking < self.nproc: + newtid, newprecv = self._precvsWaiting.popitem() + self._precvs[newtid] = newprecv + self._procs[newtid].start() + self._pworking += 1 def make_chunks(arguments, nproc, maxbatch=10): # determine how many documents to read in one go nargs = len(arguments) - chunksize = min(nargs // nproc, maxbatch) + chunksize = nargs // nproc + if chunksize >= maxbatch: + # try to improve batch size vs. number of batches + chunksize = int(sqrt(nargs/nproc * maxbatch)) if chunksize == 0: chunksize = 1 nchunks, rest = divmod(nargs, chunksize) diff --git a/sphinx/util/png.py b/sphinx/util/png.py index b308a171a..e28445a42 100644 --- a/sphinx/util/png.py +++ b/sphinx/util/png.py @@ -5,7 +5,7 @@ PNG image manipulation helpers. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 062cee739..0daa87981 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -5,7 +5,7 @@ Stuff for Python version compatibility. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -20,6 +20,8 @@ from itertools import product from six import PY3, text_type, exec_ +NoneType = type(None) + # ------------------------------------------------------------------------------ # Python 2/3 compatibility diff --git a/sphinx/util/tags.py b/sphinx/util/tags.py index a44c265d1..180cb49ec 100644 --- a/sphinx/util/tags.py +++ b/sphinx/util/tags.py @@ -3,7 +3,7 @@ sphinx.util.tags ~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/texescape.py b/sphinx/util/texescape.py index 59fcf0a27..41ab9741c 100644 --- a/sphinx/util/texescape.py +++ b/sphinx/util/texescape.py @@ -5,7 +5,7 @@ TeX escaping helper. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/websupport.py b/sphinx/util/websupport.py index 6c9cfeb9a..f91cca97a 100644 --- a/sphinx/util/websupport.py +++ b/sphinx/util/websupport.py @@ -3,7 +3,7 @@ sphinx.util.websupport ~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/versioning.py b/sphinx/versioning.py index c6691d52d..f6c446b4f 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -6,7 +6,7 @@ Implements the low-level algorithms Sphinx uses for the versioning of doctrees. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from uuid import uuid4 diff --git a/sphinx/websupport/__init__.py b/sphinx/websupport/__init__.py index 8e6288756..606d549a6 100644 --- a/sphinx/websupport/__init__.py +++ b/sphinx/websupport/__init__.py @@ -5,7 +5,7 @@ Base Module for web support functions. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/websupport/errors.py b/sphinx/websupport/errors.py index 1eda97e5c..608caaa52 100644 --- a/sphinx/websupport/errors.py +++ b/sphinx/websupport/errors.py @@ -5,7 +5,7 @@ Contains Error classes for the web support package. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/websupport/search/__init__.py b/sphinx/websupport/search/__init__.py index 3aafbda55..844a3b468 100644 --- a/sphinx/websupport/search/__init__.py +++ b/sphinx/websupport/search/__init__.py @@ -5,7 +5,7 @@ Server side search support for the web support package. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/websupport/search/nullsearch.py b/sphinx/websupport/search/nullsearch.py index 3d46555c0..9e990b1cf 100644 --- a/sphinx/websupport/search/nullsearch.py +++ b/sphinx/websupport/search/nullsearch.py @@ -5,7 +5,7 @@ The default search adapter, does nothing. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/websupport/search/whooshsearch.py b/sphinx/websupport/search/whooshsearch.py index 17a6e75a6..4b0769f50 100644 --- a/sphinx/websupport/search/whooshsearch.py +++ b/sphinx/websupport/search/whooshsearch.py @@ -5,7 +5,7 @@ Whoosh search adapter. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/websupport/search/xapiansearch.py b/sphinx/websupport/search/xapiansearch.py index c097e5e6e..ee9b33da7 100644 --- a/sphinx/websupport/search/xapiansearch.py +++ b/sphinx/websupport/search/xapiansearch.py @@ -5,7 +5,7 @@ Xapian search adapter. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/websupport/storage/__init__.py b/sphinx/websupport/storage/__init__.py index e04ac6ed5..6939fe577 100644 --- a/sphinx/websupport/storage/__init__.py +++ b/sphinx/websupport/storage/__init__.py @@ -5,7 +5,7 @@ Storage for the websupport package. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/websupport/storage/differ.py b/sphinx/websupport/storage/differ.py index 9ddd54764..393f92bc0 100644 --- a/sphinx/websupport/storage/differ.py +++ b/sphinx/websupport/storage/differ.py @@ -5,7 +5,7 @@ A differ for creating an HTML representations of proposal diffs - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/websupport/storage/sqlalchemy_db.py b/sphinx/websupport/storage/sqlalchemy_db.py index d96eb090d..b412ad488 100644 --- a/sphinx/websupport/storage/sqlalchemy_db.py +++ b/sphinx/websupport/storage/sqlalchemy_db.py @@ -6,7 +6,7 @@ SQLAlchemy table and mapper definitions used by the :class:`sphinx.websupport.storage.sqlalchemystorage.SQLAlchemyStorage`. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/websupport/storage/sqlalchemystorage.py b/sphinx/websupport/storage/sqlalchemystorage.py index 2842526c1..c8794f75c 100644 --- a/sphinx/websupport/storage/sqlalchemystorage.py +++ b/sphinx/websupport/storage/sqlalchemystorage.py @@ -5,7 +5,7 @@ An SQLAlchemy storage backend. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/writers/__init__.py b/sphinx/writers/__init__.py index fd795b0b9..ae763875a 100644 --- a/sphinx/writers/__init__.py +++ b/sphinx/writers/__init__.py @@ -5,6 +5,6 @@ Custom docutils writers. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index 13480d4fc..bd65fc52e 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -5,7 +5,7 @@ docutils writers handling Sphinx' custom nodes. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -640,6 +640,12 @@ class HTMLTranslator(BaseTranslator): self.body.append('<br />') raise nodes.SkipNode + def visit_manpage(self, node): + return self.visit_literal_emphasis(node) + + def depart_manpage(self, node): + return self.depart_literal_emphasis(node) + def depart_title(self, node): close_tag = self.context[-1] if (self.permalink_text and self.builder.add_permalinks and diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c98f10758..6286688a5 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -8,7 +8,7 @@ Much of this code is adapted from Dave Kuhlman's "docpy" writer from his docutils sandbox. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -25,6 +25,7 @@ from sphinx import highlighting from sphinx.errors import SphinxError from sphinx.locale import admonitionlabels, _ from sphinx.util import split_into +from sphinx.util.nodes import clean_astext from sphinx.util.osutil import ustrftime from sphinx.util.texescape import tex_escape_map, tex_replace_map from sphinx.util.smartypants import educate_quotes_latex @@ -45,6 +46,7 @@ HEADER = r'''%% Generated by Sphinx. %(usepackages)s %(contentsname)s %(numfig_format)s +%(pageautorefname)s %(preamble)s \title{%(title)s} @@ -53,6 +55,7 @@ HEADER = r'''%% Generated by Sphinx. \author{%(author)s} \newcommand{\sphinxlogo}{%(logo)s} \renewcommand{\releasename}{%(releasename)s} +%(tocdepth)s %(makeindex)s ''' @@ -69,6 +72,8 @@ FOOTER = r''' \end{document} ''' +URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:') + class collected_footnote(nodes.footnote): """Footnotes that are collected are assigned this class.""" @@ -98,6 +103,8 @@ class LaTeXWriter(writers.Writer): self.builder.translator_class or LaTeXTranslator) def translate(self): + transform = ShowUrlsTransform(self.document) + transform.apply() visitor = self.translator_class(self.document, self.builder) self.document.walkabout(visitor) self.output = visitor.astext() @@ -106,6 +113,10 @@ class LaTeXWriter(writers.Writer): # Helper classes class ExtBabel(Babel): + def __init__(self, language_code): + super(ExtBabel, self).__init__(language_code or '') + self.language_code = language_code + def get_shorthandoff(self): shortlang = self.language.split('_')[0] if shortlang in ('de', 'ngerman', 'sl', 'slovene', 'pt', 'portuges', @@ -120,12 +131,115 @@ class ExtBabel(Babel): 'mn', 'mongolian', 'ru', 'russian', 'uk', 'ukrainian') + def is_supported_language(self): + return bool(super(ExtBabel, self).get_language()) + + def get_language(self): + language = super(ExtBabel, self).get_language() + if not language: + return 'english' # fallback to english + else: + return language + # in latest trunk, the attribute is called Babel.language_codes and already # includes Slovene if hasattr(Babel, '_ISO639_TO_BABEL'): Babel._ISO639_TO_BABEL['sl'] = 'slovene' +class ShowUrlsTransform(object): + expanded = False + + def __init__(self, document): + self.document = document + + def apply(self): + # replace id_prefix temporarily + id_prefix = self.document.settings.id_prefix + self.document.settings.id_prefix = 'show_urls' + + self.expand_show_urls() + if self.expanded: + self.renumber_footnotes() + + # restore id_prefix + self.document.settings.id_prefix = id_prefix + + def expand_show_urls(self): + show_urls = self.document.settings.env.config.latex_show_urls + if show_urls is False or show_urls == 'no': + return + + for node in self.document.traverse(nodes.reference): + uri = node.get('refuri', '') + if uri.startswith(URI_SCHEMES): + if uri.startswith('mailto:'): + uri = uri[7:] + if node.astext() != uri: + index = node.parent.index(node) + if show_urls == 'footnote': + footnote_nodes = self.create_footnote(uri) + for i, fn in enumerate(footnote_nodes): + node.parent.insert(index + i + 1, fn) + + self.expanded = True + else: # all other true values (b/w compat) + textnode = nodes.Text(" (%s)" % uri) + node.parent.insert(index + 1, textnode) + + def create_footnote(self, uri): + label = nodes.label('', '#') + para = nodes.paragraph() + para.append(nodes.Text(uri)) + footnote = nodes.footnote(uri, label, para, auto=1) + footnote['names'].append('#') + self.document.note_autofootnote(footnote) + + label = nodes.Text('#') + footnote_ref = nodes.footnote_reference('[#]_', label, auto=1, + refid=footnote['ids'][0]) + self.document.note_autofootnote_ref(footnote_ref) + footnote.add_backref(footnote_ref['ids'][0]) + + return [footnote, footnote_ref] + + def renumber_footnotes(self): + def is_used_number(number): + for node in self.document.traverse(nodes.footnote): + if not node.get('auto') and number in node['names']: + return True + + return False + + def is_auto_footnote(node): + return isinstance(node, nodes.footnote) and node.get('auto') + + def footnote_ref_by(ids): + def is_footnote_ref(node): + return isinstance(node, nodes.footnote_reference) and ids[0] == node['refid'] + + return is_footnote_ref + + startnum = 1 + for footnote in self.document.traverse(is_auto_footnote): + while True: + label = str(startnum) + startnum += 1 + if not is_used_number(label): + break + + old_label = footnote[0].astext() + footnote.remove(footnote[0]) + footnote.insert(0, nodes.label('', label)) + if old_label in footnote['names']: + footnote['names'].remove(old_label) + footnote['names'].append(label) + + for footnote_ref in self.document.traverse(footnote_ref_by(footnote['ids'])): + footnote_ref.remove(footnote_ref[0]) + footnote_ref += nodes.Text(label) + + class Table(object): def __init__(self): self.col = 0 @@ -151,7 +265,9 @@ class LaTeXTranslator(nodes.NodeVisitor): 'classoptions': '', 'extraclassoptions': '', 'inputenc': '\\usepackage[utf8]{inputenc}', - 'utf8extra': '\\DeclareUnicodeCharacter{00A0}{\\nobreakspace}', + 'utf8extra': ('\\ifdefined\\DeclareUnicodeCharacter\n' + ' \\DeclareUnicodeCharacter{00A0}{\\nobreakspace}\n' + '\\else\\fi'), 'cmappkg': '\\usepackage{cmap}', 'fontenc': '\\usepackage[T1]{fontenc}', 'babel': '\\usepackage{babel}', @@ -176,6 +292,8 @@ class LaTeXTranslator(nodes.NodeVisitor): 'printindex': '\\printindex', 'transition': '\n\n\\bigskip\\hrule{}\\bigskip\n\n', 'figure_align': 'htbp', + 'tocdepth': '', + 'pageautorefname': '', } # sphinx specific document classes @@ -186,6 +304,22 @@ class LaTeXTranslator(nodes.NodeVisitor): self.builder = builder self.body = [] + # flags + self.in_title = 0 + self.in_production_list = 0 + self.in_footnote = 0 + self.in_caption = 0 + self.in_container_literal_block = 0 + self.in_term = 0 + self.first_document = 1 + self.this_is_the_title = 1 + self.literal_whitespace = 0 + self.no_contractions = 0 + self.compact_list = 0 + self.first_param = 0 + self.remember_multirow = {} + self.remember_multirowcol = {} + # sort out some elements papersize = builder.config.latex_paper_size + 'paper' if papersize == 'paper': # e.g. command line "-D latex_paper_size=" @@ -217,19 +351,18 @@ class LaTeXTranslator(nodes.NodeVisitor): if builder.config.latex_logo: self.elements['logo'] = '\\includegraphics{%s}\\par' % \ path.basename(builder.config.latex_logo) + # setup babel + self.babel = ExtBabel(builder.config.language) + self.elements['classoptions'] += ',' + self.babel.get_language() if builder.config.language: - babel = ExtBabel(builder.config.language) - lang = babel.get_language() - if lang: - self.elements['classoptions'] += ',' + babel.get_language() - else: + if not self.babel.is_supported_language(): self.builder.warn('no Babel option known for language %r' % builder.config.language) - self.elements['shorthandoff'] = babel.get_shorthandoff() + self.elements['shorthandoff'] = self.babel.get_shorthandoff() self.elements['fncychap'] = '\\usepackage[Sonny]{fncychap}' # Times fonts don't work with Cyrillic languages - if babel.uses_cyrillic(): + if self.babel.uses_cyrillic(): self.elements['fontpkg'] = '' # pTeX (Japanese TeX) for support @@ -240,8 +373,6 @@ class LaTeXTranslator(nodes.NodeVisitor): self.elements['babel'] = '' # disable fncychap in Japanese documents self.elements['fncychap'] = '' - else: - self.elements['classoptions'] += ',english' if getattr(builder, 'usepackages', None): def declare_package(packagename, options=None): if options: @@ -252,14 +383,22 @@ class LaTeXTranslator(nodes.NodeVisitor): self.elements['usepackages'] += "\n".join(usepackages) if getattr(document.settings, 'contentsname', None): self.elements['contentsname'] = \ - self.babel_renewcommand(builder, '\\contentsname', - document.settings.contentsname) + self.babel_renewcommand('\\contentsname', document.settings.contentsname) + self.elements['pageautorefname'] = \ + self.babel_defmacro('\\pageautorefname', self.encode(_('page'))) self.elements['numfig_format'] = self.generate_numfig_format(builder) # allow the user to override them all self.elements.update(builder.config.latex_elements) if self.elements['extraclassoptions']: self.elements['classoptions'] += ',' + \ self.elements['extraclassoptions'] + if document.get('tocdepth'): + if document.settings.docclass == 'howto': + self.elements['tocdepth'] = ('\\setcounter{tocdepth}{%d}' % + document['tocdepth']) + else: + self.elements['tocdepth'] = ('\\setcounter{tocdepth}{%d}' % + (document['tocdepth'] - 1)) self.highlighter = highlighting.PygmentsBridge( 'latex', @@ -275,7 +414,10 @@ class LaTeXTranslator(nodes.NodeVisitor): # by .. highlight:: directive in the master file self.hlsettingstack = 2 * [[builder.config.highlight_language, sys.maxsize]] + self.bodystack = [] self.footnotestack = [] + self.footnote_restricted = False + self.pending_footnotes = [] self.curfilestack = [] self.handled_abbrs = set() if document.settings.docclass == 'howto': @@ -289,19 +431,28 @@ class LaTeXTranslator(nodes.NodeVisitor): self.next_figure_ids = set() self.next_table_ids = set() self.next_literal_ids = set() - # flags - self.in_title = 0 - self.in_production_list = 0 - self.in_footnote = 0 - self.in_caption = 0 - self.first_document = 1 - self.this_is_the_title = 1 - self.literal_whitespace = 0 - self.no_contractions = 0 - self.compact_list = 0 - self.first_param = 0 - self.remember_multirow = {} - self.remember_multirowcol = {} + + def pushbody(self, newbody): + self.bodystack.append(self.body) + self.body = newbody + + def popbody(self): + body = self.body + self.body = self.bodystack.pop() + return body + + def restrict_footnote(self, node): + if self.footnote_restricted is False: + self.footnote_restricted = node + self.pending_footnotes = [] + + def unrestrict_footnote(self, node): + if self.footnote_restricted == node: + self.footnote_restricted = False + for footnode in self.pending_footnotes: + footnode['footnotetext'] = True + footnode.walkabout(self) + self.pending_footnotes = [] def format_docclass(self, docclass): """ prepends prefix to sphinx document classes @@ -325,7 +476,7 @@ class LaTeXTranslator(nodes.NodeVisitor): '\\label{%s}' % self.idescape(id) def hyperlink(self, id): - return '{\\hyperref[%s]{' % self.idescape(id) + return '{\\hyperref[%s]{' % self.hyperrefescape(id) def hyperpageref(self, id): return '\\autopageref*{%s}' % self.idescape(id) @@ -335,27 +486,28 @@ class LaTeXTranslator(nodes.NodeVisitor): encode('ascii', 'backslashreplace').decode('ascii').\ replace('\\', '_') - def babel_renewcommand(self, builder, command, definition): - if builder.config.language == 'ja': - babel_prefix = '' - babel_suffix = '' - else: - if builder.config.language: - language = ExtBabel(builder.config.language).get_language() - if language is None: - language = 'english' - else: - language = 'english' + def hyperrefescape(self, ref): + return self.idescape(ref).replace('-', '\\string-') - if self.elements['babel']: - babel_prefix = '\\addto\\captions%s{' % language - babel_suffix = '}' - else: - babel_prefix = '' - babel_suffix = '' + def babel_renewcommand(self, command, definition): + if self.elements['babel']: + prefix = '\\addto\\captions%s{' % self.babel.get_language() + suffix = '}' + else: # babel is disabled (mainly for Japanese environment) + prefix = '' + suffix = '' - return ('%s\\renewcommand{%s}{%s}%s\n' % - (babel_prefix, command, definition, babel_suffix)) + return ('%s\\renewcommand{%s}{%s}%s\n' % (prefix, command, definition, suffix)) + + def babel_defmacro(self, name, definition): + if self.elements['babel']: + prefix = '\\addto\\extras%s{' % self.babel.get_language() + suffix = '}' + else: # babel is disabled (mainly for Japanese environment) + prefix = '' + suffix = '' + + return ('%s\\def%s{%s}%s\n' % (prefix, name, definition, suffix)) def generate_numfig_format(self, builder): ret = [] @@ -365,7 +517,7 @@ class LaTeXTranslator(nodes.NodeVisitor): text_type(figure[0]).translate(tex_escape_map)) else: definition = text_type(figure[0]).translate(tex_escape_map) - ret.append(self.babel_renewcommand(builder, '\\figurename', definition)) + ret.append(self.babel_renewcommand('\\figurename', definition)) if figure[1]: ret.append('\\makeatletter\n') ret.append('\\def\\fnum@figure{\\figurename\\thefigure%s}\n' % @@ -378,7 +530,7 @@ class LaTeXTranslator(nodes.NodeVisitor): text_type(table[0]).translate(tex_escape_map)) else: definition = text_type(table[0]).translate(tex_escape_map) - ret.append(self.babel_renewcommand(builder, '\\tablename', definition)) + ret.append(self.babel_renewcommand('\\tablename', definition)) if table[1]: ret.append('\\makeatletter\n') ret.append('\\def\\fnum@table{\\tablename\\thetable%s}\n' % @@ -389,7 +541,7 @@ class LaTeXTranslator(nodes.NodeVisitor): if len(codeblock) == 1: pass # FIXME else: - ret.append('\\floatname{literal-block}{%s}\n' % + ret.append('\\SetupFloatingEnvironment{literal-block}{name=%s}\n' % text_type(codeblock[0]).translate(tex_escape_map)) if table[1]: pass # FIXME @@ -495,7 +647,8 @@ class LaTeXTranslator(nodes.NodeVisitor): fnotes = {} for fn in footnotes_under(node): num = fn.children[0].astext().strip() - fnotes[num] = [collected_footnote(*fn.children), False] + newnode = collected_footnote(*fn.children, number=num) + fnotes[num] = [newnode, False] return fnotes def depart_start_of_file(self, node): @@ -583,13 +736,18 @@ class LaTeXTranslator(nodes.NodeVisitor): self.this_is_the_title = 0 raise nodes.SkipNode elif isinstance(parent, nodes.section): + short = '' + if node.traverse(nodes.image): + short = '[%s]' % ' '.join(clean_astext(node).split()).translate(tex_escape_map) + try: - self.body.append(r'\%s{' % self.sectionnames[self.sectionlevel]) + self.body.append(r'\%s%s{' % (self.sectionnames[self.sectionlevel], short)) except IndexError: # just use "subparagraph", it's not numbered anyway - self.body.append(r'\%s{' % self.sectionnames[-1]) + self.body.append(r'\%s%s{' % (self.sectionnames[-1], short)) self.context.append('}\n') + self.restrict_footnote(node) if self.next_section_ids: for id in self.next_section_ids: self.context[-1] += self.hypertarget(id, anchor=False) @@ -602,8 +760,8 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append('{') self.context.append('}\n') elif isinstance(parent, nodes.table): - self.table.caption = self.encode(node.astext()) - raise nodes.SkipNode + # Redirect body output until title is finished. + self.pushbody([]) else: self.builder.warn( 'encountered title node not in section, topic, table, ' @@ -615,7 +773,11 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_title(self, node): self.in_title = 0 - self.body.append(self.context.pop()) + if isinstance(node.parent, nodes.table): + self.table.caption = self.popbody() + else: + self.body.append(self.context.pop()) + self.unrestrict_footnote(node) def visit_subtitle(self, node): if isinstance(node.parent, nodes.sidebar): @@ -734,8 +896,10 @@ class LaTeXTranslator(nodes.NodeVisitor): raise nodes.SkipNode self.body.append('\\paragraph{') self.context.append('}\n') + self.in_title = 1 def depart_rubric(self, node): + self.in_title = 0 self.body.append(self.context.pop()) def visit_footnote(self, node): @@ -743,7 +907,10 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_collected_footnote(self, node): self.in_footnote += 1 - self.body.append('\\footnote{') + if 'footnotetext' in node: + self.body.append('\\footnotetext[%s]{' % node['number']) + else: + self.body.append('\\footnote[%s]{' % node['number']) def depart_collected_footnote(self, node): self.body.append('}') @@ -770,16 +937,19 @@ class LaTeXTranslator(nodes.NodeVisitor): self.tablebody = [] self.tableheaders = [] # Redirect body output until table is finished. - self._body = self.body - self.body = self.tablebody + self.pushbody(self.tablebody) + self.restrict_footnote(node) def depart_table(self, node): if self.table.rowcount > 30: self.table.longtable = True - self.body = self._body + self.popbody() if not self.table.longtable and self.table.caption is not None: - self.body.append(u'\n\n\\begin{threeparttable}\n' - u'\\capstart\\caption{%s}\n' % self.table.caption) + self.body.append('\n\n\\begin{threeparttable}\n' + '\\capstart\\caption{') + for caption in self.table.caption: + self.body.append(caption) + self.body.append('}') for id in self.next_table_ids: self.body.append(self.hypertarget(id, anchor=False)) if node['ids']: @@ -812,7 +982,10 @@ class LaTeXTranslator(nodes.NodeVisitor): else: self.body.append('{|' + ('L|' * self.table.colcount) + '}\n') if self.table.longtable and self.table.caption is not None: - self.body.append(u'\\caption{%s}' % self.table.caption) + self.body.append(u'\\caption{') + for caption in self.table.caption: + self.body.append(caption) + self.body.append('}') for id in self.next_table_ids: self.body.append(self.hypertarget(id, anchor=False)) self.next_table_ids.clear() @@ -839,6 +1012,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append(endmacro) if not self.table.longtable and self.table.caption is not None: self.body.append('\\end{threeparttable}\n\n') + self.unrestrict_footnote(node) self.table = None self.tablebody = None @@ -935,8 +1109,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 @@ -947,6 +1124,8 @@ class LaTeXTranslator(nodes.NodeVisitor): context += str(extracols + 1) context += '}{l|}{}' self.table.col += extracols + if len(node.traverse(nodes.paragraph)) >= 2: + self.table.has_problematic = True self.context.append(context) def depart_entry(self, node): @@ -1004,14 +1183,18 @@ class LaTeXTranslator(nodes.NodeVisitor): pass def visit_term(self, node): + self.in_term += 1 ctx = '}] \\leavevmode' if node.get('ids'): ctx += self.hypertarget(node['ids'][0]) self.body.append('\\item[{') + self.restrict_footnote(node) self.context.append(ctx) def depart_term(self, node): self.body.append(self.context.pop()) + self.unrestrict_footnote(node) + self.in_term -= 1 def visit_termsep(self, node): self.body.append(', ') @@ -1125,12 +1308,12 @@ class LaTeXTranslator(nodes.NodeVisitor): (1, 'top'): ('', ''), (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'), (1, 'bottom'): ('\\raisebox{-\\height}{', '}'), - (0, 'center'): ('{\\hfill', '\\hfill}'), + (0, 'center'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'), # These 2 don't exactly do the right thing. The image should # be floated alongside the paragraph. See # http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG - (0, 'left'): ('{', '\\hfill}'), - (0, 'right'): ('{\\hfill', '}'), + (0, 'left'): ('{', '\\hspace*{\\fill}}'), + (0, 'right'): ('{\\hspace*{\\fill}', '}'), } try: pre.append(align_prepost[is_inline, attrs['align']][0]) @@ -1155,7 +1338,8 @@ class LaTeXTranslator(nodes.NodeVisitor): options = '' if include_graphics_options: options = '[%s]' % ','.join(include_graphics_options) - self.body.append('\\includegraphics%s{%s}' % (options, uri)) + base, ext = path.splitext(uri) + self.body.append('\\includegraphics%s{{%s}%s}' % (options, base, ext)) self.body.extend(post) def depart_image(self, node): @@ -1166,6 +1350,7 @@ class LaTeXTranslator(nodes.NodeVisitor): for id in self.next_figure_ids: ids += self.hypertarget(id, anchor=False) self.next_figure_ids.clear() + self.restrict_footnote(node) if (len(node.children) and isinstance(node.children[0], nodes.image) and node.children[0]['ids']): @@ -1193,9 +1378,15 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_figure(self, node): self.body.append(self.context.pop()) + self.unrestrict_footnote(node) def visit_caption(self, node): self.in_caption += 1 + if self.in_container_literal_block: + self.body.append('\\needspace{\\literalblockneedspace}') + self.body.append('\\vspace{\\literalblockcaptiontopvspace}') + self.body.append('\\captionof{literal-block}{') + return self.body.append('\\caption{') def depart_caption(self, node): @@ -1357,30 +1548,18 @@ class LaTeXTranslator(nodes.NodeVisitor): raise nodes.SkipNode def visit_reference(self, node): - for id in node.get('ids'): - self.body += self.hypertarget(id, anchor=True) + if not self.in_title: + for id in node.get('ids'): + anchor = not self.in_caption + self.body += self.hypertarget(id, anchor=anchor) uri = node.get('refuri', '') if not uri and node.get('refid'): uri = '%' + self.curfilestack[-1] + '#' + node['refid'] if self.in_title or not uri: self.context.append('') - elif uri.startswith('mailto:') or uri.startswith('http:') or \ - uri.startswith('https:') or uri.startswith('ftp:'): + elif uri.startswith(URI_SCHEMES): self.body.append('\\href{%s}{' % self.encode_uri(uri)) - # if configured, put the URL after the link - show_urls = self.builder.config.latex_show_urls - if node.astext() != uri and show_urls and show_urls != 'no': - if uri.startswith('mailto:'): - uri = uri[7:] - if show_urls == 'footnote' and not \ - (self.in_footnote or self.in_caption): - # obviously, footnotes in footnotes are not going to work - self.context.append( - r'}\footnote{%s}' % self.encode_uri(uri)) - else: # all other true values (b/w compat) - self.context.append('} (%s)' % self.encode_uri(uri)) - else: - self.context.append('}') + self.context.append('}') elif uri.startswith('#'): # references to labels in the same document id = self.curfilestack[-1] + ':' + uri[1:] @@ -1487,6 +1666,12 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_abbreviation(self, node): self.body.append(self.context.pop()) + def visit_manpage(self, node): + return self.visit_literal_emphasis(node) + + def depart_manpage(self, node): + return self.depart_literal_emphasis(node) + def visit_title_reference(self, node): self.body.append(r'\emph{') @@ -1531,12 +1716,15 @@ class LaTeXTranslator(nodes.NodeVisitor): # if a footnote has been inserted once, it shouldn't be repeated # by the next reference if used: - self.body.append('\\footnotemark[%s]' % num) + 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) + elif self.footnote_restricted: + self.footnotestack[-1][num][1] = True + self.body.append('\\protect\\footnotemark[%s]' % num) + self.pending_footnotes.append(footnode) else: - if self.in_caption: - raise UnsupportedError('%s:%s: footnotes in float captions ' - 'are not supported by LaTeX' % - (self.curfilestack[-1], node.line)) self.footnotestack[-1][num][1] = True footnode.walkabout(self) raise nodes.SkipChildren @@ -1717,7 +1905,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append(r'\underline{') self.context.append('}') elif classes and not self.in_title: - self.body.append(r'\DUspan{%s}{' % ','.join(classes)) + self.body.append(r'\DUrole{%s}{' % ','.join(classes)) self.context.append('}') else: self.context.append('') @@ -1739,17 +1927,19 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_container(self, node): if node.get('literal_block'): + self.in_container_literal_block += 1 ids = '' for id in self.next_literal_ids: ids += self.hypertarget(id, anchor=False) if node['ids']: ids += self.hypertarget(node['ids'][0]) self.next_literal_ids.clear() - self.body.append('\n\\begin{literal-block}\n') - self.context.append(ids + '\n\\end{literal-block}\n') + self.body.append('\n') + self.context.append(ids + '\n') def depart_container(self, node): if node.get('literal_block'): + self.in_container_literal_block -= 1 self.body.append(self.context.pop()) def visit_decoration(self, node): diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 4343f6a33..d1c65bfb7 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -5,7 +5,7 @@ Manual page writer, extended for Sphinx custom nodes. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -30,12 +30,44 @@ class ManualPageWriter(Writer): self.builder.translator_class or ManualPageTranslator) def translate(self): + transform = NestedInlineTransform(self.document) + transform.apply() visitor = self.translator_class(self.builder, self.document) self.visitor = visitor self.document.walkabout(visitor) self.output = visitor.astext() +class NestedInlineTransform(object): + """ + Flatten nested inline nodes: + + Before: + <strong>foo=<emphasis>1</emphasis> + &bar=<emphasis>2</emphasis></strong> + After: + <strong>foo=</strong><emphasis>var</emphasis> + <strong>&bar=</strong><emphasis>2</emphasis> + """ + def __init__(self, document): + self.document = document + + def apply(self): + def is_inline(node): + return isinstance(node, (nodes.literal, nodes.emphasis, nodes.strong)) + + for node in self.document.traverse(is_inline): + if any(is_inline(subnode) for subnode in node): + pos = node.parent.index(node) + for subnode in reversed(node[1:]): + node.remove(subnode) + if is_inline(subnode): + node.parent.insert(pos + 1, subnode) + else: + newnode = node.__class__('', subnode, **node.attributes) + node.parent.insert(pos + 1, newnode) + + class ManualPageTranslator(BaseTranslator): """ Custom translator. @@ -343,6 +375,12 @@ class ManualPageTranslator(BaseTranslator): def depart_abbreviation(self, node): pass + def visit_manpage(self, node): + return self.visit_strong(node) + + def depart_manpage(self, node): + return self.depart_strong(node) + # overwritten: handle section titles better than in 0.6 release def visit_title(self, node): if isinstance(node.parent, addnodes.seealso): diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index c066a3900..14345e664 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -5,7 +5,7 @@ Custom docutils writer for Texinfo. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -1476,6 +1476,12 @@ class TexinfoTranslator(nodes.NodeVisitor): def depart_abbreviation(self, node): self.body.append(self.context.pop()) + def visit_manpage(self, node): + return self.visit_literal_emphasis(node) + + def depart_manpage(self, node): + return self.depart_literal_emphasis(node) + def visit_download_reference(self, node): pass diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index a4785a980..1e8bbf023 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -5,7 +5,7 @@ Custom docutils writer for plain text. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os @@ -861,6 +861,12 @@ class TextTranslator(nodes.NodeVisitor): if node.hasattr('explanation'): self.add_text(' (%s)' % node['explanation']) + def visit_manpage(self, node): + return self.visit_literal_emphasis(node) + + def depart_manpage(self, node): + return self.depart_literal_emphasis(node) + def visit_title_reference(self, node): self.add_text('*') @@ -940,7 +946,9 @@ class TextTranslator(nodes.NodeVisitor): def visit_raw(self, node): if 'text' in node.get('format', '').split(): - self.body.append(node.astext()) + self.new_state(0) + self.add_text(node.astext()) + self.end_state(wrap = False) raise nodes.SkipNode def visit_math(self, node): diff --git a/sphinx/writers/websupport.py b/sphinx/writers/websupport.py index 0ba290249..3da5d6dba 100644 --- a/sphinx/writers/websupport.py +++ b/sphinx/writers/websupport.py @@ -5,7 +5,7 @@ sphinx.websupport writer that adds comment-related annotations. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/writers/xml.py b/sphinx/writers/xml.py index 4d44d7687..5aa0ad96a 100644 --- a/sphinx/writers/xml.py +++ b/sphinx/writers/xml.py @@ -5,7 +5,7 @@ Docutils-native XML and pseudo-XML writers. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/path.py b/tests/path.py index 2f0cdc062..f4b2612f6 100755 --- a/tests/path.py +++ b/tests/path.py @@ -4,13 +4,13 @@ path ~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os import sys import shutil -from codecs import open +from io import open from six import PY2, text_type @@ -126,35 +126,31 @@ class path(text_type): def utime(self, arg): os.utime(self, arg) - def write_text(self, text, **kwargs): + def open(self, mode='r', **kwargs): + return open(self, mode, **kwargs) + + def write_text(self, text, encoding='utf-8', **kwargs): """ Writes the given `text` to the file. """ - f = open(self, 'w', **kwargs) - try: + if isinstance(text, bytes): + text = text.decode(encoding) + with open(self, 'w', encoding=encoding, **kwargs) as f: f.write(text) - finally: - f.close() - def text(self, **kwargs): + def text(self, encoding='utf-8', **kwargs): """ Returns the text in the file. """ - f = open(self, mode='U', **kwargs) - try: + with open(self, mode='U', encoding=encoding, **kwargs) as f: return f.read() - finally: - f.close() def bytes(self): """ Returns the bytes in the file. """ - f = open(self, mode='rb') - try: + with open(self, mode='rb') as f: return f.read() - finally: - f.close() def write_bytes(self, bytes, append=False): """ @@ -167,11 +163,8 @@ class path(text_type): mode = 'ab' else: mode = 'wb' - f = open(self, mode=mode) - try: + with open(self, mode=mode) as f: f.write(bytes) - finally: - f.close() def exists(self): """ diff --git a/tests/root/conf.py b/tests/root/conf.py index bdf2f8c8d..f2684e33f 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -16,7 +16,7 @@ source_suffix = ['.txt', '.add', '.foo'] source_parsers = {'.foo': 'parsermod.Parser'} project = 'Sphinx <Tests>' -copyright = '2010-2015, Georg Brandl & Team' +copyright = '2010-2016, Georg Brandl & Team' # If this is changed, remember to update the versionchanges! version = '0.6' release = '0.6alpha1' diff --git a/tests/root/footnote.txt b/tests/root/footnote.txt index b600697fd..36ad3fadc 100644 --- a/tests/root/footnote.txt +++ b/tests/root/footnote.txt @@ -24,6 +24,17 @@ citation [bar]_ +footnotes in table +-------------------- + +.. list-table:: Table caption [#]_ + :header-rows: 1 + + * - name [#]_ + - desription + * - VIDIOC_CROPCAP + - Information about VIDIOC_CROPCAP + footenotes -------------------- @@ -39,6 +50,10 @@ footenotes .. [bar] cite +.. [#] footnotes in table caption + +.. [#] footnotes in table + missing target -------------------- diff --git a/tests/root/images.txt b/tests/root/images.txt index bd64d573a..e78f8be3c 100644 --- a/tests/root/images.txt +++ b/tests/root/images.txt @@ -26,3 +26,6 @@ Sphinx image handling .. an SVG image (for HTML at least) .. image:: svgimg.* + +.. an image with more than 1 dot in its file name +.. image:: img.foo.png diff --git a/tests/root/img.foo.png b/tests/root/img.foo.png new file mode 100644 index 000000000..4c8f89929 Binary files /dev/null and b/tests/root/img.foo.png differ diff --git a/tests/root/markup.txt b/tests/root/markup.txt index 9e8c6bc86..1700b3844 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 @@ -257,6 +266,12 @@ Code blocks false end +.. code-block:: c + + import sys + + sys.stdout.write('hello world!\n') + Misc stuff ---------- diff --git a/tests/root/math.txt b/tests/root/math.txt index 36b244943..5a209bed4 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`. @@ -7,7 +7,7 @@ This is inline math: :math:`a^2 + b^2 = c^2`. .. math:: - a^2 + b^2 = c^2 + a + 1 < b .. math:: :label: foo @@ -19,4 +19,13 @@ This is inline math: :math:`a^2 + b^2 = c^2`. e^{ix} = \cos x + i\sin x +.. math:: + + n \in \mathbb N + +.. math:: + :nowrap: + + a + 1 < b + Referencing equation :eq:`foo`. diff --git a/tests/root/objects.txt b/tests/root/objects.txt index 8bdfc3015..cd711070f 100644 --- a/tests/root/objects.txt +++ b/tests/root/objects.txt @@ -174,7 +174,17 @@ Others .. option:: arg -Link to :option:`perl +p` and :option:`arg`. +Link to :option:`perl +p` and :option:`arg` + +.. program:: hg + +.. option:: commit + +.. program:: git commit + +.. option:: -p + +Link to :option:`hg commit` and :option:`git commit -p`. User markup diff --git a/tests/roots/test-add_source_parser-conflicts-with-users-setting/conf.py b/tests/roots/test-add_source_parser-conflicts-with-users-setting/conf.py new file mode 100644 index 000000000..db9fe54a9 --- /dev/null +++ b/tests/roots/test-add_source_parser-conflicts-with-users-setting/conf.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +import os +import sys +from docutils.parsers import Parser + +sys.path.insert(0, os.path.abspath('.')) + + +class DummyTestParser(Parser): + pass + + +extensions = ['test_source_parser'] +source_suffix = ['.rst', '.test'] +source_parsers = { + '.test': DummyTestParser +} diff --git a/tests/roots/test-add_source_parser-conflicts-with-users-setting/test_source_parser.py b/tests/roots/test-add_source_parser-conflicts-with-users-setting/test_source_parser.py new file mode 100644 index 000000000..0dff7e311 --- /dev/null +++ b/tests/roots/test-add_source_parser-conflicts-with-users-setting/test_source_parser.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from docutils.parsers import Parser + + +class TestSourceParser(Parser): + pass + + +def setup(app): + app.add_source_parser('.test', TestSourceParser) diff --git a/tests/roots/test-add_source_parser/conf.py b/tests/roots/test-add_source_parser/conf.py new file mode 100644 index 000000000..f9969341a --- /dev/null +++ b/tests/roots/test-add_source_parser/conf.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +import os +import sys +from docutils.parsers import Parser + +sys.path.insert(0, os.path.abspath('.')) + + +class DummyMarkdownParser(Parser): + pass + + +extensions = ['test_source_parser'] +source_suffix = ['.rst', '.md'] +source_parsers = { + '.md': DummyMarkdownParser +} diff --git a/tests/roots/test-add_source_parser/test_source_parser.py b/tests/roots/test-add_source_parser/test_source_parser.py new file mode 100644 index 000000000..0dff7e311 --- /dev/null +++ b/tests/roots/test-add_source_parser/test_source_parser.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from docutils.parsers import Parser + + +class TestSourceParser(Parser): + pass + + +def setup(app): + app.add_source_parser('.test', TestSourceParser) diff --git a/tests/roots/test-autosummary/conf.py b/tests/roots/test-autosummary/conf.py index d9a447480..5cb589cda 100644 --- a/tests/roots/test-autosummary/conf.py +++ b/tests/roots/test-autosummary/conf.py @@ -7,3 +7,5 @@ extensions = ['sphinx.ext.autosummary'] # The suffix of source filenames. source_suffix = '.rst' autosummary_generate = True + +exclude_patterns = ['_build'] diff --git a/tests/roots/test-build-text/conf.py b/tests/roots/test-build-text/conf.py index 1ba342a65..23d0ae840 100644 --- a/tests/roots/test-build-text/conf.py +++ b/tests/roots/test-build-text/conf.py @@ -1,2 +1,3 @@ master_doc = 'contents' source_suffix = '.txt' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-circular/conf.py b/tests/roots/test-circular/conf.py index e69de29bb..027d21cda 100644 --- a/tests/roots/test-circular/conf.py +++ b/tests/roots/test-circular/conf.py @@ -0,0 +1 @@ +exclude_patterns = ['_build'] diff --git a/tests/roots/test-config/conf.py b/tests/roots/test-config/conf.py new file mode 100644 index 000000000..34e89a52d --- /dev/null +++ b/tests/roots/test-config/conf.py @@ -0,0 +1,32 @@ +value1 = 123 # wrong type +value2 = 123 # lambda with wrong type +value3 = [] # lambda with correct type +value4 = True # child type +value5 = 3 # parent type +value6 = () # other sequence type, also raises +value7 = ['foo'] # explicitly permitted + +class A(object): + pass +class B(A): + pass +class C(A): + pass + +value8 = C() # sibling type + +# both have no default or permissible types +value9 = 'foo' +value10 = 123 + +def setup(app): + app.add_config_value('value1', 'string', False) + app.add_config_value('value2', lambda conf: [], False) + app.add_config_value('value3', [], False) + app.add_config_value('value4', 100, False) + app.add_config_value('value5', False, False) + app.add_config_value('value6', [], False) + app.add_config_value('value7', 'string', False, [list]) + app.add_config_value('value8', B(), False) + app.add_config_value('value9', None, False) + app.add_config_value('value10', None, False) diff --git a/tests/roots/test-directive-code/classes.rst b/tests/roots/test-directive-code/classes.rst new file mode 100644 index 000000000..e9aa5d9c4 --- /dev/null +++ b/tests/roots/test-directive-code/classes.rst @@ -0,0 +1,21 @@ +classes +======= + +Code blocks +----------- + +.. code-block:: ruby + :class: foo bar + :name: code_block + + def ruby? + false + end + + +Literal Includes +---------------- + +.. literalinclude:: literal.inc + :class: bar baz + :name: literal_include diff --git a/tests/roots/test-directive-code/conf.py b/tests/roots/test-directive-code/conf.py index f81c30bc4..e10f5e5fb 100644 --- a/tests/roots/test-directive-code/conf.py +++ b/tests/roots/test-directive-code/conf.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- master_doc = 'index' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-directive-only/conf.py b/tests/roots/test-directive-only/conf.py index eb3a3d0d2..b9209f08b 100644 --- a/tests/roots/test-directive-only/conf.py +++ b/tests/roots/test-directive-only/conf.py @@ -1,2 +1,3 @@ project = 'test-directive-only' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-doctest/conf.py b/tests/roots/test-doctest/conf.py index f6a12edb9..fcf6a6cda 100644 --- a/tests/roots/test-doctest/conf.py +++ b/tests/roots/test-doctest/conf.py @@ -1,5 +1,6 @@ extensions = ['sphinx.ext.doctest'] project = 'test project for doctest' -master_doc = 'doctest.txt' +master_doc = 'doctest' source_suffix = '.txt' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-doctest/doctest.txt b/tests/roots/test-doctest/doctest.txt index ac5da0d15..053601f3c 100644 --- a/tests/roots/test-doctest/doctest.txt +++ b/tests/roots/test-doctest/doctest.txt @@ -136,3 +136,14 @@ umlauts: äöü. >>> print('Japanese: 日本語') Japanese: 日本語 +keep control char in raw string +------------------------------- + +.. doctest:: + + >>> print('one\ntwo') + one + two + >>> print(r'one\ntwo') + one\ntwo + diff --git a/tests/roots/test-docutilsconf/conf.py b/tests/roots/test-docutilsconf/conf.py index 67074ec64..0a88a65fd 100644 --- a/tests/roots/test-docutilsconf/conf.py +++ b/tests/roots/test-docutilsconf/conf.py @@ -3,3 +3,4 @@ project = 'Sphinx docutils conf <Tests>' source_suffix = '.txt' keep_warnings = True +exclude_patterns = ['_build'] diff --git a/tests/roots/test-domain-cpp/any-role.rst b/tests/roots/test-domain-cpp/any-role.rst new file mode 100644 index 000000000..dacd2f600 --- /dev/null +++ b/tests/roots/test-domain-cpp/any-role.rst @@ -0,0 +1,10 @@ +any role +-------- + +* :cpp:any:`Sphinx` +* ref function without parens :cpp:any:`hello`. +* ref function with parens :cpp:any:`hello()`. +* :cpp:any:`Sphinx::version` +* :cpp:any:`version` +* :cpp:any:`List` +* :cpp:any:`MyEnum` diff --git a/tests/roots/test-contentsname/conf.py b/tests/roots/test-domain-cpp/conf.py similarity index 69% rename from tests/roots/test-contentsname/conf.py rename to tests/roots/test-domain-cpp/conf.py index cf05c9b5c..c46e40773 100644 --- a/tests/roots/test-contentsname/conf.py +++ b/tests/roots/test-domain-cpp/conf.py @@ -2,3 +2,4 @@ master_doc = 'index' html_theme = 'classic' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-domain-cpp/index.rst b/tests/roots/test-domain-cpp/index.rst new file mode 100644 index 000000000..9be739f1e --- /dev/null +++ b/tests/roots/test-domain-cpp/index.rst @@ -0,0 +1,37 @@ +test-domain-cpp +=============== + +directives +---------- + +.. cpp:class:: public Sphinx + + The description of Sphinx class. + +.. cpp:function:: int hello(char *name) + + The description of hello function. + +.. cpp:member:: float Sphinx::version + + The description of Sphinx::version. + +.. cpp:var:: int version + + The description of version. + +.. cpp:type:: std::vector<int> List + + The description of List type. + +.. cpp:enum:: MyEnum + + An unscoped enum. + +.. cpp:enum-class:: MyScopedEnum + + A scoped enum. + +.. cpp:enum-struct:: protected MyScopedVisibilityEnum : std::underlying_type<MySpecificEnum>::type + + A scoped enum with non-default visibility, and with a specified underlying type. diff --git a/tests/roots/test-domain-cpp/roles.rst b/tests/roots/test-domain-cpp/roles.rst new file mode 100644 index 000000000..8baf29b4c --- /dev/null +++ b/tests/roots/test-domain-cpp/roles.rst @@ -0,0 +1,10 @@ +roles +----- + +* :cpp:class:`Sphinx` +* ref function without parens :cpp:func:`hello`. +* ref function with parens :cpp:func:`hello()`. +* :cpp:member:`Sphinx::version` +* :cpp:var:`version` +* :cpp:type:`List` +* :cpp:enum:`MyEnum` diff --git a/tests/roots/test-double-inheriting-theme/base_themes_dir/base_theme1/theme.conf b/tests/roots/test-double-inheriting-theme/base_themes_dir/base_theme1/theme.conf new file mode 100644 index 000000000..89e03bbda --- /dev/null +++ b/tests/roots/test-double-inheriting-theme/base_themes_dir/base_theme1/theme.conf @@ -0,0 +1,2 @@ +[theme] +inherit = basic diff --git a/tests/roots/test-double-inheriting-theme/base_themes_dir/base_theme2/theme.conf b/tests/roots/test-double-inheriting-theme/base_themes_dir/base_theme2/theme.conf new file mode 100644 index 000000000..a68c01858 --- /dev/null +++ b/tests/roots/test-double-inheriting-theme/base_themes_dir/base_theme2/theme.conf @@ -0,0 +1,2 @@ +[theme] +inherit = base_theme1 diff --git a/tests/roots/test-double-inheriting-theme/conf.py b/tests/roots/test-double-inheriting-theme/conf.py new file mode 100644 index 000000000..c2f8db3b0 --- /dev/null +++ b/tests/roots/test-double-inheriting-theme/conf.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +import sys, os + +templates_path = ['_templates'] +master_doc = 'index' +html_theme = 'base_theme2' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-double-inheriting-theme/index.rst b/tests/roots/test-double-inheriting-theme/index.rst new file mode 100644 index 000000000..e67f7ff45 --- /dev/null +++ b/tests/roots/test-double-inheriting-theme/index.rst @@ -0,0 +1,3 @@ +============================ +Test double inheriting theme +============================ diff --git a/tests/roots/test-ext-githubpages/conf.py b/tests/roots/test-ext-githubpages/conf.py new file mode 100644 index 000000000..a05848fa6 --- /dev/null +++ b/tests/roots/test-ext-githubpages/conf.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +extensions = ['sphinx.ext.githubpages'] +master_doc = 'index' diff --git a/tests/roots/test-ext-githubpages/index.rst b/tests/roots/test-ext-githubpages/index.rst new file mode 100644 index 000000000..711847f80 --- /dev/null +++ b/tests/roots/test-ext-githubpages/index.rst @@ -0,0 +1,3 @@ +githubpages +=========== + diff --git a/tests/roots/test-ext-graphviz/conf.py b/tests/roots/test-ext-graphviz/conf.py index cecd53668..2696280ee 100644 --- a/tests/roots/test-ext-graphviz/conf.py +++ b/tests/roots/test-ext-graphviz/conf.py @@ -2,3 +2,9 @@ extensions = ['sphinx.ext.graphviz'] master_doc = 'index' +exclude_patterns = ['_build'] + +latex_documents = [ + (master_doc, 'SphinxTests.tex', 'Sphinx Tests Documentation', + 'Georg Brandl', 'manual'), +] diff --git a/tests/roots/test-ext-graphviz/index.rst b/tests/roots/test-ext-graphviz/index.rst index 39c755e9d..c04ca42a0 100644 --- a/tests/roots/test-ext-graphviz/index.rst +++ b/tests/roots/test-ext-graphviz/index.rst @@ -5,3 +5,14 @@ graphviz :caption: caption of graph bar -> baz + +.. |graph| digraph:: bar + + bar -> baz + +Hello |graph| graphviz world + +.. digraph:: foo + :graphviz_dot: neato + + bar -> baz diff --git a/tests/roots/test-ext-ifconfig/conf.py b/tests/roots/test-ext-ifconfig/conf.py index 327bd126d..d205fe9f5 100644 --- a/tests/roots/test-ext-ifconfig/conf.py +++ b/tests/roots/test-ext-ifconfig/conf.py @@ -2,6 +2,7 @@ extensions = ['sphinx.ext.ifconfig'] master_doc = 'index' +exclude_patterns = ['_build'] confval1 = True diff --git a/tests/roots/test-ext-imgmath/conf.py b/tests/roots/test-ext-imgmath/conf.py new file mode 100644 index 000000000..3f3e2a783 --- /dev/null +++ b/tests/roots/test-ext-imgmath/conf.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +extensions = ['sphinx.ext.imgmath'] +master_doc = 'index' diff --git a/tests/roots/test-ext-imgmath/index.rst b/tests/roots/test-ext-imgmath/index.rst new file mode 100644 index 000000000..f7780a422 --- /dev/null +++ b/tests/roots/test-ext-imgmath/index.rst @@ -0,0 +1,6 @@ +Test imgmath +============ + +.. math:: a^2+b^2=c^2 + +Inline :math:`E=mc^2` diff --git a/tests/roots/test-ext-viewcode/conf.py b/tests/roots/test-ext-viewcode/conf.py index a99a72bbc..c2b358fb5 100644 --- a/tests/roots/test-ext-viewcode/conf.py +++ b/tests/roots/test-ext-viewcode/conf.py @@ -6,6 +6,7 @@ import os sys.path.insert(0, os.path.abspath('.')) extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] master_doc = 'index' +exclude_patterns = ['_build'] if 'test_linkcode' in tags: diff --git a/tests/roots/test-footnotes/conf.py b/tests/roots/test-footnotes/conf.py new file mode 100644 index 000000000..c46e40773 --- /dev/null +++ b/tests/roots/test-footnotes/conf.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +master_doc = 'index' +html_theme = 'classic' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-footnotes/index.rst b/tests/roots/test-footnotes/index.rst new file mode 100644 index 000000000..a20052037 --- /dev/null +++ b/tests/roots/test-footnotes/index.rst @@ -0,0 +1,142 @@ +=============== +test-footenotes +=============== + +The section with a reference to [AuthorYear]_ +============================================= + +.. figure:: rimg.png + + This is the figure caption with a reference to [AuthorYear]_. + +.. list-table:: The table title with a reference to [AuthorYear]_ + :header-rows: 1 + + * - Header1 + - Header2 + * - Content + - Content + +.. rubric:: The rubric title with a reference to [AuthorYear]_ + +.. [#] First + +* First footnote: [#]_ +* Second footnote: [1]_ +* `Sphinx <http://sphinx-doc.org/>`_ +* Third footnote: [#]_ +* `URL including tilde <http://sphinx-doc.org/~test/>`_ +* GitHub Page: `https://github.com/sphinx-doc/sphinx <https://github.com/sphinx-doc/sphinx>`_ +* Mailing list: `sphinx-dev@googlegroups.com <mailto:sphinx-dev@googlegroups.com>`_ + +.. [AuthorYear] Author, Title, Year +.. [1] Second +.. [#] Third + +The section with a reference to [#]_ +===================================== + +.. [#] Footnote in section + +`URL in term <http://sphinx-doc.org/>`_ + Description Description Description ... + +Footnote in term [#]_ + Description Description Description ... + + `Term in deflist <http://sphinx-doc.org/>`_ + Description2 + +.. [#] Footnote in term + +.. figure:: rimg.png + + This is the figure caption with a footnote to [#]_. + +.. [#] Footnote in caption + +.. list-table:: footnote [#]_ in caption of normal table + :widths: 1 1 + :header-rows: 1 + + * - name + - desc + * - a + - b + * - a + - b + +.. [#] Foot note in table + +.. list-table:: footnote [#]_ in caption of longtable + :widths: 1 1 + :header-rows: 1 + + * - name + - desc + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + * - a + - b + +.. [#] Foot note in longtable diff --git a/tests/roots/test-footnotes/rimg.png b/tests/roots/test-footnotes/rimg.png new file mode 100644 index 000000000..1081dc143 Binary files /dev/null and b/tests/roots/test-footnotes/rimg.png differ diff --git a/tests/roots/test-image-in-section/conf.py b/tests/roots/test-image-in-section/conf.py new file mode 100644 index 000000000..7da44fdae --- /dev/null +++ b/tests/roots/test-image-in-section/conf.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +master_doc = 'index' +exclude_patterns = ['_build'] + +rst_epilog = ''' +.. |picture| image:: pic.png + :width: 15pt + :height: 15pt + :alt: alternative_text +''' + diff --git a/tests/roots/test-image-in-section/index.rst b/tests/roots/test-image-in-section/index.rst new file mode 100644 index 000000000..a69db0a77 --- /dev/null +++ b/tests/roots/test-image-in-section/index.rst @@ -0,0 +1,18 @@ +test-image-in-section +===================== +this is dummy content + + +|picture| Test section +---------------------- +blah blah blah + + +Another section +--------------- +another blah + + +Other [blah] |picture| section +------------------------------ +other blah diff --git a/tests/roots/test-image-in-section/pic.png b/tests/roots/test-image-in-section/pic.png new file mode 100644 index 000000000..1081dc143 Binary files /dev/null and b/tests/roots/test-image-in-section/pic.png differ diff --git a/tests/roots/test-intl/conf.py b/tests/roots/test-intl/conf.py index 74446a143..aafd9ba79 100644 --- a/tests/roots/test-intl/conf.py +++ b/tests/roots/test-intl/conf.py @@ -7,3 +7,4 @@ templates_path = ['_templates'] html_additional_pages = {'index': 'index.html'} release = version = '2013.120' gettext_additional_targets = ['index'] +exclude_patterns = ['_build'] diff --git a/tests/roots/test-intl/definition_terms.po b/tests/roots/test-intl/definition_terms.po index 6de1d1c42..1752dd66f 100644 --- a/tests/roots/test-intl/definition_terms.po +++ b/tests/roots/test-intl/definition_terms.po @@ -25,14 +25,14 @@ msgstr "SOME TERM" msgid "The corresponding definition" msgstr "THE CORRESPONDING DEFINITION" -msgid "Some other term" -msgstr "SOME OTHER TERM" +msgid "Some *term* `with link <http://sphinx-doc.org/>`__" +msgstr "SOME *TERM* `WITH LINK <http://sphinx-doc.org/>`__" msgid "The corresponding definition #2" msgstr "THE CORRESPONDING DEFINITION #2" -msgid "Some term with" -msgstr "SOME TERM WITH" +msgid "Some **term** with" +msgstr "SOME **TERM** WITH" msgid "classifier1" msgstr "CLASSIFIER1" @@ -40,3 +40,8 @@ msgstr "CLASSIFIER1" msgid "classifier2" msgstr "CLASSIFIER2" +msgid "Some term with" +msgstr "SOME TERM WITH" + +msgid "classifier[]" +msgstr "CLASSIFIER[]" diff --git a/tests/roots/test-intl/definition_terms.txt b/tests/roots/test-intl/definition_terms.txt index 66230f98f..4c562881a 100644 --- a/tests/roots/test-intl/definition_terms.txt +++ b/tests/roots/test-intl/definition_terms.txt @@ -6,9 +6,11 @@ i18n with definition terms Some term The corresponding definition -Some other term +Some *term* `with link <http://sphinx-doc.org/>`__ The corresponding definition #2 -Some term with : classifier1 : classifier2 +Some **term** with : classifier1 : classifier2 The corresponding definition +Some term with : classifier[] + The corresponding definition diff --git a/tests/roots/test-contentsname/bar.rst b/tests/roots/test-latex-babel/bar.rst similarity index 100% rename from tests/roots/test-contentsname/bar.rst rename to tests/roots/test-latex-babel/bar.rst diff --git a/tests/roots/test-latex-babel/conf.py b/tests/roots/test-latex-babel/conf.py new file mode 100644 index 000000000..d35acc89f --- /dev/null +++ b/tests/roots/test-latex-babel/conf.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +master_doc = 'index' +numfig_format = { + 'figure': 'Fig. %s', + 'table': 'Table. %s', + 'code-block': 'List.', +} diff --git a/tests/roots/test-contentsname/foo.rst b/tests/roots/test-latex-babel/foo.rst similarity index 100% rename from tests/roots/test-contentsname/foo.rst rename to tests/roots/test-latex-babel/foo.rst diff --git a/tests/roots/test-contentsname/index.rst b/tests/roots/test-latex-babel/index.rst similarity index 100% rename from tests/roots/test-contentsname/index.rst rename to tests/roots/test-latex-babel/index.rst diff --git a/tests/roots/test-numbered-circular/conf.py b/tests/roots/test-numbered-circular/conf.py index e69de29bb..027d21cda 100644 --- a/tests/roots/test-numbered-circular/conf.py +++ b/tests/roots/test-numbered-circular/conf.py @@ -0,0 +1 @@ +exclude_patterns = ['_build'] diff --git a/tests/roots/test-numfig/conf.py b/tests/roots/test-numfig/conf.py index cf05c9b5c..c46e40773 100644 --- a/tests/roots/test-numfig/conf.py +++ b/tests/roots/test-numfig/conf.py @@ -2,3 +2,4 @@ master_doc = 'index' html_theme = 'classic' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-numfig/index.rst b/tests/roots/test-numfig/index.rst index 1264800d9..6dd39a93a 100644 --- a/tests/roots/test-numfig/index.rst +++ b/tests/roots/test-numfig/index.rst @@ -48,3 +48,5 @@ test-tocdepth * Table.2.2 is :numref:`Table:%s <table22>` * List.1 is :numref:`CODE_1` * List.2.2 is :numref:`Code-%s <CODE22>` +* Invalid numfig_format 01: :numref:`invalid <fig1>` +* Invalid numfig_format 02: :numref:`Fig %s %s <fig1>` diff --git a/tests/roots/test-setup/doc/conf.py b/tests/roots/test-setup/doc/conf.py index a55679a4b..b1c9acf46 100644 --- a/tests/roots/test-setup/doc/conf.py +++ b/tests/roots/test-setup/doc/conf.py @@ -3,3 +3,4 @@ project = 'Sphinx smallest project' source_suffix = '.txt' keep_warnings = True +exclude_patterns = ['_build'] diff --git a/tests/roots/test-templating/conf.py b/tests/roots/test-templating/conf.py index 225da82e7..ff8207454 100644 --- a/tests/roots/test-templating/conf.py +++ b/tests/roots/test-templating/conf.py @@ -5,6 +5,7 @@ source_suffix = '.txt' keep_warnings = True templates_path = ['_templates'] release = version = '2013.120' +exclude_patterns = ['_build'] extensions = ['sphinx.ext.autosummary'] autosummary_generate = ['autosummary_templating'] diff --git a/tests/roots/test-theming/conf.py b/tests/roots/test-theming/conf.py index 2717087d1..608afcfcd 100644 --- a/tests/roots/test-theming/conf.py +++ b/tests/roots/test-theming/conf.py @@ -2,4 +2,5 @@ html_theme = 'test-theme' master_doc = 'index' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-tocdepth/conf.py b/tests/roots/test-tocdepth/conf.py index cf05c9b5c..c46e40773 100644 --- a/tests/roots/test-tocdepth/conf.py +++ b/tests/roots/test-tocdepth/conf.py @@ -2,3 +2,4 @@ master_doc = 'index' html_theme = 'classic' +exclude_patterns = ['_build'] 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..c46e40773 --- /dev/null +++ b/tests/roots/test-toctree-glob/conf.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +master_doc = 'index' +html_theme = 'classic' +exclude_patterns = ['_build'] 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/roots/test-toctree-maxdepth/bar.rst b/tests/roots/test-toctree-maxdepth/bar.rst new file mode 100644 index 000000000..d70dec90d --- /dev/null +++ b/tests/roots/test-toctree-maxdepth/bar.rst @@ -0,0 +1,27 @@ +:tocdepth: 2 + +=== +Bar +=== + +should be 2 + +Bar A +===== + +should be 2.1 + +.. toctree:: + + baz + +Bar B +===== + +should be 2.2 + +Bar B1 +------ + +should be 2.2.1 + diff --git a/tests/roots/test-toctree-maxdepth/baz.rst b/tests/roots/test-toctree-maxdepth/baz.rst new file mode 100644 index 000000000..b07fa0507 --- /dev/null +++ b/tests/roots/test-toctree-maxdepth/baz.rst @@ -0,0 +1,5 @@ +Baz A +----- + +should be 2.1.1 + diff --git a/tests/roots/test-toctree-maxdepth/conf.py b/tests/roots/test-toctree-maxdepth/conf.py new file mode 100644 index 000000000..c46e40773 --- /dev/null +++ b/tests/roots/test-toctree-maxdepth/conf.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +master_doc = 'index' +html_theme = 'classic' +exclude_patterns = ['_build'] diff --git a/tests/roots/test-toctree-maxdepth/foo.rst b/tests/roots/test-toctree-maxdepth/foo.rst new file mode 100644 index 000000000..61fd539ff --- /dev/null +++ b/tests/roots/test-toctree-maxdepth/foo.rst @@ -0,0 +1,26 @@ +=== +Foo +=== + +should be 1 + +Foo A +===== + +should be 1.1 + +Foo A1 +------ + +should be 1.1.1 + +Foo B +===== + +should be 1.2 + +Foo B1 +------ + +should be 1.2.1 + diff --git a/tests/roots/test-toctree-maxdepth/index.rst b/tests/roots/test-toctree-maxdepth/index.rst new file mode 100644 index 000000000..30dc61c8b --- /dev/null +++ b/tests/roots/test-toctree-maxdepth/index.rst @@ -0,0 +1,9 @@ +test-toctree-max-depth +====================== + +.. toctree:: + :numbered: + :maxdepth: 2 + + foo + bar diff --git a/tests/roots/test-versioning/conf.py b/tests/roots/test-versioning/conf.py index edcf92951..fb54e7972 100644 --- a/tests/roots/test-versioning/conf.py +++ b/tests/roots/test-versioning/conf.py @@ -1,3 +1,4 @@ project = 'versioning test root' master_doc = 'index' source_suffix = '.txt' +exclude_patterns = ['_build'] diff --git a/tests/run.py b/tests/run.py index 432e2318e..b4bbf9822 100755 --- a/tests/run.py +++ b/tests/run.py @@ -6,7 +6,7 @@ This script runs the Sphinx unit test suite. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function diff --git a/tests/test_api_translator.py b/tests/test_api_translator.py index 4f862eea4..8f0f88a1d 100644 --- a/tests/test_api_translator.py +++ b/tests/test_api_translator.py @@ -5,7 +5,7 @@ Test the Sphinx API for translator. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_apidoc.py b/tests/test_apidoc.py index 215b2e571..596890041 100644 --- a/tests/test_apidoc.py +++ b/tests/test_apidoc.py @@ -5,13 +5,14 @@ Test the sphinx.apidoc module. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ 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_application.py b/tests/test_application.py index c878cc1b6..7bc970c9d 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -5,7 +5,7 @@ Test the Sphinx class. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -88,3 +88,18 @@ def test_domain_override(app, status, warning): assert app.override_domain(B) is None raises_msg(ExtensionError, 'new domain not a subclass of registered ' 'foo domain', app.override_domain, C) + + +@with_app(testroot='add_source_parser') +def test_add_source_parser(app, status, warning): + assert set(app.config.source_suffix) == set(['.rst', '.md', '.test']) + assert set(app.config.source_parsers.keys()) == set(['.md', '.test']) + assert app.config.source_parsers['.md'].__name__ == 'DummyMarkdownParser' + assert app.config.source_parsers['.test'].__name__ == 'TestSourceParser' + + +@with_app(testroot='add_source_parser-conflicts-with-users-setting') +def test_add_source_parser_conflicts_with_users_setting(app, status, warning): + assert set(app.config.source_suffix) == set(['.rst', '.test']) + assert set(app.config.source_parsers.keys()) == set(['.test']) + assert app.config.source_parsers['.test'].__name__ == 'DummyTestParser' diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 03e2063fe..2542a3faa 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -6,7 +6,7 @@ Test the autodoc extension. This tests mainly the Documenters; the auto directives are tested in a test source file translated by test_build. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -791,6 +791,7 @@ def test_generate(): 'module', 'test_autodoc') # --- generate fodder ------------ +import six, sys __all__ = ['Class'] @@ -833,6 +834,7 @@ class Base(object): def inheritedmeth(self): """Inherited function.""" + class Class(Base): """Class to document.""" diff --git a/tests/test_autodoc_py35.py b/tests/test_autodoc_py35.py new file mode 100644 index 000000000..9bacc3d65 --- /dev/null +++ b/tests/test_autodoc_py35.py @@ -0,0 +1,346 @@ +# -*- coding: utf-8 -*- +""" + test_autodoc + ~~~~~~~~~~~~ + + Test the autodoc extension. This tests mainly the Documenters; the auto + directives are tested in a test source file translated by test_build. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +# "raises" imported for usage by autodoc +from util import TestApp, Struct, raises, SkipTest +from nose.tools import with_setup, eq_ + +from six import StringIO +from docutils.statemachine import ViewList + +from sphinx.ext.autodoc import AutoDirective, add_documenter, \ + ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL + +app = None + +def setup_module(): + global app + app = TestApp() + app.builder.env.app = app + app.builder.env.temp_data['docname'] = 'dummy' + app.connect('autodoc-process-docstring', process_docstring) + app.connect('autodoc-process-signature', process_signature) + app.connect('autodoc-skip-member', skip_member) + + +def teardown_module(): + app.cleanup() + + +directive = options = None + +def setup_test(): + global options, directive + global processed_docstrings, processed_signatures, _warnings + + options = Struct( + inherited_members = False, + undoc_members = False, + private_members = False, + special_members = False, + imported_members = False, + show_inheritance = False, + noindex = False, + annotation = None, + synopsis = '', + platform = '', + deprecated = False, + members = [], + member_order = 'alphabetic', + exclude_members = set(), + ) + + directive = Struct( + env = app.builder.env, + genopt = options, + result = ViewList(), + warn = warnfunc, + filename_set = set(), + ) + + processed_docstrings = [] + processed_signatures = [] + _warnings = [] + + +_warnings = [] + +def warnfunc(msg): + _warnings.append(msg) + + +processed_docstrings = [] + +def process_docstring(app, what, name, obj, options, lines): + processed_docstrings.append((what, name)) + if name == 'bar': + lines.extend(['42', '']) + +processed_signatures = [] + +def process_signature(app, what, name, obj, options, args, retann): + processed_signatures.append((what, name)) + if name == 'bar': + return '42', None + + +def skip_member(app, what, name, obj, skip, options): + if name in ('__special1__', '__special2__'): + return skip + if name.startswith('_'): + return True + if name == 'skipmeth': + return True + + +@with_setup(setup_test) +def test_generate(): + def assert_warns(warn_str, objtype, name, **kw): + inst = AutoDirective._registry[objtype](directive, name) + inst.generate(**kw) + assert len(directive.result) == 0, directive.result + assert len(_warnings) == 1, _warnings + assert warn_str in _warnings[0], _warnings + del _warnings[:] + + def assert_works(objtype, name, **kw): + inst = AutoDirective._registry[objtype](directive, name) + inst.generate(**kw) + assert directive.result + #print '\n'.join(directive.result) + assert len(_warnings) == 0, _warnings + del directive.result[:] + + def assert_processes(items, objtype, name, **kw): + del processed_docstrings[:] + del processed_signatures[:] + assert_works(objtype, name, **kw) + assert set(processed_docstrings) | set(processed_signatures) == \ + set(items) + + def assert_result_contains(item, objtype, name, **kw): + inst = AutoDirective._registry[objtype](directive, name) + inst.generate(**kw) + #print '\n'.join(directive.result) + assert len(_warnings) == 0, _warnings + assert item in directive.result + del directive.result[:] + + def assert_order(items, objtype, name, member_order, **kw): + inst = AutoDirective._registry[objtype](directive, name) + inst.options.member_order = member_order + inst.generate(**kw) + assert len(_warnings) == 0, _warnings + items = list(reversed(items)) + lineiter = iter(directive.result) + #for line in directive.result: + # if line.strip(): + # print repr(line) + while items: + item = items.pop() + for line in lineiter: + if line == item: + break + else: # ran out of items! + assert False, 'item %r not found in result or not in the ' \ + ' correct order' % item + del directive.result[:] + + options.members = [] + + # no module found? + assert_warns("import for autodocumenting 'foobar'", + 'function', 'foobar', more_content=None) + # importing + assert_warns("failed to import module 'test_foobar'", + 'module', 'test_foobar', more_content=None) + # attributes missing + assert_warns("failed to import function 'foobar' from module 'util'", + 'function', 'util.foobar', more_content=None) + # method missing + assert_warns("failed to import method 'Class.foobar' from module 'test_autodoc_py35';", + 'method', 'test_autodoc_py35.Class.foobar', more_content=None) + + # test auto and given content mixing + directive.env.ref_context['py:module'] = 'test_autodoc_py35' + assert_result_contains(' Function.', 'method', 'Class.meth') + add_content = ViewList() + add_content.append('Content.', '', 0) + assert_result_contains(' Function.', 'method', + 'Class.meth', more_content=add_content) + assert_result_contains(' Content.', 'method', + 'Class.meth', more_content=add_content) + + # test check_module + inst = FunctionDocumenter(directive, 'raises') + inst.generate(check_module=True) + assert len(directive.result) == 0 + + # assert that exceptions can be documented + assert_works('exception', 'test_autodoc_py35.CustomEx', all_members=True) + assert_works('exception', 'test_autodoc_py35.CustomEx') + + # test diverse inclusion settings for members + should = [('class', 'test_autodoc_py35.Class')] + assert_processes(should, 'class', 'Class') + should.extend([('method', 'test_autodoc_py35.Class.meth')]) + options.members = ['meth'] + options.exclude_members = set(['excludemeth']) + assert_processes(should, 'class', 'Class') + should.extend([('attribute', 'test_autodoc_py35.Class.prop'), + ('attribute', 'test_autodoc_py35.Class.descr'), + ('attribute', 'test_autodoc_py35.Class.attr'), + ('attribute', 'test_autodoc_py35.Class.docattr'), + ('attribute', 'test_autodoc_py35.Class.udocattr'), + ('attribute', 'test_autodoc_py35.Class.mdocattr'), + ('attribute', 'test_autodoc_py35.Class.inst_attr_comment'), + ('attribute', 'test_autodoc_py35.Class.inst_attr_inline'), + ('attribute', 'test_autodoc_py35.Class.inst_attr_string'), + ('method', 'test_autodoc_py35.Class.moore'), + ]) + if six.PY3 and sys.version_info[:2] >= (3, 5): + should.extend([ + ('method', 'test_autodoc_py35.Class.do_coroutine'), + ]) + options.members = ALL + assert_processes(should, 'class', 'Class') + options.undoc_members = True + should.extend((('attribute', 'test_autodoc_py35.Class.skipattr'), + ('method', 'test_autodoc_py35.Class.undocmeth'), + ('method', 'test_autodoc_py35.Class.roger'))) + assert_processes(should, 'class', 'Class') + options.inherited_members = True + should.append(('method', 'test_autodoc_py35.Class.inheritedmeth')) + assert_processes(should, 'class', 'Class') + + # test special members + options.special_members = ['__special1__'] + should.append(('method', 'test_autodoc_py35.Class.__special1__')) + assert_processes(should, 'class', 'Class') + options.special_members = ALL + should.append(('method', 'test_autodoc_py35.Class.__special2__')) + assert_processes(should, 'class', 'Class') + options.special_members = False + + +# --- generate fodder ------------ +import six, sys + +__all__ = ['Class'] + +#: documentation for the integer +integer = 1 + +class CustomEx(Exception): + """My custom exception.""" + + def f(self): + """Exception method.""" + +class CustomDataDescriptor(object): + """Descriptor class docstring.""" + + def __init__(self, doc): + self.__doc__ = doc + + def __get__(self, obj, type=None): + if obj is None: + return self + return 42 + + def meth(self): + """Function.""" + return "The Answer" + +def _funky_classmethod(name, b, c, d, docstring=None): + """Generates a classmethod for a class from a template by filling out + some arguments.""" + def template(cls, a, b, c, d=4, e=5, f=6): + return a, b, c, d, e, f + from functools import partial + function = partial(template, b=b, c=c, d=d) + function.__name__ = name + function.__doc__ = docstring + return classmethod(function) + +class Base(object): + def inheritedmeth(self): + """Inherited function.""" + +if six.PY3 and sys.version_info[:2] >= (3, 5): + + async def _other_coro_func(): + return "run" + + +class Class(Base): + """Class to document.""" + + descr = CustomDataDescriptor("Descriptor instance docstring.") + + def meth(self): + """Function.""" + + def undocmeth(self): + pass + + def skipmeth(self): + """Method that should be skipped.""" + + def excludemeth(self): + """Method that should be excluded.""" + + # should not be documented + skipattr = 'foo' + + #: should be documented -- süß + attr = 'bar' + + @property + def prop(self): + """Property.""" + + docattr = 'baz' + """should likewise be documented -- süß""" + + udocattr = 'quux' + u"""should be documented as well - süß""" + + # initialized to any class imported from another module + mdocattr = StringIO() + """should be documented as well - süß""" + + roger = _funky_classmethod("roger", 2, 3, 4) + + moore = _funky_classmethod("moore", 9, 8, 7, + docstring="moore(a, e, f) -> happiness") + + def __init__(self, arg): + self.inst_attr_inline = None #: an inline documented instance attr + #: a documented instance attribute + self.inst_attr_comment = None + self.inst_attr_string = None + """a documented instance attribute""" + + def __special1__(self): + """documented special method""" + + def __special2__(self): + # undocumented special method + pass + + if six.PY3 and sys.version_info[:2] >= (3, 5): + + async def do_coroutine(self): + """A documented coroutine function""" + + attr_coro_result = await _other_coro_func() diff --git a/tests/test_build.py b/tests/test_build.py index ed39f6971..a8a6455b8 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -5,13 +5,14 @@ Test all builders. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from six import BytesIO from textwrap import dedent +from sphinx.errors import SphinxError from util import with_app, rootdir, tempdir, SkipTest, TestApp @@ -58,12 +59,12 @@ def test_build_all(): """)) master_doc = srcdir / 'contents.txt' - master_doc.write_bytes((master_doc.text() + dedent(""" + master_doc.write_text(master_doc.text() + dedent(u""" .. toctree:: %(test_name)s/%(test_name)s """ % {'test_name': test_name}) - ).encode('utf-8')) + ) # note: no 'html' - if it's ok with dirhtml it's ok with html for buildername in ['dirhtml', 'singlehtml', 'latex', 'texinfo', 'pickle', @@ -73,6 +74,18 @@ def test_build_all(): yield verify_build, buildername, srcdir +@with_app(buildername='text') +def test_master_doc_not_found(app, status, warning): + (app.srcdir / 'contents.txt').move(app.srcdir / 'contents.txt.bak') + try: + app.builder.build_all() + assert False # SphinxError not raised + except Exception as exc: + assert isinstance(exc, SphinxError) + finally: + (app.srcdir / 'contents.txt.bak').move(app.srcdir / 'contents.txt') + + @with_app(buildername='text', testroot='circular') def test_circular_toctree(app, status, warning): app.builder.build_all() diff --git a/tests/test_build_applehelp.py b/tests/test_build_applehelp.py index 163cc86e3..66b24e1bd 100644 --- a/tests/test_build_applehelp.py +++ b/tests/test_build_applehelp.py @@ -7,7 +7,7 @@ test the HTML itself; that's already handled by :file:`test_build_html.py`. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_build_gettext.py b/tests/test_build_gettext.py index ed2b6e5f1..22fec975e 100644 --- a/tests/test_build_gettext.py +++ b/tests/test_build_gettext.py @@ -5,7 +5,7 @@ Test the build process with gettext builder with the test root. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 9679b2629..40fda97f9 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -5,7 +5,7 @@ Test the HTML builder and check output against XPath. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -16,7 +16,7 @@ from six import PY3, iteritems from six.moves import html_entities from sphinx import __display_version__ -from util import remove_unicode_literals, gen_with_app +from util import remove_unicode_literals, gen_with_app, with_app from etree13 import ElementTree as ET @@ -31,18 +31,16 @@ http://www.python.org/logo.png reading included file u'.*?wrongenc.inc' seems to be wrong, try giving an \ :encoding: option\\n? %(root)s/includes.txt:4: WARNING: download file not readable: .*?nonexisting.png -(%(root)s/markup.txt:\\d+: WARNING: Malformed :option: u'&option', does \ -not contain option marker - or -- or / or \\+ -%(root)s/undecodable.txt:3: WARNING: undecodable source characters, replacing \ +(%(root)s/markup.txt:357: WARNING: invalid single index entry u'')? +(%(root)s/undecodable.txt:3: WARNING: undecodable source characters, replacing \ with "\\?": b?'here: >>>(\\\\|/)xbb<<<' )?""" HTML_WARNINGS = ENV_WARNINGS + """\ %(root)s/images.txt:20: WARNING: no matching candidate for image URI u'foo.\\*' -None:\\d+: WARNING: citation not found: missing -%(root)s/markup.txt:: WARNING: invalid single index entry u'' -%(root)s/markup.txt:: WARNING: invalid pair index entry u'' -%(root)s/markup.txt:: WARNING: invalid pair index entry u'keyword; ' +%(root)s/markup.txt:269: WARNING: Could not parse literal_block as "c". highlighting skipped. +%(root)s/footnote.txt:60: WARNING: citation not found: missing +%(root)s/markup.txt:158: WARNING: unknown option: &option """ if PY3: @@ -85,7 +83,7 @@ HTML_XPATH = { (".//a[@href='_downloads/img1.png']", ''), (".//pre", u'"quotes"'), (".//pre", u"'included'"), - (".//pre/span[@class='s']", u'üöä'), + (".//pre/span[@class='s2']", u'üöä'), (".//div[@class='inc-pyobj1 highlight-text']//pre", r'^class Foo:\n pass\n\s*$'), (".//div[@class='inc-pyobj2 highlight-text']//pre", @@ -234,6 +232,23 @@ HTML_XPATH = { (".//td[@class='field-body']/ul/li/strong", '^hour$'), (".//td[@class='field-body']/ul/li/em", '^DuplicateType$'), (".//td[@class='field-body']/ul/li/em", tail_check(r'.* Some parameter')), + # others + (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-+p']/code/span", + 'perl'), + (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-+p']/code/span", + '\+p'), + (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-arg']/code/span", + 'arg'), + (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", + 'hg'), + (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", + 'commit'), + (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", + 'git'), + (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", + 'commit'), + (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", + '-p'), ], 'contents.html': [ (".//meta[@name='hc'][@content='hcval']", ''), @@ -280,14 +295,18 @@ HTML_XPATH = { (".//dt/a", "double"), ], 'footnote.html': [ - (".//a[@class='footnote-reference'][@href='#id5'][@id='id1']", r"\[1\]"), - (".//a[@class='footnote-reference'][@href='#id6'][@id='id2']", r"\[2\]"), + (".//a[@class='footnote-reference'][@href='#id7'][@id='id1']", r"\[1\]"), + (".//a[@class='footnote-reference'][@href='#id8'][@id='id2']", r"\[2\]"), (".//a[@class='footnote-reference'][@href='#foo'][@id='id3']", r"\[3\]"), (".//a[@class='reference internal'][@href='#bar'][@id='id4']", r"\[bar\]"), + (".//a[@class='footnote-reference'][@href='#id9'][@id='id5']", r"\[4\]"), + (".//a[@class='footnote-reference'][@href='#id10'][@id='id6']", r"\[5\]"), (".//a[@class='fn-backref'][@href='#id1']", r"\[1\]"), (".//a[@class='fn-backref'][@href='#id2']", r"\[2\]"), (".//a[@class='fn-backref'][@href='#id3']", r"\[3\]"), (".//a[@class='fn-backref'][@href='#id4']", r"\[bar\]"), + (".//a[@class='fn-backref'][@href='#id5']", r"\[4\]"), + (".//a[@class='fn-backref'][@href='#id6']", r"\[5\]"), ], 'otherext.html': [ (".//h1", "Generated section"), @@ -356,7 +375,7 @@ def check_extra_entries(outdir): assert (outdir / 'robots.txt').isfile() -@gen_with_app(buildername='html', +@gen_with_app(buildername='html', freshenv=True, # use freshenv to check warnings confoverrides={'html_context.hckey_co': 'hcval_co'}, tags=['testtag']) def test_html_output(app, status, warning): @@ -372,11 +391,8 @@ def test_html_output(app, status, warning): for fname, paths in iteritems(HTML_XPATH): parser = NslessParser() parser.entity.update(html_entities.entitydefs) - fp = open(os.path.join(app.outdir, fname), 'rb') - try: + with (app.outdir / fname).open('rb') as fp: etree = ET.parse(fp, parser) - finally: - fp.close() for path, check in paths: yield check_xpath, etree, fname, path, check @@ -425,11 +441,8 @@ def test_tocdepth(app, status, warning): for fname, paths in iteritems(expects): parser = NslessParser() parser.entity.update(html_entities.entitydefs) - fp = open(os.path.join(app.outdir, fname), 'rb') - try: + with (app.outdir / fname).open('rb') as fp: etree = ET.parse(fp, parser) - finally: - fp.close() for xpath, check, be_found in paths: yield check_xpath, etree, fname, xpath, check, be_found @@ -470,11 +483,8 @@ def test_tocdepth_singlehtml(app, status, warning): for fname, paths in iteritems(expects): parser = NslessParser() parser.entity.update(html_entities.entitydefs) - fp = open(os.path.join(app.outdir, fname), 'rb') - try: + with (app.outdir / fname).open('rb') as fp: etree = ET.parse(fp, parser) - finally: - fp.close() for xpath, check, be_found in paths: yield check_xpath, etree, fname, xpath, check, be_found @@ -484,6 +494,11 @@ def test_tocdepth_singlehtml(app, status, warning): def test_numfig_disabled(app, status, warning): app.builder.build_all() + assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' + in warning.getvalue()) + assert 'index.rst:51: WARNING: invalid numfig_format: invalid' not in warning.getvalue() + assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' not in warning.getvalue() + expects = { 'index.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -524,11 +539,8 @@ def test_numfig_disabled(app, status, warning): for fname, paths in iteritems(expects): parser = NslessParser() parser.entity.update(html_entities.entitydefs) - fp = open(os.path.join(app.outdir, fname), 'rb') - try: + with (app.outdir / fname).open('rb') as fp: etree = ET.parse(fp, parser) - finally: - fp.close() for xpath, check, be_found in paths: yield check_xpath, etree, fname, xpath, check, be_found @@ -543,6 +555,11 @@ def test_numfig_without_numbered_toctree(app, status, warning): (app.srcdir / 'index.rst').write_text(index, encoding='utf-8') app.builder.build_all() + assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' + not in warning.getvalue()) + assert 'index.rst:51: WARNING: invalid numfig_format: invalid' in warning.getvalue() + assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' in warning.getvalue() + expects = { 'index.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -623,11 +640,8 @@ def test_numfig_without_numbered_toctree(app, status, warning): for fname, paths in iteritems(expects): parser = NslessParser() parser.entity.update(html_entities.entitydefs) - fp = open(os.path.join(app.outdir, fname), 'rb') - try: + with (app.outdir / fname).open('rb') as fp: etree = ET.parse(fp, parser) - finally: - fp.close() for xpath, check, be_found in paths: yield check_xpath, etree, fname, xpath, check, be_found @@ -638,6 +652,11 @@ def test_numfig_without_numbered_toctree(app, status, warning): def test_numfig_with_numbered_toctree(app, status, warning): app.builder.build_all() + assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' + not in warning.getvalue()) + assert 'index.rst:51: WARNING: invalid numfig_format: invalid' in warning.getvalue() + assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' in warning.getvalue() + expects = { 'index.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -718,11 +737,8 @@ def test_numfig_with_numbered_toctree(app, status, warning): for fname, paths in iteritems(expects): parser = NslessParser() parser.entity.update(html_entities.entitydefs) - fp = open(os.path.join(app.outdir, fname), 'rb') - try: + with (app.outdir / fname).open('rb') as fp: etree = ET.parse(fp, parser) - finally: - fp.close() for xpath, check, be_found in paths: yield check_xpath, etree, fname, xpath, check, be_found @@ -736,6 +752,11 @@ def test_numfig_with_numbered_toctree(app, status, warning): def test_numfig_with_prefix(app, status, warning): app.builder.build_all() + assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' + not in warning.getvalue()) + assert 'index.rst:51: WARNING: invalid numfig_format: invalid' in warning.getvalue() + assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' in warning.getvalue() + expects = { 'index.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -816,11 +837,8 @@ def test_numfig_with_prefix(app, status, warning): for fname, paths in iteritems(expects): parser = NslessParser() parser.entity.update(html_entities.entitydefs) - fp = open(os.path.join(app.outdir, fname), 'rb') - try: + with (app.outdir / fname).open('rb') as fp: etree = ET.parse(fp, parser) - finally: - fp.close() for xpath, check, be_found in paths: yield check_xpath, etree, fname, xpath, check, be_found @@ -831,6 +849,11 @@ def test_numfig_with_prefix(app, status, warning): def test_numfig_with_secnum_depth(app, status, warning): app.builder.build_all() + assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' + not in warning.getvalue()) + assert 'index.rst:51: WARNING: invalid numfig_format: invalid' in warning.getvalue() + assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' in warning.getvalue() + expects = { 'index.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -911,11 +934,23 @@ def test_numfig_with_secnum_depth(app, status, warning): for fname, paths in iteritems(expects): parser = NslessParser() parser.entity.update(html_entities.entitydefs) - fp = open(os.path.join(app.outdir, fname), 'rb') - try: + with (app.outdir / fname).open('rb') as fp: etree = ET.parse(fp, parser) - finally: - fp.close() for xpath, check, be_found in paths: yield check_xpath, etree, fname, xpath, check, be_found + + +@with_app(buildername='html') +def test_jsmath(app, status, warning): + app.builder.build_all() + content = (app.outdir / 'math.html').text() + + assert '<div class="math">\na^2 + b^2 = c^2</div>' in content + assert '<div class="math">\n\\begin{split}a + 1 < b\\end{split}</div>' in content + assert ('<span class="eqno">(1)</span><div class="math" id="equation-foo">\n' + 'e^{i\\pi} = 1</div>' in content) + assert ('<span class="eqno">(2)</span><div class="math">\n' + 'e^{ix} = \\cos x + i\\sin x</div>' in content) + assert '<div class="math">\nn \\in \\mathbb N</div>' in content + assert '<div class="math">\na + 1 < b</div>' in content diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 639204ca1..98d21a6dd 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -5,7 +5,7 @@ Test the build process with LaTeX builder with the test root. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -16,6 +16,7 @@ from subprocess import Popen, PIPE from six import PY3 +from sphinx.errors import SphinxError from sphinx.writers.latex import LaTeXTranslator from util import SkipTest, remove_unicode_literals, with_app @@ -23,17 +24,45 @@ from test_build_html import ENV_WARNINGS LATEX_WARNINGS = ENV_WARNINGS + """\ -None:None: WARNING: citation not found: missing -None:None: WARNING: no matching candidate for image URI u'foo.\\*' -WARNING: invalid pair index entry u'' -WARNING: invalid pair index entry u'keyword; ' +%(root)s/markup.txt:158: WARNING: unknown option: &option +%(root)s/footnote.txt:60: WARNING: citation not found: missing +%(root)s/images.txt:20: WARNING: no matching candidate for image URI u'foo.\\*' +%(root)s/markup.txt:269: WARNING: Could not parse literal_block as "c". highlighting skipped. """ if PY3: LATEX_WARNINGS = remove_unicode_literals(LATEX_WARNINGS) -@with_app(buildername='latex') +def run_latex(outdir): + """Run pdflatex, xelatex, and lualatex in the outdir""" + cwd = os.getcwd() + os.chdir(outdir) + try: + latexes = ('pdflatex', 'xelatex', 'lualatex') + available_latexes = len(latexes) + for latex in latexes: + try: + os.mkdir(latex) + p = Popen([latex, '--interaction=nonstopmode', + '-output-directory=%s' % latex, 'SphinxTests.tex'], + stdout=PIPE, stderr=PIPE) + except OSError: # most likely the latex executable was not found + available_latexes -= 1 + else: + stdout, stderr = p.communicate() + if p.returncode != 0: + print(stdout) + print(stderr) + assert False, '%s exited with return code %s' % ( + latex, p.returncode) + finally: + os.chdir(cwd) + + if available_latexes == 0: # no latex is available, skip the test + raise SkipTest + +@with_app(buildername='latex', freshenv=True) # use freshenv to check warnings def test_latex(app, status, warning): LaTeXTranslator.ignore_missing_images = True app.builder.build_all() @@ -74,26 +103,10 @@ def test_latex(app, status, warning): 'seem to be installed' % filename) # now, try to run latex over it - cwd = os.getcwd() - os.chdir(app.outdir) - try: - try: - p = Popen(['pdflatex', '--interaction=nonstopmode', - 'SphinxTests.tex'], stdout=PIPE, stderr=PIPE) - except OSError: - raise SkipTest # most likely pdflatex was not found - else: - stdout, stderr = p.communicate() - 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) + run_latex(app.outdir) -@with_app(buildername='latex', +@with_app(buildername='latex', freshenv=True, # use freshenv to check warnings confoverrides={'latex_documents': [ ('contents', 'SphinxTests.tex', 'Sphinx Tests Documentation', 'Georg Brandl \\and someone else', 'howto'), @@ -139,23 +152,7 @@ def test_latex_howto(app, status, warning): 'seem to be installed' % filename) # now, try to run latex over it - cwd = os.getcwd() - os.chdir(app.outdir) - try: - try: - p = Popen(['pdflatex', '--interaction=nonstopmode', - 'SphinxTests.tex'], stdout=PIPE, stderr=PIPE) - except OSError: - raise SkipTest # most likely pdflatex was not found - else: - stdout, stderr = p.communicate() - if p.returncode != 0: - print(stdout) - print(stderr) - app.cleanup() - assert False, 'latex exited with return code %s' % p.returncode - finally: - os.chdir(cwd) + run_latex(app.outdir) @with_app(buildername='latex', testroot='numfig', @@ -168,7 +165,7 @@ def test_numref(app, status, warning): print(warning.getvalue()) assert '\\addto\\captionsenglish{\\renewcommand{\\figurename}{Fig. }}' in result assert '\\addto\\captionsenglish{\\renewcommand{\\tablename}{Table }}' in result - assert '\\floatname{literal-block}{Listing }' in result + assert '\\SetupFloatingEnvironment{literal-block}{name=Listing }' in result assert '\\hyperref[index:fig1]{Fig. \\ref{index:fig1}}' in result assert '\\hyperref[baz:fig22]{Figure\\ref{baz:fig22}}' in result assert '\\hyperref[index:table-1]{Table \\ref{index:table-1}}' in result @@ -190,7 +187,7 @@ def test_numref_with_prefix1(app, status, warning): print(warning.getvalue()) assert '\\addto\\captionsenglish{\\renewcommand{\\figurename}{Figure:}}' in result assert '\\addto\\captionsenglish{\\renewcommand{\\tablename}{Tab\\_}}' in result - assert '\\floatname{literal-block}{Code-}' in result + assert '\\SetupFloatingEnvironment{literal-block}{name=Code-}' in result assert '\\ref{index:fig1}' in result assert '\\ref{baz:fig22}' in result assert '\\ref{index:table-1}' in result @@ -220,7 +217,7 @@ def test_numref_with_prefix2(app, status, warning): assert '\\def\\fnum@figure{\\figurename\\thefigure.}' in result assert '\\addto\\captionsenglish{\\renewcommand{\\tablename}{Tab\\_}}' in result assert '\\def\\fnum@table{\\tablename\\thetable:}' in result - assert '\\floatname{literal-block}{Code-}' in result + assert '\\SetupFloatingEnvironment{literal-block}{name=Code-}' in result assert '\\hyperref[index:fig1]{Figure:\\ref{index:fig1}.}' in result assert '\\hyperref[baz:fig22]{Figure\\ref{baz:fig22}}' in result assert '\\hyperref[index:table-1]{Tab\\_\\ref{index:table-1}:}' in result @@ -239,7 +236,7 @@ def test_numref_with_language_el(app, status, warning): print(warning.getvalue()) assert '\\addto\\captionsgreek{\\renewcommand{\\figurename}{Fig. }}' in result assert '\\addto\\captionsgreek{\\renewcommand{\\tablename}{Table }}' in result - assert '\\floatname{literal-block}{Listing }' in result + assert '\\SetupFloatingEnvironment{literal-block}{name=Listing }' in result assert '\\hyperref[index:fig1]{Fig. \\ref{index:fig1}}' in result assert '\\hyperref[baz:fig22]{Figure\\ref{baz:fig22}}' in result assert '\\hyperref[index:table-1]{Table \\ref{index:table-1}}' in result @@ -258,7 +255,7 @@ def test_numref_with_language_ja(app, status, warning): print(warning.getvalue()) assert u'\\renewcommand{\\figurename}{\u56f3 }' in result assert '\\renewcommand{\\tablename}{TABLE }' in result - assert '\\floatname{literal-block}{LIST }' in result + assert '\\SetupFloatingEnvironment{literal-block}{name=LIST }' in result assert u'\\hyperref[index:fig1]{\u56f3 \\ref{index:fig1}}' in result assert '\\hyperref[baz:fig22]{Figure\\ref{baz:fig22}}' in result assert '\\hyperref[index:table-1]{TABLE \\ref{index:table-1}}' in result @@ -277,23 +274,273 @@ def test_latex_add_latex_package(app, status, warning): assert '\\usepackage[baz]{bar}' in result -@with_app(buildername='latex', testroot='contentsname') -def test_contentsname(app, status, warning): +@with_app(buildername='latex', testroot='latex-babel') +def test_babel_with_no_language_settings(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') print(result) print(status.getvalue()) print(warning.getvalue()) - assert ('\\addto\\captionsenglish{\\renewcommand{\\contentsname}{Table of content}}' + assert '\\documentclass[letterpaper,10pt,english]{sphinxmanual}' in result + assert '\\usepackage{babel}' in result + assert '\\usepackage{times}' in result + assert '\\usepackage[Bjarne]{fncychap}' in result + assert ('\\addto\\captionsenglish{\\renewcommand{\\contentsname}{Table of content}}\n' in result) + assert '\\addto\\captionsenglish{\\renewcommand{\\figurename}{Fig. }}\n' in result + assert '\\addto\\captionsenglish{\\renewcommand{\\tablename}{Table. }}\n' in result + assert '\\addto\\extrasenglish{\\def\\pageautorefname{page}}\n' in result -@with_app(buildername='latex', testroot='contentsname', - confoverrides={'language': 'ja'}) -def test_contentsname_with_language_ja(app, status, warning): +@with_app(buildername='latex', testroot='latex-babel', + confoverrides={'language': 'de'}) +def test_babel_with_language_de(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') print(result) print(status.getvalue()) print(warning.getvalue()) - assert '\\renewcommand{\\contentsname}{Table of content}' in result + assert '\\documentclass[letterpaper,10pt,ngerman]{sphinxmanual}' in result + assert '\\usepackage{babel}' in result + assert '\\usepackage{times}' in result + assert '\\usepackage[Sonny]{fncychap}' in result + assert ('\\addto\\captionsngerman{\\renewcommand{\\contentsname}{Table of content}}\n' + in result) + assert '\\addto\\captionsngerman{\\renewcommand{\\figurename}{Fig. }}\n' in result + assert '\\addto\\captionsngerman{\\renewcommand{\\tablename}{Table. }}\n' in result + assert '\\addto\\extrasngerman{\\def\\pageautorefname{page}}\n' in result + + +@with_app(buildername='latex', testroot='latex-babel', + confoverrides={'language': 'ru'}) +def test_babel_with_language_ru(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert '\\documentclass[letterpaper,10pt,russian]{sphinxmanual}' in result + assert '\\usepackage{babel}' in result + assert '\\usepackage{times}' not in result + assert '\\usepackage[Sonny]{fncychap}' in result + assert ('\\addto\\captionsrussian{\\renewcommand{\\contentsname}{Table of content}}\n' + in result) + assert '\\addto\\captionsrussian{\\renewcommand{\\figurename}{Fig. }}\n' in result + assert '\\addto\\captionsrussian{\\renewcommand{\\tablename}{Table. }}\n' in result + assert '\\addto\\extrasrussian{\\def\\pageautorefname{page}}\n' in result + + +@with_app(buildername='latex', testroot='latex-babel', + confoverrides={'language': 'ja'}) +def test_babel_with_language_ja(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert '\\documentclass[letterpaper,10pt,dvipdfmx]{sphinxmanual}' in result + assert '\\usepackage{babel}' not in result + assert '\\usepackage{times}' in result + assert '\\usepackage[Sonny]{fncychap}' not in result + assert '\\renewcommand{\\contentsname}{Table of content}\n' in result + assert '\\renewcommand{\\figurename}{Fig. }\n' in result + assert '\\renewcommand{\\tablename}{Table. }\n' in result + assert '\\def\\pageautorefname{page}\n' in result + + +@with_app(buildername='latex', testroot='latex-babel', + confoverrides={'language': 'unknown'}) +def test_babel_with_unknown_language(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert '\\documentclass[letterpaper,10pt,english]{sphinxmanual}' in result + assert '\\usepackage{babel}' in result + assert '\\usepackage{times}' in result + assert '\\usepackage[Sonny]{fncychap}' in result + assert ('\\addto\\captionsenglish{\\renewcommand{\\contentsname}{Table of content}}\n' + in result) + assert '\\addto\\captionsenglish{\\renewcommand{\\figurename}{Fig. }}\n' in result + assert '\\addto\\captionsenglish{\\renewcommand{\\tablename}{Table. }}\n' in result + assert '\\addto\\extrasenglish{\\def\\pageautorefname{page}}\n' in result + + assert "WARNING: no Babel option known for language 'unknown'" in warning.getvalue() + + +@with_app(buildername='latex') +def test_footnote(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'SphinxTests.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert '\\footnote[1]{\nnumbered\n}' in result + assert '\\footnote[2]{\nauto numbered\n}' in result + assert '\\footnote[3]{\nnamed\n}' in result + assert '{\\hyperref[footnote:bar]{\\emph{{[}bar{]}}}}' in result + assert '\\bibitem[bar]{bar}{\\phantomsection\\label{footnote:bar} ' in result + assert '\\bibitem[bar]{bar}{\\phantomsection\\label{footnote:bar} \ncite' in result + assert '\\bibitem[bar]{bar}{\\phantomsection\\label{footnote:bar} \ncite\n}' in result + assert '\\capstart\\caption{Table caption \\protect\\footnotemark[4]}' in result + assert 'name \\protect\\footnotemark[5]' in result + assert ('\\end{threeparttable}\n\n' + '\\footnotetext[4]{\nfootnotes in table caption\n}' + '\\footnotetext[5]{\nfootnotes in table\n}' in result) + + +@with_app(buildername='latex', testroot='footnotes') +def test_reference_in_caption(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert ('\\caption{This is the figure caption with a reference to \\label{index:id2}' + '{\\hyperref[index:authoryear]{\\emph{{[}AuthorYear{]}}}}.}' in result) + 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[4]}\n' + '\\label{index:the-section-with-a-reference-to}' + '\\footnotetext[4]{\nFootnote in section\n}' in result) + assert ('\\caption{This is the figure caption with a footnote to ' + '\\protect\\footnotemark[6].}\end{figure}\n' + '\\footnotetext[6]{\nFootnote in caption\n}')in result + assert ('\\caption{footnote \\protect\\footnotemark[7] ' + 'in caption of normal table}') in result + assert '\\end{threeparttable}\n\n\\footnotetext[7]{\nFoot note in table\n}' in result + assert '\\caption{footnote \\protect\\footnotemark[8] in caption of longtable}' in result + assert '\end{longtable}\n\n\\footnotetext[8]{\nFoot note in longtable\n}' in result + + +@with_app(buildername='latex', testroot='footnotes', + confoverrides={'latex_show_urls': 'inline'}) +def test_latex_show_urls_is_inline(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert 'First footnote: \\footnote[2]{\nFirst\n}' in result + assert 'Second footnote: \\footnote[1]{\nSecond\n}' in result + assert '\\href{http://sphinx-doc.org/}{Sphinx} (http://sphinx-doc.org/)' in result + assert 'Third footnote: \\footnote[3]{\nThird\n}' in result + assert ('\\href{http://sphinx-doc.org/~test/}{URL including tilde} ' + '(http://sphinx-doc.org/\\textasciitilde{}test/)' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{URL in term} (http://sphinx-doc.org/)}] ' + '\\leavevmode\nDescription' in result) + assert ('\\item[{Footnote in term \\protect\\footnotemark[5]}] ' + '\\leavevmode\\footnotetext[5]{\nFootnote in term\n}\nDescription' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist} ' + '(http://sphinx-doc.org/)}] \\leavevmode\nDescription' in result) + assert ('\\href{https://github.com/sphinx-doc/sphinx}' + '{https://github.com/sphinx-doc/sphinx}\n' in result) + assert ('\\href{mailto:sphinx-dev@googlegroups.com}' + '{sphinx-dev@googlegroups.com}' in result) + + +@with_app(buildername='latex', testroot='footnotes', + confoverrides={'latex_show_urls': 'footnote'}) +def test_latex_show_urls_is_footnote(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert 'First footnote: \\footnote[2]{\nFirst\n}' in result + assert 'Second footnote: \\footnote[1]{\nSecond\n}' in result + assert ('\\href{http://sphinx-doc.org/}{Sphinx}' + '\\footnote[3]{\nhttp://sphinx-doc.org/\n}' in result) + assert 'Third footnote: \\footnote[5]{\nThird\n}' in result + assert ('\\href{http://sphinx-doc.org/~test/}{URL including tilde}' + '\\footnote[4]{\nhttp://sphinx-doc.org/\\textasciitilde{}test/\n}' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{URL in term}\\protect\\footnotemark[7]}] ' + '\\leavevmode\\footnotetext[7]{\nhttp://sphinx-doc.org/\n}\nDescription' in result) + assert ('\\item[{Footnote in term \\protect\\footnotemark[9]}] ' + '\\leavevmode\\footnotetext[9]{\nFootnote in term\n}\nDescription' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist}\\protect' + '\\footnotemark[8]}] ' + '\\leavevmode\\footnotetext[8]{\nhttp://sphinx-doc.org/\n}\nDescription' in result) + assert ('\\href{https://github.com/sphinx-doc/sphinx}' + '{https://github.com/sphinx-doc/sphinx}\n' in result) + assert ('\\href{mailto:sphinx-dev@googlegroups.com}' + '{sphinx-dev@googlegroups.com}\n' in result) + + +@with_app(buildername='latex', testroot='footnotes', + confoverrides={'latex_show_urls': 'no'}) +def test_latex_show_urls_is_no(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert 'First footnote: \\footnote[2]{\nFirst\n}' in result + assert 'Second footnote: \\footnote[1]{\nSecond\n}' in result + assert '\\href{http://sphinx-doc.org/}{Sphinx}' in result + assert 'Third footnote: \\footnote[3]{\nThird\n}' in result + assert '\\href{http://sphinx-doc.org/~test/}{URL including tilde}' in result + assert ('\\item[{\\href{http://sphinx-doc.org/}{URL in term}}] ' + '\\leavevmode\nDescription' in result) + assert ('\\item[{Footnote in term \\protect\\footnotemark[5]}] ' + '\\leavevmode\\footnotetext[5]{\nFootnote in term\n}\nDescription' in result) + assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist}}] ' + '\\leavevmode\nDescription' in result) + assert ('\\href{https://github.com/sphinx-doc/sphinx}' + '{https://github.com/sphinx-doc/sphinx}\n' in result) + assert ('\\href{mailto:sphinx-dev@googlegroups.com}' + '{sphinx-dev@googlegroups.com}\n' in result) + + +@with_app(buildername='latex', testroot='image-in-section') +def test_image_in_section(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'Python.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert ('\chapter[Test section]' + '{\includegraphics[width=15pt,height=15pt]{{pic}.png} Test section}' + in result) + assert ('\chapter[Other {[}blah{]} section]{Other {[}blah{]} ' + '\includegraphics[width=15pt,height=15pt]{{pic}.png} section}' in result) + assert ('\chapter{Another section}' in result) + + +@with_app(buildername='latex', confoverrides={'latex_logo': 'notfound.jpg'}) +def test_latex_logo_if_not_found(app, status, warning): + try: + app.builder.build_all() + assert False # SphinxError not raised + except Exception as exc: + assert isinstance(exc, SphinxError) + + +@with_app(buildername='latex', testroot='toctree-maxdepth', + confoverrides={'latex_documents': [ + ('index', 'SphinxTests.tex', 'Sphinx Tests Documentation', + 'Georg Brandl', 'manual'), + ]}) +def test_toctree_maxdepth_manual(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'SphinxTests.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert '\\setcounter{tocdepth}{1}' in result + + +@with_app(buildername='latex', testroot='toctree-maxdepth', + confoverrides={'latex_documents': [ + ('index', 'SphinxTests.tex', 'Sphinx Tests Documentation', + 'Georg Brandl', 'howto'), + ]}) +def test_toctree_maxdepth_howto(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'SphinxTests.tex').text(encoding='utf8') + print(result) + print(status.getvalue()) + print(warning.getvalue()) + assert '\\setcounter{tocdepth}{2}' in result diff --git a/tests/test_build_manpage.py b/tests/test_build_manpage.py new file mode 100644 index 000000000..26fa8c494 --- /dev/null +++ b/tests/test_build_manpage.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +""" + test_build_manpage + ~~~~~~~~~~~~~~~~~~ + + Test the build process with manpage builder with the test root. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +from __future__ import print_function + +from util import with_app + + +@with_app(buildername='man') +def test_all(app, status, warning): + app.builder.build_all() + assert (app.outdir / 'SphinxTests.1').exists() + + content = (app.outdir / 'SphinxTests.1').text() + assert r'\fBprint \fP\fIi\fP\fB\en\fP' in content + assert r'\fBmanpage\en\fP' in content diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py index 8050695d9..4f6813653 100644 --- a/tests/test_build_texinfo.py +++ b/tests/test_build_texinfo.py @@ -5,7 +5,7 @@ Test the build process with Texinfo builder with the test root. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -23,16 +23,17 @@ from test_build_html import ENV_WARNINGS TEXINFO_WARNINGS = ENV_WARNINGS + """\ -None:None: WARNING: citation not found: missing -None:None: WARNING: no matching candidate for image URI u'foo.\\*' -None:None: WARNING: no matching candidate for image URI u'svgimg.\\*' +%(root)s/markup.txt:158: WARNING: unknown option: &option +%(root)s/footnote.txt:60: WARNING: citation not found: missing +%(root)s/images.txt:20: WARNING: no matching candidate for image URI u'foo.\\*' +%(root)s/images.txt:29: WARNING: no matching candidate for image URI u'svgimg.\\*' """ if PY3: TEXINFO_WARNINGS = remove_unicode_literals(TEXINFO_WARNINGS) -@with_app('texinfo') +@with_app('texinfo', freshenv=True) # use freshenv to check warnings def test_texinfo(app, status, warning): TexinfoTranslator.ignore_missing_images = True app.builder.build_all() @@ -58,7 +59,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_build_text.py b/tests/test_build_text.py index 5a1ec227f..613d95d1f 100644 --- a/tests/test_build_text.py +++ b/tests/test_build_text.py @@ -5,7 +5,7 @@ Test the build process with Text builder with the test root. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index 7b1cc05e0..ed9eb5fa3 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -5,7 +5,7 @@ Test the base build process. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import shutil diff --git a/tests/test_config.py b/tests/test_config.py index 5bf208288..506071d3a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,12 +6,13 @@ Test the sphinx.config.Config class and its handling in the Application class. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -from six import PY2, PY3, StringIO +from six import PY2, PY3, StringIO, iteritems -from util import TestApp, with_app, with_tempdir, raises, raises_msg +from util import TestApp, with_app, gen_with_app, with_tempdir, \ + raises, raises_msg, assert_in, assert_not_in from sphinx.config import Config from sphinx.errors import ExtensionError, ConfigError, VersionRequirementError @@ -135,36 +136,36 @@ def test_config_eol(tmpdir): assert cfg.project == u'spam' -TYPECHECK_OVERRIDES = [ - # configuration key, override value, should warn, default type - ('master_doc', 123, True, str), - ('man_pages', 123, True, list), # lambda - ('man_pages', [], False, list), - ('epub_tocdepth', True, True, int), # child type - ('nitpicky', 3, False, bool), # parent type - ('templates_path', (), True, list), # other sequence, also raises -] -if PY2: - # Run a check for proper sibling detection in Python 2. Under py3k, the - # default types do not have any siblings. - TYPECHECK_OVERRIDES.append( - ('html_add_permalinks', 'bar', False, unicode)) +@with_app(confoverrides={ + 'master_doc': 123, + 'language': 'foo', + 'primary_domain': None}) +def test_builtin_conf(app, status, warning): + warnings = warning.getvalue() + assert_in('master_doc', warnings, + 'override on builtin "master_doc" should raise a type warning') + assert_not_in('language', warnings, 'explicitly permitted ' + 'override on builtin "language" should NOT raise a type warning') + assert_not_in('primary_domain', warnings, 'override to None on builtin ' + '"primary_domain" should NOT raise a type warning') -def test_gen_check_types(): - for key, value, should, deftype in TYPECHECK_OVERRIDES: - warning = StringIO() - app = TestApp(confoverrides={key: value}, warning=warning) - app.cleanup() - real = type(value).__name__ - msg = ("WARNING: the config value %r has type `%s'," - " defaults to `%s.'\n" % (key, real, deftype.__name__)) - def test(): - warning_list = warning.getvalue() - assert (msg in warning_list) == should, \ - "Setting %s to %r should%s raise: %s" % \ - (key, value, " not" if should else "", msg) - test.description = "test_check_type_%s_on_%s" % \ - (real, type(Config.config_values[key][0]).__name__) - - yield test +# See roots/test-config/conf.py. +TYPECHECK_WARNINGS = { + 'value1': True, + 'value2': True, + 'value3': False, + 'value4': True, + 'value5': False, + 'value6': True, + 'value7': False, + 'value8': False, + 'value9': False, + 'value10': False, +} +@gen_with_app(testroot='config') +def test_gen_check_types(app, status, warning): + for key, should in iteritems(TYPECHECK_WARNINGS): + yield assert_in if should else assert_not_in, key, warning.getvalue(), \ + 'override on "%s" should%s raise a type warning' % \ + (key, '' if should else ' NOT') diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index be7ee3add..adcd9ae83 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -5,7 +5,7 @@ Test the code-block directive. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -64,7 +64,7 @@ def test_code_block_caption_html(app, status, warning): def test_code_block_caption_latex(app, status, warning): app.builder.build_all() latex = (app.outdir / 'Python.tex').text(encoding='utf-8') - caption = '\\caption{caption \\emph{test} rb}' + caption = '\\captionof{literal-block}{caption \\emph{test} rb}' assert caption in latex @@ -205,5 +205,22 @@ def test_literalinclude_caption_html(app, status, warning): def test_literalinclude_caption_latex(app, status, warning): app.builder.build('index') latex = (app.outdir / 'Python.tex').text(encoding='utf-8') - caption = '\\caption{caption \\textbf{test} py}' + caption = '\\captionof{literal-block}{caption \\textbf{test} py}' assert caption in latex + + +@with_app('xml', testroot='directive-code') +def test_literalinclude_classes(app, status, warning): + app.builder.build(['classes']) + et = ElementTree.parse(app.outdir / 'classes.xml') + secs = et.findall('./section/section') + + code_block = secs[0].findall('literal_block') + assert len(code_block) > 0 + assert 'foo bar' == code_block[0].get('classes') + assert 'code_block' == code_block[0].get('names') + + literalinclude = secs[1].findall('literal_block') + assert len(literalinclude) > 0 + assert 'bar baz' == literalinclude[0].get('classes') + assert 'literal_include' == literalinclude[0].get('names') diff --git a/tests/test_directive_only.py b/tests/test_directive_only.py index de7bea46b..7e499a3a1 100644 --- a/tests/test_directive_only.py +++ b/tests/test_directive_only.py @@ -5,7 +5,7 @@ Test the only directive with the test root. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_docutilsconf.py b/tests/test_docutilsconf.py index 5f956942d..ebd0782bd 100644 --- a/tests/test_docutilsconf.py +++ b/tests/test_docutilsconf.py @@ -5,7 +5,7 @@ Test docutils.conf support for several writers. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index bcabbc88d..b4c795125 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -5,14 +5,17 @@ Tests the C++ Domain - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +import re + from six import text_type -from util import raises +from util import raises, with_app +from sphinx import addnodes from sphinx.domains.cpp import DefinitionParser, DefinitionError, NoOldIdError from sphinx.domains.cpp import Symbol import sphinx.domains.cpp as cppDomain @@ -46,9 +49,12 @@ def check(name, input, idv1output=None, idv2output=None, output=None): print("Result: ", res) print("Expected: ", output) raise DefinitionError("") - rootSymbol = Symbol(None, None, None, None, None) - symbol = rootSymbol.add_declaration(ast) - ast.describe_signature([], 'lastIsName', symbol) + rootSymbol = Symbol(None, None, None, None, None, None) + symbol = rootSymbol.add_declaration(ast, docname="Test") + parentNode = addnodes.desc() + signode = addnodes.desc_signature(input, '') + parentNode += signode + ast.describe_signature(signode, 'lastIsName', symbol) if idv2output: idv2output = "_CPPv2" + idv2output @@ -74,6 +80,27 @@ def check(name, input, idv1output=None, idv2output=None, output=None): #print ".. %s:: %s" % (name, input) +def test_fundamental_types(): + # see http://en.cppreference.com/w/cpp/language/types + for t, id_v2 in cppDomain._id_fundamental_v2.items(): + if t == "decltype(auto)": + continue + + def makeIdV1(): + id = t.replace(" ", "-").replace("long", "l").replace("int", "i") + id = id.replace("bool", "b").replace("char", "c") + id = id.replace("wc_t", "wchar_t").replace("c16_t", "char16_t") + id = id.replace("c32_t", "char32_t") + return "f__%s" % id + + def makeIdV2(): + id = id_v2 + if t == "std::nullptr_t": + id = "NSt9nullptr_tE" + return "1f%s" % id + check("function", "void f(%s arg)" % t, makeIdV1(), makeIdV2()) + + def test_type_definitions(): check("type", "public bool b", "b", "1b", "bool b") check("type", "bool A::b", "A::b", "N1A1bE") @@ -103,6 +130,10 @@ def test_type_definitions(): # test name in global scope check("type", "bool ::B::b", "B::b", "N1B1bE") + check('type', 'A = B', None, '1A') + + +def test_member_definitions(): check('member', ' const std::string & name = 42', "name__ssCR", "4name", output='const std::string &name = 42') check('member', ' const std::string & name', "name__ssCR", "4name", @@ -113,7 +144,10 @@ def test_type_definitions(): "name__std::vector:unsigned-i.l:CR", "4name", output='const std::vector<unsigned int, long> &name') check('member', 'module::myclass foo[n]', "foo__module::myclassA", "3foo") + check('member', 'int *const p', 'p__iPC', '1p') + +def test_function_definitions(): check('function', 'operator bool() const', "castto-b-operatorC", "NKcvbEv") check('function', 'A::operator bool() const', "A::castto-b-operatorC", "NK1AcvbEv") @@ -219,6 +253,8 @@ def test_type_definitions(): "A::f__doubleC", "NK1A1fEd") check("function", "void f(std::shared_ptr<int(double)> ptr)", None, "1fNSt10shared_ptrIFidEEE") + check("function", "void f(int *const p)", "f__iPC", "1fPCi") + check("function", "void f(int *volatile const p)", "f__iPVC", "1fPVCi") # TODO: make tests for functions in a template, e.g., Test<int&&()> # such that the id generation for function type types is correct. @@ -233,9 +269,72 @@ def test_type_definitions(): check('function', 'void f(enum E e)', 'f__E', '1f1E') check('function', 'void f(union E e)', 'f__E', '1f1E') + # pointer to member (function) + check('function', 'void f(int C::*)', None, '1fM1Ci') + check('function', 'void f(int C::* p)', None, '1fM1Ci') + check('function', 'void f(int ::C::* p)', None, '1fM1Ci') + check('function', 'void f(int C::* const)', None, '1fKM1Ci') + check('function', 'void f(int C::* const&)', None, '1fRKM1Ci') + check('function', 'void f(int C::* volatile)', None, '1fVM1Ci') + check('function', 'void f(int C::* const volatile)', None, '1fVKM1Ci', + output='void f(int C::* volatile const)') + check('function', 'void f(int C::* volatile const)', None, '1fVKM1Ci') + check('function', 'void f(int (C::*)(float, double))', None, '1fM1CFifdE') + check('function', 'void f(int (C::* p)(float, double))', None, '1fM1CFifdE') + check('function', 'void f(int (::C::* p)(float, double))', None, '1fM1CFifdE') + check('function', 'void f(void (C::*)() const &)', None, '1fM1CKRFvvE') + check('function', 'int C::* f(int, double)', None, '1fid') + check('function', 'void f(int C::* *)', None, '1fPM1Ci') + + +def test_operators(): + check('function', 'void operator new [ ] ()', + "new-array-operator", "nav", output='void operator new[]()') + check('function', 'void operator delete ()', + "delete-operator", "dlv", output='void operator delete()') + check('function', 'operator bool() const', + "castto-b-operatorC", "NKcvbEv", output='operator bool() const') + + check('function', 'void operator * ()', + "mul-operator", "mlv", output='void operator*()') + check('function', 'void operator - ()', + "sub-operator", "miv", output='void operator-()') + check('function', 'void operator + ()', + "add-operator", "plv", output='void operator+()') + check('function', 'void operator = ()', + "assign-operator", "aSv", output='void operator=()') + check('function', 'void operator / ()', + "div-operator", "dvv", output='void operator/()') + check('function', 'void operator % ()', + "mod-operator", "rmv", output='void operator%()') + check('function', 'void operator ! ()', + "not-operator", "ntv", output='void operator!()') + + check('function', 'void operator "" _udl()', + None, 'li4_udlv', output='void operator""_udl()') + + +def test_class_definitions(): check('class', 'public A', "A", "1A", output='A') check('class', 'private A', "A", "1A") + check('class', 'A final', 'A', '1A') + # test bases + check('class', 'A', "A", "1A") + check('class', 'A::B::C', "A::B::C", "N1A1B1CE") + check('class', 'A : B', "A", "1A") + check('class', 'A : private B', "A", "1A", output='A : B') + check('class', 'A : public B', "A", "1A") + check('class', 'A : B, C', "A", "1A") + check('class', 'A : B, protected C, D', "A", "1A") + check('class', 'A : virtual private B', 'A', '1A', output='A : virtual B') + check('class', 'A : B, virtual C', 'A', '1A') + check('class', 'A : public virtual B', 'A', '1A') + check('class', 'A : B, C...', 'A', '1A') + check('class', 'A : B..., C', 'A', '1A') + + +def test_enum_definitions(): check('enum', 'A', None, "1A") check('enum', 'A : std::underlying_type<B>::type', None, "1A") check('enum', 'A : unsigned int', None, "1A") @@ -246,29 +345,6 @@ def test_type_definitions(): check('enumerator', 'A = std::numeric_limits<unsigned long>::max()', None, "1A") - check('type', 'A = B', None, '1A') - - -def test_fundamental_types(): - # see http://en.cppreference.com/w/cpp/language/types - for t, id_v2 in cppDomain._id_fundamental_v2.items(): - if t == "decltype(auto)": - continue - - def makeIdV1(): - id = t.replace(" ", "-").replace("long", "l").replace("int", "i") - id = id.replace("bool", "b").replace("char", "c") - id = id.replace("wc_t", "wchar_t").replace("c16_t", "char16_t") - id = id.replace("c32_t", "char32_t") - return "f__%s" % id - - def makeIdV2(): - id = id_v2 - if t == "std::nullptr_t": - id = "NSt9nullptr_tE" - return "1f%s" % id - check("function", "void f(%s arg)" % t, makeIdV1(), makeIdV2()) - def test_templates(): check('class', "A<T>", None, "IE1AI1TE", output="template<> A<T>") @@ -304,51 +380,64 @@ def test_templates(): "void allow(F *f, typename func<F, B, G!=1>::type tt)", None, "I0E5allowP1FN4funcI1F1BXG!=1EE4typeE") + # from #2058 + check('function', + "template<typename Char, typename Traits> " + "inline std::basic_ostream<Char, Traits> &operator<<(" + "std::basic_ostream<Char, Traits> &os, " + "const c_string_view_base<const Char, Traits> &str)", + None, "I00ElsRNSt13basic_ostreamI4Char6TraitsEE" + "RK18c_string_view_baseIK4Char6TraitsE") -def test_class(): - check('class', 'A final', 'A', '1A') - - -def test_bases(): - check('class', 'A', "A", "1A") - check('class', 'A::B::C', "A::B::C", "N1A1B1CE") - check('class', 'A : B', "A", "1A") - check('class', 'A : private B', "A", "1A", output='A : B') - check('class', 'A : public B', "A", "1A") - check('class', 'A : B, C', "A", "1A") - check('class', 'A : B, protected C, D', "A", "1A") - check('class', 'A : virtual private B', 'A', '1A', output='A : virtual B') - check('class', 'A : B, virtual C', 'A', '1A') - check('class', 'A : public virtual B', 'A', '1A') - check('class', 'A : B, C...', 'A', '1A') - check('class', 'A : B..., C', 'A', '1A') - - -def test_operators(): - check('function', 'void operator new [ ] ()', - "new-array-operator", "nav", output='void operator new[]()') - check('function', 'void operator delete ()', - "delete-operator", "dlv", output='void operator delete()') - check('function', 'operator bool() const', - "castto-b-operatorC", "NKcvbEv", output='operator bool() const') - - check('function', 'void operator * ()', - "mul-operator", "mlv", output='void operator*()') - check('function', 'void operator - ()', - "sub-operator", "miv", output='void operator-()') - check('function', 'void operator + ()', - "add-operator", "plv", output='void operator+()') - check('function', 'void operator = ()', - "assign-operator", "aSv", output='void operator=()') - check('function', 'void operator / ()', - "div-operator", "dvv", output='void operator/()') - check('function', 'void operator % ()', - "mod-operator", "rmv", output='void operator%()') - check('function', 'void operator ! ()', - "not-operator", "ntv", output='void operator!()') #def test_print(): # # used for getting all the ids out for checking # for a in ids: # print(a) # raise DefinitionError("") + + +@with_app(testroot='domain-cpp') +def test_build_domain_cpp(app, status, warning): + app.builder.build_all() + + roles = (app.outdir / 'roles.html').text() + assert re.search('<li><a .*?><code .*?><span .*?>Sphinx</span></code></a></li>', roles) + assert re.search(('<li>ref function without parens <a .*?><code .*?><span .*?>' + 'hello\(\)</span></code></a>\.</li>'), roles) + assert re.search(('<li>ref function with parens <a .*?><code .*?><span .*?>' + 'hello\(\)</span></code></a>\.</li>'), roles) + assert re.search('<li><a .*?><code .*?><span .*?>Sphinx::version</span></code></a></li>', + roles) + assert re.search('<li><a .*?><code .*?><span .*?>version</span></code></a></li>', roles) + assert re.search('<li><a .*?><code .*?><span .*?>List</span></code></a></li>', roles) + assert re.search('<li><a .*?><code .*?><span .*?>MyEnum</span></code></a></li>', roles) + + any_role = (app.outdir / 'any-role.html').text() + assert re.search('<li><a .*?><code .*?><span .*?>Sphinx</span></code></a></li>', any_role) + assert re.search(('<li>ref function without parens <a .*?><code .*?><span .*?>' + 'hello\(\)</span></code></a>\.</li>'), any_role) + assert re.search(('<li>ref function with parens <a .*?><code .*?><span .*?>' + 'hello\(\)</span></code></a>\.</li>'), any_role) + assert re.search('<li><a .*?><code .*?><span .*?>Sphinx::version</span></code></a></li>', + any_role) + assert re.search('<li><a .*?><code .*?><span .*?>version</span></code></a></li>', any_role) + assert re.search('<li><a .*?><code .*?><span .*?>List</span></code></a></li>', any_role) + assert re.search('<li><a .*?><code .*?><span .*?>MyEnum</span></code></a></li>', any_role) + + +@with_app(testroot='domain-cpp', confoverrides={'add_function_parentheses': False}) +def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, warning): + app.builder.build_all() + + roles = (app.outdir / 'roles.html').text() + assert re.search(('<li>ref function without parens <a .*?><code .*?><span .*?>' + 'hello</span></code></a>\.</li>'), roles) + assert re.search(('<li>ref function with parens <a .*?><code .*?><span .*?>' + 'hello</span></code></a>\.</li>'), roles) + + any_role = (app.outdir / 'any-role.html').text() + assert re.search(('<li>ref function without parens <a .*?><code .*?><span .*?>' + 'hello</span></code></a>\.</li>'), any_role) + assert re.search(('<li>ref function with parens <a .*?><code .*?><span .*?>' + 'hello</span></code></a>\.</li>'), any_role) diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 3918662dd..568038cfd 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -5,7 +5,7 @@ Tests the Python Domain - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_domain_rst.py b/tests/test_domain_rst.py index 7dabebe06..c1f8c6caa 100644 --- a/tests/test_domain_rst.py +++ b/tests/test_domain_rst.py @@ -5,7 +5,7 @@ Tests the reStructuredText domain. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_domain_std.py b/tests/test_domain_std.py index 1e361f172..6a6ff5315 100644 --- a/tests/test_domain_std.py +++ b/tests/test_domain_std.py @@ -5,7 +5,7 @@ Tests the std domain - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_environment.py b/tests/test_environment.py index 1e639aeb6..5b5c166d3 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -5,7 +5,7 @@ Test the BuildEnvironment class. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -63,9 +63,10 @@ def test_images(): image_uri_message = remove_unicode_literals(image_uri_message) assert image_uri_message in app._warning.content[-1] assert set(htmlbuilder.images.keys()) == \ - set(['subdir/img.png', 'img.png', 'subdir/simg.png', 'svgimg.svg']) + set(['subdir/img.png', 'img.png', 'subdir/simg.png', 'svgimg.svg', + 'img.foo.png']) assert set(htmlbuilder.images.values()) == \ - set(['img.png', 'img1.png', 'simg.png', 'svgimg.svg']) + set(['img.png', 'img1.png', 'simg.png', 'svgimg.svg', 'img.foo.png']) app._warning.reset() latexbuilder = LaTeXBuilder(app) @@ -73,9 +74,10 @@ def test_images(): assert image_uri_message in app._warning.content[-1] assert set(latexbuilder.images.keys()) == \ set(['subdir/img.png', 'subdir/simg.png', 'img.png', 'img.pdf', - 'svgimg.pdf']) + 'svgimg.pdf', 'img.foo.png']) assert set(latexbuilder.images.values()) == \ - set(['img.pdf', 'img.png', 'img1.png', 'simg.png', 'svgimg.pdf']) + set(['img.pdf', 'img.png', 'img1.png', 'simg.png', + 'svgimg.pdf', 'img.foo.png']) def test_second_update(): diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index 6bf9f3b9c..b2b04aea0 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -5,7 +5,7 @@ Test the autosummary extension. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_coverage.py b/tests/test_ext_coverage.py index 9f401670d..ddf8efe51 100644 --- a/tests/test_ext_coverage.py +++ b/tests/test_ext_coverage.py @@ -5,7 +5,7 @@ Test the coverage builder. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_doctest.py b/tests/test_ext_doctest.py index 68a0b17e8..d2a2d90ce 100644 --- a/tests/test_ext_doctest.py +++ b/tests/test_ext_doctest.py @@ -5,7 +5,7 @@ Test the doctest extension. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_githubpages.py b/tests/test_ext_githubpages.py new file mode 100644 index 000000000..65276df7a --- /dev/null +++ b/tests/test_ext_githubpages.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" + test_ext_githubpages + ~~~~~~~~~~~~~~~~~~~~ + + Test sphinx.ext.githubpages extension. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from util import with_app + + +@with_app('html', testroot='ext-githubpages') +def test_githubpages(app, status, warning): + app.builder.build_all() + assert (app.outdir / '.nojekyll').exists() diff --git a/tests/test_ext_graphviz.py b/tests/test_ext_graphviz.py index d0cbbc42f..d39adccd1 100644 --- a/tests/test_ext_graphviz.py +++ b/tests/test_ext_graphviz.py @@ -5,7 +5,7 @@ Test sphinx.ext.graphviz extension. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -15,12 +15,31 @@ from util import with_app, SkipTest @with_app('html', testroot='ext-graphviz') -def test_graphviz(app, status, warning): +def test_graphviz_html(app, status, warning): app.builder.build_all() if "dot command 'dot' cannot be run" in warning.getvalue(): raise SkipTest('graphviz "dot" is not available') content = (app.outdir / 'index.html').text() - html = ('<p class="graphviz">\s*<img .*?/>\s*</p>\s*' - '<p class="caption"><span class="caption-text">caption of graph</span>') + html = ('<div class="figure" .*?>\s*<img .*?/>\s*<p class="caption">' + '<span class="caption-text">caption of graph</span>.*</p>\s*</div>') assert re.search(html, content, re.S) + + html = 'Hello <img .*?/>\n graphviz world' + assert re.search(html, content, re.S) + + +@with_app('latex', testroot='ext-graphviz') +def test_graphviz_latex(app, status, warning): + app.builder.build_all() + if "dot command 'dot' cannot be run" in warning.getvalue(): + raise SkipTest('graphviz "dot" is not available') + + content = (app.outdir / 'SphinxTests.tex').text() + macro = ('\\\\begin{figure}\[htbp\]\n\\\\centering\n\\\\capstart\n\n' + '\\\\includegraphics{graphviz-\w+.pdf}\n' + '\\\\caption{caption of graph}\\\\end{figure}') + assert re.search(macro, content, re.S) + + macro = 'Hello \\\\includegraphics{graphviz-\w+.pdf} graphviz world' + assert re.search(macro, content, re.S) diff --git a/tests/test_ext_ifconfig.py b/tests/test_ext_ifconfig.py index 3f1bdd29a..56e31acae 100644 --- a/tests/test_ext_ifconfig.py +++ b/tests/test_ext_ifconfig.py @@ -5,7 +5,7 @@ Test sphinx.ext.ifconfig extension. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_imgmath.py b/tests/test_ext_imgmath.py new file mode 100644 index 000000000..2e28d2baa --- /dev/null +++ b/tests/test_ext_imgmath.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" + test_ext_imgmath + ~~~~~~~~~~~~~~~~ + + Test sphinx.ext.imgmath extension. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from util import with_app, SkipTest + + +@with_app('html', testroot='ext-imgmath') +def test_imgmath_png(app, status, warning): + app.builder.build_all() + if "LaTeX command 'latex' cannot be run" in warning.getvalue(): + raise SkipTest('LaTeX command "latex" is not available') + if "dvipng command 'dvipng' cannot be run" in warning.getvalue(): + raise SkipTest('dvipng command "dvipng" is not available') + + content = (app.outdir / 'index.html').text() + html = ('<div class="math">\s*<p>\s*<img src="_images/math/\w+.png"' + '\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>') + assert re.search(html, content, re.S) + +@with_app('html', testroot='ext-imgmath', + confoverrides={'imgmath_image_format': 'svg'}) +def test_imgmath_svg(app, status, warning): + app.builder.build_all() + if "LaTeX command 'latex' cannot be run" in warning.getvalue(): + raise SkipTest('LaTeX command "latex" is not available') + if "dvisvgm command 'dvisvgm' cannot be run" in warning.getvalue(): + raise SkipTest('dvisvgm command "dvisvgm" is not available') + + content = (app.outdir / 'index.html').text() + html = ('<div class="math">\s*<p>\s*<img src="_images/math/\w+.svg"' + '\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>') + assert re.search(html, content, re.S) diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index 427bc87db..8bf3f92a7 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -5,21 +5,23 @@ Test the intersphinx extension. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -import zlib import posixpath +import unittest +import zlib from six import BytesIO from docutils import nodes from sphinx import addnodes from sphinx.ext.intersphinx import read_inventory_v1, read_inventory_v2, \ - load_mappings, missing_reference + load_mappings, missing_reference, _strip_basic_auth, _read_from_url, \ + _get_safe_url -from util import with_app, with_tempdir +from util import with_app, with_tempdir, mock inventory_v1 = '''\ @@ -175,3 +177,75 @@ def test_load_mappings_warnings(tempdir, app, status, warning): # load the inventory and check if it's done correctly load_mappings(app) assert warning.getvalue().count('\n') == 2 + + +class TestStripBasicAuth(unittest.TestCase): + """Tests for sphinx.ext.intersphinx._strip_basic_auth()""" + def test_auth_stripped(self): + """basic auth creds stripped from URL containing creds""" + url = 'https://user:12345@domain.com/project/objects.inv' + expected = 'https://domain.com/project/objects.inv' + actual_url, actual_username, actual_password = _strip_basic_auth(url) + self.assertEqual(expected, actual_url) + self.assertEqual('user', actual_username) + self.assertEqual('12345', actual_password) + + def test_no_auth(self): + """url unchanged if param doesn't contain basic auth creds""" + url = 'https://domain.com/project/objects.inv' + expected = 'https://domain.com/project/objects.inv' + actual_url, actual_username, actual_password = _strip_basic_auth(url) + self.assertEqual(expected, actual_url) + self.assertEqual(None, actual_username) + self.assertEqual(None, actual_password) + + +@mock.patch('six.moves.urllib.request.HTTPBasicAuthHandler') +@mock.patch('six.moves.urllib.request.HTTPPasswordMgrWithDefaultRealm') +@mock.patch('six.moves.urllib.request.build_opener') +def test_readfromurl_authed(m_build_opener, m_HTTPPasswordMgrWithDefaultRealm, + m_HTTPBasicAuthHandler): + # read from URL containing basic auth creds + password_mgr = mock.Mock() + m_HTTPPasswordMgrWithDefaultRealm.return_value = password_mgr + + url = 'https://user:12345@domain.com/project/objects.inv' + _read_from_url(url) + + m_HTTPPasswordMgrWithDefaultRealm.assert_called_once_with() + password_mgr.add_password.assert_called_with( + None, 'https://domain.com/project/objects.inv', 'user', '12345') + + +@mock.patch('six.moves.urllib.request.HTTPBasicAuthHandler') +@mock.patch('six.moves.urllib.request.HTTPPasswordMgrWithDefaultRealm') +@mock.patch('sphinx.ext.intersphinx.default_opener') +def test_readfromurl_unauthed(m_default_opener, m_HTTPPasswordMgrWithDefaultRealm, + m_HTTPBasicAuthHandler): + # read from URL without auth creds + password_mgr = mock.Mock() + m_HTTPPasswordMgrWithDefaultRealm.return_value = password_mgr + + url = 'https://domain.com/project/objects.inv' + _read_from_url(url) + + # assert password manager not created + assert m_HTTPPasswordMgrWithDefaultRealm.call_args is None + # assert no password added to the password manager + assert password_mgr.add_password.call_args is None + + +def test_getsafeurl_authed(): + """_get_safe_url() with a url with basic auth""" + url = 'https://user:12345@domain.com/project/objects.inv' + expected = 'https://user@domain.com/project/objects.inv' + actual = _get_safe_url(url) + assert expected == actual + + +def test_getsafeurl_unauthed(): + """_get_safe_url() with a url without basic auth""" + url = 'https://domain.com/project/objects.inv' + expected = 'https://domain.com/project/objects.inv' + actual = _get_safe_url(url) + assert expected == actual diff --git a/tests/test_ext_napoleon.py b/tests/test_ext_napoleon.py index 162f98e81..d4ce96001 100644 --- a/tests/test_ext_napoleon.py +++ b/tests/test_ext_napoleon.py @@ -6,7 +6,7 @@ Tests for :mod:`sphinx.ext.napoleon.__init__` module. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 7da361512..e53efdf34 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -6,7 +6,7 @@ Tests for :mod:`sphinx.ext.napoleon.docstring` module. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -732,8 +732,8 @@ class NumpyDocstringTest(BaseDocstringTest): Single line summary :Parameters: * **arg1** (*str*) -- Extended description of arg1 - * ***args** -- Variable length argument list. - * ****kwargs** -- Arbitrary keyword arguments. + * **\\*args** -- Variable length argument list. + * **\\*\\*kwargs** -- Arbitrary keyword arguments. """ ), ( """ diff --git a/tests/test_ext_napoleon_iterators.py b/tests/test_ext_napoleon_iterators.py index b99e50f96..2faa11d40 100644 --- a/tests/test_ext_napoleon_iterators.py +++ b/tests/test_ext_napoleon_iterators.py @@ -6,7 +6,7 @@ Tests for :mod:`sphinx.ext.napoleon.iterators` module. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_viewcode.py b/tests/test_ext_viewcode.py index 42700f4de..93e681a5d 100644 --- a/tests/test_ext_viewcode.py +++ b/tests/test_ext_viewcode.py @@ -5,7 +5,7 @@ Test sphinx.ext.viewcode extension. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_highlighting.py b/tests/test_highlighting.py index 02b928916..d3c36f478 100644 --- a/tests/test_highlighting.py +++ b/tests/test_highlighting.py @@ -5,7 +5,7 @@ Test the Pygments highlighting bridge. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 986b62566..fcb35fc42 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -5,7 +5,7 @@ Test locale features. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_intl.py b/tests/test_intl.py index dabb2a3f5..b24ec65d2 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -6,7 +6,7 @@ Test message patching for internationalization purposes. Runs the text builder in the test root. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -16,6 +16,7 @@ import re from subprocess import Popen, PIPE from xml.etree import ElementTree +from babel.messages import pofile from nose.tools import assert_equal from six import string_types @@ -40,6 +41,11 @@ def gen_with_intl_app(builder, confoverrides={}, *args, **kw): return gen_with_app(builder, *args, **default_kw) +def read_po(pathname): + with pathname.open() as f: + return pofile.read_po(f) + + def setup_module(): if not root.exists(): (rootdir / 'roots' / 'test-intl').copytree(root) @@ -175,16 +181,18 @@ def test_text_builder(app, status, warning): u'WARNING: Literal block expected; none found.' yield assert_re_search, expected_warning_expr, warnings - # --- definition terms: regression test for #975 + # --- definition terms: regression test for #975, #2198, #2205 result = (app.outdir / 'definition_terms.txt').text(encoding='utf-8') expect = (u"\nI18N WITH DEFINITION TERMS" u"\n**************************\n" u"\nSOME TERM" u"\n THE CORRESPONDING DEFINITION\n" - u"\nSOME OTHER TERM" + u"\nSOME *TERM* WITH LINK" u"\n THE CORRESPONDING DEFINITION #2\n" - u"\nSOME TERM WITH : CLASSIFIER1 : CLASSIFIER2" + u"\nSOME **TERM** WITH : CLASSIFIER1 : CLASSIFIER2" + u"\n THE CORRESPONDING DEFINITION\n" + u"\nSOME TERM WITH : CLASSIFIER[]" u"\n THE CORRESPONDING DEFINITION\n" ) yield assert_equal, result, expect @@ -280,8 +288,8 @@ def test_text_builder(app, status, warning): u"\n * **foo** -- DESCRIPTION OF PARAMETER foo\n" u"\n * **bar** -- DESCRIPTION OF PARAMETER bar\n" u"\nclass Cls3(values)\n" - u"\n Raises ValueError:" - u"\n IF THE VALUES ARE OUT OF RANGE\n" + u"\n Raises:" + u"\n **ValueError** -- IF THE VALUES ARE OUT OF RANGE\n" u"\nclass Cls4(values)\n" u"\n Raises:" u"\n * **TypeError** -- IF THE VALUES ARE NOT VALID\n" @@ -304,6 +312,31 @@ def test_text_builder(app, status, warning): yield assert_in, d.upper() + " BODY", result +@gen_with_intl_app('gettext', freshenv=True) +def test_gettext_builder(app, status, warning): + app.builder.build_all() + + # --- definition terms: regression test for #2198, #2205 + expect = read_po(app.srcdir / 'definition_terms.po') + actual = read_po(app.outdir / 'definition_terms.pot') + for expect_msg in [m for m in expect if m.id]: + yield assert_in, expect_msg.id, [m.id for m in actual if m.id] + + # --- glossary terms: regression test for #1090 + expect = read_po(app.srcdir / 'glossary_terms.po') + actual = read_po(app.outdir / 'glossary_terms.pot') + for expect_msg in [m for m in expect if m.id]: + yield assert_in, expect_msg.id, [m.id for m in actual if m.id] + warnings = warning.getvalue().replace(os.sep, '/') + yield assert_not_in, 'term not in glossary', warnings + + # --- glossary term inconsistencies: regression test for #1090 + expect = read_po(app.srcdir / 'glossary_terms_inconsistency.po') + actual = read_po(app.outdir / 'glossary_terms_inconsistency.pot') + for expect_msg in [m for m in expect if m.id]: + yield assert_in, expect_msg.id, [m.id for m in actual if m.id] + + @gen_with_intl_app('html', freshenv=True) def test_html_builder(app, status, warning): app.builder.build_all() @@ -661,14 +694,15 @@ def test_additional_targets_should_not_be_translated(app, status, warning): yield assert_count(expected_expr, result, 1) # C code block with lang should not be translated but be *C* highlighted - expected_expr = """<span class="cp">#include <stdio.h></span>""" + expected_expr = ("""<span class="cp">#include</span> """ + """<span class="cpf"><stdio.h></span>""") yield assert_count(expected_expr, result, 1) # doctest block should not be translated but be highlighted expected_expr = ( """<span class="gp">>>> </span>""" """<span class="kn">import</span> <span class="nn">sys</span> """ - """<span class="c"># sys importing</span>""") + """<span class="c1"># sys importing</span>""") yield assert_count(expected_expr, result, 1) ## raw.txt @@ -721,14 +755,15 @@ def test_additional_targets_should_be_translated(app, status, warning): yield assert_count(expected_expr, result, 1) # C code block with lang should be translated and be *C* highlighted - expected_expr = """<span class="cp">#include <STDIO.H></span>""" + expected_expr = ("""<span class="cp">#include</span> """ + """<span class="cpf"><STDIO.H></span>""") yield assert_count(expected_expr, result, 1) # doctest block should not be translated but be highlighted expected_expr = ( """<span class="gp">>>> </span>""" """<span class="kn">import</span> <span class="nn">sys</span> """ - """<span class="c"># SYS IMPORTING</span>""") + """<span class="c1"># SYS IMPORTING</span>""") yield assert_count(expected_expr, result, 1) ## raw.txt diff --git a/tests/test_markup.py b/tests/test_markup.py index a5804366f..c5ab8187b 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -5,7 +5,7 @@ Test various Sphinx-specific markup extensions. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_metadata.py b/tests/test_metadata.py index d61f1b522..14ab51ccd 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -5,7 +5,7 @@ Test our handling of metadata in files with bibliographic metadata. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 013700417..ac6507ce5 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -5,7 +5,7 @@ Test the sphinx.quickstart module. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -181,10 +181,11 @@ def test_quickstart_all_answers(tempdir): 'intersphinx': 'no', 'todo': 'y', 'coverage': 'no', - 'pngmath': 'N', + 'imgmath': 'N', 'mathjax': 'no', 'ifconfig': 'no', 'viewcode': 'no', + 'githubpages': 'no', 'Create Makefile': 'no', 'Create Windows command file': 'no', 'Do you want to use the epub builder': 'yes', diff --git a/tests/test_search.py b/tests/test_search.py index 391c2ee83..6b20f14d1 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -5,7 +5,7 @@ Test the search index builder. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_searchadapters.py b/tests/test_searchadapters.py index 7818ec807..f6a389fea 100644 --- a/tests/test_searchadapters.py +++ b/tests/test_searchadapters.py @@ -5,7 +5,7 @@ Test the Web Support Package search adapters. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_setup_command.py b/tests/test_setup_command.py index 7816fd183..77c9ade46 100644 --- a/tests/test_setup_command.py +++ b/tests/test_setup_command.py @@ -5,7 +5,7 @@ Test setup_command for distutils. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_templating.py b/tests/test_templating.py index 3cf331bbc..3cfb69ba2 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -5,7 +5,7 @@ Test templating. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_theming.py b/tests/test_theming.py index 45229c842..07787ecca 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -5,7 +5,7 @@ Test the Theme class. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -14,7 +14,7 @@ import zipfile from sphinx.theming import Theme, ThemeError -from util import with_app, raises, TestApp +from util import with_app, raises, mock, path @with_app(confoverrides={'html_theme': 'ziptheme', @@ -79,3 +79,19 @@ def test_js_source(app, status, warning): assert 'Underscore.js {v}'.format(v=v) in underscore_min, msg underscore_src = (app.outdir / '_static' / 'underscore-{v}.js'.format(v=v)).text() assert 'Underscore.js {v}'.format(v=v) in underscore_src, msg + + +def test_double_inheriting_theme(): + from sphinx.theming import load_theme_plugins # load original before patching + + def load_themes(): + roots = path(__file__).abspath().parent / 'roots' + yield roots / 'test-double-inheriting-theme' / 'base_themes_dir' + for t in load_theme_plugins(): + yield t + + @mock.patch('sphinx.theming.load_theme_plugins', side_effect=load_themes) + @with_app(testroot='double-inheriting-theme') + def test_double_inheriting_theme_(app, status, warning, m_): + pass + yield test_double_inheriting_theme_ diff --git a/tests/test_toctree.py b/tests/test_toctree.py new file mode 100644 index 000000000..64cb8cfd6 --- /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-2016 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 diff --git a/tests/test_util.py b/tests/test_util.py new file mode 100644 index 000000000..23c4ad1ca --- /dev/null +++ b/tests/test_util.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" + test_util + ~~~~~~~~~~~~~~~ + + Tests util functions. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +from sphinx.util import encode_uri + + +def test_encode_uri(): + expected = (u'https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_' + u'%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F_' + u'%D0%B1%D0%B0%D0%B7%D0%B0%D0%BC%D0%B8_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85') + uri = (u'https://ru.wikipedia.org/wiki' + u'/Система_управления_базами_данных') + assert expected, encode_uri(uri) + + expected = (u'https://github.com/search?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+is%3A' + u'sprint-friendly+user%3Ajupyter&type=Issues&ref=searchresults') + uri = (u'https://github.com/search?utf8=✓&q=is%3Aissue+is%3Aopen+is%3A' + u'sprint-friendly+user%3Ajupyter&type=Issues&ref=searchresults') + assert expected, encode_uri(uri) diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index 47ef8ecce..df59653b3 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -5,7 +5,7 @@ Test i18n util. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -55,7 +55,8 @@ def test_catalog_write_mo(dir): cat = i18n.CatalogInfo(dir, 'test', 'utf-8') cat.write_mo('en') assert path.exists(cat.mo_path) - assert read_mo(open(cat.mo_path, 'rb')) is not None + with open(cat.mo_path, 'rb') as f: + assert read_mo(f) is not None @with_tempdir diff --git a/tests/test_util_nodes.py b/tests/test_util_nodes.py index 33b8f8913..a41af3cc8 100644 --- a/tests/test_util_nodes.py +++ b/tests/test_util_nodes.py @@ -5,7 +5,7 @@ Tests uti.nodes functions. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from textwrap import dedent @@ -137,8 +137,7 @@ def test_extract_messages_without_rawsource(): Check node.rawsource is fall-backed by using node.astext() value. `extract_message` which is used from Sphinx i18n feature drop ``not node.rawsource`` - nodes. - So, all nodes which want to translate must have ``rawsource`` value. + nodes. So, all nodes which want to translate must have ``rawsource`` value. However, sometimes node.rawsource is not set. For example: recommonmark-0.2.0 doesn't set rawsource to `paragraph` node. diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 140082e15..f5b5057d7 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -5,7 +5,7 @@ Test the versioning implementation. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_websupport.py b/tests/test_websupport.py index 6ea4588dd..bb41ae1ab 100644 --- a/tests/test_websupport.py +++ b/tests/test_websupport.py @@ -5,7 +5,7 @@ Test the Web Support Package - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/util.py b/tests/util.py index df952d457..1e20e73e2 100644 --- a/tests/util.py +++ b/tests/util.py @@ -3,7 +3,7 @@ Sphinx test suite utilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -94,14 +94,15 @@ def assert_startswith(thing, prefix): assert False, '%r does not start with %r' % (thing, prefix) -def assert_in(x, thing): - if x not in thing: - assert False, '%r is not in %r' % (x, thing) - - -def assert_not_in(x, thing): - if x in thing: - assert False, '%r is in %r' % (x, thing) +try: + from nose.tools import assert_in, assert_not_in +except ImportError: + def assert_in(x, thing, msg=''): + if x not in thing: + assert False, msg or '%r is not in %r%r' % (x, thing) + def assert_not_in(x, thing, msg=''): + if x in thing: + assert False, msg or '%r is in %r%r' % (x, thing) def skip_if(condition, msg=None): diff --git a/tox.ini b/tox.ini index 2855db009..8fcb7b177 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=flake8,py26,py27,py33,py34,pypy,du12,du11,du10 +envlist=flake8,py26,py27,py33,py34,py35,pypy,du12,du11,du10 [testenv] deps= @@ -9,7 +9,7 @@ deps= setenv = SPHINX_TEST_TEMPDIR = {envdir}/testbuild commands= - {envpython} tests/run.py -m '^[tT]est' {posargs} + {envpython} tests/run.py -I py35 -m '^[tT]est' {posargs} sphinx-build -q -W -b html -d {envtmpdir}/doctrees doc {envtmpdir}/html [testenv:py26] @@ -49,3 +49,8 @@ deps= [testenv:flake8] deps=flake8 commands=flake8 + +[testenv:py35] +commands= + {envpython} tests/run.py -m '^[tT]est' {posargs} + sphinx-build -q -W -b html -d {envtmpdir}/doctrees doc {envtmpdir}/html diff --git a/utils/check_sources.py b/utils/check_sources.py index 91bab1f20..16bc918cb 100755 --- a/utils/check_sources.py +++ b/utils/check_sources.py @@ -7,7 +7,7 @@ Make sure each Python file has a correct file header including copyright and license information. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function @@ -81,7 +81,7 @@ def check_syntax(fn, lines): @checker('.py') def check_style(fn, lines): for lno, line in enumerate(lines): - if len(line) > 95: + if len(line.rstrip('\n')) > 95: yield lno+1, "line too long" if line.strip().startswith('#'): continue