diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6f4fe672f..c8f8969a1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,7 +7,7 @@ Subject: - Critical or severe bugs: X.Y.Z - Others: X.Y - For more details, see https://www.sphinx-doc.org/en/master/devguide.html#branch-model + For more details, see https://www.sphinx-doc.org/en/master/internals/release-process.html#branch-model --> ### Feature or Bugfix diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..680a0e3b5 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,8 @@ +version: 2 +python: + version: 3 + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/CHANGES b/CHANGES index efd2c7f38..ebdd331ed 100644 --- a/CHANGES +++ b/CHANGES @@ -1,9 +1,193 @@ -Release 3.4.0 (in development) +Release 3.5.0 (in development) ============================== Dependencies ------------ +* LaTeX: ``multicol`` (it is anyhow a required part of the official latex2e + base distribution) + +Incompatible changes +-------------------- + +* Update Underscore.js to 1.12.0 +* #6550: html: The config variable ``html_add_permalinks`` is replaced by + :confval:`html_permalinks` and :confval:`html_permalinks_icon` + +Deprecated +---------- + +* pending_xref node for viewcode extension +* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.broken`` +* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.good`` +* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.redirected`` +* ``sphinx.builders.linkcheck.node_line_or_0()`` +* ``sphinx.ext.autodoc.AttributeDocumenter.isinstanceattribute()`` +* ``sphinx.ext.autodoc.directive.DocumenterBridge.reporter`` +* ``sphinx.ext.autodoc.importer.get_module_members()`` +* ``sphinx.ext.autosummary.generate._simple_info()`` +* ``sphinx.ext.autosummary.generate._simple_warn()`` +* ``sphinx.writers.html.HTMLTranslator.permalink_text`` +* ``sphinx.writers.html5.HTML5Translator.permalink_text`` + +Features added +-------------- + +* #8022: autodoc: autodata and autoattribute directives does not show right-hand + value of the variable if docstring contains ``:meta hide-value:`` in + info-field-list +* #8514: autodoc: Default values of overloaded functions are taken from actual + implementation if they're ellipsis +* #8619: html: kbd role generates customizable HTML tags for compound keys +* #8634: html: Allow to change the order of JS/CSS via ``priority`` parameter + for :meth:`Sphinx.add_js_file()` and :meth:`Sphinx.add_css_file()` +* #6241: html: Allow to add JS/CSS files to the specific page when an extension + calls ``app.add_js_file()`` or ``app.add_css_file()`` on + :event:`html-page-context` event +* #6550: html: Allow to use HTML permalink texts via + :confval:`html_permalinks_icon` +* #1638: html: Add permalink icons to glossary terms +* #8649: imgconverter: Skip availability check if builder supports the image + type +* #8573: napoleon: Allow to change the style of custom sections using + :confval:`napoleon_custom_styles` +* #8004: napoleon: Type definitions in Google style docstrings are rendered as + references when :confval:`napoleon_preprocess_types` enabled +* #6241: mathjax: Include mathjax.js only on the document using equations +* #8651: std domain: cross-reference for a rubric having inline item is broken +* #7642: std domain: Optimize case-insensitive match of term +* #8681: viewcode: Support incremental build +* #8132: Add :confval:`project_copyright` as an alias of :confval:`copyright` +* #207: Now :confval:`highlight_language` supports multiple languages +* #2030: :rst:dir:`code-block` and :rst:dir:`literalinclude` supports automatic + dedent via no-argument ``:dedent:`` option +* C++, also hyperlink operator overloads in expressions and alias declarations. +* #8247: Allow production lists to refer to tokens from other production groups + +Bugs fixed +---------- + +* #8727: apidoc: namespace module file is not generated if no submodules there +* #741: autodoc: inherited-members doesn't work for instance attributes on super + class +* #8592: autodoc: ``:meta public:`` does not effect to variables +* #8594: autodoc: empty __all__ attribute is ignored +* #8315: autodoc: Failed to resolve struct.Struct type annotation +* #8652: autodoc: All variable comments in the module are ignored if the module + contains invalid type comments +* #8693: autodoc: Default values for overloaded functions are rendered as string +* #8134: autodoc: crashes when mocked decorator takes arguments +* #8306: autosummary: mocked modules are documented as empty page when using + :recursive: option +* #8232: graphviz: Image node is not rendered if graph file is in subdirectory +* #8618: html: kbd role produces incorrect HTML when compound-key separators (-, + + or ^) are used as keystrokes +* #8629: html: A type warning for html_use_opensearch is shown twice +* #8714: html: kbd role with "Caps Lock" rendered incorrectly +* #8123: html search: fix searching for terms containing + (Requires a custom + search language that does not split on +) +* #8665: html theme: Could not override globaltoc_maxdepth in theme.conf +* #8446: html: consecutive spaces are displayed as single space +* #8745: i18n: crashes with KeyError when translation message adds a new auto + footnote reference +* #4304: linkcheck: Fix race condition that could lead to checking the + availability of the same URL twice +* #7118: sphinx-quickstart: questionare got Mojibake if libreadline unavailable +* #8094: texinfo: image files on the different directory with document are not + copied +* #8720: viewcode: module pages are generated for epub on incremental build +* #8704: viewcode: anchors are generated in incremental build after singlehtml +* #8756: viewcode: highlighted code is generated even if not referenced +* #8671: :confval:`highlight_options` is not working +* #8341: C, fix intersphinx lookup types for names in declarations. +* C, C++: in general fix intersphinx and role lookup types. +* #8683: :confval:`html_last_updated_fmt` does not support UTC offset (%z) +* #8683: :confval:`html_last_updated_fmt` generates wrong time zone for %Z +* #1112: ``download`` role creates duplicated copies when relative path is + specified +* #7576: LaTeX with French babel and memoir crash: "Illegal parameter number + in definition of ``\FNH@prefntext``" +* #8072: LaTeX: Directive :rst:dir:`hlist` not implemented in LaTeX +* #8214: LaTeX: The :rst:role:`index` role and the glossary generate duplicate + entries in the LaTeX index (if both used for same term) +* #8735: LaTeX: wrong internal links in pdf to captioned code-blocks when + :confval:`numfig` is not True +* #8442: LaTeX: some indexed terms are ignored when using xelatex engine + (or pdflatex and :confval:`latex_use_xindy` set to True) with memoir class +* #8780: LaTeX: long words in narrow columns may not be hyphenated +* #8788: LaTeX: ``\titleformat`` last argument in sphinx.sty should be + bracketed, not braced (and is anyhow not needed) + +Testing +-------- + +Release 3.4.4 (in development) +============================== + +Dependencies +------------ + +Incompatible changes +-------------------- + +Deprecated +---------- + +Features added +-------------- + +Bugs fixed +---------- + +* #8655: autodoc: Failed to generate document if target module contains an + object that raises an exception on ``hasattr()`` +* C, ``expr`` role should start symbol lookup in the current scope. +* #8796: LaTeX: potentially critical low level TeX coding mistake has gone + unnoticed so far + +Testing +-------- + +Release 3.4.3 (released Jan 08, 2021) +===================================== + +Bugs fixed +---------- + +* #8655: autodoc: Failed to generate document if target module contains an + object that raises an exception on ``hasattr()`` + +Release 3.4.2 (released Jan 04, 2021) +===================================== + +Bugs fixed +---------- + +* #8164: autodoc: Classes that inherit mocked class are not documented +* #8602: autodoc: The ``autodoc-process-docstring`` event is emitted to the + non-datadescriptors unexpectedly +* #8616: autodoc: AttributeError is raised on non-class object is passed to + autoclass directive + +Release 3.4.1 (released Dec 25, 2020) +===================================== + +Bugs fixed +---------- + +* #8559: autodoc: AttributeError is raised when using forward-reference type + annotations +* #8568: autodoc: TypeError is raised on checking slots attribute +* #8567: autodoc: Instance attributes are incorrectly added to Parent class +* #8566: autodoc: The ``autodoc-process-docstring`` event is emitted to the + alias classes unexpectedly +* #8583: autodoc: Unnecessary object comparision via ``__eq__`` method +* #8565: linkcheck: Fix PriorityQueue crash when link tuples are not + comparable + +Release 3.4.0 (released Dec 20, 2020) +===================================== + Incompatible changes -------------------- @@ -14,8 +198,18 @@ Deprecated ---------- * The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()`` +* The ``no_docstring`` argument of + ``sphinx.ext.autodoc.Documenter.add_content()`` +* ``sphinx.ext.autodoc.Documenter.get_object_members()`` * ``sphinx.ext.autodoc.DataDeclarationDocumenter`` +* ``sphinx.ext.autodoc.GenericAliasDocumenter`` +* ``sphinx.ext.autodoc.InstanceAttributeDocumenter`` +* ``sphinx.ext.autodoc.SlotsAttributeDocumenter`` +* ``sphinx.ext.autodoc.TypeVarDocumenter`` +* ``sphinx.ext.autodoc.importer._getannotations()`` +* ``sphinx.ext.autodoc.importer._getmro()`` * ``sphinx.pycode.ModuleAnalyzer.parse()`` +* ``sphinx.util.osutil.movefile()`` * ``sphinx.util.requests.is_ssl_error()`` Features added @@ -32,9 +226,14 @@ Features added * #8209: autodoc: Add ``:no-value:`` option to :rst:dir:`autoattribute` and :rst:dir:`autodata` directive to suppress the default value of the variable * #8460: autodoc: Support custom types defined by typing.NewType +* #8285: napoleon: Add :confval:`napoleon_attr_annotations` to merge type hints + on source code automatically if any type is specified in docstring +* #8236: napoleon: Support numpydoc's "Receives" section * #6914: Add a new event :event:`warn-missing-reference` to custom warning messages when failed to resolve a cross-reference * #6914: Emit a detailed warning when failed to resolve a ``:ref:`` reference +* #6629: linkcheck: The builder now handles rate limits. See + :confval:`linkcheck_retry_on_rate_limit` for details. Bugs fixed ---------- @@ -49,36 +248,40 @@ Bugs fixed type annotated variables * #8443: autodoc: autoattribute directive can't create document for PEP-526 based uninitalized variables +* #8480: autodoc: autoattribute could not create document for __slots__ + attributes +* #8503: autodoc: autoattribute could not create document for a GenericAlias as + class attributes correctly +* #8534: autodoc: autoattribute could not create document for a commented + attribute in alias class * #8452: autodoc: autodoc_type_aliases doesn't work when autodoc_typehints is set to "description" -* #8446: html: consecutive spaces are displayed as single space +* #8541: autodoc: autodoc_type_aliases doesn't work for the type annotation to + instance attributes +* #8460: autodoc: autodata and autoattribute directives do not display type + information of TypeVars +* #8493: autodoc: references to builtins not working in class aliases +* #8522: autodoc: ``__bool__`` method could be called +* #8067: autodoc: A typehint for the instance variable having type_comment on + super class is not displayed +* #8545: autodoc: a __slots__ attribute is not documented even having docstring +* #741: autodoc: inherited-members doesn't work for instance attributes on super + class +* #8477: autosummary: non utf-8 reST files are generated when template contains + multibyte characters +* #8501: autosummary: summary extraction splits text after "el at." unexpectedly +* #8524: html: Wrong url_root has been generated on a document named "index" * #8419: html search: Do not load ``language_data.js`` in non-search pages +* #8549: i18n: ``-D gettext_compact=0`` is no longer working * #8454: graphviz: The layout option for graph and digraph directives don't work +* #8131: linkcheck: Use GET when HEAD requests cause Too Many Redirects, to + accommodate infinite redirect loops on HEAD * #8437: Makefile: ``make clean`` with empty BUILDDIR is dangerous - -Testing --------- - -Release 3.3.2 (in development) -============================== - -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- - -Features added --------------- - -Bugs fixed ----------- - -Testing --------- +* #8365: py domain: ``:type:`` and ``:rtype:`` gives false ambiguous class + lookup warnings +* #8352: std domain: Failed to parse an option that starts with bracket +* #8519: LaTeX: Prevent page brake in the middle of a seealso +* #8520: C, fix copying of AliasNode. Release 3.3.1 (released Nov 12, 2020) ===================================== diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a20192679..bd164694d 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -14,4 +14,5 @@ You can also browse it from this repository from ``doc/internals/contributing.rst`` Sphinx uses GitHub to host source code, track patches and bugs, and more. -Please make an effort to provide as much possible when filing bugs. +Please make an effort to provide as much detail as possible when filing +bugs. diff --git a/EXAMPLES b/EXAMPLES index 74fa36510..348e76c8c 100644 --- a/EXAMPLES +++ b/EXAMPLES @@ -318,6 +318,7 @@ Documentation using a custom theme or integrated in a website * `Django `__ * `Doctrine `__ * `Enterprise Toolkit for Acrobat products `__ +* `FreeFEM `__ * `Gameduino `__ * `gensim `__ * `GeoServer `__ diff --git a/LICENSE b/LICENSE index f709c9ad7..2249594da 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ License for Sphinx ================== -Copyright (c) 2007-2020 by the Sphinx team (see AUTHORS file). +Copyright (c) 2007-2021 by the Sphinx team (see AUTHORS file). All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/doc/_templates/index.html b/doc/_templates/index.html index 5e588acc4..9e9f7af56 100644 --- a/doc/_templates/index.html +++ b/doc/_templates/index.html @@ -33,9 +33,9 @@
  • {%trans path=pathto('ext/builtins')%}Extensions: automatic testing of code snippets, inclusion of docstrings from Python modules (API docs), and more{%endtrans%}
  • -
  • {%trans path=pathto('develop')%}Contributed extensions: more than - 50 extensions contributed by users - in a second repository; most of them installable from PyPI{%endtrans%}
  • +
  • {%trans path=pathto("usage/extensions")%}Contributed extensions: dozens of + extensions contributed by users; + most of them installable from PyPI{%endtrans%}
  • {%trans%} Sphinx uses reStructuredText diff --git a/doc/_themes/sphinx13/layout.html b/doc/_themes/sphinx13/layout.html index e3bb37dce..238fb52b7 100644 --- a/doc/_themes/sphinx13/layout.html +++ b/doc/_themes/sphinx13/layout.html @@ -67,7 +67,7 @@

  • Home
  • Get it
  • Docs
  • -
  • Extend/Develop
  • +
  • Extend
  • diff --git a/doc/changes.rst b/doc/changes.rst index b4872784a..829c7f7ed 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -8,4 +8,9 @@ Changelog ========= +.. raw:: latex + + \hypersetup{bookmarksdepth=1}% pdf bookmarks + \addtocontents{toc}{\protect\setcounter{tocdepth}{1}}% + .. include:: ../CHANGES diff --git a/doc/conf.py b/doc/conf.py index 80ee2b01c..53f036d3e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -14,7 +14,7 @@ templates_path = ['_templates'] exclude_patterns = ['_build'] project = 'Sphinx' -copyright = '2007-2020, Georg Brandl and the Sphinx team' +copyright = '2007-2021, Georg Brandl and the Sphinx team' version = sphinx.__display_version__ release = version show_authors = True @@ -69,8 +69,15 @@ latex_elements = { \substitutefont{X2}{\sfdefault}{cmss} \substitutefont{X2}{\ttdefault}{cmtt} ''', - 'passoptionstopackages': '\\PassOptionsToPackage{svgnames}{xcolor}', - 'preamble': '\\DeclareUnicodeCharacter{229E}{\\ensuremath{\\boxplus}}', + 'passoptionstopackages': r''' +\PassOptionsToPackage{svgnames}{xcolor} +\PassOptionsToPackage{bookmarksdepth=3}{hyperref}% depth of pdf bookmarks +''', + 'preamble': r''' +\DeclareUnicodeCharacter{229E}{\ensuremath{\boxplus}} +\setcounter{tocdepth}{3}% depth of what is kept from toc file +\setcounter{secnumdepth}{1}% depth of section numbering +''', 'fvset': '\\fvset{fontsize=auto}', # fix missing index entry due to RTD doing only once pdflatex after makeindex 'printindex': r''' @@ -110,7 +117,10 @@ texinfo_documents = [ 1), ] -intersphinx_mapping = {'python': ('https://docs.python.org/3/', None)} +intersphinx_mapping = { + 'python': ('https://docs.python.org/3/', None), + 'requests': ('https://requests.readthedocs.io/en/master', None), +} # Sphinx document translation with sphinx gettext feature uses these settings: locale_dirs = ['locale/'] diff --git a/doc/develop.rst b/doc/develop.rst deleted file mode 100644 index 3bbc220b8..000000000 --- a/doc/develop.rst +++ /dev/null @@ -1,153 +0,0 @@ -:orphan: - -Sphinx development -================== - -Sphinx is a maintained by a group of volunteers. We value every contribution! - -* The code can be found in a Git repository, at - https://github.com/sphinx-doc/sphinx/. -* Issues and feature requests should be raised in the `tracker - `_. -* The mailing list for development is at `Google Groups - `_. -* There is also the #sphinx-doc IRC channel on `freenode - `_. - -For more about our development process and methods, refer to -:doc:`/internals/index`. - -Extensions -========== - -To learn how to write your own extension, see :ref:`dev-extensions`. - -The `sphinx-contrib `_ repository contains many -contributed extensions. Some of them have their own releases on PyPI, others you -can install from a checkout. - -This is the current list of contributed extensions in that repository: - -- aafig: render embedded ASCII art as nice images using aafigure_ -- actdiag: embed activity diagrams by using actdiag_ -- adadomain: an extension for Ada support (Sphinx 1.0 needed) -- ansi: parse ANSI color sequences inside documents -- argdoc: automatically generate documentation for command-line arguments, - descriptions and help text -- astah: embed diagram by using astah -- autoanysrc: Gather reST documentation from any source files -- autorun: Execute code in a ``runblock`` directive -- beamer_: A builder for Beamer (LaTeX) output. -- blockdiag: embed block diagrams by using blockdiag_ -- cacoo: embed diagram from Cacoo -- cf3domain: a domain for CFEngine 3 policies -- cheader: The missing c:header directive for Sphinx's built-in C domain -- cheeseshop: easily link to PyPI packages -- clearquest: create tables from ClearQuest_ queries -- cmakedomain_: a domain for CMake_ -- coffeedomain: a domain for (auto)documenting CoffeeScript source code -- context: a builder for ConTeXt -- disqus: embed Disqus comments in documents -- documentedlist: converts a Python list to a table in the generated - documentation -- doxylink: Link to external Doxygen-generated HTML documentation -- domaintools_: A tool for easy domain creation -- email: obfuscate email addresses -- erlangdomain: an extension for Erlang support (Sphinx 1.0 needed) -- exceltable: embed Excel spreadsheets into documents using exceltable_ -- feed: an extension for creating syndication feeds and time-based overviews - from your site content -- findanything_: an extension to add Sublime Text 2-like findanything panels - to your documentation to find pages, sections and index entries while typing -- gnuplot: produces images using gnuplot_ language -- googleanalytics: track web visitor statistics by using `Google Analytics`_ -- googlechart: embed charts by using `Google Chart`_ -- googlemaps: embed maps by using `Google Maps`_ -- httpdomain: a domain for documenting RESTful HTTP APIs -- hyphenator: client-side hyphenation of HTML using hyphenator_ -- imgur: embed Imgur images, albums, and metadata in documents -- inlinesyntaxhighlight_: inline syntax highlighting -- lassodomain: a domain for documenting Lasso_ source code -- libreoffice: an extension to include any drawing supported by LibreOffice - (e.g. odg, vsd, ...) -- lilypond: an extension inserting music scripts from Lilypond_ in PNG format -- makedomain_: a domain for `GNU Make`_ -- matlabdomain: document MATLAB_ code -- mockautodoc: mock imports -- mscgen: embed mscgen-formatted MSC (Message Sequence Chart)s -- napoleon: supports `Google style`_ and `NumPy style`_ docstrings -- nicovideo: embed videos from nicovideo -- nwdiag: embed network diagrams by using nwdiag_ -- omegat: support tools to collaborate with OmegaT_ (Sphinx 1.1 needed) -- osaka: convert standard Japanese doc to Osaka dialect (this is a joke - extension) -- paverutils: an alternate integration of Sphinx with Paver_ -- phpdomain: an extension for PHP support -- plantuml: embed UML diagram by using PlantUML_ -- py_directive: Execute python code in a ``py`` directive and return a math - node -- rawfiles: copy raw files, like a CNAME -- requirements: declare requirements wherever you need (e.g. in test - docstrings), mark statuses and collect them in a single list -- restbuilder: a builder for reST (reStructuredText) files -- rubydomain: an extension for Ruby support (Sphinx 1.0 needed) -- sadisplay: display SqlAlchemy model sadisplay_ -- sdedit: an extension inserting sequence diagram by using Quick Sequence - Diagram Editor (sdedit_) -- seqdiag: embed sequence diagrams by using seqdiag_ -- slide: embed presentation slides on slideshare_ and other sites -- swf_: embed flash files -- sword: an extension inserting Bible verses from Sword_ -- tikz: draw pictures with the `TikZ/PGF LaTeX package`_ -- traclinks: create TracLinks_ to a Trac_ instance from within Sphinx -- versioning: Sphinx extension that allows building versioned docs for - self-hosting -- whooshindex: whoosh indexer extension -- youtube: embed videos from YouTube_ -- zopeext: provide an ``autointerface`` directive for using `Zope interfaces`_ - - -See the :doc:`extension tutorials <../development/tutorials/index>` on getting -started with writing your own extensions. - - -.. _aafigure: https://launchpad.net/aafigure -.. _gnuplot: http://www.gnuplot.info/ -.. _paver: https://paver.readthedocs.io/en/latest/ -.. _Sword: https://www.crosswire.org/sword/ -.. _Lilypond: http://lilypond.org/ -.. _sdedit: http://sdedit.sourceforge.net/ -.. _Trac: https://trac.edgewall.org/ -.. _TracLinks: https://trac.edgewall.org/wiki/TracLinks -.. _OmegaT: https://omegat.org/ -.. _PlantUML: http://plantuml.com/ -.. _PyEnchant: https://pythonhosted.org/pyenchant/ -.. _sadisplay: https://bitbucket.org/estin/sadisplay/wiki/Home -.. _blockdiag: http://blockdiag.com/en/ -.. _seqdiag: http://blockdiag.com/en/ -.. _actdiag: http://blockdiag.com/en/ -.. _nwdiag: http://blockdiag.com/en/ -.. _Google Analytics: https://www.google.com/analytics/ -.. _Google Chart: https://developers.google.com/chart/ -.. _Google Maps: https://www.google.com/maps -.. _Google style: https://google.github.io/styleguide/pyguide.html -.. _NumPy style: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt -.. _hyphenator: https://github.com/mnater/hyphenator -.. _exceltable: https://pythonhosted.org/sphinxcontrib-exceltable/ -.. _YouTube: https://www.youtube.com/ -.. _ClearQuest: https://www.ibm.com/us-en/marketplace/rational-clearquest -.. _Zope interfaces: https://zopeinterface.readthedocs.io/en/latest/README.html -.. _slideshare: https://www.slideshare.net/ -.. _TikZ/PGF LaTeX package: https://sourceforge.net/projects/pgf/ -.. _MATLAB: https://www.mathworks.com/products/matlab.html -.. _swf: https://github.com/sphinx-contrib/swf -.. _findanything: https://github.com/sphinx-contrib/findanything -.. _cmakedomain: https://github.com/sphinx-contrib/cmakedomain -.. _GNU Make: https://www.gnu.org/software/make/ -.. _makedomain: https://github.com/sphinx-contrib/makedomain -.. _inlinesyntaxhighlight: https://sphinxcontrib-inlinesyntaxhighlight.readthedocs.io/ -.. _CMake: https://cmake.org -.. _domaintools: https://github.com/sphinx-contrib/domaintools -.. _restbuilder: https://pypi.org/project/sphinxcontrib-restbuilder/ -.. _Lasso: http://www.lassosoft.com/ -.. _beamer: https://pypi.org/project/sphinxcontrib-beamer/ diff --git a/doc/development/index.rst b/doc/development/index.rst index 04918acd6..b4a7920ba 100644 --- a/doc/development/index.rst +++ b/doc/development/index.rst @@ -2,10 +2,12 @@ Extending Sphinx ================ -This guide is aimed at those wishing to develop their own extensions for -Sphinx. Sphinx possesses significant extensibility capabilities including the -ability to hook into almost every point of the build process. If you simply -wish to use Sphinx with existing extensions, refer to :doc:`/usage/index`. +This guide is aimed at giving a quick introduction for those wishing to +develop their own extensions for Sphinx. Sphinx possesses significant +extensibility capabilities including the ability to hook into almost every +point of the build process. If you simply wish to use Sphinx with existing +extensions, refer to :doc:`/usage/index`. For a more detailed discussion of +the extension interface see :doc:`/extdev/index`. .. toctree:: :maxdepth: 2 diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index 9f2c10676..4585df949 100644 --- a/doc/extdev/appapi.rst +++ b/doc/extdev/appapi.rst @@ -25,75 +25,75 @@ package. .. currentmodule:: sphinx.application -.. automethod:: Sphinx.setup_extension(name) +.. automethod:: Sphinx.setup_extension -.. automethod:: Sphinx.require_sphinx(version) +.. automethod:: Sphinx.require_sphinx -.. automethod:: Sphinx.connect(event, callback) +.. automethod:: Sphinx.connect -.. automethod:: Sphinx.disconnect(listener_id) +.. automethod:: Sphinx.disconnect -.. automethod:: Sphinx.add_builder(builder) +.. automethod:: Sphinx.add_builder -.. automethod:: Sphinx.add_config_value(name, default, rebuild) +.. automethod:: Sphinx.add_config_value -.. automethod:: Sphinx.add_event(name) +.. automethod:: Sphinx.add_event -.. automethod:: Sphinx.set_translator(name, translator_class) +.. automethod:: Sphinx.set_translator -.. automethod:: Sphinx.add_node(node, \*\*kwds) +.. automethod:: Sphinx.add_node -.. automethod:: Sphinx.add_enumerable_node(node, figtype, title_getter=None, \*\*kwds) +.. automethod:: Sphinx.add_enumerable_node -.. automethod:: Sphinx.add_directive(name, directiveclass) +.. automethod:: Sphinx.add_directive -.. automethod:: Sphinx.add_role(name, role) +.. automethod:: Sphinx.add_role -.. automethod:: Sphinx.add_generic_role(name, nodeclass) +.. automethod:: Sphinx.add_generic_role -.. automethod:: Sphinx.add_domain(domain) +.. automethod:: Sphinx.add_domain -.. automethod:: Sphinx.add_directive_to_domain(domain, name, directiveclass) +.. automethod:: Sphinx.add_directive_to_domain -.. automethod:: Sphinx.add_role_to_domain(domain, name, role) +.. automethod:: Sphinx.add_role_to_domain -.. automethod:: Sphinx.add_index_to_domain(domain, index) +.. automethod:: Sphinx.add_index_to_domain -.. automethod:: Sphinx.add_object_type(directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None, objname='', doc_field_types=[]) +.. automethod:: Sphinx.add_object_type -.. automethod:: Sphinx.add_crossref_type(directivename, rolename, indextemplate='', ref_nodeclass=None, objname='') +.. automethod:: Sphinx.add_crossref_type -.. automethod:: Sphinx.add_transform(transform) +.. automethod:: Sphinx.add_transform -.. automethod:: Sphinx.add_post_transform(transform) +.. automethod:: Sphinx.add_post_transform -.. automethod:: Sphinx.add_js_file(filename, **kwargs) +.. automethod:: Sphinx.add_js_file -.. automethod:: Sphinx.add_css_file(filename, **kwargs) +.. automethod:: Sphinx.add_css_file -.. automethod:: Sphinx.add_latex_package(packagename, options=None) +.. automethod:: Sphinx.add_latex_package -.. automethod:: Sphinx.add_lexer(alias, lexer) +.. automethod:: Sphinx.add_lexer -.. automethod:: Sphinx.add_autodocumenter(cls) +.. automethod:: Sphinx.add_autodocumenter -.. automethod:: Sphinx.add_autodoc_attrgetter(type, getter) +.. automethod:: Sphinx.add_autodoc_attrgetter -.. automethod:: Sphinx.add_search_language(cls) +.. automethod:: Sphinx.add_search_language -.. automethod:: Sphinx.add_source_suffix(suffix, filetype) +.. automethod:: Sphinx.add_source_suffix -.. automethod:: Sphinx.add_source_parser(parser) +.. automethod:: Sphinx.add_source_parser -.. automethod:: Sphinx.add_env_collector(collector) +.. automethod:: Sphinx.add_env_collector -.. automethod:: Sphinx.add_html_theme(name, theme_path) +.. automethod:: Sphinx.add_html_theme -.. automethod:: Sphinx.add_html_math_renderer(name, inline_renderers, block_renderers) +.. automethod:: Sphinx.add_html_math_renderer -.. automethod:: Sphinx.add_message_catalog(catalog, locale_dir) +.. automethod:: Sphinx.add_message_catalog -.. automethod:: Sphinx.is_parallel_allowed(typ) +.. automethod:: Sphinx.is_parallel_allowed .. exception:: ExtensionError @@ -107,9 +107,9 @@ Emitting events .. class:: Sphinx :noindex: - .. automethod:: emit(event, \*arguments) + .. automethod:: emit - .. automethod:: emit_firstresult(event, \*arguments) + .. automethod:: emit_firstresult Sphinx runtime information @@ -369,6 +369,9 @@ Here is a more detailed list of these events. You can return a string from the handler, it will then replace ``'page.html'`` as the HTML template for this page. + .. note:: You can install JS/CSS files for the specific page via + :meth:`Sphinx.add_js_file` and :meth:`Sphinx.add_css_file` since v3.5.0. + .. versionadded:: 0.4 .. versionchanged:: 1.3 diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index b55a67c32..1350085ef 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -12,35 +12,137 @@ The following is a list of deprecated interfaces. .. tabularcolumns:: |>{\raggedright}\Y{.4}|>{\centering}\Y{.1}|>{\centering}\Y{.12}|>{\raggedright\arraybackslash}\Y{.38}| -.. |LaTeXHyphenate| raw:: latex - - \hspace{0pt} - .. list-table:: deprecated APIs :header-rows: 1 :class: deprecated :widths: 40, 10, 10, 40 * - Target - - |LaTeXHyphenate|\ Deprecated + - Deprecated - (will be) Removed - Alternatives + * - pending_xref node for viewcode extension + - 3.5 + - 5.0 + - ``sphinx.ext.viewcode.viewcode_anchor`` + + * - ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.broken`` + - 3.5 + - 5.0 + - N/A + + * - ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.good`` + - 3.5 + - 5.0 + - N/A + + * - ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.redirected`` + - 3.5 + - 5.0 + - N/A + + * - ``sphinx.builders.linkcheck.node_line_or_0()`` + - 3.5 + - 5.0 + - ``sphinx.util.nodes.get_node_line()`` + + * - ``sphinx.ext.autodoc.AttributeDocumenter.isinstanceattribute()`` + - 3.5 + - 5.0 + - N/A + + * - ``sphinx.ext.autodoc.importer.get_module_members()`` + - 3.5 + - 5.0 + - ``sphinx.ext.autodoc.ModuleDocumenter.get_module_members()`` + + * - ``sphinx.ext.autosummary.generate._simple_info()`` + - 3.5 + - 5.0 + - :ref:`logging-api` + + * - ``sphinx.ext.autosummary.generate._simple_warn()`` + - 3.5 + - 5.0 + - :ref:`logging-api` + + * - ``sphinx.writers.html.HTMLTranslator.permalink_text`` + - 3.5 + - 5.0 + - :confval:`html_permalinks_icon` + + * - ``sphinx.writers.html5.HTML5Translator.permalink_text`` + - 3.5 + - 5.0 + - :confval:`html_permalinks_icon` + * - The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()`` - 3.4 - 5.0 - N/A + * - The ``no_docstring`` argument of + ``sphinx.ext.autodoc.Documenter.add_content()`` + - 3.4 + - 5.0 + - ``sphinx.ext.autodoc.Documenter.get_doc()`` + + * - ``sphinx.ext.autodoc.Documenter.get_object_members()`` + - 3.4 + - 6.0 + - ``sphinx.ext.autodoc.ClassDocumenter.get_object_members()`` + * - ``sphinx.ext.autodoc.DataDeclarationDocumenter`` - 3.4 - 5.0 - ``sphinx.ext.autodoc.DataDocumenter`` + * - ``sphinx.ext.autodoc.GenericAliasDocumenter`` + - 3.4 + - 5.0 + - ``sphinx.ext.autodoc.DataDocumenter`` + + * - ``sphinx.ext.autodoc.InstanceAttributeDocumenter`` + - 3.4 + - 5.0 + - ``sphinx.ext.autodoc.AttributeDocumenter`` + + * - ``sphinx.ext.autodoc.SlotsAttributeDocumenter`` + - 3.4 + - 5.0 + - ``sphinx.ext.autodoc.AttributeDocumenter`` + + * - ``sphinx.ext.autodoc.TypeVarDocumenter`` + - 3.4 + - 5.0 + - ``sphinx.ext.autodoc.DataDocumenter`` + + * - ``sphinx.ext.autodoc.directive.DocumenterBridge.reporter`` + - 3.5 + - 5.0 + - ``sphinx.util.logging`` + + * - ``sphinx.ext.autodoc.importer._getannotations()`` + - 3.4 + - 4.0 + - ``sphinx.util.inspect.getannotations()`` + + * - ``sphinx.ext.autodoc.importer._getmro()`` + - 3.4 + - 4.0 + - ``sphinx.util.inspect.getmro()`` + * - ``sphinx.pycode.ModuleAnalyzer.parse()`` - 3.4 - 5.0 - ``sphinx.pycode.ModuleAnalyzer.analyze()`` + * - ``sphinx.util.osutil.movefile()`` + - 3.4 + - 5.0 + - ``os.replace()`` + * - ``sphinx.util.requests.is_ssl_error()`` - 3.4 - 5.0 diff --git a/doc/extdev/domainapi.rst b/doc/extdev/domainapi.rst index d6ecf0633..674a3aa9a 100644 --- a/doc/extdev/domainapi.rst +++ b/doc/extdev/domainapi.rst @@ -1,7 +1,7 @@ .. _domain-api: Domain API ----------- +========== .. module:: sphinx.domains @@ -12,3 +12,16 @@ Domain API .. autoclass:: Index :members: + + +Python Domain +------------- + +.. module:: sphinx.domains.python + +.. autoclass:: PythonDomain + + .. autoattribute:: objects + .. autoattribute:: modules + .. automethod:: note_object + .. automethod:: note_module diff --git a/doc/internals/contributing.rst b/doc/internals/contributing.rst index a52e6e72d..798736b03 100644 --- a/doc/internals/contributing.rst +++ b/doc/internals/contributing.rst @@ -138,10 +138,7 @@ Coding style Please follow these guidelines when writing code for Sphinx: -* Try to use the same code style as used in the rest of the project. See the - `Pocoo Styleguide`__ for more information. - - __ http://flask.pocoo.org/docs/styleguide/ +* Try to use the same code style as used in the rest of the project. * For non-trivial changes, please update the :file:`CHANGES` file. If your changes alter existing behavior, please document this. diff --git a/doc/latex.rst b/doc/latex.rst index 53fe9301a..39d4b8147 100644 --- a/doc/latex.rst +++ b/doc/latex.rst @@ -95,6 +95,12 @@ Keys that you may want to override include: A string which will be positioned early in the preamble, designed to contain ``\\PassOptionsToPackage{options}{foo}`` commands. + .. hint:: + + It may be also used for loading LaTeX packages very early in the + preamble. For example package ``fancybox`` is incompatible with + being loaded via the ``'preamble'`` key, it must be loaded earlier. + Default: ``''`` .. versionadded:: 1.4 @@ -195,8 +201,8 @@ Keys that you may want to override include: "Bjornstrup". You can also set this to ``''`` to disable fncychap. Default: ``'\\usepackage[Bjarne]{fncychap}'`` for English documents, - ``'\\usepackage[Sonny]{fncychap}'`` for internationalized documents, and - ``''`` for Japanese documents. + ``'\\usepackage[Sonny]{fncychap}'`` for internationalized documents, and + ``''`` for Japanese documents. ``'preamble'`` Additional preamble content. One may move all needed macros into some file @@ -300,7 +306,7 @@ Keys that don't need to be overridden unless in special cases are: "inputenc" package inclusion. Default: ``'\\usepackage[utf8]{inputenc}'`` when using pdflatex, else - ``''`` + ``''`` .. versionchanged:: 1.4.3 Previously ``'\\usepackage[utf8]{inputenc}'`` was used for all @@ -389,7 +395,7 @@ Keys that don't need to be overridden unless in special cases are: key is ignored. Default: ``'\\usepackage{textalpha}'`` or ``''`` if ``fontenc`` does not - include the ``LGR`` option. + include the ``LGR`` option. .. versionadded:: 2.0 @@ -407,7 +413,7 @@ Keys that don't need to be overridden unless in special cases are: `. Default: ``'\\usepackage{geometry}'`` (or - ``'\\usepackage[dvipdfm]{geometry}'`` for Japanese documents) + ``'\\usepackage[dvipdfm]{geometry}'`` for Japanese documents) .. versionadded:: 1.5 @@ -784,14 +790,14 @@ macros may be significant. |warningbdcolors| The colour for the admonition frame. - Default: ``{rgb}{0,0,0}`` (black) + Default: ``{rgb}{0,0,0}`` (black) .. only:: latex |wgbdcolorslatex| The colour for the admonition frame. - Default: ``{rgb}{0,0,0}`` (black) + Default: ``{rgb}{0,0,0}`` (black) |warningbgcolors| The background colours for the respective admonitions. @@ -1018,6 +1024,14 @@ Environments Miscellany ~~~~~~~~~~ +- Every text paragraph in document body starts with ``\sphinxAtStartPar``. + Currently, this is used to insert a zero width horizontal skip which + is a trick to allow TeX hyphenation of the first word of a paragraph + in a narrow context (like a table cell). For ``'lualatex'`` which + does not need the trick, the ``\sphinxAtStartPar`` does nothing. + + .. versionadded:: 3.5.0 + - The section, subsection, ... headings are set using *titlesec*'s ``\titleformat`` command. diff --git a/doc/usage/builders/index.rst b/doc/usage/builders/index.rst index db6706944..c45a8062f 100644 --- a/doc/usage/builders/index.rst +++ b/doc/usage/builders/index.rst @@ -442,6 +442,10 @@ name is ``rinoh``. Refer to the `rinohtype manual`_ for details. Since Sphinx-1.5, the linkcheck builder comes to use requests module. + .. versionchanged:: 3.4 + + The linkcheck builder retries links when servers apply rate limits. + .. module:: sphinx.builders.xml .. class:: XMLBuilder diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 4881b8629..c2250eb7d 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -73,6 +73,12 @@ Project information A copyright statement in the style ``'2008, Author Name'``. +.. confval:: project_copyright + + An alias of :confval:`copyright`. + + .. versionadded:: 3.5 + .. confval:: version The major project version, used as the replacement for ``|version|``. For @@ -556,7 +562,7 @@ General configuration * Otherwise, the current time is formatted using :func:`time.strftime` and the format given in :confval:`today_fmt`. - The default is now :confval:`today` and a :confval:`today_fmt` of ``'%B %d, + The default is now :confval:`today` and a :confval:`today_fmt` of ``'%b %d, %Y'`` (or, if translation is enabled with :confval:`language`, an equivalent format for the selected locale). @@ -577,12 +583,27 @@ General configuration .. confval:: highlight_options - A dictionary of options that modify how the lexer specified by - :confval:`highlight_language` generates highlighted source code. These are - lexer-specific; for the options understood by each, see the - `Pygments documentation `_. + A dictionary that maps language names to options for the lexer modules of + Pygments. These are lexer-specific; for the options understood by each, + see the `Pygments documentation `_. + + Example:: + + highlight_options = { + 'default': {'stripall': True}, + 'php': {'startinline': True}, + } + + A single dictionary of options are also allowed. Then it is recognized + as options to the lexer specified by :confval:`highlight_language`:: + + # configuration for the ``highlight_language`` + highlight_options = {'stripall': True} .. versionadded:: 1.3 + .. versionchanged:: 3.5 + + Allow to configure highlight options for multiple languages .. confval:: pygments_style @@ -937,8 +958,11 @@ that use Sphinx's HTMLWriter class. .. confval:: html_baseurl - The URL which points to the root of the HTML documentation. It is used to - indicate the location of document like ``canonical_url``. + The base URL which points to the root of the HTML documentation. It is used + to indicate the location of document using `The Canonical Link Relation`_. + Default: ``''``. + + .. _The Canonical Link Relation: https://tools.ietf.org/html/rfc6596 .. versionadded:: 1.8 @@ -996,7 +1020,14 @@ that use Sphinx's HTMLWriter class. 'https://example.com/css/custom.css', ('print.css', {'media': 'print'})] + As a special attribute, *priority* can be set as an integer to load the CSS + file earlier or lazier step. For more information, refer + :meth:`Sphinx.add_css_files()`. + .. versionadded:: 1.8 + .. versionchanged:: 3.5 + + Support priority attribute .. confval:: html_js_files @@ -1012,7 +1043,14 @@ that use Sphinx's HTMLWriter class. 'https://example.com/scripts/custom.js', ('custom.js', {'async': 'async'})] + As a special attribute, *priority* can be set as an integer to load the CSS + file earlier or lazier step. For more information, refer + :meth:`Sphinx.add_css_files()`. + .. versionadded:: 1.8 + .. versionchanged:: 3.5 + + Support priority attribute .. confval:: html_static_path @@ -1096,6 +1134,23 @@ that use Sphinx's HTMLWriter class. This can now be a string to select the actual text of the link. Previously, only boolean values were accepted. + .. deprecated:: 3.5 + This has been replaced by :confval:`html_permalinks` + +.. confval:: html_permalinks + + If true, Sphinx will add "permalinks" for each heading and description + environment. Default: ``True``. + + .. versionadded:: 3.5 + +.. confval:: html_permalinks_icon + + A text for permalinks for each heading and description environment. HTML + tags are allowed. Default: a paragraph sign; ``¶`` + + .. versionadded:: 3.5 + .. confval:: html_sidebars Custom sidebar templates, must be a dictionary that maps document names to @@ -2525,6 +2580,23 @@ Options for the linkcheck builder .. versionadded:: 2.3 +.. confval:: linkcheck_rate_limit_timeout + + The ``linkcheck`` builder may issue a large number of requests to the same + site over a short period of time. This setting controls the builder behavior + when servers indicate that requests are rate-limited. + + If a server indicates when to retry (using the `Retry-After`_ header), + ``linkcheck`` always follows the server indication. + + Otherwise, ``linkcheck`` waits for a minute before to retry and keeps + doubling the wait time between attempts until it succeeds or exceeds the + ``linkcheck_rate_limit_timeout``. By default, the timeout is 5 minutes. + + .. _Retry-After: https://tools.ietf.org/html/rfc7231#section-7.1.3 + + .. versionadded:: 3.4 + Options for the XML builder --------------------------- diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index 382d3160c..5222592ab 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -157,7 +157,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, ``:meta private:`` in its :ref:`info-field-lists`. For example: - .. code-block:: rst + .. code-block:: python def my_function(my_arg, my_other_arg): """blah blah blah @@ -172,7 +172,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, an underscore. For example: - .. code-block:: rst + .. code-block:: python def _my_function(my_arg, my_other_arg): """blah blah blah @@ -182,6 +182,16 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. versionadded:: 3.1 + * autodoc considers a variable member does not have any default value if its + docstring contains ``:meta hide-value:`` in its :ref:`info-field-lists`. + Example: + + .. code-block:: python + + var1 = None #: :meta hide-value: + + .. versionadded:: 3.5 + * Python "special" members (that is, those named like ``__special__``) will be included if the ``special-members`` flag option is given:: @@ -554,7 +564,7 @@ There are also config values that you can set: ... If you set ``autodoc_type_aliases`` as - ``{'AliasType': 'your.module.TypeAlias'}``, it generates a following document + ``{'AliasType': 'your.module.AliasType'}``, it generates the following document internally:: .. py:function:: f() -> your.module.AliasType: diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst index d50abd89e..03ea7548e 100644 --- a/doc/usage/extensions/autosummary.rst +++ b/doc/usage/extensions/autosummary.rst @@ -304,7 +304,7 @@ The following variables available in the templates: .. data:: modules List containing names of "public" modules in the package. Only available for - modules that are packages. + modules that are packages and the ``recursive`` option is on. .. versionadded:: 3.1 diff --git a/doc/usage/extensions/example_google.py b/doc/usage/extensions/example_google.py index 97ffe8a05..5fde6e226 100644 --- a/doc/usage/extensions/example_google.py +++ b/doc/usage/extensions/example_google.py @@ -294,3 +294,21 @@ class ExampleClass: def _private_without_docstring(self): pass + +class ExamplePEP526Class: + """The summary line for a class docstring should fit on one line. + + If the class has public attributes, they may be documented here + in an ``Attributes`` section and follow the same formatting as a + function's ``Args`` section. If ``napoleon_attr_annotations`` + is True, types can be specified in the class body using ``PEP 526`` + annotations. + + Attributes: + attr1: Description of `attr1`. + attr2: Description of `attr2`. + + """ + + attr1: str + attr2: int \ No newline at end of file diff --git a/doc/usage/extensions/index.rst b/doc/usage/extensions/index.rst index 6aab8adb2..0d446cb61 100644 --- a/doc/usage/extensions/index.rst +++ b/doc/usage/extensions/index.rst @@ -41,22 +41,20 @@ These extensions are built in and can be activated by respective entries in the Third-party extensions ---------------------- -.. todo:: This should reference the GitHub organization now +You can find several extensions contributed by users in the `sphinx-contrib`__ +organization. If you wish to include your extension in this organization, +simply follow the instructions provided in the `github-administration`__ +project. This is optional and there are several extensions hosted elsewhere. +The `awesome-sphinxdoc`__ project contains a curated list of Sphinx packages, +and many packages use the `Framework :: Sphinx :: Extension`__ and +`Framework :: Sphinx :: Theme`__ trove classifiers for Sphinx extensions and +themes, respectively. -You can find several extensions contributed by users in the `Sphinx Contrib`_ -repository. It is open for anyone who wants to maintain an extension publicly; -just send a short message asking for write permissions. - -There are also several extensions hosted elsewhere. The `Sphinx extension -survey `__ and `awesome-sphinxdoc -`__ contains a comprehensive -list. - -If you write an extension that you think others will find useful or you think -should be included as a part of Sphinx, please write to the project mailing -list (`join here `_). - -.. _Sphinx Contrib: https://bitbucket.org/birkenfeld/sphinx-contrib +.. __: https://github.com/sphinx-contrib/ +.. __: https://github.com/sphinx-contrib/github-administration +.. __: https://github.com/yoloseem/awesome-sphinxdoc +.. __: https://pypi.org/search/?c=Framework+%3A%3A+Sphinx+%3A%3A+Extension +.. __: https://pypi.org/search/?c=Framework+%3A%3A+Sphinx+%3A%3A+Theme Where to put your own extensions? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/usage/extensions/intersphinx.rst b/doc/usage/extensions/intersphinx.rst index 619ec8c20..178655cae 100644 --- a/doc/usage/extensions/intersphinx.rst +++ b/doc/usage/extensions/intersphinx.rst @@ -75,8 +75,9 @@ linking: A dictionary mapping unique identifiers to a tuple ``(target, inventory)``. Each ``target`` is the base URI of a foreign Sphinx documentation set and can be a local path or an HTTP URI. The ``inventory`` indicates where the - inventory file can be found: it can be ``None`` (at the same location as - the base URI) or another local or HTTP URI. + inventory file can be found: it can be ``None`` (an :file:`objects.inv` file + at the same location as the base URI) or another local file path or a full + HTTP URI to an inventory file. The unique identifier can be used to prefix cross-reference targets, so that it is clear which intersphinx set the target belongs to. A link like @@ -106,7 +107,7 @@ linking: ``https://docs.python.org/3``. It is up to you to update the inventory file as new objects are added to the Python documentation. - **Multiple target for the inventory** + **Multiple targets for the inventory** .. versionadded:: 1.3 @@ -120,6 +121,16 @@ linking: intersphinx_mapping = {'python': ('https://docs.python.org/3', (None, 'python-inv.txt'))} + For a set of books edited and tested locally and then published + together, it could be helpful to try a local inventory file first, + to check references before publication:: + + intersphinx_mapping = { + 'otherbook': + ('https://myproj.readthedocs.io/projects/otherbook/en/latest', + ('../../otherbook/build/html/objects.inv', None)), + } + .. confval:: intersphinx_cache_limit The maximum number of days to cache remote inventories. The default is diff --git a/doc/usage/extensions/napoleon.rst b/doc/usage/extensions/napoleon.rst index b16577e2d..066c56e2d 100644 --- a/doc/usage/extensions/napoleon.rst +++ b/doc/usage/extensions/napoleon.rst @@ -203,7 +203,8 @@ Type Annotations This is an alternative to expressing types directly in docstrings. One benefit of expressing types according to `PEP 484`_ is that type checkers and IDEs can take advantage of them for static code -analysis. +analysis. `PEP 484`_ was then extended by `PEP 526`_ which introduced +a similar way to annotate variables (and attributes). Google style with Python 3 type annotations:: @@ -221,6 +222,19 @@ Google style with Python 3 type annotations:: """ return True + + class Class: + """Summary line. + + Extended description of class + + Attributes: + attr1: Description of attr1 + attr2: Description of attr2 + """ + + attr1: int + attr2: str Google style with types in docstrings:: @@ -238,6 +252,16 @@ Google style with types in docstrings:: """ return True + + class Class: + """Summary line. + + Extended description of class + + Attributes: + attr1 (int): Description of attr1 + attr2 (str): Description of attr2 + """ .. Note:: `Python 2/3 compatible annotations`_ aren't currently @@ -246,6 +270,9 @@ Google style with types in docstrings:: .. _PEP 484: https://www.python.org/dev/peps/pep-0484/ +.. _PEP 526: + https://www.python.org/dev/peps/pep-0526/ + .. _Python 2/3 compatible annotations: https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code @@ -275,6 +302,7 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`:: napoleon_use_param = True napoleon_use_rtype = True napoleon_type_aliases = None + napoleon_attr_annotations = True .. _Google style: https://google.github.io/styleguide/pyguide.html @@ -511,3 +539,35 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`:: :type arg2: :term:`dict-like ` .. versionadded:: 3.2 + +.. confval:: napoleon_attr_annotations + + True to allow using `PEP 526`_ attributes annotations in classes. + If an attribute is documented in the docstring without a type and + has an annotation in the class body, that type is used. + + .. versionadded:: 3.4 + +.. confval:: napoleon_custom_sections + + Add a list of custom sections to include, expanding the list of parsed sections. + *Defaults to None.* + + The entries can either be strings or tuples, depending on the intention: + + * To create a custom "generic" section, just pass a string. + * To create an alias for an existing section, pass a tuple containing the + alias name and the original, in that order. + * To create a custom section that displays like the parameters or returns + section, pass a tuple containing the custom section name and a string + value, "params_style" or "returns_style". + + If an entry is just a string, it is interpreted as a header for a generic + section. If the entry is a tuple/list/indexed container, the first entry + is the name of the section, the second is the section key to emulate. If the + second entry value is "params_style" or "returns_style", the custom section + will be displayed like the parameters section or returns section. + + .. versionadded:: 1.8 + .. versionchanged:: 3.5 + Support ``params_style`` and ``returns_style`` \ No newline at end of file diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst index 92bf78489..e8b88c21b 100644 --- a/doc/usage/restructuredtext/directives.rst +++ b/doc/usage/restructuredtext/directives.rst @@ -569,12 +569,28 @@ __ http://pygments.org/docs/lexers print 'Explicit is better than implicit.' + In order to cross-reference a code-block using either the + :rst:role:`ref` or the :rst:role:`numref` role, it is necessary + that both :strong:`name` and :strong:`caption` be defined. The + argument of :strong:`name` can then be given to :rst:role:`numref` + to generate the cross-reference. Example:: + + See :numref:`this-py` for an example. + + When using :rst:role:`ref`, it is possible to generate a cross-reference + with only :strong:`name` defined, provided an explicit title is + given. Example:: + + See :ref:`this code snippet ` for an example. + .. versionadded:: 1.3 .. rst:directive:option:: dedent: number - :type: number + :type: number or no value - Strip indentation characters from the code block. For example:: + Strip indentation characters from the code block. When number given, + leading N characters are removed. When no argument given, leading spaces + are removed via :func:`textwrap.dedent()`. For example:: .. code-block:: ruby :dedent: 4 @@ -582,6 +598,8 @@ __ http://pygments.org/docs/lexers some ruby code .. versionadded:: 1.3 + .. versionchanged:: 3.5 + Support automatic dedent. .. rst:directive:option:: force :type: no value @@ -742,6 +760,9 @@ __ http://pygments.org/docs/lexers .. versionchanged:: 2.1 Added the ``force`` option. + .. versionchanged:: 3.5 + Support automatic dedent. + .. _glossary-directive: Glossary @@ -1199,20 +1220,29 @@ the definition of the symbol. There is this directive: the following definition. If the definition spans multiple lines, each continuation line must begin with a colon placed at the same column as in the first line. + Blank lines are not allowed within ``productionlist`` directive arguments. + + The definition can contain token names which are marked as interpreted text + (e.g., "``sum ::= `integer` "+" `integer```") -- this generates + cross-references to the productions of these tokens. Outside of the + production list, you can reference to token productions using + :rst:role:`token`. The *productionGroup* argument to :rst:dir:`productionlist` serves to distinguish different sets of production lists that belong to different grammars. Multiple production lists with the same *productionGroup* thus define rules in the same scope. - Blank lines are not allowed within ``productionlist`` directive arguments. + Inside of the production list, tokens implicitly refer to productions + from the current group. You can refer to the production of another + grammar by prefixing the token with its group name and a colon, e.g, + "``otherGroup:sum``". If the group of the token should not be shown in + the production, it can be prefixed by a tilde, e.g., + "``~otherGroup:sum``". To refer to a production from an unnamed + grammar, the token should be prefixed by a colon, e.g., "``:sum``". - The definition can contain token names which are marked as interpreted text - (e.g. "``sum ::= `integer` "+" `integer```") -- this generates - cross-references to the productions of these tokens. Outside of the - production list, you can reference to token productions using - :rst:role:`token`. - However, if you have given a *productionGroup* argument you must prefix the + Outside of the production list, + if you have given a *productionGroup* argument you must prefix the token name in the cross-reference with the group name and a colon, e.g., "``myGroup:sum``" instead of just "``sum``". If the group should not be shown in the title of the link either diff --git a/karma.conf.js b/karma.conf.js index d0c11aa21..82be18a71 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -18,6 +18,7 @@ module.exports = function(config) { 'sphinx/themes/basic/static/underscore.js', 'sphinx/themes/basic/static/jquery.js', 'sphinx/themes/basic/static/doctools.js', + 'sphinx/themes/basic/static/searchtools.js', 'tests/js/*.js' ], diff --git a/setup.cfg b/setup.cfg index a4d46044b..7d629a9c5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,7 @@ filterwarnings = all ignore::DeprecationWarning:docutils.io ignore::DeprecationWarning:pyximport.pyximport + ignore::ImportWarning:importlib._bootstrap ignore::PendingDeprecationWarning:sphinx.util.pycompat markers = apidoc diff --git a/setup.py b/setup.py index ca08a6d4a..dfc80578f 100644 --- a/setup.py +++ b/setup.py @@ -44,14 +44,14 @@ extras_require = { 'lint': [ 'flake8>=3.5.0', 'isort', - 'mypy>=0.790', + 'mypy>=0.800', 'docutils-stubs', ], 'test': [ 'pytest', 'pytest-cov', 'html5lib', - 'typed_ast', # for py35-37 + "typed_ast; python_version < '3.8'", 'cython', ], } diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 2b5d853e5..23a867fa0 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -4,7 +4,7 @@ The Sphinx documentation toolchain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -32,8 +32,8 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '3.4.0+' -__released__ = '3.4.0' # used when Sphinx builds its own docs +__version__ = '3.5.0+' +__released__ = '3.5.0' # used when Sphinx builds its own docs #: Version info for better programmatic use. #: @@ -43,7 +43,7 @@ __released__ = '3.4.0' # used when Sphinx builds its own docs #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. -version_info = (3, 4, 0, 'beta', 0) +version_info = (3, 5, 0, 'beta', 0) package_dir = path.abspath(path.dirname(__file__)) diff --git a/sphinx/__main__.py b/sphinx/__main__.py index 5da409015..6192f52ae 100644 --- a/sphinx/__main__.py +++ b/sphinx/__main__.py @@ -4,7 +4,7 @@ The Sphinx documentation toolchain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 33503bb08..5f371e46b 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -4,7 +4,7 @@ Additional docutils nodes. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/application.py b/sphinx/application.py index 5bdccdbb1..f61a6a549 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -6,7 +6,7 @@ Gracefully adapted from the TextPress system by Armin. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -404,9 +404,10 @@ class Sphinx: def require_sphinx(self, version: str) -> None: """Check the Sphinx version if requested. - Compare *version* (which must be a ``major.minor`` version string, e.g. - ``'1.1'``) with the version of the running Sphinx, and abort the build - when it is too old. + Compare *version* with the version of the running Sphinx, and abort the + build when it is too old. + + :param version: The required version in the form of ``major.minor``. .. versionadded:: 1.0 """ @@ -420,11 +421,11 @@ class Sphinx: For details on available core events and the arguments of callback functions, please see :ref:`events`. - Registered callbacks will be invoked on event in the order of *priority* and - registration. The priority is ascending order. - - The method returns a "listener ID" that can be used as an argument to - :meth:`disconnect`. + :param event: The name of target event + :param callback: Callback function for the event + :param priority: The priority of the callback. The callbacks will be invoked + in the order of *priority* in asending. + :return: A listener ID. It can be used for :meth:`disconnect`. .. versionchanged:: 3.0 @@ -436,7 +437,10 @@ class Sphinx: return listener_id def disconnect(self, listener_id: int) -> None: - """Unregister callback by *listener_id*.""" + """Unregister callback by *listener_id*. + + :param listener_id: A listener_id that :meth:`connect` returns + """ logger.debug('[app] disconnecting event: [id=%s]', listener_id) self.events.disconnect(listener_id) @@ -447,6 +451,10 @@ class Sphinx: Return the return values of all callbacks as a list. Do not emit core Sphinx events in extensions! + :param event: The name of event that will be emitted + :param args: The arguments for the event + :param allowed_exceptions: The list of exceptions that are allowed in the callbacks + .. versionchanged:: 3.1 Added *allowed_exceptions* to specify path-through exceptions @@ -459,6 +467,10 @@ class Sphinx: Return the result of the first callback that doesn't return ``None``. + :param event: The name of event that will be emitted + :param args: The arguments for the event + :param allowed_exceptions: The list of exceptions that are allowed in the callbacks + .. versionadded:: 0.5 .. versionchanged:: 3.1 @@ -472,10 +484,9 @@ class Sphinx: def add_builder(self, builder: "Type[Builder]", override: bool = False) -> None: """Register a new builder. - *builder* must be a class that inherits from :class:`~sphinx.builders.Builder`. - - If *override* is True, the given *builder* is forcedly installed even if - a builder having the same name is already installed. + :param builder: A builder class + :param override: If true, install the builder forcedly even if another builder + is already installed as the same name .. versionchanged:: 1.8 Add *override* keyword. @@ -488,27 +499,34 @@ class Sphinx: """Register a configuration value. This is necessary for Sphinx to recognize new values and set default - values accordingly. The *name* should be prefixed with the extension - name, to avoid clashes. The *default* value can be any Python object. - The string value *rebuild* must be one of those values: + values accordingly. - * ``'env'`` if a change in the setting only takes effect when a - document is parsed -- this means that the whole environment must be - rebuilt. - * ``'html'`` if a change in the setting needs a full rebuild of HTML - documents. - * ``''`` if a change in the setting will not need any special rebuild. - .. versionchanged:: 0.6 - Changed *rebuild* from a simple boolean (equivalent to ``''`` or - ``'env'``) to a string. However, booleans are still accepted and - converted internally. + :param name: The name of configuration value. It is recommended to be prefixed + with the extension name (ex. ``html_logo``, ``epub_title``) + :param default: The default value of the configuration. + :param rebuild: The condition of rebuild. It must be one of those values: + + * ``'env'`` if a change in the setting only takes effect when a + document is parsed -- this means that the whole environment must be + rebuilt. + * ``'html'`` if a change in the setting needs a full rebuild of HTML + documents. + * ``''`` if a change in the setting will not need any special rebuild. + :param types: The type of configuration value. A list of types can be specified. For + example, ``[str]`` is used to describe a configuration that takes string + value. .. versionchanged:: 0.4 If the *default* value is a callable, it will be called with the config object as its argument in order to get the default value. This can be used to implement config values whose default depends on other values. + + .. versionchanged:: 0.6 + Changed *rebuild* from a simple boolean (equivalent to ``''`` or + ``'env'``) to a string. However, booleans are still accepted and + converted internally. """ logger.debug('[app] adding config value: %r', (name, default, rebuild) + ((types,) if types else ())) @@ -520,6 +538,8 @@ class Sphinx: """Register an event called *name*. This is needed to be able to emit it. + + :param name: The name of the event """ logger.debug('[app] adding event: %r', name) self.events.add(name) @@ -532,8 +552,10 @@ class Sphinx: builtin translator. This allows extensions to use custom translator and define custom nodes for the translator (see :meth:`add_node`). - If *override* is True, the given *translator_class* is forcedly installed even if - a translator for *name* is already installed. + :param name: The name of builder for the translator + :param translator_class: A translator class + :param override: If true, install the translator forcedly even if another translator + is already installed as the same name .. versionadded:: 1.3 .. versionchanged:: 1.8 @@ -548,6 +570,11 @@ class Sphinx: This is necessary for Docutils internals. It may also be used in the future to validate nodes in the parsed documents. + :param node: A node class + :param kwargs: Visitor functions for each builder (see below) + :param override: If true, install the node forcedly even if another node is already + installed as the same name + Node visitor functions for the Sphinx HTML, LaTeX, text and manpage writers can be given as keyword arguments: the keyword should be one or more of ``'html'``, ``'latex'``, ``'text'``, ``'man'``, ``'texinfo'`` @@ -569,9 +596,6 @@ class Sphinx: Obviously, translators for which you don't specify visitor methods will choke on the node when encountered in a document to translate. - If *override* is True, the given *node* is forcedly installed even if - a node having the same name is already installed. - .. versionchanged:: 0.5 Added the support for keyword arguments giving visit functions. """ @@ -591,24 +615,21 @@ class Sphinx: Sphinx numbers the node automatically. And then the users can refer it using :rst:role:`numref`. - *figtype* is a type of enumerable nodes. Each figtypes have individual - numbering sequences. As a system figtypes, ``figure``, ``table`` and - ``code-block`` are defined. It is able to add custom nodes to these - default figtypes. It is also able to define new custom figtype if new - figtype is given. - - *title_getter* is a getter function to obtain the title of node. It - takes an instance of the enumerable node, and it must return its title - as string. The title is used to the default title of references for - :rst:role:`ref`. By default, Sphinx searches - ``docutils.nodes.caption`` or ``docutils.nodes.title`` from the node as - a title. - - Other keyword arguments are used for node visitor functions. See the - :meth:`.Sphinx.add_node` for details. - - If *override* is True, the given *node* is forcedly installed even if - a node having the same name is already installed. + :param node: A node class + :param figtype: The type of enumerable nodes. Each figtypes have individual numbering + sequences. As a system figtypes, ``figure``, ``table`` and + ``code-block`` are defined. It is able to add custom nodes to these + default figtypes. It is also able to define new custom figtype if new + figtype is given. + :param title_getter: A getter function to obtain the title of node. It takes an + instance of the enumerable node, and it must return its title as + string. The title is used to the default title of references for + :rst:role:`ref`. By default, Sphinx searches + ``docutils.nodes.caption`` or ``docutils.nodes.title`` from the + node as a title. + :param kwargs: Visitor functions for each builder (same as :meth:`add_node`) + :param override: If true, install the node forcedly even if another node is already + installed as the same name .. versionadded:: 1.4 """ @@ -618,10 +639,10 @@ class Sphinx: def add_directive(self, name: str, cls: "Type[Directive]", override: bool = False) -> None: """Register a Docutils directive. - *name* must be the prospective directive name. *cls* is a directive - class which inherits ``docutils.parsers.rst.Directive``. For more - details, see `the Docutils docs - `_ . + :param name: The name of directive + :param cls: A directive class + :param override: If true, install the directive forcedly even if another directive + is already installed as the same name For example, a custom directive named ``my-directive`` would be added like this: @@ -646,8 +667,8 @@ class Sphinx: def setup(app): add_directive('my-directive', MyDirective) - If *override* is True, the given *cls* is forcedly installed even if - a directive named as *name* is already installed. + For more details, see `the Docutils docs + `__ . .. versionchanged:: 0.6 Docutils 0.5-style directive classes are now supported. @@ -666,13 +687,13 @@ class Sphinx: def add_role(self, name: str, role: Any, override: bool = False) -> None: """Register a Docutils role. - *name* must be the role name that occurs in the source, *role* the role - function. Refer to the `Docutils documentation - `_ for - more information. + :param name: The name of role + :param role: A role function + :param override: If true, install the role forcedly even if another role is already + installed as the same name - If *override* is True, the given *role* is forcedly installed even if - a role named as *name* is already installed. + For more details about role functions, see `the Docutils docs + `__ . .. versionchanged:: 1.8 Add *override* keyword. @@ -708,11 +729,9 @@ class Sphinx: def add_domain(self, domain: "Type[Domain]", override: bool = False) -> None: """Register a domain. - Make the given *domain* (which must be a class; more precisely, a - subclass of :class:`~sphinx.domains.Domain`) known to Sphinx. - - If *override* is True, the given *domain* is forcedly installed even if - a domain having the same name is already installed. + :param domain: A domain class + :param override: If true, install the domain forcedly even if another domain + is already installed as the same name .. versionadded:: 1.0 .. versionchanged:: 1.8 @@ -727,8 +746,11 @@ class Sphinx: Like :meth:`add_directive`, but the directive is added to the domain named *domain*. - If *override* is True, the given *directive* is forcedly installed even if - a directive named as *name* is already installed. + :param domain: The name of target domain + :param name: A name of directive + :param cls: A directive class + :param override: If true, install the directive forcedly even if another directive + is already installed as the same name .. versionadded:: 1.0 .. versionchanged:: 1.8 @@ -743,8 +765,11 @@ class Sphinx: Like :meth:`add_role`, but the role is added to the domain named *domain*. - If *override* is True, the given *role* is forcedly installed even if - a role named as *name* is already installed. + :param domain: The name of target domain + :param name: A name of role + :param role: A role function + :param override: If true, install the role forcedly even if another role is already + installed as the same name .. versionadded:: 1.0 .. versionchanged:: 1.8 @@ -756,11 +781,12 @@ class Sphinx: ) -> None: """Register a custom index for a domain. - Add a custom *index* class to the domain named *domain*. *index* must - be a subclass of :class:`~sphinx.domains.Index`. + Add a custom *index* class to the domain named *domain*. - If *override* is True, the given *index* is forcedly installed even if - an index having the same name is already installed. + :param domain: The name of target domain + :param index: A index class + :param override: If true, install the index forcedly even if another index is + already installed as the same name .. versionadded:: 1.0 .. versionchanged:: 1.8 @@ -881,6 +907,8 @@ class Sphinx: the list of transforms that are applied after Sphinx parses a reST document. + :param transform: A transform class + .. list-table:: priority range categories for Sphinx transforms :widths: 20,80 @@ -913,25 +941,29 @@ class Sphinx: Add the standard docutils :class:`Transform` subclass *transform* to the list of transforms that are applied before Sphinx writes a document. + + :param transform: A transform class """ self.registry.add_post_transform(transform) - def add_javascript(self, filename: str, **kwargs: str) -> None: + def add_javascript(self, filename: str, **kwargs: Any) -> None: """An alias of :meth:`add_js_file`.""" warnings.warn('The app.add_javascript() is deprecated. ' 'Please use app.add_js_file() instead.', RemovedInSphinx40Warning, stacklevel=2) self.add_js_file(filename, **kwargs) - def add_js_file(self, filename: str, **kwargs: str) -> None: + def add_js_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None: """Register a JavaScript file to include in the HTML output. Add *filename* to the list of JavaScript files that the default HTML - template will include. The filename must be relative to the HTML - static path , or a full URI with scheme. If the keyword argument - ``body`` is given, its value will be added between the - `` + .. list-table:: priority range for JavaScript files + :widths: 20,80 + + * - Priority + - Main purpose in Sphinx + * - 200 + - default priority for built-in JavaScript files + * - 500 + - default priority for extensions + * - 800 + - default priority for :confval:`html_js_files` + + A JavaScript file can be added to the specific HTML page when on extension + calls this method on :event:`html-page-context` event. + .. versionadded:: 0.5 .. versionchanged:: 1.8 Renamed from ``app.add_javascript()``. And it allows keyword arguments as attributes of script tag. - """ - self.registry.add_js_file(filename, **kwargs) - if hasattr(self.builder, 'add_js_file'): - self.builder.add_js_file(filename, **kwargs) # type: ignore - def add_css_file(self, filename: str, **kwargs: str) -> None: + .. versionchanged:: 3.5 + Take priority argument. Allow to add a JavaScript file to the specific page. + """ + self.registry.add_js_file(filename, priority=priority, **kwargs) + if hasattr(self.builder, 'add_js_file'): + self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore + + def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None: """Register a stylesheet to include in the HTML output. Add *filename* to the list of CSS files that the default HTML template - will include. The filename must be relative to the HTML static path, - or a full URI with scheme. The keyword arguments are also accepted for - attributes of ```` tag. + will include in order of *priority* (ascending). The filename must be + relative to the HTML static path, or a full URI with scheme. If the + priority of CSS file is the same as others, the CSS files will be + included in order of the registration. The keyword arguments are also + accepted for attributes of ```` tag. Example:: @@ -975,6 +1027,19 @@ class Sphinx: # => + .. list-table:: priority range for CSS files + :widths: 20,80 + + * - Priority + - Main purpose in Sphinx + * - 500 + - default priority for extensions + * - 800 + - default priority for :confval:`html_css_files` + + A CSS file can be added to the specific HTML page when on extension calls + this method on :event:`html-page-context` event. + .. versionadded:: 1.0 .. versionchanged:: 1.6 @@ -987,11 +1052,14 @@ class Sphinx: .. versionchanged:: 1.8 Renamed from ``app.add_stylesheet()``. And it allows keyword arguments as attributes of link tag. + + .. versionchanged:: 3.5 + Take priority argument. Allow to add a CSS file to the specific page. """ logger.debug('[app] adding stylesheet: %r', filename) - self.registry.add_css_files(filename, **kwargs) + self.registry.add_css_files(filename, priority=priority, **kwargs) if hasattr(self.builder, 'add_css_file'): - self.builder.add_css_file(filename, **kwargs) # type: ignore + self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None ) -> None: @@ -1000,7 +1068,7 @@ class Sphinx: 'Please use app.add_css_file() instead.', RemovedInSphinx40Warning, stacklevel=2) - attributes = {} # type: Dict[str, str] + attributes = {} # type: Dict[str, Any] if alternate: attributes['rel'] = 'alternate stylesheet' else: @@ -1175,9 +1243,10 @@ class Sphinx: def add_message_catalog(self, catalog: str, locale_dir: str) -> None: """Register a message catalog. - The *catalog* is a name of catalog, and *locale_dir* is a base path - of message catalog. For more details, see - :func:`sphinx.locale.get_translation()`. + :param catalog: A name of catalog + :param locale_dir: The base path of message catalog + + For more details, see :func:`sphinx.locale.get_translation()`. .. versionadded:: 1.8 """ @@ -1188,7 +1257,7 @@ class Sphinx: def is_parallel_allowed(self, typ: str) -> bool: """Check parallel processing is allowed or not. - ``typ`` is a type of processing; ``'read'`` or ``'write'``. + :param typ: A type of processing; ``'read'`` or ``'write'``. """ if typ == 'read': attrname = 'parallel_read_safe' diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index b2cae7f47..58030bb6c 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -4,7 +4,7 @@ Builder superclass for all builders. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index 9751fae80..7df3f8df5 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -4,7 +4,7 @@ Base class of epub2/epub3 builders. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/applehelp.py b/sphinx/builders/applehelp.py index 08669a8af..bfc8c8dc8 100644 --- a/sphinx/builders/applehelp.py +++ b/sphinx/builders/applehelp.py @@ -4,7 +4,7 @@ Build Apple help books. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index ea3a778fc..87dd03fb8 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -4,7 +4,7 @@ Changelog builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 f14e3a2da..9625b62f4 100644 --- a/sphinx/builders/devhelp.py +++ b/sphinx/builders/devhelp.py @@ -6,7 +6,7 @@ .. _Devhelp: https://wiki.gnome.org/Apps/Devhelp - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/dirhtml.py b/sphinx/builders/dirhtml.py index 6fab8cf82..5e6b17259 100644 --- a/sphinx/builders/dirhtml.py +++ b/sphinx/builders/dirhtml.py @@ -4,7 +4,7 @@ Directory HTML builders. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/dummy.py b/sphinx/builders/dummy.py index 33d2506ac..722e70d1c 100644 --- a/sphinx/builders/dummy.py +++ b/sphinx/builders/dummy.py @@ -4,7 +4,7 @@ Do syntax checks, but no writing. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index cb2f2dbd6..d1cf64eb3 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -5,7 +5,7 @@ Build epub3 files. Originally derived from epub.py. - :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 cbc6fc444..75c95c0bc 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -4,7 +4,7 @@ The MessageCatalogBuilder class. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -315,7 +315,7 @@ class MessageCatalogBuilder(I18nBuilder): def setup(app: Sphinx) -> Dict[str, Any]: app.add_builder(MessageCatalogBuilder) - app.add_config_value('gettext_compact', True, 'gettext', Any) + app.add_config_value('gettext_compact', True, 'gettext', {bool, str}) app.add_config_value('gettext_location', True, 'gettext') app.add_config_value('gettext_uuid', False, 'gettext') app.add_config_value('gettext_auto_build', True, 'env') diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 6d263a9ed..f5152f6da 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -4,11 +4,12 @@ Several HTML builders. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import html +import os import posixpath import re import sys @@ -44,7 +45,7 @@ from sphinx.util.fileutil import copy_asset from sphinx.util.i18n import format_date from sphinx.util.inventory import InventoryFile from sphinx.util.matching import DOTFILES, Matcher, patmatch -from sphinx.util.osutil import copyfile, ensuredir, movefile, os_path, relative_uri +from sphinx.util.osutil import copyfile, ensuredir, os_path, relative_uri from sphinx.util.tags import Tags from sphinx.writers.html import HTMLTranslator, HTMLWriter @@ -89,10 +90,13 @@ class Stylesheet(str): attributes = None # type: Dict[str, str] filename = None # type: str + priority = None # type: int - def __new__(cls, filename: str, *args: str, **attributes: str) -> "Stylesheet": - self = str.__new__(cls, filename) # type: ignore + def __new__(cls, filename: str, *args: str, priority: int = 500, **attributes: Any + ) -> "Stylesheet": + self = str.__new__(cls, filename) self.filename = filename + self.priority = priority self.attributes = attributes self.attributes.setdefault('rel', 'stylesheet') self.attributes.setdefault('type', 'text/css') @@ -112,10 +116,12 @@ class JavaScript(str): attributes = None # type: Dict[str, str] filename = None # type: str + priority = None # type: int - def __new__(cls, filename: str, **attributes: str) -> "JavaScript": - self = str.__new__(cls, filename) # type: ignore + def __new__(cls, filename: str, priority: int = 500, **attributes: str) -> "JavaScript": + self = str.__new__(cls, filename) self.filename = filename + self.priority = priority self.attributes = attributes return self @@ -289,29 +295,31 @@ class StandaloneHTMLBuilder(Builder): self.add_css_file(filename, **attrs) for filename, attrs in self.get_builder_config('css_files', 'html'): + attrs.setdefault('priority', 800) # User's CSSs are loaded after extensions' self.add_css_file(filename, **attrs) - def add_css_file(self, filename: str, **kwargs: str) -> None: + def add_css_file(self, filename: str, **kwargs: Any) -> None: if '://' not in filename: filename = posixpath.join('_static', filename) self.css_files.append(Stylesheet(filename, **kwargs)) # type: ignore def init_js_files(self) -> None: - self.add_js_file('jquery.js') - self.add_js_file('underscore.js') - self.add_js_file('doctools.js') + self.add_js_file('jquery.js', priority=200) + self.add_js_file('underscore.js', priority=200) + self.add_js_file('doctools.js', priority=200) for filename, attrs in self.app.registry.js_files: self.add_js_file(filename, **attrs) for filename, attrs in self.get_builder_config('js_files', 'html'): + attrs.setdefault('priority', 800) # User's JSs are loaded after extensions' self.add_js_file(filename, **attrs) if self.config.language and self._get_translations_js(): self.add_js_file('translations.js') - def add_js_file(self, filename: str, **kwargs: str) -> None: + def add_js_file(self, filename: str, **kwargs: Any) -> None: if filename and '://' not in filename: filename = posixpath.join('_static', filename) @@ -447,9 +455,6 @@ class StandaloneHTMLBuilder(Builder): logo = path.basename(self.config.html_logo) if self.config.html_logo else '' favicon = path.basename(self.config.html_favicon) if self.config.html_favicon else '' - if not isinstance(self.config.html_use_opensearch, str): - logger.warning(__('html_use_opensearch config value must now be a string')) - self.relations = self.env.collect_relations() rellinks = [] # type: List[Tuple[str, str, str, str]] @@ -461,6 +466,10 @@ class StandaloneHTMLBuilder(Builder): rellinks.append((indexname, indexcls.localname, '', indexcls.shortname)) + # back up script_files and css_files to allow adding JS/CSS files to a specific page. + self._script_files = list(self.script_files) + self._css_files = list(self.css_files) + if self.config.html_style is not None: stylename = self.config.html_style elif self.theme: @@ -1011,12 +1020,20 @@ class StandaloneHTMLBuilder(Builder): self.add_sidebars(pagename, ctx) ctx.update(addctx) + # revert script_files and css_files + self.script_files[:] = self._script_files + self.css_files[:] = self.css_files + self.update_page_context(pagename, templatename, ctx, event_arg) newtmpl = self.app.emit_firstresult('html-page-context', pagename, templatename, ctx, event_arg) if newtmpl: templatename = newtmpl + # sort JS/CSS before rendering HTML + ctx['script_files'].sort(key=lambda js: js.priority) + ctx['css_files'].sort(key=lambda js: js.priority) + try: output = self.templates.render(templatename, ctx) except UnicodeError: @@ -1070,7 +1087,7 @@ class StandaloneHTMLBuilder(Builder): else: with open(searchindexfn + '.tmp', 'wb') as fb: self.indexer.dump(fb, self.indexer_format) - movefile(searchindexfn + '.tmp', searchindexfn) + os.replace(searchindexfn + '.tmp', searchindexfn) def convert_html_css_files(app: Sphinx, config: Config) -> None: @@ -1188,6 +1205,16 @@ def validate_html_favicon(app: Sphinx, config: Config) -> None: config.html_favicon = None # type: ignore +def migrate_html_add_permalinks(app: Sphinx, config: Config) -> None: + """Migrate html_add_permalinks to html_permalinks*.""" + if config.html_add_permalinks: + if (isinstance(config.html_add_permalinks, bool) and + config.html_add_permalinks is False): + config.html_permalinks = False # type: ignore + else: + config.html_permalinks_icon = html.escape(config.html_add_permalinks) # type: ignore # NOQA + + # for compatibility import sphinxcontrib.serializinghtml # NOQA @@ -1218,7 +1245,9 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('html_sidebars', {}, 'html') app.add_config_value('html_additional_pages', {}, 'html') app.add_config_value('html_domain_indices', True, 'html', [list]) - app.add_config_value('html_add_permalinks', '¶', 'html') + app.add_config_value('html_add_permalinks', None, 'html') + app.add_config_value('html_permalinks', True, 'html') + app.add_config_value('html_permalinks_icon', '¶', 'html') app.add_config_value('html_use_index', True, 'html') app.add_config_value('html_split_index', False, 'html') app.add_config_value('html_copy_source', True, 'html') @@ -1243,9 +1272,14 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('html_math_renderer', None, 'env') app.add_config_value('html4_writer', False, 'html') + # events + app.add_event('html-collect-pages') + app.add_event('html-page-context') + # event handlers app.connect('config-inited', convert_html_css_files, priority=800) app.connect('config-inited', convert_html_js_files, priority=800) + app.connect('config-inited', migrate_html_add_permalinks, priority=800) app.connect('config-inited', validate_html_extra_path, priority=800) app.connect('config-inited', validate_html_static_path, priority=800) app.connect('config-inited', validate_html_logo, priority=800) diff --git a/sphinx/builders/html/transforms.py b/sphinx/builders/html/transforms.py index c91da57e9..cb9af5f28 100644 --- a/sphinx/builders/html/transforms.py +++ b/sphinx/builders/html/transforms.py @@ -4,12 +4,12 @@ Transforms for HTML builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re -from typing import Any, Dict +from typing import Any, Dict, List from docutils import nodes @@ -28,7 +28,7 @@ class KeyboardTransform(SphinxPostTransform): After:: - + Control - @@ -37,18 +37,30 @@ class KeyboardTransform(SphinxPostTransform): """ default_priority = 400 builders = ('html',) - pattern = re.compile(r'(-|\+|\^|\s+)') + pattern = re.compile(r'(?<=.)(-|\+|\^|\s+)(?=.)') + multiwords_keys = (('caps', 'lock'), + ('page' 'down'), + ('page', 'up'), + ('scroll' 'lock'), + ('num', 'lock'), + ('sys' 'rq'), + ('back' 'space')) def run(self, **kwargs: Any) -> None: matcher = NodeMatcher(nodes.literal, classes=["kbd"]) for node in self.document.traverse(matcher): # type: nodes.literal parts = self.pattern.split(node[-1].astext()) - if len(parts) == 1: + if len(parts) == 1 or self.is_multiwords_key(parts): continue + node['classes'].append('compound') node.pop() while parts: - key = parts.pop(0) + if self.is_multiwords_key(parts): + key = ''.join(parts[:3]) + parts[:3] = [] + else: + key = parts.pop(0) node += nodes.literal('', key, classes=["kbd"]) try: @@ -58,6 +70,16 @@ class KeyboardTransform(SphinxPostTransform): except IndexError: pass + def is_multiwords_key(self, parts: List[str]) -> bool: + if len(parts) >= 3 and parts[1].strip() == '': + name = parts[0].lower(), parts[2].lower() + if name in self.multiwords_keys: + return True + else: + return False + else: + return False + def setup(app: Sphinx) -> Dict[str, Any]: app.add_post_transform(KeyboardTransform) diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py index 642ae7da6..08719712c 100644 --- a/sphinx/builders/htmlhelp.py +++ b/sphinx/builders/htmlhelp.py @@ -5,7 +5,7 @@ Build HTML help support files. Parts adapted from Python's Doc/tools/prechm.py. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 77825f0ea..5c7a7412a 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -4,7 +4,7 @@ LaTeX builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/latex/constants.py b/sphinx/builders/latex/constants.py index aab64c679..0b20c7cef 100644 --- a/sphinx/builders/latex/constants.py +++ b/sphinx/builders/latex/constants.py @@ -4,7 +4,7 @@ consntants for LaTeX builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/latex/nodes.py b/sphinx/builders/latex/nodes.py index e6b1e5aee..eaed6b862 100644 --- a/sphinx/builders/latex/nodes.py +++ b/sphinx/builders/latex/nodes.py @@ -4,7 +4,7 @@ Additional nodes for LaTeX writer. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py index 130bded4a..5af79e8a2 100644 --- a/sphinx/builders/latex/theming.py +++ b/sphinx/builders/latex/theming.py @@ -4,7 +4,7 @@ Theming support for LaTeX builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py index 5b8370d6c..402cc3fe5 100644 --- a/sphinx/builders/latex/transforms.py +++ b/sphinx/builders/latex/transforms.py @@ -4,7 +4,7 @@ Transforms for LaTeX builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/latex/util.py b/sphinx/builders/latex/util.py index 0e3eb739d..4f2391c4e 100644 --- a/sphinx/builders/latex/util.py +++ b/sphinx/builders/latex/util.py @@ -4,7 +4,7 @@ Utilities for LaTeX builder. - :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 1dc0337c3..f3b6e8c4a 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -4,7 +4,7 @@ The CheckExternalLinksBuilder class. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -13,18 +13,25 @@ import queue import re import socket import threading +import time +import warnings +from datetime import datetime, timezone +from email.utils import parsedate_to_datetime from html.parser import HTMLParser from os import path -from typing import Any, Dict, List, Set, Tuple +from typing import Any, Dict, List, NamedTuple, Optional, Set, Tuple, cast from urllib.parse import unquote, urlparse from docutils import nodes -from docutils.nodes import Node -from requests.exceptions import HTTPError +from docutils.nodes import Element +from requests import Response +from requests.exceptions import HTTPError, TooManyRedirects from sphinx.application import Sphinx -from sphinx.builders import Builder +from sphinx.builders.dummy import DummyBuilder +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.locale import __ +from sphinx.transforms.post_transforms import SphinxPostTransform from sphinx.util import encode_uri, logging, requests from sphinx.util.console import darkgray, darkgreen, purple, red, turquoise # type: ignore from sphinx.util.nodes import get_node_line @@ -33,10 +40,28 @@ logger = logging.getLogger(__name__) uri_re = re.compile('([a-z]+:)?//') # matches to foo:// and // (a protocol relative URL) +Hyperlink = NamedTuple('Hyperlink', (('next_check', float), + ('uri', Optional[str]), + ('docname', Optional[str]), + ('lineno', Optional[int]))) +RateLimit = NamedTuple('RateLimit', (('delay', float), ('next_check', float))) DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8', } +CHECK_IMMEDIATELY = 0 +QUEUE_POLL_SECS = 1 +DEFAULT_DELAY = 60.0 + + +def node_line_or_0(node: Element) -> int: + """ + PriorityQueue items must be comparable. The line number is part of the + tuple used by the PriorityQueue, keep an homogeneous type for comparison. + """ + warnings.warn('node_line_or_0() is deprecated.', + RemovedInSphinx50Warning, stacklevel=2) + return get_node_line(node) or 0 class AnchorCheckParser(HTMLParser): @@ -73,7 +98,7 @@ def check_anchor(response: requests.requests.Response, anchor: str) -> bool: return parser.found -class CheckExternalLinksBuilder(Builder): +class CheckExternalLinksBuilder(DummyBuilder): """ Checks for broken external links. """ @@ -82,34 +107,59 @@ class CheckExternalLinksBuilder(Builder): '%(outdir)s/output.txt') def init(self) -> None: - self.to_ignore = [re.compile(x) for x in self.app.config.linkcheck_ignore] + self.hyperlinks = {} # type: Dict[str, Hyperlink] + self.to_ignore = [re.compile(x) for x in self.config.linkcheck_ignore] self.anchors_ignore = [re.compile(x) - for x in self.app.config.linkcheck_anchors_ignore] + for x in self.config.linkcheck_anchors_ignore] self.auth = [(re.compile(pattern), auth_info) for pattern, auth_info - in self.app.config.linkcheck_auth] - self.good = set() # type: Set[str] - self.broken = {} # type: Dict[str, str] - self.redirected = {} # type: Dict[str, Tuple[str, int]] + in self.config.linkcheck_auth] + self._good = set() # type: Set[str] + self._broken = {} # type: Dict[str, str] + self._redirected = {} # type: Dict[str, Tuple[str, int]] # set a timeout for non-responding servers socket.setdefaulttimeout(5.0) - # create output file - open(path.join(self.outdir, 'output.txt'), 'w').close() - # create JSON output file - open(path.join(self.outdir, 'output.json'), 'w').close() # create queues and worker threads - self.wqueue = queue.Queue() # type: queue.Queue + self.rate_limits = {} # type: Dict[str, RateLimit] + self.wqueue = queue.PriorityQueue() # type: queue.PriorityQueue self.rqueue = queue.Queue() # type: queue.Queue self.workers = [] # type: List[threading.Thread] - for i in range(self.app.config.linkcheck_workers): + for i in range(self.config.linkcheck_workers): thread = threading.Thread(target=self.check_thread, daemon=True) thread.start() self.workers.append(thread) + @property + def good(self) -> Set[str]: + warnings.warn( + "%s.%s is deprecated." % (self.__class__.__name__, "good"), + RemovedInSphinx50Warning, + stacklevel=2, + ) + return self._good + + @property + def broken(self) -> Dict[str, str]: + warnings.warn( + "%s.%s is deprecated." % (self.__class__.__name__, "broken"), + RemovedInSphinx50Warning, + stacklevel=2, + ) + return self._broken + + @property + def redirected(self) -> Dict[str, Tuple[str, int]]: + warnings.warn( + "%s.%s is deprecated." % (self.__class__.__name__, "redirected"), + RemovedInSphinx50Warning, + stacklevel=2, + ) + return self._redirected + def check_thread(self) -> None: kwargs = {} - if self.app.config.linkcheck_timeout: - kwargs['timeout'] = self.app.config.linkcheck_timeout + if self.config.linkcheck_timeout: + kwargs['timeout'] = self.config.linkcheck_timeout def get_request_headers() -> Dict: url = urlparse(uri) @@ -155,9 +205,9 @@ class CheckExternalLinksBuilder(Builder): kwargs['headers'] = get_request_headers() try: - if anchor and self.app.config.linkcheck_anchors: + if anchor and self.config.linkcheck_anchors: # Read the whole document and see if #anchor exists - response = requests.get(req_url, stream=True, config=self.app.config, + response = requests.get(req_url, stream=True, config=self.config, auth=auth_info, **kwargs) response.raise_for_status() found = check_anchor(response, unquote(anchor)) @@ -169,19 +219,28 @@ class CheckExternalLinksBuilder(Builder): # try a HEAD request first, which should be easier on # the server and the network response = requests.head(req_url, allow_redirects=True, - config=self.app.config, auth=auth_info, + config=self.config, auth=auth_info, **kwargs) response.raise_for_status() - except HTTPError: + except (HTTPError, TooManyRedirects) as err: + if isinstance(err, HTTPError) and err.response.status_code == 429: + raise # retry with GET request if that fails, some servers # don't like HEAD requests. - response = requests.get(req_url, stream=True, config=self.app.config, + response = requests.get(req_url, stream=True, + config=self.config, auth=auth_info, **kwargs) response.raise_for_status() except HTTPError as err: if err.response.status_code == 401: # We'll take "Unauthorized" as working. return 'working', ' - unauthorized', 0 + elif err.response.status_code == 429: + next_check = self.limit_rate(err.response) + if next_check is not None: + self.wqueue.put((next_check, uri, docname, lineno), False) + return 'rate-limited', '', 0 + return 'broken', str(err), 0 elif err.response.status_code == 503: # We'll take "Service Unavailable" as ignored. return 'ignored', str(err), 0 @@ -189,6 +248,12 @@ class CheckExternalLinksBuilder(Builder): return 'broken', str(err), 0 except Exception as err: return 'broken', str(err), 0 + else: + netloc = urlparse(req_url).netloc + try: + del self.rate_limits[netloc] + except KeyError: + pass if response.url.rstrip('/') == req_url.rstrip('/'): return 'working', '', 0 else: @@ -219,39 +284,97 @@ class CheckExternalLinksBuilder(Builder): if rex.match(uri): return 'ignored', '', 0 else: - self.broken[uri] = '' + self._broken[uri] = '' return 'broken', '', 0 - elif uri in self.good: + elif uri in self._good: return 'working', 'old', 0 - elif uri in self.broken: - return 'broken', self.broken[uri], 0 - elif uri in self.redirected: - return 'redirected', self.redirected[uri][0], self.redirected[uri][1] + elif uri in self._broken: + return 'broken', self._broken[uri], 0 + elif uri in self._redirected: + return 'redirected', self._redirected[uri][0], self._redirected[uri][1] for rex in self.to_ignore: if rex.match(uri): return 'ignored', '', 0 # need to actually check the URI - for _ in range(self.app.config.linkcheck_retries): + for _ in range(self.config.linkcheck_retries): status, info, code = check_uri() if status != "broken": break if status == "working": - self.good.add(uri) + self._good.add(uri) elif status == "broken": - self.broken[uri] = info + self._broken[uri] = info elif status == "redirected": - self.redirected[uri] = (info, code) + self._redirected[uri] = (info, code) return (status, info, code) while True: - uri, docname, lineno = self.wqueue.get() + next_check, uri, docname, lineno = self.wqueue.get() if uri is None: break + netloc = urlparse(uri).netloc + try: + # Refresh rate limit. + # When there are many links in the queue, workers are all stuck waiting + # for responses, but the builder keeps queuing. Links in the queue may + # have been queued before rate limits were discovered. + next_check = self.rate_limits[netloc].next_check + except KeyError: + pass + if next_check > time.time(): + # Sleep before putting message back in the queue to avoid + # waking up other threads. + time.sleep(QUEUE_POLL_SECS) + self.wqueue.put((next_check, uri, docname, lineno), False) + self.wqueue.task_done() + continue status, info, code = check(docname) - self.rqueue.put((uri, docname, lineno, status, info, code)) + if status == 'rate-limited': + logger.info(darkgray('-rate limited- ') + uri + darkgray(' | sleeping...')) + else: + self.rqueue.put((uri, docname, lineno, status, info, code)) + self.wqueue.task_done() + + def limit_rate(self, response: Response) -> Optional[float]: + next_check = None + retry_after = response.headers.get("Retry-After") + if retry_after: + try: + # Integer: time to wait before next attempt. + delay = float(retry_after) + except ValueError: + try: + # An HTTP-date: time of next attempt. + until = parsedate_to_datetime(retry_after) + except (TypeError, ValueError): + # TypeError: Invalid date format. + # ValueError: Invalid date, e.g. Oct 52th. + pass + else: + next_check = datetime.timestamp(until) + delay = (until - datetime.now(timezone.utc)).total_seconds() + else: + next_check = time.time() + delay + netloc = urlparse(response.url).netloc + if next_check is None: + max_delay = self.config.linkcheck_rate_limit_timeout + try: + rate_limit = self.rate_limits[netloc] + except KeyError: + delay = DEFAULT_DELAY + else: + last_wait_time = rate_limit.delay + delay = 2.0 * last_wait_time + if delay > max_delay and last_wait_time < max_delay: + delay = max_delay + if delay > max_delay: + return None + next_check = time.time() + delay + self.rate_limits[netloc] = RateLimit(delay, next_check) + return next_check def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None: uri, docname, lineno, status, info, code = result @@ -305,62 +428,71 @@ class CheckExternalLinksBuilder(Builder): self.write_entry('redirected ' + text, docname, filename, lineno, uri + ' to ' + info) self.write_linkstat(linkstat) + else: + raise ValueError("Unknown status %s." % status) - def get_target_uri(self, docname: str, typ: str = None) -> str: - return '' + def write_entry(self, what: str, docname: str, filename: str, line: int, + uri: str) -> None: + self.txt_outfile.write("%s:%s: [%s] %s\n" % (filename, line, what, uri)) - def get_outdated_docs(self) -> Set[str]: - return self.env.found_docs + def write_linkstat(self, data: dict) -> None: + self.json_outfile.write(json.dumps(data)) + self.json_outfile.write('\n') - def prepare_writing(self, docnames: Set[str]) -> None: - return - - def write_doc(self, docname: str, doctree: Node) -> None: + def finish(self) -> None: logger.info('') - n = 0 + + for hyperlink in self.hyperlinks.values(): + self.wqueue.put(hyperlink, False) + + total_links = len(self.hyperlinks) + done = 0 + with open(path.join(self.outdir, 'output.txt'), 'w') as self.txt_outfile,\ + open(path.join(self.outdir, 'output.json'), 'w') as self.json_outfile: + while done < total_links: + self.process_result(self.rqueue.get()) + done += 1 + + if self._broken: + self.app.statuscode = 1 + + self.wqueue.join() + # Shutdown threads. + for worker in self.workers: + self.wqueue.put((CHECK_IMMEDIATELY, None, None, None), False) + + +class HyperlinkCollector(SphinxPostTransform): + builders = ('linkcheck',) + default_priority = 800 + + def run(self, **kwargs: Any) -> None: + builder = cast(CheckExternalLinksBuilder, self.app.builder) + hyperlinks = builder.hyperlinks # reference nodes - for refnode in doctree.traverse(nodes.reference): + for refnode in self.document.traverse(nodes.reference): if 'refuri' not in refnode: continue uri = refnode['refuri'] lineno = get_node_line(refnode) - self.wqueue.put((uri, docname, lineno), False) - n += 1 + uri_info = Hyperlink(CHECK_IMMEDIATELY, uri, self.env.docname, lineno) + if uri not in hyperlinks: + hyperlinks[uri] = uri_info # image nodes - for imgnode in doctree.traverse(nodes.image): + for imgnode in self.document.traverse(nodes.image): uri = imgnode['candidates'].get('?') if uri and '://' in uri: lineno = get_node_line(imgnode) - self.wqueue.put((uri, docname, lineno), False) - n += 1 - - done = 0 - while done < n: - self.process_result(self.rqueue.get()) - done += 1 - - if self.broken: - self.app.statuscode = 1 - - def write_entry(self, what: str, docname: str, filename: str, line: int, - uri: str) -> None: - with open(path.join(self.outdir, 'output.txt'), 'a') as output: - output.write("%s:%s: [%s] %s\n" % (filename, line, what, uri)) - - def write_linkstat(self, data: dict) -> None: - with open(path.join(self.outdir, 'output.json'), 'a') as output: - output.write(json.dumps(data)) - output.write('\n') - - def finish(self) -> None: - for worker in self.workers: - self.wqueue.put((None, None, None), False) + uri_info = Hyperlink(CHECK_IMMEDIATELY, uri, self.env.docname, lineno) + if uri not in hyperlinks: + hyperlinks[uri] = uri_info def setup(app: Sphinx) -> Dict[str, Any]: app.add_builder(CheckExternalLinksBuilder) + app.add_post_transform(HyperlinkCollector) app.add_config_value('linkcheck_ignore', [], None) app.add_config_value('linkcheck_auth', [], None) @@ -372,6 +504,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: # Anchors starting with ! are ignored since they are # commonly used for dynamic pages app.add_config_value('linkcheck_anchors_ignore', ["^!"], None) + app.add_config_value('linkcheck_rate_limit_timeout', 300.0, None) return { 'version': 'builtin', diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index b5e8c77cb..292f495de 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -4,7 +4,7 @@ Manual pages builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 a1fe44666..c5219dd75 100644 --- a/sphinx/builders/qthelp.py +++ b/sphinx/builders/qthelp.py @@ -4,7 +4,7 @@ Build input files for the Qt collection generator. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/builders/singlehtml.py b/sphinx/builders/singlehtml.py index d8ab978ed..2e72887e3 100644 --- a/sphinx/builders/singlehtml.py +++ b/sphinx/builders/singlehtml.py @@ -4,7 +4,7 @@ Single HTML builders. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 345934bd4..1a56be0f9 100644 --- a/sphinx/builders/texinfo.py +++ b/sphinx/builders/texinfo.py @@ -4,7 +4,7 @@ Texinfo builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -179,7 +179,8 @@ class TexinfoBuilder(Builder): try: imagedir = path.join(self.outdir, targetname + '-figures') ensuredir(imagedir) - copy_asset_file(path.join(self.srcdir, dest), imagedir) + copy_asset_file(path.join(self.srcdir, src), + path.join(imagedir, dest)) except Exception as err: logger.warning(__('cannot copy image file %r: %s'), path.join(self.srcdir, src), err) diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py index 463f4bc55..ae770818c 100644 --- a/sphinx/builders/text.py +++ b/sphinx/builders/text.py @@ -4,7 +4,7 @@ Plain-text Sphinx builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 c31c6d799..1b051119b 100644 --- a/sphinx/builders/xml.py +++ b/sphinx/builders/xml.py @@ -4,7 +4,7 @@ Docutils-native XML and pseudo-XML builders. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/cmd/__init__.py b/sphinx/cmd/__init__.py index 33fbf747b..583e50ed7 100644 --- a/sphinx/cmd/__init__.py +++ b/sphinx/cmd/__init__.py @@ -4,6 +4,6 @@ Modules for command line executables. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index f75b13fc8..32a89eb29 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -4,7 +4,7 @@ Build documentation from a provided source. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/cmd/make_mode.py b/sphinx/cmd/make_mode.py index 9043868e1..aaa40fba0 100644 --- a/sphinx/cmd/make_mode.py +++ b/sphinx/cmd/make_mode.py @@ -10,7 +10,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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index f9def2656..4234c039c 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -4,7 +4,7 @@ Quickly setup documentation source to work with Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -29,6 +29,7 @@ try: readline.parse_and_bind("tab: complete") USE_LIBEDIT = False except ImportError: + readline = None USE_LIBEDIT = False from docutils.utils import column_width @@ -169,8 +170,11 @@ def do_prompt(text: str, default: str = None, validator: Callable[[str], Any] = # sequence (see #5335). To avoid the problem, all prompts are not colored # on libedit. pass - else: + elif readline: + # pass input_mode=True if readline available prompt = colorize(COLOR_QUESTION, prompt, input_mode=True) + else: + prompt = colorize(COLOR_QUESTION, prompt, input_mode=False) x = term_input(prompt).strip() if default and not x: x = default diff --git a/sphinx/config.py b/sphinx/config.py index b3cd1dc6d..4c038b061 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -4,7 +4,7 @@ Build configuration file handling. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -98,7 +98,8 @@ class Config: # general options 'project': ('Python', 'env', []), 'author': ('unknown', 'env', []), - 'copyright': ('', 'html', []), + 'project_copyright': ('', 'html', [str]), + 'copyright': (lambda c: c.project_copyright, 'html', [str]), 'version': ('', 'env', []), 'release': ('', 'env', []), 'today': ('', 'env', []), @@ -180,6 +181,14 @@ class Config: defvalue = self.values[name][0] if self.values[name][2] == Any: return value + elif self.values[name][2] == {bool, str}: + if value == '0': + # given falsy string from command line option + return False + elif value == '1': + return True + else: + return value elif type(defvalue) is bool or self.values[name][2] == [bool]: if value == '0': # given falsy string from command line option @@ -358,6 +367,18 @@ def convert_source_suffix(app: "Sphinx", config: Config) -> None: "But `%r' is given." % source_suffix)) +def convert_highlight_options(app: "Sphinx", config: Config) -> None: + """Convert old styled highlight_options to new styled one. + + * old style: options + * new style: dict that maps language names to options + """ + options = config.highlight_options + if options and not all(isinstance(v, dict) for v in options.values()): + # old styled option detected because all values are not dictionary. + config.highlight_options = {config.highlight_language: options} # type: ignore + + def init_numfig_format(app: "Sphinx", config: Config) -> None: """Initialize :confval:`numfig_format`.""" numfig_format = {'section': _('Section %s'), @@ -478,6 +499,7 @@ def check_master_doc(app: "Sphinx", env: "BuildEnvironment", added: Set[str], def setup(app: "Sphinx") -> Dict[str, Any]: app.connect('config-inited', convert_source_suffix, priority=800) + app.connect('config-inited', convert_highlight_options, priority=800) app.connect('config-inited', init_numfig_format, priority=800) app.connect('config-inited', correct_copyright_year, priority=800) app.connect('config-inited', check_confval_types, priority=800) diff --git a/sphinx/deprecation.py b/sphinx/deprecation.py index 48db97760..161d7bcac 100644 --- a/sphinx/deprecation.py +++ b/sphinx/deprecation.py @@ -4,7 +4,7 @@ Sphinx deprecation classes and utilities. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -26,6 +26,10 @@ class RemovedInSphinx50Warning(PendingDeprecationWarning): pass +class RemovedInSphinx60Warning(PendingDeprecationWarning): + pass + + RemovedInNextVersionWarning = RemovedInSphinx40Warning diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index ecfbbd69d..e386b3eaa 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -4,12 +4,12 @@ Handlers for additional ReST directives. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re -from typing import Any, Dict, List, Tuple, cast +from typing import Any, Dict, Generic, List, Tuple, TypeVar, cast from docutils import nodes from docutils.nodes import Node @@ -33,6 +33,8 @@ if False: nl_escape_re = re.compile(r'\\\n') strip_backslash_re = re.compile(r'\\(.)') +T = TypeVar('T') + def optional_int(argument: str) -> int: """ @@ -47,7 +49,7 @@ def optional_int(argument: str) -> int: return value -class ObjectDescription(SphinxDirective): +class ObjectDescription(SphinxDirective, Generic[T]): """ Directive to describe a class, function or similar object. Not used directly, but subclassed (in domain-specific directives) to add custom @@ -97,7 +99,7 @@ class ObjectDescription(SphinxDirective): else: return [line.strip() for line in lines] - def handle_signature(self, sig: str, signode: desc_signature) -> Any: + def handle_signature(self, sig: str, signode: desc_signature) -> T: """ Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole @@ -109,7 +111,7 @@ class ObjectDescription(SphinxDirective): """ raise ValueError - def add_target_and_index(self, name: Any, sig: str, signode: desc_signature) -> None: + def add_target_and_index(self, name: T, sig: str, signode: desc_signature) -> None: """ Add cross-reference IDs and entries to self.indexnode, if applicable. @@ -173,7 +175,7 @@ class ObjectDescription(SphinxDirective): if self.domain: node['classes'].append(self.domain) - self.names = [] # type: List[Any] + self.names = [] # type: List[T] signatures = self.get_signatures() for i, sig in enumerate(signatures): # add a signature node for each signature in the current unit diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index df193017d..e01b8f9ec 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -2,11 +2,12 @@ sphinx.directives.code ~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import sys +import textwrap import warnings from difflib import unified_diff from typing import Any, Dict, List, Tuple @@ -19,6 +20,7 @@ from docutils.statemachine import StringList from sphinx import addnodes from sphinx.config import Config from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.directives import optional_int from sphinx.locale import __ from sphinx.util import logging, parselinenos from sphinx.util.docutils import SphinxDirective @@ -68,7 +70,7 @@ class HighlightLang(Highlight): def dedent_lines(lines: List[str], dedent: int, location: Tuple[str, int] = None) -> List[str]: if not dedent: - return lines + return textwrap.dedent(''.join(lines)).splitlines(True) if any(s[:dedent].strip() for s in lines): logger.warning(__('non-whitespace stripped by dedent'), location=location) @@ -117,7 +119,7 @@ class CodeBlock(SphinxDirective): option_spec = { 'force': directives.flag, 'linenos': directives.flag, - 'dedent': int, + 'dedent': optional_int, 'lineno-start': int, 'emphasize-lines': directives.unchanged_required, 'caption': directives.unchanged_required, @@ -391,7 +393,7 @@ class LiteralInclude(SphinxDirective): optional_arguments = 0 final_argument_whitespace = True option_spec = { - 'dedent': int, + 'dedent': optional_int, 'linenos': directives.flag, 'lineno-start': int, 'lineno-match': directives.flag, diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 8158f56a8..2ace3738f 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -2,7 +2,7 @@ sphinx.directives.other ~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -276,6 +276,7 @@ class HList(SphinxDirective): npercol, nmore = divmod(len(fulllist), ncolumns) index = 0 newnode = addnodes.hlist() + newnode['ncolumns'] = str(ncolumns) for column in range(ncolumns): endindex = index + ((npercol + 1) if column < nmore else npercol) bullet_list = nodes.bullet_list() diff --git a/sphinx/directives/patches.py b/sphinx/directives/patches.py index acb79f636..1eae6d0c8 100644 --- a/sphinx/directives/patches.py +++ b/sphinx/directives/patches.py @@ -2,7 +2,7 @@ sphinx.directives.patches ~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 c351a30c2..60142f584 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -5,7 +5,7 @@ Support for domains, which are groupings of description directives and roles describing e.g. constructs of one programming language. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 dc69c257a..5336b003c 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -4,7 +4,7 @@ The C language domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -137,8 +137,7 @@ class ASTIdentifier(ASTBaseBase): reftype='identifier', reftarget=targetText, modname=None, classname=None) - key = symbol.get_lookup_key() - pnode['c:parent_key'] = key + pnode['c:parent_key'] = symbol.get_lookup_key() if self.is_anon(): pnode += nodes.strong(text="[anonymous]") else: @@ -1582,13 +1581,11 @@ class Symbol: def get_all_symbols(self) -> Iterator["Symbol"]: yield self for sChild in self._children: - for s in sChild.get_all_symbols(): - yield s + yield from sChild.get_all_symbols() @property def children(self) -> Iterator["Symbol"]: - for c in self._children: - yield c + yield from self._children @property def children_recurse_anon(self) -> Iterator["Symbol"]: @@ -3101,7 +3098,7 @@ def _make_phony_error_name() -> ASTNestedName: return ASTNestedName([ASTIdentifier("PhonyNameDueToError")], rooted=False) -class CObject(ObjectDescription): +class CObject(ObjectDescription[ASTDeclaration]): """ Description of a C language object. """ @@ -3206,7 +3203,8 @@ class CObject(ObjectDescription): def parse_pre_v3_type_definition(self, parser: DefinitionParser) -> ASTDeclaration: return parser.parse_pre_v3_type_definition() - def describe_signature(self, signode: TextElement, ast: Any, options: Dict) -> None: + def describe_signature(self, signode: TextElement, ast: ASTDeclaration, + options: Dict) -> None: ast.describe_signature(signode, 'lastIsName', self.env, options) def run(self) -> List[Node]: @@ -3453,8 +3451,9 @@ class AliasNode(nodes.Element): assert parentKey is not None self.parentKey = parentKey - def copy(self: T) -> T: - return self.__class__(self.sig, env=None, parentKey=self.parentKey) # type: ignore + def copy(self) -> 'AliasNode': + return self.__class__(self.sig, self.maxdepth, self.document, + env=None, parentKey=self.parentKey) class AliasTransform(SphinxTransform): @@ -3643,7 +3642,7 @@ class CExprRole(SphinxRole): location=self.get_source_info()) # see below return [self.node_type(text, text, classes=classes)], [] - parentSymbol = self.env.temp_data.get('cpp:parent_symbol', None) + parentSymbol = self.env.temp_data.get('c:parent_symbol', None) if parentSymbol is None: parentSymbol = self.env.domaindata['c']['root_symbol'] # ...most if not all of these classes should really apply to the individual references, @@ -3658,15 +3657,18 @@ class CDomain(Domain): name = 'c' label = 'C' object_types = { - 'function': ObjType(_('function'), 'func'), - 'member': ObjType(_('member'), 'member'), - 'macro': ObjType(_('macro'), 'macro'), - 'type': ObjType(_('type'), 'type'), - 'var': ObjType(_('variable'), 'data'), - 'enum': ObjType(_('enum'), 'enum'), - 'enumerator': ObjType(_('enumerator'), 'enumerator'), - 'struct': ObjType(_('struct'), 'struct'), - 'union': ObjType(_('union'), 'union'), + # 'identifier' is the one used for xrefs generated in signatures, not in roles + 'member': ObjType(_('member'), 'var', 'member', 'data', 'identifier'), + 'var': ObjType(_('variable'), 'var', 'member', 'data', 'identifier'), + 'function': ObjType(_('function'), 'func', 'identifier', 'type'), + 'macro': ObjType(_('macro'), 'macro', 'identifier'), + 'struct': ObjType(_('struct'), 'struct', 'identifier', 'type'), + 'union': ObjType(_('union'), 'union', 'identifier', 'type'), + 'enum': ObjType(_('enum'), 'enum', 'identifier', 'type'), + 'enumerator': ObjType(_('enumerator'), 'enumerator', 'identifier'), + 'type': ObjType(_('type'), 'identifier', 'type'), + # generated object types + 'functionParam': ObjType(_('function parameter'), 'identifier', 'var', 'member', 'data'), # noqa } directives = { diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py index d51e2f1a1..ba323b729 100644 --- a/sphinx/domains/changeset.py +++ b/sphinx/domains/changeset.py @@ -4,7 +4,7 @@ The changeset domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/citation.py b/sphinx/domains/citation.py index 9546211d5..1ceb53037 100644 --- a/sphinx/domains/citation.py +++ b/sphinx/domains/citation.py @@ -4,7 +4,7 @@ The citation domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 35df74027..25e6f1421 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -4,7 +4,7 @@ The C++ language domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -1592,6 +1592,15 @@ class ASTOperator(ASTBase): identifier = str(self) if mode == 'lastIsName': signode += addnodes.desc_name(identifier, identifier) + elif mode == 'markType': + targetText = prefix + identifier + templateArgs + pnode = addnodes.pending_xref('', refdomain='cpp', + reftype='identifier', + reftarget=targetText, modname=None, + classname=None) + pnode['cpp:parent_key'] = symbol.get_lookup_key() + pnode += nodes.Text(identifier) + signode += pnode else: signode += addnodes.desc_addname(identifier, identifier) @@ -3927,8 +3936,7 @@ class Symbol: def get_all_symbols(self) -> Iterator[Any]: yield self for sChild in self._children: - for s in sChild.get_all_symbols(): - yield s + yield from sChild.get_all_symbols() @property def children_recurse_anon(self) -> Generator["Symbol", None, None]: @@ -6671,7 +6679,7 @@ def _make_phony_error_name() -> ASTNestedName: return ASTNestedName([nne], [False], rooted=False) -class CPPObject(ObjectDescription): +class CPPObject(ObjectDescription[ASTDeclaration]): """Description of a C++ language object.""" doc_field_types = [ @@ -7051,8 +7059,8 @@ class AliasNode(nodes.Element): assert parentKey is not None self.parentKey = parentKey - def copy(self: T) -> T: - return self.__class__(self.sig, env=None, parentKey=self.parentKey) # type: ignore + def copy(self) -> 'AliasNode': + return self.__class__(self.sig, env=None, parentKey=self.parentKey) class AliasTransform(SphinxTransform): @@ -7252,14 +7260,18 @@ class CPPDomain(Domain): name = 'cpp' label = 'C++' object_types = { - 'class': ObjType(_('class'), 'class', 'type', 'identifier'), - 'union': ObjType(_('union'), 'union', 'type', 'identifier'), - 'function': ObjType(_('function'), 'function', 'func', 'type', 'identifier'), - 'member': ObjType(_('member'), 'member', 'var'), - 'type': ObjType(_('type'), 'type', 'identifier'), - 'concept': ObjType(_('concept'), 'concept', 'identifier'), - 'enum': ObjType(_('enum'), 'enum', 'type', 'identifier'), - 'enumerator': ObjType(_('enumerator'), 'enumerator') + 'class': ObjType(_('class'), 'class', 'struct', 'identifier', 'type'), + 'union': ObjType(_('union'), 'union', 'identifier', 'type'), + 'function': ObjType(_('function'), 'func', 'identifier', 'type'), + 'member': ObjType(_('member'), 'member', 'var', 'identifier'), + 'type': ObjType(_('type'), 'identifier', 'type'), + 'concept': ObjType(_('concept'), 'concept', 'identifier'), + 'enum': ObjType(_('enum'), 'enum', 'identifier', 'type'), + 'enumerator': ObjType(_('enumerator'), 'enumerator', 'identifier'), + # generated object types + 'functionParam': ObjType(_('function parameter'), 'identifier', 'member', 'var'), # noqa + 'templateParam': ObjType(_('template parameter'), + 'identifier', 'class', 'struct', 'union', 'member', 'var', 'type'), # noqa } directives = { @@ -7436,30 +7448,19 @@ class CPPDomain(Domain): if typ.startswith('cpp:'): typ = typ[4:] - origTyp = typ - if typ == 'func': - typ = 'function' - if typ == 'struct': - typ = 'class' declTyp = s.declaration.objectType def checkType() -> bool: - if typ == 'any' or typ == 'identifier': + if typ == 'any': return True - if declTyp == 'templateParam': - # TODO: perhaps this should be strengthened one day - return True - if declTyp == 'functionParam': - if typ == 'var' or typ == 'member': - return True objtypes = self.objtypes_for_role(typ) if objtypes: return declTyp in objtypes - print("Type is %s (originally: %s), declType is %s" % (typ, origTyp, declTyp)) + print("Type is %s, declaration type is %s" % (typ, declTyp)) assert False if not checkType(): logger.warning("cpp:%s targets a %s (%s).", - origTyp, s.declaration.objectType, + typ, s.declaration.objectType, s.get_full_nested_name(), location=node) @@ -7489,10 +7490,10 @@ class CPPDomain(Domain): if env.config.add_function_parentheses and typ == 'any': addParen += 1 # and now this stuff for operator() - if (env.config.add_function_parentheses and typ == 'function' and + if (env.config.add_function_parentheses and typ == 'func' and title.endswith('operator()')): addParen += 1 - if ((typ == 'any' or typ == 'function') and + if ((typ == 'any' or typ == 'func') and title.endswith('operator') and displayName.endswith('operator()')): addParen += 1 @@ -7501,7 +7502,7 @@ class CPPDomain(Domain): if env.config.add_function_parentheses: if typ == 'any' and displayName.endswith('()'): addParen += 1 - elif typ == 'function': + elif typ == 'func': if title.endswith('()') and not displayName.endswith('()'): title = title[:-2] else: diff --git a/sphinx/domains/index.py b/sphinx/domains/index.py index 552b46cf9..4a91d6ad1 100644 --- a/sphinx/domains/index.py +++ b/sphinx/domains/index.py @@ -4,7 +4,7 @@ The index domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index 6a431540c..f612fb914 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -4,7 +4,7 @@ The JavaScript domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -32,7 +32,7 @@ from sphinx.util.nodes import make_id, make_refnode logger = logging.getLogger(__name__) -class JSObject(ObjectDescription): +class JSObject(ObjectDescription[Tuple[str, str]]): """ Description of a JavaScript object. """ diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py index a2f4bd964..248a7a2a6 100644 --- a/sphinx/domains/math.py +++ b/sphinx/domains/math.py @@ -4,7 +4,7 @@ The math domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -157,8 +157,11 @@ class MathDomain(Domain): targets = [eq for eq in self.equations.values() if eq[0] == docname] return len(targets) + 1 - def has_equations(self) -> bool: - return any(self.data['has_equations'].values()) + def has_equations(self, docname: str = None) -> bool: + if docname: + return self.data['has_equations'].get(docname, False) + else: + return any(self.data['has_equations'].values()) def setup(app: "Sphinx") -> Dict[str, Any]: diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 79d7e4f46..c2af9886f 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -4,7 +4,7 @@ The Python domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -272,6 +272,8 @@ class PyXrefMixin: result = super().make_xref(rolename, domain, target, # type: ignore innernode, contnode, env) result['refspecific'] = True + result['py:module'] = env.ref_context.get('py:module') + result['py:class'] = env.ref_context.get('py:class') if target.startswith(('.', '~')): prefix, result['reftarget'] = target[0], target[1:] if prefix == '.': @@ -332,7 +334,7 @@ class PyTypedField(PyXrefMixin, TypedField): return super().make_xref(rolename, domain, target, innernode, contnode, env) -class PyObject(ObjectDescription): +class PyObject(ObjectDescription[Tuple[str, str]]): """ Description of a general Python object. diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index bc9ffda07..07bf46b75 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -4,7 +4,7 @@ The reStructuredText domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -31,7 +31,7 @@ logger = logging.getLogger(__name__) dir_sig_re = re.compile(r'\.\. (.+?)::(.*)$') -class ReSTMarkup(ObjectDescription): +class ReSTMarkup(ObjectDescription[str]): """ Description of generic reST markup. """ diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 4bde57930..352e5c7f3 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -4,7 +4,7 @@ The standard domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -43,12 +43,12 @@ logger = logging.getLogger(__name__) # RE for option descriptions -option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=[]+)(=?\s*.*)') +option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)') # RE for grammar tokens -token_re = re.compile(r'`(\w+)`', re.U) +token_re = re.compile(r'`((~?\w*:)?\w+)`', re.U) -class GenericObject(ObjectDescription): +class GenericObject(ObjectDescription[str]): """ A generic x-ref directive registered with Sphinx.add_object_type(). """ @@ -178,7 +178,7 @@ class Target(SphinxDirective): return self.name + '-' + name -class Cmdoption(ObjectDescription): +class Cmdoption(ObjectDescription[str]): """ Description of a command-line option (.. option). """ @@ -197,6 +197,11 @@ class Cmdoption(ObjectDescription): location=signode) continue optname, args = m.groups() + if optname.endswith('[') and args.endswith(']'): + # optional value surrounded by brackets (ex. foo[=bar]) + optname = optname[:-1] + args = '[' + args + if count: signode += addnodes.desc_addname(', ', ', ') signode += addnodes.desc_name(optname, optname) @@ -318,7 +323,7 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index term['ids'].append(node_id) std = cast(StandardDomain, env.get_domain('std')) - std.note_object('term', termtext, node_id, location=term) + std._note_term(termtext, node_id, location=term) # add an index entry too indexnode = addnodes.index() @@ -459,9 +464,23 @@ def token_xrefs(text: str, productionGroup: str = '') -> List[Node]: if m.start() > pos: txt = text[pos:m.start()] retnodes.append(nodes.Text(txt, txt)) - refnode = pending_xref(m.group(1), reftype='token', refdomain='std', - reftarget=productionGroup + m.group(1)) - refnode += nodes.literal(m.group(1), m.group(1), classes=['xref']) + token = m.group(1) + if ':' in token: + if token[0] == '~': + _, title = token.split(':') + target = token[1:] + elif token[0] == ':': + title = token[1:] + target = title + else: + title = token + target = token + else: + title = token + target = productionGroup + token + refnode = pending_xref(title, reftype='token', refdomain='std', + reftarget=target) + refnode += nodes.literal(token, title, classes=['xref']) retnodes.append(refnode) pos = m.end() if pos < len(text): @@ -675,6 +694,20 @@ class StandardDomain(Domain): RemovedInSphinx50Warning, stacklevel=2) self.objects[objtype, name] = (docname, labelid) + @property + def _terms(self) -> Dict[str, Tuple[str, str]]: + """.. note:: Will be removed soon. internal use only.""" + return self.data.setdefault('terms', {}) # (name) -> docname, labelid + + def _note_term(self, term: str, labelid: str, location: Any = None) -> None: + """Note a term for cross reference. + + .. note:: Will be removed soon. internal use only. + """ + self.note_object('term', term, labelid, location) + + self._terms[term.lower()] = (self.env.docname, labelid) + @property def progoptions(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid @@ -695,6 +728,9 @@ class StandardDomain(Domain): for key, (fn, _l) in list(self.objects.items()): if fn == docname: del self.objects[key] + for key, (fn, _l) in list(self._terms.items()): + if fn == docname: + del self._terms[key] for key, (fn, _l, _l) in list(self.labels.items()): if fn == docname: del self.labels[key] @@ -710,6 +746,9 @@ class StandardDomain(Domain): for key, data in otherdata['objects'].items(): if data[0] in docnames: self.objects[key] = data + for key, data in otherdata['terms'].items(): + if data[0] in docnames: + self._terms[key] = data for key, data in otherdata['labels'].items(): if data[0] in docnames: self.labels[key] = data @@ -740,9 +779,11 @@ class StandardDomain(Domain): name, env.doc2path(self.labels[name][0]), location=node) self.anonlabels[name] = docname, labelid - if node.tagname in ('section', 'rubric'): + if node.tagname == 'section': title = cast(nodes.title, node[0]) sectname = clean_astext(title) + elif node.tagname == 'rubric': + sectname = clean_astext(node) elif self.is_enumerable_node(node): sectname = self.get_numfig_title(node) if not sectname: @@ -852,8 +893,9 @@ class StandardDomain(Domain): if fignumber is None: return contnode except ValueError: - logger.warning(__("no number is assigned for %s: %s"), figtype, labelid, - location=node) + logger.warning(__("Failed to create a cross reference. Any number is not " + "assigned: %s"), + labelid, location=node) return contnode try: @@ -945,19 +987,12 @@ class StandardDomain(Domain): if result: return result else: - for objtype, term in self.objects: - if objtype == 'term' and term.lower() == target.lower(): - docname, labelid = self.objects[objtype, term] - logger.warning(__('term %s not found in case sensitive match.' - 'made a reference to %s instead.'), - target, term, location=node, type='ref', subtype='term') - break + # fallback to case insentive match + if target.lower() in self._terms: + docname, labelid = self._terms[target.lower()] + return make_refnode(builder, fromdocname, docname, labelid, contnode) else: - docname, labelid = '', '' - if not docname: return None - return make_refnode(builder, fromdocname, docname, - labelid, contnode) def _resolve_obj_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, @@ -1106,7 +1141,7 @@ class StandardDomain(Domain): def warn_missing_reference(app: "Sphinx", domain: Domain, node: pending_xref) -> bool: - if domain.name != 'std' or node['reftype'] != 'ref': + if (domain and domain.name != 'std') or node['reftype'] != 'ref': return None else: target = node['reftarget'] @@ -1125,7 +1160,7 @@ def setup(app: "Sphinx") -> Dict[str, Any]: return { 'version': 'builtin', - 'env_version': 1, + 'env_version': 2, 'parallel_read_safe': True, 'parallel_write_safe': True, } diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index d52e895be..af3e2b8d5 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -4,12 +4,13 @@ Global creation environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os import pickle +import posixpath import warnings from collections import defaultdict from copy import copy @@ -356,9 +357,9 @@ class BuildEnvironment: docdir = path.dirname(self.doc2path(docname or self.docname, base=None)) rel_fn = path.join(docdir, filename) - # the path.abspath() might seem redundant, but otherwise artifacts - # such as ".." will remain in the path - return rel_fn, path.abspath(path.join(self.srcdir, rel_fn)) + + return (posixpath.normpath(rel_fn), + path.normpath(path.join(self.srcdir, rel_fn))) @property def found_docs(self) -> Set[str]: diff --git a/sphinx/environment/adapters/__init__.py b/sphinx/environment/adapters/__init__.py index c9cde6364..1e763cb01 100644 --- a/sphinx/environment/adapters/__init__.py +++ b/sphinx/environment/adapters/__init__.py @@ -4,6 +4,6 @@ Sphinx environment adapters - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/environment/adapters/asset.py b/sphinx/environment/adapters/asset.py index 38e4eb064..f16a5f7d3 100644 --- a/sphinx/environment/adapters/asset.py +++ b/sphinx/environment/adapters/asset.py @@ -4,7 +4,7 @@ Assets adapter for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/environment/adapters/indexentries.py b/sphinx/environment/adapters/indexentries.py index 427024a00..e662bfe4a 100644 --- a/sphinx/environment/adapters/indexentries.py +++ b/sphinx/environment/adapters/indexentries.py @@ -4,7 +4,7 @@ Index entries adapters for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py index c49da5637..93555d172 100644 --- a/sphinx/environment/adapters/toctree.py +++ b/sphinx/environment/adapters/toctree.py @@ -4,7 +4,7 @@ Toctree adapter for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -320,8 +320,10 @@ class TocTree: toctrees = [] # type: List[Element] if 'includehidden' not in kwargs: kwargs['includehidden'] = True - if 'maxdepth' not in kwargs: + if 'maxdepth' not in kwargs or not kwargs['maxdepth']: kwargs['maxdepth'] = 0 + else: + kwargs['maxdepth'] = int(kwargs['maxdepth']) kwargs['collapse'] = collapse for toctreenode in doctree.traverse(addnodes.toctree): toctree = self.resolve(docname, builder, toctreenode, prune=True, **kwargs) diff --git a/sphinx/environment/collectors/__init__.py b/sphinx/environment/collectors/__init__.py index 53e3a1c72..4b7ac3472 100644 --- a/sphinx/environment/collectors/__init__.py +++ b/sphinx/environment/collectors/__init__.py @@ -4,7 +4,7 @@ The data collector components for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/environment/collectors/asset.py b/sphinx/environment/collectors/asset.py index 7f913ebbc..b8d7ae4c0 100644 --- a/sphinx/environment/collectors/asset.py +++ b/sphinx/environment/collectors/asset.py @@ -4,7 +4,7 @@ The image collector for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/environment/collectors/dependencies.py b/sphinx/environment/collectors/dependencies.py index cc0af037e..cd8de918f 100644 --- a/sphinx/environment/collectors/dependencies.py +++ b/sphinx/environment/collectors/dependencies.py @@ -4,7 +4,7 @@ The dependencies collector components for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/environment/collectors/indexentries.py b/sphinx/environment/collectors/indexentries.py index 8c60fa985..351cdc2de 100644 --- a/sphinx/environment/collectors/indexentries.py +++ b/sphinx/environment/collectors/indexentries.py @@ -4,7 +4,7 @@ Index entries collector for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/environment/collectors/metadata.py b/sphinx/environment/collectors/metadata.py index ef872759b..7b3628c9a 100644 --- a/sphinx/environment/collectors/metadata.py +++ b/sphinx/environment/collectors/metadata.py @@ -4,7 +4,7 @@ The metadata collector components for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/environment/collectors/title.py b/sphinx/environment/collectors/title.py index 10cd6c5b0..5225155e8 100644 --- a/sphinx/environment/collectors/title.py +++ b/sphinx/environment/collectors/title.py @@ -4,7 +4,7 @@ The title collector components for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py index 14ff4ed74..772451c56 100644 --- a/sphinx/environment/collectors/toctree.py +++ b/sphinx/environment/collectors/toctree.py @@ -4,7 +4,7 @@ Toctree collector for sphinx.environment. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/errors.py b/sphinx/errors.py index d9a83712c..c632d8dbc 100644 --- a/sphinx/errors.py +++ b/sphinx/errors.py @@ -5,7 +5,7 @@ Contains SphinxError and a few subclasses (in an extra module to avoid circular import problems). - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/events.py b/sphinx/events.py index c8d07eb61..806a6e55d 100644 --- a/sphinx/events.py +++ b/sphinx/events.py @@ -6,7 +6,7 @@ Gracefully adapted from the TextPress system by Armin. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -50,8 +50,6 @@ core_events = { 'warn-missing-reference': 'domain, node', 'doctree-resolved': 'doctree, docname', 'env-updated': 'env', - 'html-collect-pages': 'builder', - 'html-page-context': 'pagename, context, doctree or None', 'build-finished': 'exception', } diff --git a/sphinx/ext/__init__.py b/sphinx/ext/__init__.py index 4f4fd0127..80c185741 100644 --- a/sphinx/ext/__init__.py +++ b/sphinx/ext/__init__.py @@ -4,6 +4,6 @@ Contains Sphinx features not activated by default. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index b01604617..ebbdadbe8 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -10,7 +10,7 @@ Copyright 2008 Société des arts technologiques (SAT), https://sat.qc.ca/ - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -24,7 +24,7 @@ from copy import copy from fnmatch import fnmatch from importlib.machinery import EXTENSION_SUFFIXES from os import path -from typing import Any, List, Tuple +from typing import Any, Generator, List, Tuple import sphinx.locale from sphinx import __display_version__, package_dir @@ -264,14 +264,46 @@ def is_skipped_module(filename: str, opts: Any, excludes: List[str]) -> bool: return False +def walk(rootpath: str, excludes: List[str], opts: Any + ) -> Generator[Tuple[str, List[str], List[str]], None, None]: + """Walk through the directory and list files and subdirectories up.""" + followlinks = getattr(opts, 'followlinks', False) + includeprivate = getattr(opts, 'includeprivate', False) + + for root, subs, files in os.walk(rootpath, followlinks=followlinks): + # document only Python module files (that aren't excluded) + files = sorted(f for f in files + if f.endswith(PY_SUFFIXES) and + not is_excluded(path.join(root, f), excludes)) + + # remove hidden ('.') and private ('_') directories, as well as + # excluded dirs + if includeprivate: + exclude_prefixes = ('.',) # type: Tuple[str, ...] + else: + exclude_prefixes = ('.', '_') + + subs[:] = sorted(sub for sub in subs if not sub.startswith(exclude_prefixes) and + not is_excluded(path.join(root, sub), excludes)) + + yield root, subs, files + + +def has_child_module(rootpath: str, excludes: List[str], opts: Any) -> bool: + """Check the given directory contains child modules at least one.""" + for root, subs, files in walk(rootpath, excludes, opts): + if files: + return True + + return False + + def recurse_tree(rootpath: str, excludes: List[str], opts: Any, user_template_dir: str = None) -> List[str]: """ Look for every file in the directory tree and create the corresponding ReST files. """ - followlinks = getattr(opts, 'followlinks', False) - includeprivate = getattr(opts, 'includeprivate', False) implicit_namespaces = getattr(opts, 'implicit_namespaces', False) # check if the base directory is a package and get its name @@ -282,48 +314,36 @@ def recurse_tree(rootpath: str, excludes: List[str], opts: Any, root_package = None toplevels = [] - for root, subs, files in os.walk(rootpath, followlinks=followlinks): - # document only Python module files (that aren't excluded) - py_files = sorted(f for f in files - if f.endswith(PY_SUFFIXES) and - not is_excluded(path.join(root, f), excludes)) - is_pkg = is_packagedir(None, py_files) + for root, subs, files in walk(rootpath, excludes, opts): + is_pkg = is_packagedir(None, files) is_namespace = not is_pkg and implicit_namespaces if is_pkg: - for f in py_files[:]: + for f in files[:]: if is_initpy(f): - py_files.remove(f) - py_files.insert(0, f) + files.remove(f) + files.insert(0, f) elif root != rootpath: # only accept non-package at toplevel unless using implicit namespaces if not implicit_namespaces: del subs[:] continue - # remove hidden ('.') and private ('_') directories, as well as - # excluded dirs - if includeprivate: - exclude_prefixes = ('.',) # type: Tuple[str, ...] - else: - exclude_prefixes = ('.', '_') - subs[:] = sorted(sub for sub in subs if not sub.startswith(exclude_prefixes) and - not is_excluded(path.join(root, sub), excludes)) if is_pkg or is_namespace: # we are in a package with something to document - if subs or len(py_files) > 1 or not is_skipped_package(root, opts): + if subs or len(files) > 1 or not is_skipped_package(root, opts): subpackage = root[len(rootpath):].lstrip(path.sep).\ replace(path.sep, '.') # if this is not a namespace or # a namespace and there is something there to document - if not is_namespace or len(py_files) > 0: + if not is_namespace or has_child_module(root, excludes, opts): create_package_file(root, root_package, subpackage, - py_files, opts, subs, is_namespace, excludes, + files, opts, subs, is_namespace, excludes, user_template_dir) toplevels.append(module_join(root_package, subpackage)) else: # if we are at the root level, we don't require it to be a package assert root == rootpath and root_package is None - for py_file in py_files: + for py_file in files: if not is_skipped_module(path.join(rootpath, py_file), opts, excludes): module = py_file.split('.')[0] create_module_file(root_package, module, opts, user_template_dir) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 101a62c9e..1490d1551 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -6,11 +6,10 @@ the doctree, thus avoiding duplication between docstrings and documentation for those who like elaborate docstrings. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -import importlib import re import warnings from inspect import Parameter, Signature @@ -23,10 +22,12 @@ from docutils.statemachine import StringList import sphinx from sphinx.application import Sphinx from sphinx.config import ENUM, Config -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warning, + RemovedInSphinx60Warning) from sphinx.environment import BuildEnvironment -from sphinx.ext.autodoc.importer import get_module_members, get_object_members, import_object -from sphinx.ext.autodoc.mock import mock +from sphinx.ext.autodoc.importer import (get_class_members, get_object_members, import_module, + import_object) +from sphinx.ext.autodoc.mock import ismock, mock, undecorate from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import inspect, logging @@ -90,7 +91,7 @@ SLOTSATTR = object() def members_option(arg: Any) -> Union[object, List[str]]: """Used to convert the :members: option to auto directives.""" - if arg is None or arg is True: + if arg in (None, True): return ALL elif arg is False: return None @@ -109,14 +110,14 @@ def members_set_option(arg: Any) -> Union[object, Set[str]]: def exclude_members_option(arg: Any) -> Union[object, Set[str]]: """Used to convert the :exclude-members: option.""" - if arg is None: + if arg in (None, True): return EMPTY return {x.strip() for x in arg.split(',') if x.strip()} def inherited_members_option(arg: Any) -> Union[object, Set[str]]: """Used to convert the :members: option to auto directives.""" - if arg is None: + if arg in (None, True): return 'object' else: return arg @@ -124,7 +125,7 @@ def inherited_members_option(arg: Any) -> Union[object, Set[str]]: def member_order_option(arg: Any) -> Optional[str]: """Used to convert the :members: option to auto directives.""" - if arg is None: + if arg in (None, True): return None elif arg in ('alphabetical', 'bysource', 'groupwise'): return arg @@ -136,7 +137,7 @@ SUPPRESS = object() def annotation_option(arg: Any) -> Any: - if arg is None: + if arg in (None, True): # suppress showing the representation of the object return SUPPRESS else: @@ -273,10 +274,13 @@ class ObjectMember(tuple): def __new__(cls, name: str, obj: Any, **kwargs: Any) -> Any: return super().__new__(cls, (name, obj)) # type: ignore - def __init__(self, name: str, obj: Any, skipped: bool = False) -> None: + def __init__(self, name: str, obj: Any, docstring: Optional[str] = None, + class_: Any = None, skipped: bool = False) -> None: self.__name__ = name self.object = obj + self.docstring = docstring self.skipped = skipped + self.class_ = class_ ObjectMembers = Union[List[ObjectMember], List[Tuple[str, Any]]] @@ -418,6 +422,8 @@ class Documenter: attrgetter=self.get_attr, warningiserror=self.config.autodoc_warningiserror) self.module, self.parent, self.object_name, self.object = ret + if ismock(self.object): + self.object = undecorate(self.object) return True except ImportError as exc: if raiseerror: @@ -534,8 +540,12 @@ class Documenter: # etc. don't support a prepended module name self.add_line(' :module: %s' % self.modname, sourcename) - def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: - """Decode and return lines of the docstring(s) for the object.""" + def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: + """Decode and return lines of the docstring(s) for the object. + + When it returns None value, autodoc-process-docstring will not be called for this + object. + """ if encoding is not None: warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated." % self.__class__.__name__, @@ -583,6 +593,11 @@ class Documenter: def add_content(self, more_content: Optional[StringList], no_docstring: bool = False ) -> None: """Add content from docstrings, attribute documentation and user.""" + if no_docstring: + warnings.warn("The 'no_docstring' argument to %s.add_content() is deprecated." + % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + # set sourcename and add content from attribute documentation sourcename = self.get_sourcename() if self.analyzer: @@ -601,13 +616,17 @@ class Documenter: # add content from docstrings if not no_docstring: docstrings = self.get_doc() - if not docstrings: - # append at least a dummy docstring, so that the event - # autodoc-process-docstring is fired and can add some - # content if desired - docstrings.append([]) - for i, line in enumerate(self.process_doc(docstrings)): - self.add_line(line, sourcename, i) + if docstrings is None: + # Do not call autodoc-process-docstring on get_doc() returns None. + pass + else: + if not docstrings: + # append at least a dummy docstring, so that the event + # autodoc-process-docstring is fired and can add some + # content if desired + docstrings.append([]) + for i, line in enumerate(self.process_doc(docstrings)): + self.add_line(line, sourcename, i) # add additional content (e.g. from document), if present if more_content: @@ -621,6 +640,8 @@ class Documenter: If *want_all* is True, return all members. Else, only return those members given by *self.options.members* (which may also be none). """ + warnings.warn('The implementation of Documenter.get_object_members() will be ' + 'removed from Sphinx-6.0.', RemovedInSphinx60Warning) members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer) if not want_all: if not self.options.members: @@ -655,7 +676,7 @@ class Documenter: The user can override the skipping decision by connecting to the ``autodoc-skip-member`` event. """ - def is_filtered_inherited_member(name: str) -> bool: + def is_filtered_inherited_member(name: str, obj: Any) -> bool: if inspect.isclass(self.object): for cls in self.object.__mro__: if cls.__name__ == self.options.inherited_members and cls != self.object: @@ -665,6 +686,8 @@ class Documenter: return False elif name in self.get_attr(cls, '__annotations__', {}): return False + elif isinstance(obj, ObjectMember) and obj.class_ is cls: + return False return False @@ -700,6 +723,11 @@ class Documenter: cls_doc = self.get_attr(cls, '__doc__', None) if cls_doc == doc: doc = None + + if isinstance(obj, ObjectMember) and obj.docstring: + # hack for ClassDocumenter to inject docstring via ObjectMember + doc = obj.docstring + has_doc = bool(doc) metadata = extract_metadata(doc) @@ -713,7 +741,7 @@ class Documenter: isprivate = membername.startswith('_') keep = False - if safe_getattr(member, '__sphinx_mock__', False): + if ismock(member): # mocked module or object pass elif self.options.exclude_members and membername in self.options.exclude_members: @@ -724,7 +752,7 @@ class Documenter: if self.options.special_members and membername in self.options.special_members: if membername == '__doc__': keep = False - elif is_filtered_inherited_member(membername): + elif is_filtered_inherited_member(membername, obj): keep = False else: keep = has_doc or self.options.undoc_members @@ -744,14 +772,15 @@ class Documenter: if has_doc or self.options.undoc_members: if self.options.private_members is None: keep = False - elif is_filtered_inherited_member(membername): + elif is_filtered_inherited_member(membername, obj): keep = False else: keep = membername in self.options.private_members else: keep = False else: - if self.options.members is ALL and is_filtered_inherited_member(membername): + if (self.options.members is ALL and + is_filtered_inherited_member(membername, obj)): keep = False else: # ignore undocumented members if :undoc-members: is not given @@ -1016,30 +1045,56 @@ class ModuleDocumenter(Documenter): if self.options.deprecated: self.add_line(' :deprecated:', sourcename) + def get_module_members(self) -> Dict[str, ObjectMember]: + """Get members of target module.""" + if self.analyzer: + attr_docs = self.analyzer.attr_docs + else: + attr_docs = {} + + members = {} # type: Dict[str, ObjectMember] + for name in dir(self.object): + try: + value = safe_getattr(self.object, name, None) + if ismock(value): + value = undecorate(value) + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, value, docstring="\n".join(docstring)) + except AttributeError: + continue + + # annotation only member (ex. attr: int) + try: + for name in inspect.getannotations(self.object): + if name not in members: + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, INSTANCEATTR, + docstring="\n".join(docstring)) + except AttributeError: + pass + + return members + def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: + members = self.get_module_members() if want_all: - members = get_module_members(self.object) - if not self.__all__: + if self.__all__ is None: # for implicit module members, check __module__ to avoid # documenting imported objects - return True, members + return True, list(members.values()) else: - ret = [] - for name, value in members: - if name in self.__all__: - ret.append(ObjectMember(name, value)) - else: - ret.append(ObjectMember(name, value, skipped=True)) + for member in members.values(): + if member.__name__ not in self.__all__: + member.skipped = True - return False, ret + return False, list(members.values()) else: memberlist = self.options.members or [] ret = [] for name in memberlist: - try: - value = safe_getattr(self.object, name) - ret.append(ObjectMember(name, value)) - except AttributeError: + if name in members: + ret.append(members[name]) + else: logger.warning(__('missing attribute mentioned in :members: option: ' 'module %s, attribute %s') % (safe_getattr(self.object, '__name__', '???'), name), @@ -1142,6 +1197,8 @@ class DocstringSignatureMixin: valid_names.extend(cls.__name__ for cls in self.object.__mro__) docstrings = self.get_doc() + if docstrings is None: + return None, None self._new_docstrings = docstrings[:] self._signatures = [] result = None @@ -1192,7 +1249,7 @@ class DocstringSignatureMixin: return result - def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: + def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: if encoding is not None: warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated." % self.__class__.__name__, @@ -1302,8 +1359,11 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ documenter.objpath = [None] sigs.append(documenter.format_signature()) if overloaded: + actual = inspect.signature(self.object, + type_aliases=self.config.autodoc_type_aliases) __globals__ = safe_getattr(self.object, '__globals__', {}) for overload in self.analyzer.overloads.get('.'.join(self.objpath)): + overload = self.merge_default_value(actual, overload) overload = evaluate_signature(overload, __globals__, self.config.autodoc_type_aliases) @@ -1312,6 +1372,16 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ return "\n".join(sigs) + def merge_default_value(self, actual: Signature, overload: Signature) -> Signature: + """Merge default values of actual implementation to the overload variants.""" + parameters = list(overload.parameters.values()) + for i, param in enumerate(parameters): + actual_param = actual.parameters.get(param.name) + if actual_param and param.default == '...': + parameters[i] = param.replace(default=actual_param.default) + + return overload.replace(parameters=parameters) + def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: """Annotate type hint to the first argument of function if needed.""" try: @@ -1336,19 +1406,6 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ return -class SingledispatchFunctionDocumenter(FunctionDocumenter): - """ - Used to be a specialized Documenter subclass for singledispatch'ed functions. - - Retained for backwards compatibility, now does the same as the FunctionDocumenter - """ - - def __init__(self, *args: Any, **kwargs: Any) -> None: - warnings.warn("%s is deprecated." % self.__class__.__name__, - RemovedInSphinx50Warning, stacklevel=2) - super().__init__(*args, **kwargs) - - class DecoratorDocumenter(FunctionDocumenter): """ Specialized Documenter subclass for decorator functions. @@ -1580,11 +1637,34 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: bases = [restify(cls) for cls in self.object.__bases__] self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename) - def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: + def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: + members = get_class_members(self.object, self.objpath, self.get_attr) + if not want_all: + if not self.options.members: + return False, [] # type: ignore + # specific members given + selected = [] + for name in self.options.members: # type: str + if name in members: + selected.append(members[name]) + else: + logger.warning(__('missing attribute %s in object %s') % + (name, self.fullname), type='autodoc') + return False, selected + elif self.options.inherited_members: + return False, list(members.values()) + else: + return False, [m for m in members.values() if m.class_ == self.object] + + def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: if encoding is not None: warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated." % self.__class__.__name__, RemovedInSphinx40Warning, stacklevel=2) + if self.doc_as_attr: + # Don't show the docstring of the class when it is an alias. + return None + lines = getattr(self, '_new_docstrings', None) if lines is not None: return lines @@ -1631,18 +1711,12 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: def add_content(self, more_content: Optional[StringList], no_docstring: bool = False ) -> None: if self.doc_as_attr: - classname = safe_getattr(self.object, '__qualname__', None) - if not classname: - classname = safe_getattr(self.object, '__name__', None) - if classname: - module = safe_getattr(self.object, '__module__', None) - parentmodule = safe_getattr(self.parent, '__module__', None) - if module and module != parentmodule: - classname = str(module) + '.' + str(classname) - content = StringList([_('alias of :class:`%s`') % classname], source='') - super().add_content(content, no_docstring=True) - else: - super().add_content(more_content) + try: + more_content = StringList([_('alias of %s') % restify(self.object)], source='') + except AttributeError: + pass # Invalid class object is passed. + + super().add_content(more_content) def document_members(self, all_members: bool = False) -> None: if self.doc_as_attr: @@ -1677,25 +1751,149 @@ class ExceptionDocumenter(ClassDocumenter): return isinstance(member, type) and issubclass(member, BaseException) -class NewTypeMixin: +class DataDocumenterMixinBase: + # define types of instance variables + config = None # type: Config + env = None # type: BuildEnvironment + modname = None # type: str + parent = None # type: Any + object = None # type: Any + objpath = None # type: List[str] + + def should_suppress_directive_header(self) -> bool: + """Check directive header should be suppressed.""" + return False + + def should_suppress_value_header(self) -> bool: + """Check :value: header should be suppressed.""" + return False + + def update_content(self, more_content: StringList) -> None: + """Update docstring for the NewType object.""" + pass + + +class GenericAliasMixin(DataDocumenterMixinBase): + """ + Mixin for DataDocumenter and AttributeDocumenter to provide the feature for + supporting GenericAliases. + """ + + def should_suppress_directive_header(self) -> bool: + return (inspect.isgenericalias(self.object) or + super().should_suppress_directive_header()) + + def update_content(self, more_content: StringList) -> None: + if inspect.isgenericalias(self.object): + alias = stringify_typehint(self.object) + more_content.append(_('alias of %s') % alias, '') + more_content.append('', '') + + super().update_content(more_content) + + +class NewTypeMixin(DataDocumenterMixinBase): """ Mixin for DataDocumenter and AttributeDocumenter to provide the feature for supporting NewTypes. """ def should_suppress_directive_header(self) -> bool: - """Check directive header should be suppressed.""" - return inspect.isNewType(self.object) # type: ignore + return (inspect.isNewType(self.object) or + super().should_suppress_directive_header()) def update_content(self, more_content: StringList) -> None: - """Update docstring for the NewType object.""" - if inspect.isNewType(self.object): # type: ignore - supertype = restify(self.object.__supertype__) # type: ignore + if inspect.isNewType(self.object): + supertype = restify(self.object.__supertype__) more_content.append(_('alias of %s') % supertype, '') more_content.append('', '') + super().update_content(more_content) -class DataDocumenter(ModuleLevelDocumenter, NewTypeMixin): + +class TypeVarMixin(DataDocumenterMixinBase): + """ + Mixin for DataDocumenter and AttributeDocumenter to provide the feature for + supporting TypeVars. + """ + + def should_suppress_directive_header(self) -> bool: + return (isinstance(self.object, TypeVar) or + super().should_suppress_directive_header()) + + def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: + if ignore is not None: + warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." + % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + + if isinstance(self.object, TypeVar): + if self.object.__doc__ != TypeVar.__doc__: + return super().get_doc() # type: ignore + else: + return [] + else: + return super().get_doc() # type: ignore + + def update_content(self, more_content: StringList) -> None: + if isinstance(self.object, TypeVar): + attrs = [repr(self.object.__name__)] + for constraint in self.object.__constraints__: + attrs.append(stringify_typehint(constraint)) + if self.object.__covariant__: + attrs.append("covariant=True") + if self.object.__contravariant__: + attrs.append("contravariant=True") + + more_content.append(_('alias of TypeVar(%s)') % ", ".join(attrs), '') + more_content.append('', '') + + super().update_content(more_content) + + +class UninitializedGlobalVariableMixin(DataDocumenterMixinBase): + """ + Mixin for DataDocumenter to provide the feature for supporting uninitialized + (type annotation only) global variables. + """ + + def import_object(self, raiseerror: bool = False) -> bool: + try: + return super().import_object(raiseerror=True) # type: ignore + except ImportError as exc: + # annotation only instance variable (PEP-526) + try: + with mock(self.config.autodoc_mock_imports): + parent = import_module(self.modname, self.config.autodoc_warningiserror) + annotations = get_type_hints(parent, None, + self.config.autodoc_type_aliases) + if self.objpath[-1] in annotations: + self.object = UNINITIALIZED_ATTR + self.parent = parent + return True + except ImportError: + pass + + if raiseerror: + raise + else: + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False + + def should_suppress_value_header(self) -> bool: + return (self.object is UNINITIALIZED_ATTR or + super().should_suppress_value_header()) + + def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: + if self.object is UNINITIALIZED_ATTR: + return [] + else: + return super().get_doc(encoding, ignore) # type: ignore + + +class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, + UninitializedGlobalVariableMixin, ModuleLevelDocumenter): """ Specialized Documenter subclass for data items. """ @@ -1711,27 +1909,37 @@ class DataDocumenter(ModuleLevelDocumenter, NewTypeMixin): ) -> bool: return isinstance(parent, ModuleDocumenter) and isattr - def import_object(self, raiseerror: bool = False) -> bool: + def update_annotations(self, parent: Any) -> None: + """Update __annotations__ to support type_comment and so on.""" try: - return super().import_object(raiseerror=True) - except ImportError as exc: - # annotation only instance variable (PEP-526) - try: - self.parent = importlib.import_module(self.modname) - annotations = get_type_hints(self.parent, None, - self.config.autodoc_type_aliases) - if self.objpath[-1] in annotations: - self.object = UNINITIALIZED_ATTR - return True - except ImportError: - pass + annotations = dict(inspect.getannotations(parent)) + parent.__annotations__ = annotations - if raiseerror: - raise - else: - logger.warning(exc.args[0], type='autodoc', subtype='import_object') - self.env.note_reread() - return False + analyzer = ModuleAnalyzer.for_module(self.modname) + analyzer.analyze() + for (classname, attrname), annotation in analyzer.annotations.items(): + if classname == '' and attrname not in annotations: + annotations[attrname] = annotation + except AttributeError: + pass + + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + if self.parent: + self.update_annotations(self.parent) + + return ret + + def should_suppress_value_header(self) -> bool: + if super().should_suppress_value_header(): + return True + else: + doc = self.get_doc() + metadata = extract_metadata('\n'.join(sum(doc, []))) + if 'hide-value' in metadata: + return True + + return False def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) @@ -1747,14 +1955,9 @@ class DataDocumenter(ModuleLevelDocumenter, NewTypeMixin): if self.objpath[-1] in annotations: objrepr = stringify_typehint(annotations.get(self.objpath[-1])) self.add_line(' :type: ' + objrepr, sourcename) - else: - key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) - if self.analyzer and key in self.analyzer.annotations: - self.add_line(' :type: ' + self.analyzer.annotations[key], - sourcename) try: - if self.object is UNINITIALIZED_ATTR or self.options.no_value: + if self.options.no_value or self.should_suppress_value_header(): pass else: objrepr = object_description(self.object) @@ -1769,61 +1972,37 @@ class DataDocumenter(ModuleLevelDocumenter, NewTypeMixin): return self.get_attr(self.parent or self.object, '__module__', None) \ or self.modname - def add_content(self, more_content: Optional[StringList], no_docstring: bool = False - ) -> None: - if self.object is UNINITIALIZED_ATTR: - # suppress docstring of the value - super().add_content(more_content, no_docstring=True) + def get_module_comment(self, attrname: str) -> Optional[List[str]]: + try: + analyzer = ModuleAnalyzer.for_module(self.modname) + analyzer.analyze() + key = ('', attrname) + if key in analyzer.attr_docs: + return list(analyzer.attr_docs[key]) + except PycodeError: + pass + + return None + + def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: + # Check the variable has a docstring-comment + comment = self.get_module_comment(self.objpath[-1]) + if comment: + return [comment] else: - if not more_content: - more_content = StringList() - - self.update_content(more_content) - super().add_content(more_content, no_docstring=no_docstring) - - -class DataDeclarationDocumenter(DataDocumenter): - """ - Specialized Documenter subclass for data that cannot be imported - because they are declared without initial value (refs: PEP-526). - """ - objtype = 'datadecl' - directivetype = 'data' - member_order = 60 - - # must be higher than AttributeDocumenter - priority = 11 - - def __init__(self, *args: Any, **kwargs: Any) -> None: - warnings.warn("%s is deprecated." % self.__class__.__name__, - RemovedInSphinx50Warning, stacklevel=2) - super().__init__(*args, **kwargs) - - -class GenericAliasDocumenter(DataDocumenter): - """ - Specialized Documenter subclass for GenericAliases. - """ - - objtype = 'genericalias' - directivetype = 'data' - priority = DataDocumenter.priority + 1 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - return inspect.isgenericalias(member) - - def add_directive_header(self, sig: str) -> None: - self.options = Options(self.options) - self.options['annotation'] = SUPPRESS - super().add_directive_header(sig) + return super().get_doc(encoding, ignore) def add_content(self, more_content: Optional[StringList], no_docstring: bool = False ) -> None: - name = stringify_typehint(self.object) - content = StringList([_('alias of %s') % name], source='') - super().add_content(content) + # Disable analyzing variable comment on Documenter.add_content() to control it on + # DataDocumenter.add_content() + self.analyzer = None + + if not more_content: + more_content = StringList() + + self.update_content(more_content) + super().add_content(more_content, no_docstring=no_docstring) class NewTypeDataDocumenter(DataDocumenter): @@ -1844,50 +2023,6 @@ class NewTypeDataDocumenter(DataDocumenter): return inspect.isNewType(member) and isattr -class TypeVarDocumenter(DataDocumenter): - """ - Specialized Documenter subclass for TypeVars. - """ - - objtype = 'typevar' - directivetype = 'data' - priority = DataDocumenter.priority + 1 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - return isinstance(member, TypeVar) and isattr - - def add_directive_header(self, sig: str) -> None: - self.options = Options(self.options) - self.options['annotation'] = SUPPRESS - super().add_directive_header(sig) - - def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: - if ignore is not None: - warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." - % self.__class__.__name__, - RemovedInSphinx50Warning, stacklevel=2) - - if self.object.__doc__ != TypeVar.__doc__: - return super().get_doc() - else: - return [] - - def add_content(self, more_content: Optional[StringList], no_docstring: bool = False - ) -> None: - attrs = [repr(self.object.__name__)] - for constraint in self.object.__constraints__: - attrs.append(stringify_typehint(constraint)) - if self.object.__covariant__: - attrs.append("covariant=True") - if self.object.__contravariant__: - attrs.append("contravariant=True") - - content = StringList([_('alias of TypeVar(%s)') % ", ".join(attrs)], source='') - super().add_content(content) - - class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for methods (normal, static and class). @@ -1999,8 +2134,16 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: documenter.objpath = [None] sigs.append(documenter.format_signature()) if overloaded: + if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name): + actual = inspect.signature(self.object, bound_method=False, + type_aliases=self.config.autodoc_type_aliases) + else: + actual = inspect.signature(self.object, bound_method=True, + type_aliases=self.config.autodoc_type_aliases) + __globals__ = safe_getattr(self.object, '__globals__', {}) for overload in self.analyzer.overloads.get('.'.join(self.objpath)): + overload = self.merge_default_value(actual, overload) overload = evaluate_signature(overload, __globals__, self.config.autodoc_type_aliases) @@ -2013,6 +2156,16 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: return "\n".join(sigs) + def merge_default_value(self, actual: Signature, overload: Signature) -> Signature: + """Merge default values of actual implementation to the overload variants.""" + parameters = list(overload.parameters.values()) + for i, param in enumerate(parameters): + actual_param = actual.parameters.get(param.name) + if actual_param and param.default == '...': + parameters[i] = param.replace(default=actual_param.default) + + return overload.replace(parameters=parameters) + def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: """Annotate type hint to the first argument of function if needed.""" try: @@ -2036,20 +2189,196 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: return -class SingledispatchMethodDocumenter(MethodDocumenter): +class NonDataDescriptorMixin(DataDocumenterMixinBase): """ - Used to be a specialized Documenter subclass for singledispatch'ed methods. + Mixin for AttributeDocumenter to provide the feature for supporting non + data-descriptors. - Retained for backwards compatibility, now does the same as the MethodDocumenter + .. note:: This mix-in must be inherited after other mix-ins. Otherwise, docstring + and :value: header will be suppressed unexpectedly. """ - def __init__(self, *args: Any, **kwargs: Any) -> None: - warnings.warn("%s is deprecated." % self.__class__.__name__, - RemovedInSphinx50Warning, stacklevel=2) - super().__init__(*args, **kwargs) + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) # type: ignore + if ret and not inspect.isattributedescriptor(self.object): + self.non_data_descriptor = True + else: + self.non_data_descriptor = False + + return ret + + def should_suppress_value_header(self) -> bool: + return (not getattr(self, 'non_data_descriptor', False) or + super().should_suppress_directive_header()) + + def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: + if getattr(self, 'non_data_descriptor', False): + # the docstring of non datadescriptor is very probably the wrong thing + # to display + return None + else: + return super().get_doc(encoding, ignore) # type: ignore -class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter, NewTypeMixin): # type: ignore # NOQA +class SlotsMixin(DataDocumenterMixinBase): + """ + Mixin for AttributeDocumenter to provide the feature for supporting __slots__. + """ + + def isslotsattribute(self) -> bool: + """Check the subject is an attribute in __slots__.""" + try: + __slots__ = inspect.getslots(self.parent) + if __slots__ and self.objpath[-1] in __slots__: + return True + else: + return False + except (AttributeError, ValueError, TypeError): + return False + + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) # type: ignore + if self.isslotsattribute(): + self.object = SLOTSATTR + + return ret + + def should_suppress_directive_header(self) -> bool: + if self.object is SLOTSATTR: + self._datadescriptor = True + return True + else: + return super().should_suppress_directive_header() + + def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: + if self.object is SLOTSATTR: + try: + __slots__ = inspect.getslots(self.parent) + if __slots__ and __slots__.get(self.objpath[-1]): + docstring = prepare_docstring(__slots__[self.objpath[-1]]) + return [docstring] + else: + return [] + except (AttributeError, ValueError) as exc: + logger.warning(__('Invalid __slots__ found on %s. Ignored.'), + (self.parent.__qualname__, exc), type='autodoc') + return [] + else: + return super().get_doc(encoding, ignore) # type: ignore + + +class RuntimeInstanceAttributeMixin(DataDocumenterMixinBase): + """ + Mixin for AttributeDocumenter to provide the feature for supporting runtime + instance attributes (that are defined in __init__() methods with doc-comments). + + Example: + + class Foo: + def __init__(self): + self.attr = None #: This is a target of this mix-in. + """ + + RUNTIME_INSTANCE_ATTRIBUTE = object() + + def is_runtime_instance_attribute(self, parent: Any) -> bool: + """Check the subject is an attribute defined in __init__().""" + # An instance variable defined in __init__(). + if self.get_attribute_comment(parent, self.objpath[-1]): # type: ignore + return True + else: + return False + + def import_object(self, raiseerror: bool = False) -> bool: + """Check the existence of runtime instance attribute when failed to import the + attribute.""" + try: + return super().import_object(raiseerror=True) # type: ignore + except ImportError as exc: + try: + with mock(self.config.autodoc_mock_imports): + ret = import_object(self.modname, self.objpath[:-1], 'class', + attrgetter=self.get_attr, # type: ignore + warningiserror=self.config.autodoc_warningiserror) + parent = ret[3] + if self.is_runtime_instance_attribute(parent): + self.object = self.RUNTIME_INSTANCE_ATTRIBUTE + self.parent = parent + return True + except ImportError: + pass + + if raiseerror: + raise + else: + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False + + def should_suppress_value_header(self) -> bool: + return (self.object is self.RUNTIME_INSTANCE_ATTRIBUTE or + super().should_suppress_value_header()) + + +class UninitializedInstanceAttributeMixin(DataDocumenterMixinBase): + """ + Mixin for AttributeDocumenter to provide the feature for supporting uninitialized + instance attributes (PEP-526 styled, annotation only attributes). + + Example: + + class Foo: + attr: int #: This is a target of this mix-in. + """ + + def is_uninitialized_instance_attribute(self, parent: Any) -> bool: + """Check the subject is an annotation only attribute.""" + annotations = get_type_hints(parent, None, self.config.autodoc_type_aliases) + if self.objpath[-1] in annotations: + return True + else: + return False + + def import_object(self, raiseerror: bool = False) -> bool: + """Check the exisitence of uninitialized instance attribute when failed to import + the attribute.""" + try: + return super().import_object(raiseerror=True) # type: ignore + except ImportError as exc: + try: + ret = import_object(self.modname, self.objpath[:-1], 'class', + attrgetter=self.get_attr, # type: ignore + warningiserror=self.config.autodoc_warningiserror) + parent = ret[3] + if self.is_uninitialized_instance_attribute(parent): + self.object = UNINITIALIZED_ATTR + self.parent = parent + return True + except ImportError: + pass + + if raiseerror: + raise + else: + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False + + def should_suppress_value_header(self) -> bool: + return (self.object is UNINITIALIZED_ATTR or + super().should_suppress_value_header()) + + def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: + if self.object is UNINITIALIZED_ATTR: + return None + else: + return super().get_doc(encoding, ignore) # type: ignore + + +class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: ignore + TypeVarMixin, RuntimeInstanceAttributeMixin, + UninitializedInstanceAttributeMixin, NonDataDescriptorMixin, + DocstringStripSignatureMixin, ClassLevelDocumenter): """ Specialized Documenter subclass for attributes. """ @@ -2084,6 +2413,8 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter, Ne def isinstanceattribute(self) -> bool: """Check the subject is an instance attribute.""" + warnings.warn('AttributeDocumenter.isinstanceattribute() is deprecated.', + RemovedInSphinx50Warning) # uninitialized instance variable (PEP-526) with mock(self.config.autodoc_mock_imports): try: @@ -2099,42 +2430,38 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter, Ne except ImportError: pass - # An instance variable defined inside __init__(). - try: - analyzer = ModuleAnalyzer.for_module(self.modname) - attr_docs = analyzer.find_attr_docs() - if self.objpath: - key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) - if key in attr_docs: - return True - - return False - except PycodeError: - pass - return False - def import_object(self, raiseerror: bool = False) -> bool: + def update_annotations(self, parent: Any) -> None: + """Update __annotations__ to support type_comment and so on.""" try: - ret = super().import_object(raiseerror=True) - if inspect.isenumattribute(self.object): - self.object = self.object.value - if inspect.isattributedescriptor(self.object): - self._datadescriptor = True - else: - # if it's not a data descriptor - self._datadescriptor = False - except ImportError as exc: - if self.isinstanceattribute(): - self.object = INSTANCEATTR - self._datadescriptor = False - ret = True - elif raiseerror: - raise - else: - logger.warning(exc.args[0], type='autodoc', subtype='import_object') - self.env.note_reread() - ret = False + annotations = dict(inspect.getannotations(parent)) + parent.__annotations__ = annotations + + for cls in inspect.getmro(parent): + try: + module = safe_getattr(cls, '__module__') + qualname = safe_getattr(cls, '__qualname__') + + analyzer = ModuleAnalyzer.for_module(module) + analyzer.analyze() + for (classname, attrname), annotation in analyzer.annotations.items(): + if classname == qualname and attrname not in annotations: + annotations[attrname] = annotation + except (AttributeError, PycodeError): + pass + except AttributeError: + pass + except TypeError: + # Failed to set __annotations__ (built-in, extensions, etc.) + pass + + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + if inspect.isenumattribute(self.object): + self.object = self.object.value + if self.parent: + self.update_annotations(self.parent) return ret @@ -2142,6 +2469,18 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter, Ne return self.get_attr(self.parent or self.object, '__module__', None) \ or self.modname + def should_suppress_value_header(self) -> bool: + if super().should_suppress_value_header(): + return True + else: + doc = self.get_doc() + if doc: + metadata = extract_metadata('\n'.join(sum(doc, []))) + if 'hide-value' in metadata: + return True + + return False + def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() @@ -2155,24 +2494,42 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter, Ne if self.objpath[-1] in annotations: objrepr = stringify_typehint(annotations.get(self.objpath[-1])) self.add_line(' :type: ' + objrepr, sourcename) - else: - key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) - if self.analyzer and key in self.analyzer.annotations: - self.add_line(' :type: ' + self.analyzer.annotations[key], - sourcename) - # data descriptors do not have useful values - if not self._datadescriptor: - try: - if self.object is INSTANCEATTR or self.options.no_value: - pass - else: - objrepr = object_description(self.object) - self.add_line(' :value: ' + objrepr, sourcename) - except ValueError: + try: + if self.options.no_value or self.should_suppress_value_header(): pass + else: + objrepr = object_description(self.object) + self.add_line(' :value: ' + objrepr, sourcename) + except ValueError: + pass + + def get_attribute_comment(self, parent: Any, attrname: str) -> Optional[List[str]]: + try: + for cls in inspect.getmro(parent): + try: + module = safe_getattr(cls, '__module__') + qualname = safe_getattr(cls, '__qualname__') + + analyzer = ModuleAnalyzer.for_module(module) + analyzer.analyze() + if qualname and self.objpath: + key = (qualname, attrname) + if key in analyzer.attr_docs: + return list(analyzer.attr_docs[key]) + except (AttributeError, PycodeError): + pass + except (AttributeError, PycodeError): + pass + + return None + + def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: + # Check the attribute has a docstring-comment + comment = self.get_attribute_comment(self.parent, self.objpath[-1]) + if comment: + return [comment] - def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: try: # Disable `autodoc_inherit_docstring` temporarily to avoid to obtain # a docstring from the value which descriptor returns unexpectedly. @@ -2185,10 +2542,9 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter, Ne def add_content(self, more_content: Optional[StringList], no_docstring: bool = False ) -> None: - if not self._datadescriptor: - # if it's not a data descriptor, its docstring is very probably the - # wrong thing to display - no_docstring = True + # Disable analyzing attribute comment on Documenter.add_content() to control it on + # AttributeDocumenter.add_content() + self.analyzer = None if more_content is None: more_content = StringList() @@ -2227,111 +2583,6 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # self.add_line(' :property:', sourcename) -class InstanceAttributeDocumenter(AttributeDocumenter): - """ - Specialized Documenter subclass for attributes that cannot be imported - because they are instance attributes (e.g. assigned in __init__). - """ - objtype = 'instanceattribute' - directivetype = 'attribute' - member_order = 60 - - # must be higher than AttributeDocumenter - priority = 11 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - """This documents only INSTANCEATTR members.""" - return (not isinstance(parent, ModuleDocumenter) and - isattr and - member is INSTANCEATTR) - - def import_parent(self) -> Any: - try: - parent = importlib.import_module(self.modname) - for name in self.objpath[:-1]: - parent = self.get_attr(parent, name) - - return parent - except (ImportError, AttributeError): - return None - - def import_object(self, raiseerror: bool = False) -> bool: - """Never import anything.""" - # disguise as an attribute - self.objtype = 'attribute' - self.object = INSTANCEATTR - self.parent = self.import_parent() - self._datadescriptor = False - return True - - def add_content(self, more_content: Optional[StringList], no_docstring: bool = False - ) -> None: - """Never try to get a docstring from the object.""" - super().add_content(more_content, no_docstring=True) - - -class SlotsAttributeDocumenter(AttributeDocumenter): - """ - Specialized Documenter subclass for attributes that cannot be imported - because they are attributes in __slots__. - """ - objtype = 'slotsattribute' - directivetype = 'attribute' - member_order = 60 - - # must be higher than AttributeDocumenter - priority = 11 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - """This documents only SLOTSATTR members.""" - return member is SLOTSATTR - - def import_object(self, raiseerror: bool = False) -> bool: - """Never import anything.""" - # disguise as an attribute - self.objtype = 'attribute' - self._datadescriptor = True - - with mock(self.config.autodoc_mock_imports): - try: - ret = import_object(self.modname, self.objpath[:-1], 'class', - attrgetter=self.get_attr, - warningiserror=self.config.autodoc_warningiserror) - self.module, _, _, self.parent = ret - return True - except ImportError as exc: - if raiseerror: - raise - else: - logger.warning(exc.args[0], type='autodoc', subtype='import_object') - self.env.note_reread() - return False - - def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: - """Decode and return lines of the docstring(s) for the object.""" - if ignore is not None: - warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." - % self.__class__.__name__, - RemovedInSphinx50Warning, stacklevel=2) - name = self.objpath[-1] - - try: - __slots__ = inspect.getslots(self.parent) - if __slots__ and isinstance(__slots__.get(name, None), str): - docstring = prepare_docstring(__slots__[name]) - return [docstring] - else: - return [] - except (AttributeError, ValueError) as exc: - logger.warning(__('Invalid __slots__ found on %s. Ignored.'), - (self.parent.__qualname__, exc), type='autodoc') - return [] - - class NewTypeAttributeDocumenter(AttributeDocumenter): """ Specialized Documenter subclass for NewTypes. @@ -2373,21 +2624,27 @@ def migrate_autodoc_member_order(app: Sphinx, config: Config) -> None: config.autodoc_member_order = 'alphabetical' # type: ignore +# for compatibility +from sphinx.ext.autodoc.deprecated import DataDeclarationDocumenter # NOQA +from sphinx.ext.autodoc.deprecated import GenericAliasDocumenter # NOQA +from sphinx.ext.autodoc.deprecated import InstanceAttributeDocumenter # NOQA +from sphinx.ext.autodoc.deprecated import SingledispatchFunctionDocumenter # NOQA +from sphinx.ext.autodoc.deprecated import SingledispatchMethodDocumenter # NOQA +from sphinx.ext.autodoc.deprecated import SlotsAttributeDocumenter # NOQA +from sphinx.ext.autodoc.deprecated import TypeVarDocumenter # NOQA + + def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(ModuleDocumenter) app.add_autodocumenter(ClassDocumenter) app.add_autodocumenter(ExceptionDocumenter) app.add_autodocumenter(DataDocumenter) - app.add_autodocumenter(GenericAliasDocumenter) app.add_autodocumenter(NewTypeDataDocumenter) - app.add_autodocumenter(TypeVarDocumenter) app.add_autodocumenter(FunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(AttributeDocumenter) app.add_autodocumenter(PropertyDocumenter) - app.add_autodocumenter(InstanceAttributeDocumenter) - app.add_autodocumenter(SlotsAttributeDocumenter) app.add_autodocumenter(NewTypeAttributeDocumenter) app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init')) diff --git a/sphinx/ext/autodoc/deprecated.py b/sphinx/ext/autodoc/deprecated.py new file mode 100644 index 000000000..80a94f0ea --- /dev/null +++ b/sphinx/ext/autodoc/deprecated.py @@ -0,0 +1,126 @@ +""" + sphinx.ext.autodoc.deprecated + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + The deprecated Documenters for autodoc. + + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import warnings +from typing import Any + +from sphinx.deprecation import RemovedInSphinx50Warning +from sphinx.ext.autodoc import (AttributeDocumenter, DataDocumenter, FunctionDocumenter, + MethodDocumenter) + + +class SingledispatchFunctionDocumenter(FunctionDocumenter): + """ + Used to be a specialized Documenter subclass for singledispatch'ed functions. + + Retained for backwards compatibility, now does the same as the FunctionDocumenter + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn("%s is deprecated." % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + super().__init__(*args, **kwargs) + + +class DataDeclarationDocumenter(DataDocumenter): + """ + Specialized Documenter subclass for data that cannot be imported + because they are declared without initial value (refs: PEP-526). + """ + objtype = 'datadecl' + directivetype = 'data' + member_order = 60 + + # must be higher than AttributeDocumenter + priority = 11 + + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn("%s is deprecated." % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + super().__init__(*args, **kwargs) + + +class TypeVarDocumenter(DataDocumenter): + """ + Specialized Documenter subclass for TypeVars. + """ + + objtype = 'typevar' + directivetype = 'data' + priority = DataDocumenter.priority + 1 # type: ignore + + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn("%s is deprecated." % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + super().__init__(*args, **kwargs) + + +class SingledispatchMethodDocumenter(MethodDocumenter): + """ + Used to be a specialized Documenter subclass for singledispatch'ed methods. + + Retained for backwards compatibility, now does the same as the MethodDocumenter + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn("%s is deprecated." % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + super().__init__(*args, **kwargs) + + +class InstanceAttributeDocumenter(AttributeDocumenter): + """ + Specialized Documenter subclass for attributes that cannot be imported + because they are instance attributes (e.g. assigned in __init__). + """ + objtype = 'instanceattribute' + directivetype = 'attribute' + member_order = 60 + + # must be higher than AttributeDocumenter + priority = 11 + + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn("%s is deprecated." % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + super().__init__(*args, **kwargs) + + +class SlotsAttributeDocumenter(AttributeDocumenter): + """ + Specialized Documenter subclass for attributes that cannot be imported + because they are attributes in __slots__. + """ + objtype = 'slotsattribute' + directivetype = 'attribute' + member_order = 60 + + # must be higher than AttributeDocumenter + priority = 11 + + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn("%s is deprecated." % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + super().__init__(*args, **kwargs) + + +class GenericAliasDocumenter(DataDocumenter): + """ + Specialized Documenter subclass for GenericAliases. + """ + + objtype = 'genericalias' + directivetype = 'data' + priority = DataDocumenter.priority + 1 # type: ignore + + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn("%s is deprecated." % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + super().__init__(*args, **kwargs) diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py index 9a3428f5d..b4c5fd28d 100644 --- a/sphinx/ext/autodoc/directive.py +++ b/sphinx/ext/autodoc/directive.py @@ -2,7 +2,7 @@ sphinx.ext.autodoc.directive ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -16,7 +16,7 @@ from docutils.statemachine import StringList from docutils.utils import Reporter, assemble_option_dict from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.environment import BuildEnvironment from sphinx.ext.autodoc import Documenter, Options from sphinx.util import logging @@ -55,7 +55,7 @@ class DocumenterBridge: def __init__(self, env: BuildEnvironment, reporter: Reporter, options: Options, lineno: int, state: Any = None) -> None: self.env = env - self.reporter = reporter + self._reporter = reporter self.genopt = options self.lineno = lineno self.filename_set = set() # type: Set[str] @@ -74,6 +74,12 @@ class DocumenterBridge: def warn(self, msg: str) -> None: logger.warning(msg, location=(self.env.docname, self.lineno)) + @property + def reporter(self) -> Reporter: + warnings.warn('DocumenterBridge.reporter is deprecated.', + RemovedInSphinx50Warning, stacklevel=2) + return self._reporter + def process_documenter_options(documenter: "Type[Documenter]", config: Config, options: Dict ) -> Options: diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index cf8fcc554..d7c9b93f5 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -4,7 +4,7 @@ Importer utilities for autodoc - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -13,15 +13,20 @@ import traceback import warnings from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tuple -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias -from sphinx.pycode import ModuleAnalyzer +from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warning, + deprecated_alias) +from sphinx.ext.autodoc.mock import ismock, undecorate +from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import logging -from sphinx.util.inspect import getslots, isclass, isenumclass, safe_getattr +from sphinx.util.inspect import (getannotations, getmro, getslots, isclass, isenumclass, + safe_getattr) if False: # For type annotation from typing import Type # NOQA + from sphinx.ext.autodoc import ObjectMember + logger = logging.getLogger(__name__) @@ -140,6 +145,9 @@ def get_module_members(module: Any) -> List[Tuple[str, Any]]: """Get members of target module.""" from sphinx.ext.autodoc import INSTANCEATTR + warnings.warn('sphinx.ext.autodoc.importer.get_module_members() is deprecated.', + RemovedInSphinx50Warning) + members = {} # type: Dict[str, Tuple[str, Any]] for name in dir(module): try: @@ -149,10 +157,12 @@ def get_module_members(module: Any) -> List[Tuple[str, Any]]: continue # annotation only member (ex. attr: int) - if hasattr(module, '__annotations__'): - for name in module.__annotations__: + try: + for name in getannotations(module): if name not in members: members[name] = (name, INSTANCEATTR) + except AttributeError: + pass return sorted(list(members.values())) @@ -163,21 +173,15 @@ Attribute = NamedTuple('Attribute', [('name', str), def _getmro(obj: Any) -> Tuple["Type", ...]: - """Get __mro__ from given *obj* safely.""" - __mro__ = safe_getattr(obj, '__mro__', None) - if isinstance(__mro__, tuple): - return __mro__ - else: - return tuple() + warnings.warn('sphinx.ext.autodoc.importer._getmro() is deprecated.', + RemovedInSphinx40Warning) + return getmro(obj) def _getannotations(obj: Any) -> Mapping[str, Any]: - """Get __annotations__ from given *obj* safely.""" - __annotations__ = safe_getattr(obj, '__annotations__', None) - if isinstance(__annotations__, Mapping): - return __annotations__ - else: - return {} + warnings.warn('sphinx.ext.autodoc.importer._getannotations() is deprecated.', + RemovedInSphinx40Warning) + return getannotations(obj) def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, @@ -225,11 +229,14 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, continue # annotation only member (ex. attr: int) - for i, cls in enumerate(_getmro(subject)): - for name in _getannotations(cls): - name = unmangle(cls, name) - if name and name not in members: - members[name] = Attribute(name, i == 0, INSTANCEATTR) + for i, cls in enumerate(getmro(subject)): + try: + for name in getannotations(cls): + name = unmangle(cls, name) + if name and name not in members: + members[name] = Attribute(name, i == 0, INSTANCEATTR) + except AttributeError: + pass if analyzer: # append instance attributes (cf. self.attr1) if analyzer knows @@ -241,6 +248,85 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, return members +def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable + ) -> Dict[str, "ObjectMember"]: + """Get members and attributes of target class.""" + from sphinx.ext.autodoc import INSTANCEATTR, ObjectMember + + # the members directly defined in the class + obj_dict = attrgetter(subject, '__dict__', {}) + + members = {} # type: Dict[str, ObjectMember] + + # enum members + if isenumclass(subject): + for name, value in subject.__members__.items(): + if name not in members: + members[name] = ObjectMember(name, value, class_=subject) + + superclass = subject.__mro__[1] + for name in obj_dict: + if name not in superclass.__dict__: + value = safe_getattr(subject, name) + members[name] = ObjectMember(name, value, class_=subject) + + # members in __slots__ + try: + __slots__ = getslots(subject) + if __slots__: + from sphinx.ext.autodoc import SLOTSATTR + + for name, docstring in __slots__.items(): + members[name] = ObjectMember(name, SLOTSATTR, class_=subject, + docstring=docstring) + except (AttributeError, TypeError, ValueError): + pass + + # other members + for name in dir(subject): + try: + value = attrgetter(subject, name) + if ismock(value): + value = undecorate(value) + + unmangled = unmangle(subject, name) + if unmangled and unmangled not in members: + if name in obj_dict: + members[unmangled] = ObjectMember(unmangled, value, class_=subject) + else: + members[unmangled] = ObjectMember(unmangled, value) + except AttributeError: + continue + + try: + for cls in getmro(subject): + # annotation only member (ex. attr: int) + try: + for name in getannotations(cls): + name = unmangle(cls, name) + if name and name not in members: + members[name] = ObjectMember(name, INSTANCEATTR, class_=cls) + except AttributeError: + pass + + # append instance attributes (cf. self.attr1) if analyzer knows + try: + modname = safe_getattr(cls, '__module__') + qualname = safe_getattr(cls, '__qualname__') + analyzer = ModuleAnalyzer.for_module(modname) + analyzer.analyze() + for (ns, name), docstring in analyzer.attr_docs.items(): + if ns == qualname and name not in members: + members[name] = ObjectMember(name, INSTANCEATTR, class_=cls, + docstring='\n'.join(docstring)) + except (AttributeError, PycodeError): + pass + except AttributeError: + pass + + return members + + from sphinx.ext.autodoc.mock import (MockFinder, MockLoader, _MockModule, _MockObject, # NOQA mock) diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index 40258a135..d3f4291c2 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -4,7 +4,7 @@ mock for autodoc - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -13,10 +13,11 @@ import os import sys from importlib.abc import Loader, MetaPathFinder from importlib.machinery import ModuleSpec -from types import FunctionType, MethodType, ModuleType +from types import ModuleType from typing import Any, Generator, Iterator, List, Sequence, Tuple, Union from sphinx.util import logging +from sphinx.util.inspect import safe_getattr logger = logging.getLogger(__name__) @@ -26,6 +27,7 @@ class _MockObject: __display_name__ = '_MockObject' __sphinx_mock__ = True + __sphinx_decorator_args__ = () # type: Tuple[Any, ...] def __new__(cls, *args: Any, **kwargs: Any) -> Any: if len(args) == 3 and isinstance(args[1], tuple): @@ -59,18 +61,19 @@ class _MockObject: return _make_subclass(key, self.__display_name__, self.__class__)() def __call__(self, *args: Any, **kwargs: Any) -> Any: - if args and type(args[0]) in [type, FunctionType, MethodType]: - # Appears to be a decorator, pass through unchanged - return args[0] - return self + call = self.__class__() + call.__sphinx_decorator_args__ = args + return call def __repr__(self) -> str: return self.__display_name__ def _make_subclass(name: str, module: str, superclass: Any = _MockObject, - attributes: Any = None) -> Any: - attrs = {'__module__': module, '__display_name__': module + '.' + name} + attributes: Any = None, decorator_args: Tuple = ()) -> Any: + attrs = {'__module__': module, + '__display_name__': module + '.' + name, + '__sphinx_decorator_args__': decorator_args} attrs.update(attributes or {}) return type(name, (superclass,), attrs) @@ -147,3 +150,38 @@ def mock(modnames: List[str]) -> Generator[None, None, None]: finally: sys.meta_path.remove(finder) finder.invalidate_caches() + + +def ismock(subject: Any) -> bool: + """Check if the object is mocked.""" + # check the object has '__sphinx_mock__' attribute + try: + if safe_getattr(subject, '__sphinx_mock__', None) is None: + return False + except AttributeError: + return False + + # check the object is mocked module + if isinstance(subject, _MockModule): + return True + + try: + # check the object is mocked object + __mro__ = safe_getattr(type(subject), '__mro__', []) + if len(__mro__) > 2 and __mro__[1] is _MockObject: + return True + except AttributeError: + pass + + return False + + +def undecorate(subject: _MockObject) -> Any: + """Unwrap mock if *subject* is decorated by mocked object. + + If not decorated, returns given *subject* itself. + """ + if ismock(subject) and subject.__sphinx_decorator_args__: + return subject.__sphinx_decorator_args__[0] + else: + return subject diff --git a/sphinx/ext/autodoc/type_comment.py b/sphinx/ext/autodoc/type_comment.py index debbc1442..4db13c695 100644 --- a/sphinx/ext/autodoc/type_comment.py +++ b/sphinx/ext/autodoc/type_comment.py @@ -4,7 +4,7 @@ Update annotations info of living objects using type_comments. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/autodoc/typehints.py b/sphinx/ext/autodoc/typehints.py index e6451b52c..533b71e42 100644 --- a/sphinx/ext/autodoc/typehints.py +++ b/sphinx/ext/autodoc/typehints.py @@ -4,7 +4,7 @@ Generating content for autodoc using typehints - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index 73a460117..be8ad0bc1 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -4,7 +4,7 @@ Allow reference sections by :ref: role using its title. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 0d2353583..b268127d0 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -48,7 +48,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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -98,7 +98,7 @@ logger = logging.getLogger(__name__) periods_re = re.compile(r'\.(?:\s+)') literal_re = re.compile(r'::\s*$') -WELL_KNOWN_ABBREVIATIONS = (' i.e.',) +WELL_KNOWN_ABBREVIATIONS = ('et al.', ' i.e.',) # -- autosummary_toc node ------------------------------------------------------ diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 14bd1c789..0240d2c7c 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -13,7 +13,7 @@ generate: sphinx-autogen -o source/generated source/*.rst - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -40,6 +40,7 @@ from sphinx.builders import Builder from sphinx.config import Config from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.ext.autodoc import Documenter +from sphinx.ext.autodoc.importer import import_module from sphinx.ext.autosummary import get_documenter, import_by_name, import_ivar_by_name from sphinx.locale import __ from sphinx.pycode import ModuleAnalyzer, PycodeError @@ -87,27 +88,27 @@ AutosummaryEntry = NamedTuple('AutosummaryEntry', [('name', str), def setup_documenters(app: Any) -> None: from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter, DataDocumenter, DecoratorDocumenter, ExceptionDocumenter, - FunctionDocumenter, GenericAliasDocumenter, - InstanceAttributeDocumenter, MethodDocumenter, - ModuleDocumenter, NewTypeAttributeDocumenter, - NewTypeDataDocumenter, PropertyDocumenter, - SingledispatchFunctionDocumenter, SlotsAttributeDocumenter) + FunctionDocumenter, MethodDocumenter, ModuleDocumenter, + NewTypeAttributeDocumenter, NewTypeDataDocumenter, + PropertyDocumenter) documenters = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter, - NewTypeDataDocumenter, AttributeDocumenter, InstanceAttributeDocumenter, - DecoratorDocumenter, PropertyDocumenter, SlotsAttributeDocumenter, - GenericAliasDocumenter, SingledispatchFunctionDocumenter, + NewTypeDataDocumenter, AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, ] # type: List[Type[Documenter]] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) def _simple_info(msg: str) -> None: + warnings.warn('_simple_info() is deprecated.', + RemovedInSphinx50Warning, stacklevel=2) print(msg) def _simple_warn(msg: str) -> None: + warnings.warn('_simple_warn() is deprecated.', + RemovedInSphinx50Warning, stacklevel=2) print('WARNING: ' + msg, file=sys.stderr) @@ -288,6 +289,13 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, items = [] # type: List[str] for _, modname, ispkg in pkgutil.iter_modules(obj.__path__): fullname = name + '.' + modname + try: + module = import_module(fullname) + if module and hasattr(module, '__sphinx_mock__'): + continue + except ImportError: + pass + items.append(fullname) public = [x for x in items if not x.split('.')[-1].startswith('_')] return public, items diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index b971b5f25..f052b8810 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -5,7 +5,7 @@ Check Python modules and C API for coverage. Mostly written by Josip Dzolonga for the Google Highly Open Participation contest. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 aeeb7f5ec..20afa2ec6 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -5,7 +5,7 @@ Mimic doctest by automatically executing code snippets and checking their results. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/duration.py b/sphinx/ext/duration.py index 7073f7769..f714dedfb 100644 --- a/sphinx/ext/duration.py +++ b/sphinx/ext/duration.py @@ -4,7 +4,7 @@ Measure durations of Sphinx processing. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/extlinks.py b/sphinx/ext/extlinks.py index ff3adb357..d82c0378b 100644 --- a/sphinx/ext/extlinks.py +++ b/sphinx/ext/extlinks.py @@ -19,7 +19,7 @@ You can also give an explicit caption, e.g. :exmpl:`Foo `. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/githubpages.py b/sphinx/ext/githubpages.py index 4564ce60e..e760bb4b6 100644 --- a/sphinx/ext/githubpages.py +++ b/sphinx/ext/githubpages.py @@ -4,7 +4,7 @@ To publish HTML docs at GitHub Pages, create .nojekyll file. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index dc1cbb686..f10285086 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -5,7 +5,7 @@ Allow graphviz-formatted graphs to be included in Sphinx-generated documents inline. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -142,6 +142,7 @@ class Graphviz(SphinxDirective): 'it failed') % filename, line=self.lineno)] else: dotcode = '\n'.join(self.content) + rel_filename = None if not dotcode.strip(): return [self.state_machine.reporter.warning( __('Ignoring "graphviz" directive without content.'), @@ -160,6 +161,8 @@ class Graphviz(SphinxDirective): node['align'] = self.options['align'] if 'class' in self.options: node['classes'] = self.options['class'] + if rel_filename: + node['filename'] = rel_filename if 'caption' not in self.options: self.add_name(node) @@ -213,8 +216,8 @@ class GraphvizSimple(SphinxDirective): return [figure] -def render_dot(self: SphinxTranslator, code: str, options: Dict, - format: str, prefix: str = 'graphviz') -> Tuple[str, str]: +def render_dot(self: SphinxTranslator, code: str, options: Dict, format: str, + prefix: str = 'graphviz', filename: str = None) -> Tuple[str, str]: """Render graphviz code into a PNG or PDF output file.""" graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot) hashkey = (code + str(options) + str(graphviz_dot) + @@ -238,7 +241,10 @@ def render_dot(self: SphinxTranslator, code: str, options: Dict, dot_args.extend(['-T' + format, '-o' + outfn]) docname = options.get('docname', 'index') - cwd = path.dirname(path.join(self.builder.srcdir, docname)) + if filename: + cwd = path.dirname(path.join(self.builder.srcdir, filename)) + else: + cwd = path.dirname(path.join(self.builder.srcdir, docname)) if format == 'png': dot_args.extend(['-Tcmapx', '-o%s.map' % outfn]) @@ -263,14 +269,14 @@ def render_dot(self: SphinxTranslator, code: str, options: Dict, def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Dict, - prefix: str = 'graphviz', imgcls: str = None, alt: str = None - ) -> Tuple[str, str]: + prefix: str = 'graphviz', imgcls: str = None, alt: str = None, + filename: str = None) -> Tuple[str, str]: format = self.builder.config.graphviz_output_format try: if format not in ('png', 'svg'): raise GraphvizError(__("graphviz_output_format must be one of 'png', " "'svg', but is %r") % format) - fname, outfn = render_dot(self, code, options, format, prefix) + fname, outfn = render_dot(self, code, options, format, prefix, filename) except GraphvizError as exc: logger.warning(__('dot code %r: %s'), code, exc) raise nodes.SkipNode from exc @@ -315,13 +321,14 @@ def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Di def html_visit_graphviz(self: HTMLTranslator, node: graphviz) -> None: - render_dot_html(self, node, node['code'], node['options']) + render_dot_html(self, node, node['code'], node['options'], filename=node.get('filename')) def render_dot_latex(self: LaTeXTranslator, node: graphviz, code: str, - options: Dict, prefix: str = 'graphviz') -> None: + options: Dict, prefix: str = 'graphviz', filename: str = None + ) -> None: try: - fname, outfn = render_dot(self, code, options, 'pdf', prefix) + fname, outfn = render_dot(self, code, options, 'pdf', prefix, filename) except GraphvizError as exc: logger.warning(__('dot code %r: %s'), code, exc) raise nodes.SkipNode from exc @@ -352,7 +359,7 @@ def render_dot_latex(self: LaTeXTranslator, node: graphviz, code: str, def latex_visit_graphviz(self: LaTeXTranslator, node: graphviz) -> None: - render_dot_latex(self, node, node['code'], node['options']) + render_dot_latex(self, node, node['code'], node['options'], filename=node.get('filename')) def render_dot_texinfo(self: TexinfoTranslator, node: graphviz, code: str, diff --git a/sphinx/ext/ifconfig.py b/sphinx/ext/ifconfig.py index bae4554cf..df8545028 100644 --- a/sphinx/ext/ifconfig.py +++ b/sphinx/ext/ifconfig.py @@ -15,7 +15,7 @@ namespace of the project configuration (that is, all variables from ``conf.py`` are available.) - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py index a82b35a5e..b0d40b551 100644 --- a/sphinx/ext/imgconverter.py +++ b/sphinx/ext/imgconverter.py @@ -4,7 +4,7 @@ Image converter extension for Sphinx - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index 9e124749e..cef50d548 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -4,7 +4,7 @@ Render math in HTML via dvipng or dvisvgm. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -93,7 +93,7 @@ depthsvgcomment_re = re.compile(r'') def read_svg_depth(filename: str) -> int: """Read the depth from comment at last line of SVG file """ - with open(filename, 'r') as f: + with open(filename) as f: for line in f: pass # Only last line is checked diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index 9e38041df..63a171087 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -31,7 +31,7 @@ r""" The graph is inserted as a PNG+image map into HTML and a PDF in LaTeX. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 52ba11782..5569ad9de 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -19,7 +19,7 @@ also be specified individually, e.g. if the docs should be buildable without Internet access. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py index 1d4eb4826..cec40e224 100644 --- a/sphinx/ext/jsmath.py +++ b/sphinx/ext/jsmath.py @@ -5,7 +5,7 @@ Set up everything for use of JSMath to display math in HTML via JavaScript. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/linkcode.py b/sphinx/ext/linkcode.py index 68e8603f7..5c118a9fb 100644 --- a/sphinx/ext/linkcode.py +++ b/sphinx/ext/linkcode.py @@ -4,7 +4,7 @@ Add external links to module code in Python object descriptions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py index 6317a1315..ff8ef3718 100644 --- a/sphinx/ext/mathjax.py +++ b/sphinx/ext/mathjax.py @@ -6,7 +6,7 @@ Sphinx's HTML writer -- requires the MathJax JavaScript library on your webserver/computer. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -17,14 +17,17 @@ from docutils import nodes import sphinx from sphinx.application import Sphinx -from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.domains.math import MathDomain -from sphinx.environment import BuildEnvironment from sphinx.errors import ExtensionError from sphinx.locale import _ from sphinx.util.math import get_node_equation_number from sphinx.writers.html import HTMLTranslator +# more information for mathjax secure url is here: +# https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn +MATHJAX_URL = ('https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?' + 'config=TeX-AMS-MML_HTMLorMML') + def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None: self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight')) @@ -66,25 +69,25 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None raise nodes.SkipNode -def install_mathjax(app: Sphinx, env: BuildEnvironment) -> None: +def install_mathjax(app: Sphinx, pagename: str, templatename: str, context: Dict, + event_arg: Any) -> None: if app.builder.format != 'html' or app.builder.math_renderer_name != 'mathjax': # type: ignore # NOQA return if not app.config.mathjax_path: raise ExtensionError('mathjax_path config value must be set for the ' 'mathjax extension to work') - builder = cast(StandaloneHTMLBuilder, app.builder) - domain = cast(MathDomain, env.get_domain('math')) - if domain.has_equations(): + domain = cast(MathDomain, app.env.get_domain('math')) + if domain.has_equations(pagename): # Enable mathjax only if equations exists options = {'async': 'async'} if app.config.mathjax_options: options.update(app.config.mathjax_options) - builder.add_js_file(app.config.mathjax_path, **options) + app.add_js_file(app.config.mathjax_path, **options) # type: ignore if app.config.mathjax_config: body = "MathJax.Hub.Config(%s)" % json.dumps(app.config.mathjax_config) - builder.add_js_file(None, type="text/x-mathjax-config", body=body) + app.add_js_file(None, type="text/x-mathjax-config", body=body) def setup(app: Sphinx) -> Dict[str, Any]: @@ -92,15 +95,11 @@ def setup(app: Sphinx) -> Dict[str, Any]: (html_visit_math, None), (html_visit_displaymath, None)) - # more information for mathjax secure url is here: - # https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn - app.add_config_value('mathjax_path', - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?' - 'config=TeX-AMS-MML_HTMLorMML', 'html') + app.add_config_value('mathjax_path', MATHJAX_URL, 'html') app.add_config_value('mathjax_options', {}, 'html') app.add_config_value('mathjax_inline', [r'\(', r'\)'], 'html') app.add_config_value('mathjax_display', [r'\[', r'\]'], 'html') app.add_config_value('mathjax_config', None, 'html') - app.connect('env-updated', install_mathjax) + app.connect('html-page-context', install_mathjax) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py index e2ff5439d..4a8c2135a 100644 --- a/sphinx/ext/napoleon/__init__.py +++ b/sphinx/ext/napoleon/__init__.py @@ -4,7 +4,7 @@ Support for NumPy and Google style docstrings. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -44,6 +44,7 @@ class Config: napoleon_preprocess_types = False napoleon_type_aliases = None napoleon_custom_sections = None + napoleon_attr_annotations = True .. _Google style: https://google.github.io/styleguide/pyguide.html @@ -252,11 +253,19 @@ class Config: * To create a custom "generic" section, just pass a string. * To create an alias for an existing section, pass a tuple containing the alias name and the original, in that order. + * To create a custom section that displays like the parameters or returns + section, pass a tuple containing the custom section name and a string + value, "params_style" or "returns_style". If an entry is just a string, it is interpreted as a header for a generic section. If the entry is a tuple/list/indexed container, the first entry - is the name of the section, the second is the section key to emulate. + is the name of the section, the second is the section key to emulate. If the + second entry value is "params_style" or "returns_style", the custom section + will be displayed like the parameters section or returns section. + napoleon_attr_annotations : :obj:`bool` (Defaults to True) + Use the type annotations of class attributes that are documented in the docstring + but do not have a type in the docstring. """ _config_values = { @@ -274,7 +283,8 @@ class Config: 'napoleon_use_keyword': (True, 'env'), 'napoleon_preprocess_types': (False, 'env'), 'napoleon_type_aliases': (None, 'env'), - 'napoleon_custom_sections': (None, 'env') + 'napoleon_custom_sections': (None, 'env'), + 'napoleon_attr_annotations': (True, 'env'), } def __init__(self, **settings: Any) -> None: diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index ddcf3f01b..bd1c44509 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -6,7 +6,7 @@ Classes for docstring parsing and formatting. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -21,6 +21,8 @@ from sphinx.config import Config as SphinxConfig from sphinx.ext.napoleon.iterators import modify_iter from sphinx.locale import _, __ from sphinx.util import logging +from sphinx.util.inspect import stringify_annotation +from sphinx.util.typing import get_type_hints if False: # For type annotation @@ -57,6 +59,19 @@ _default_regex = re.compile( _SINGLETONS = ("None", "True", "False", "Ellipsis") +def _convert_type_spec(_type: str, translations: Dict[str, str] = {}) -> str: + """Convert type specification to reference in reST.""" + if _type in translations: + return translations[_type] + else: + if _type == 'None': + return ':obj:`None`' + else: + return ':class:`%s`' % _type + + return _type + + class GoogleDocstring: """Convert Google style docstrings to reStructuredText. @@ -177,6 +192,8 @@ class GoogleDocstring: 'notes': self._parse_notes_section, 'other parameters': self._parse_other_parameters_section, 'parameters': self._parse_parameters_section, + 'receive': self._parse_receives_section, + 'receives': self._parse_receives_section, 'return': self._parse_returns_section, 'returns': self._parse_returns_section, 'raise': self._parse_raises_section, @@ -261,6 +278,10 @@ class GoogleDocstring: if prefer_type and not _type: _type, _name = _name, _type + + if _type and self._config.napoleon_preprocess_types: + _type = _convert_type_spec(_type, self._config.napoleon_type_aliases or {}) + indent = self._get_indent(line) + 1 _descs = [_desc] + self._dedent(self._consume_indented_block(indent)) _descs = self.__class__(_descs, self._config).lines() @@ -289,7 +310,8 @@ class GoogleDocstring: _descs = self.__class__(_descs, self._config).lines() return _type, _descs - def _consume_returns_section(self) -> List[Tuple[str, str, List[str]]]: + def _consume_returns_section(self, preprocess_types: bool = False + ) -> List[Tuple[str, str, List[str]]]: lines = self._dedent(self._consume_to_next_section()) if lines: before, colon, after = self._partition_field_on_colon(lines[0]) @@ -303,6 +325,10 @@ class GoogleDocstring: _type = before + if (_type and preprocess_types and + self._config.napoleon_preprocess_types): + _type = _convert_type_spec(_type, self._config.napoleon_type_aliases or {}) + _desc = self.__class__(_desc, self._config).lines() return [(_name, _type, _desc,)] else: @@ -545,11 +571,18 @@ class GoogleDocstring: self._sections[entry.lower()] = self._parse_custom_generic_section else: # otherwise, assume entry is container; - # [0] is new section, [1] is the section to alias. - # in the case of key mismatch, just handle as generic section. - self._sections[entry[0].lower()] = \ - self._sections.get(entry[1].lower(), - self._parse_custom_generic_section) + if entry[1] == "params_style": + self._sections[entry[0].lower()] = \ + self._parse_custom_params_style_section + elif entry[1] == "returns_style": + self._sections[entry[0].lower()] = \ + self._parse_custom_returns_style_section + else: + # [0] is new section, [1] is the section to alias. + # in the case of key mismatch, just handle as generic section. + self._sections[entry[0].lower()] = \ + self._sections.get(entry[1].lower(), + self._parse_custom_generic_section) def _parse(self) -> None: self._parsed_lines = self._consume_empty() @@ -600,6 +633,8 @@ class GoogleDocstring: def _parse_attributes_section(self, section: str) -> List[str]: lines = [] for _name, _type, _desc in self._consume_fields(): + if not _type: + _type = self._lookup_annotation(_name) if self._config.napoleon_use_ivar: _name = self._qualify_name(_name, self._obj) field = ':ivar %s: ' % _name @@ -635,6 +670,13 @@ class GoogleDocstring: # for now, no admonition for simple custom sections return self._parse_generic_section(section, False) + def _parse_custom_params_style_section(self, section: str) -> List[str]: + return self._format_fields(section, self._consume_fields()) + + def _parse_custom_returns_style_section(self, section: str) -> List[str]: + fields = self._consume_returns_section(preprocess_types=True) + return self._format_fields(section, fields) + def _parse_usage_section(self, section: str) -> List[str]: header = ['.. rubric:: Usage:', ''] block = ['.. code-block:: python', ''] @@ -710,6 +752,15 @@ class GoogleDocstring: lines.append('') return lines + def _parse_receives_section(self, section: str) -> List[str]: + if self._config.napoleon_use_param: + # Allow to declare multiple parameters at once (ex: x, y: int) + fields = self._consume_fields(multiple=True) + return self._format_docutils_params(fields) + else: + fields = self._consume_fields() + return self._format_fields(_('Receives'), fields) + def _parse_references_section(self, section: str) -> List[str]: use_admonition = self._config.napoleon_use_admonition_for_references return self._parse_generic_section(_('References'), use_admonition) @@ -749,7 +800,7 @@ class GoogleDocstring: return self._format_fields(_('Warns'), self._consume_fields()) def _parse_yields_section(self, section: str) -> List[str]: - fields = self._consume_returns_section() + fields = self._consume_returns_section(preprocess_types=True) return self._format_fields(_('Yields'), fields) def _partition_field_on_colon(self, line: str) -> Tuple[str, str, str]: @@ -804,6 +855,21 @@ class GoogleDocstring: lines = lines[start:end + 1] return lines + def _lookup_annotation(self, _name: str) -> str: + if self._config.napoleon_attr_annotations: + if self._what in ("module", "class", "exception") and self._obj: + # cache the class annotations + if not hasattr(self, "_annotations"): + localns = getattr(self._config, "autodoc_type_aliases", {}) + localns.update(getattr( + self._config, "napoleon_type_aliases", {} + ) or {}) + self._annotations = get_type_hints(self._obj, None, localns) + if _name in self._annotations: + return stringify_annotation(self._annotations[_name]) + # No annotation found + return "" + def _recombine_set_tokens(tokens: List[str]) -> List[str]: token_queue = collections.deque(tokens) @@ -1108,6 +1174,9 @@ class NumpyDocstring(GoogleDocstring): _name, _type = _name.strip(), _type.strip() _name = self._escape_args_and_kwargs(_name) + if parse_type and not _type: + _type = self._lookup_annotation(_name) + if prefer_type and not _type: _type, _name = _name, _type @@ -1123,7 +1192,8 @@ class NumpyDocstring(GoogleDocstring): _desc = self.__class__(_desc, self._config).lines() return _name, _type, _desc - def _consume_returns_section(self) -> List[Tuple[str, str, List[str]]]: + def _consume_returns_section(self, preprocess_types: bool = False + ) -> List[Tuple[str, str, List[str]]]: return self._consume_fields(prefer_type=True) def _consume_section_header(self) -> str: diff --git a/sphinx/ext/napoleon/iterators.py b/sphinx/ext/napoleon/iterators.py index fc41afdb3..e0f5cca9c 100644 --- a/sphinx/ext/napoleon/iterators.py +++ b/sphinx/ext/napoleon/iterators.py @@ -6,7 +6,7 @@ A collection of helpful iterators. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 273cf3f73..640c93935 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -7,7 +7,7 @@ all todos of your project and lists them along with a backlink to the original location. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 812b3d6a2..21cff6a03 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -4,12 +4,15 @@ Add links to module code in Python object descriptions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +import posixpath import traceback -from typing import Any, Dict, Iterable, Iterator, Set, Tuple +import warnings +from os import path +from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, cast from docutils import nodes from docutils.nodes import Element, Node @@ -17,16 +20,32 @@ from docutils.nodes import Element, Node import sphinx from sphinx import addnodes from sphinx.application import Sphinx +from sphinx.builders import Builder +from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer +from sphinx.transforms.post_transforms import SphinxPostTransform from sphinx.util import get_full_modname, logging, status_iterator from sphinx.util.nodes import make_refnode logger = logging.getLogger(__name__) -def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str: +OUTPUT_DIRNAME = '_modules' + + +class viewcode_anchor(Element): + """Node for viewcode anchors. + + This node will be processed in the resolving phase. + For viewcode supported builders, they will be all converted to the anchors. + For not supported builders, they will be removed. + """ + + +def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str]: try: return get_full_modname(modname, attribute) except AttributeError: @@ -44,14 +63,21 @@ def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str: return None +def is_supported_builder(builder: Builder) -> bool: + if builder.format != 'html': + return False + elif builder.name == 'singlehtml': + return False + elif builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: + return False + else: + return True + + def doctree_read(app: Sphinx, doctree: Node) -> None: env = app.builder.env if not hasattr(env, '_viewcode_modules'): env._viewcode_modules = {} # type: ignore - if app.builder.name == "singlehtml": - return - if app.builder.name.startswith("epub") and not env.config.viewcode_enable_epub: - return def has_tag(modname: str, fullname: str, docname: str, refname: str) -> bool: entry = env._viewcode_modules.get(modname, None) # type: ignore @@ -108,13 +134,8 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: # only one link per name, please continue names.add(fullname) - pagename = '_modules/' + modname.replace('.', '/') - inline = nodes.inline('', _('[source]'), classes=['viewcode-link']) - onlynode = addnodes.only(expr='html') - onlynode += addnodes.pending_xref('', inline, reftype='viewcode', refdomain='std', - refexplicit=False, reftarget=pagename, - refid=fullname, refdoc=env.docname) - signode += onlynode + pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/')) + signode += viewcode_anchor(reftarget=pagename, refid=fullname, refdoc=env.docname) def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str], @@ -128,20 +149,92 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str], env._viewcode_modules.update(other._viewcode_modules) # type: ignore +def env_purge_doc(app: Sphinx, env: BuildEnvironment, docname: str) -> None: + modules = getattr(env, '_viewcode_modules', {}) + + for modname, (code, tags, used, refname) in list(modules.items()): + for fullname in list(used): + if used[fullname] == docname: + used.pop(fullname) + + if len(used) == 0: + modules.pop(modname) + + +class ViewcodeAnchorTransform(SphinxPostTransform): + """Convert or remove viewcode_anchor nodes depends on builder.""" + default_priority = 100 + + def run(self, **kwargs: Any) -> None: + if is_supported_builder(self.app.builder): + self.convert_viewcode_anchors() + else: + self.remove_viewcode_anchors() + + def convert_viewcode_anchors(self) -> None: + for node in self.document.traverse(viewcode_anchor): + anchor = nodes.inline('', _('[source]'), classes=['viewcode-link']) + refnode = make_refnode(self.app.builder, node['refdoc'], node['reftarget'], + node['refid'], anchor) + node.replace_self(refnode) + + def remove_viewcode_anchors(self) -> None: + for node in self.document.traverse(viewcode_anchor): + node.parent.remove(node) + + def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node - ) -> Node: + ) -> Optional[Node]: # resolve our "viewcode" reference nodes -- they need special treatment if node['reftype'] == 'viewcode': + warnings.warn('viewcode extension is no longer use pending_xref node. ' + 'Please update your extension.', RemovedInSphinx50Warning) return make_refnode(app.builder, node['refdoc'], node['reftarget'], node['refid'], contnode) return None -def collect_pages(app: Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]: +def get_module_filename(app: Sphinx, modname: str) -> Optional[str]: + """Get module filename for *modname*.""" + source_info = app.emit_firstresult('viewcode-find-source', modname) + if source_info: + return None + else: + try: + filename, source = ModuleAnalyzer.get_module_source(modname) + return filename + except Exception: + return None + + +def should_generate_module_page(app: Sphinx, modname: str) -> bool: + """Check generation of module page is needed.""" + module_filename = get_module_filename(app, modname) + if module_filename is None: + # Always (re-)generate module page when module filename is not found. + return True + + builder = cast(StandaloneHTMLBuilder, app.builder) + basename = modname.replace('.', '/') + builder.out_suffix + page_filename = path.join(app.outdir, '_modules/', basename) + + try: + if path.getmtime(module_filename) <= path.getmtime(page_filename): + # generation is not needed if the HTML page is newer than module file. + return False + except IOError: + pass + + return True + + +def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], None, None]: env = app.builder.env if not hasattr(env, '_viewcode_modules'): return + if not is_supported_builder(app.builder): + return highlighter = app.builder.highlighter # type: ignore urito = app.builder.get_relative_uri @@ -154,9 +247,12 @@ def collect_pages(app: Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]: app.verbosity, lambda x: x[0]): if not entry: continue + if not should_generate_module_page(app, modname): + continue + code, tags, used, refname = entry # construct a page name for the highlighted source - pagename = '_modules/' + modname.replace('.', '/') + pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/')) # highlight the source using the builder's highlighter if env.config.highlight_language in ('python3', 'default', 'none'): lexer = env.config.highlight_language @@ -188,10 +284,10 @@ def collect_pages(app: Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]: parent = parent.rsplit('.', 1)[0] if parent in modnames: parents.append({ - 'link': urito(pagename, '_modules/' + - parent.replace('.', '/')), + 'link': urito(pagename, + posixpath.join(OUTPUT_DIRNAME, parent.replace('.', '/'))), 'title': parent}) - parents.append({'link': urito(pagename, '_modules/index'), + parents.append({'link': urito(pagename, posixpath.join(OUTPUT_DIRNAME, 'index')), 'title': _('Module code')}) parents.reverse() # putting it all together @@ -220,7 +316,8 @@ def collect_pages(app: Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]: html.append('') stack.append(modname + '.') html.append('
  • %s
  • \n' % ( - urito('_modules/index', '_modules/' + modname.replace('.', '/')), + urito(posixpath.join(OUTPUT_DIRNAME, 'index'), + posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))), modname)) html.append('' * (len(stack) - 1)) context = { @@ -229,7 +326,7 @@ def collect_pages(app: Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]: ''.join(html)), } - yield ('_modules/index', context, 'page.html') + yield (posixpath.join(OUTPUT_DIRNAME, 'index'), context, 'page.html') def setup(app: Sphinx) -> Dict[str, Any]: @@ -238,12 +335,14 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('viewcode_follow_imported_members', True, False) app.connect('doctree-read', doctree_read) app.connect('env-merge-info', env_merge_info) + app.connect('env-purge-doc', env_purge_doc) app.connect('html-collect-pages', collect_pages) app.connect('missing-reference', missing_reference) # app.add_config_value('viewcode_include_modules', [], 'env') # app.add_config_value('viewcode_exclude_modules', [], 'env') app.add_event('viewcode-find-source') app.add_event('viewcode-follow-imported') + app.add_post_transform(ViewcodeAnchorTransform) return { 'version': sphinx.__display_version__, 'env_version': 1, diff --git a/sphinx/extension.py b/sphinx/extension.py index fa7a0d490..8129d89af 100644 --- a/sphinx/extension.py +++ b/sphinx/extension.py @@ -4,7 +4,7 @@ Utilities for Sphinx extensions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index a341212cf..8425009f7 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -4,7 +4,7 @@ Highlight code blocks using Pygments. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/io.py b/sphinx/io.py index e4c2e00b0..3508dd58d 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -4,7 +4,7 @@ Input/Output files - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import codecs diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index 0a26fc982..c890455f3 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -4,7 +4,7 @@ Glue code for the jinja2 templating engine. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 5210dc725..bedd9e1cb 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -4,7 +4,7 @@ Locale utilities. - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/parsers.py b/sphinx/parsers.py index 19f726b0d..17c291af7 100644 --- a/sphinx/parsers.py +++ b/sphinx/parsers.py @@ -4,7 +4,7 @@ A Base class for additional parsers. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/project.py b/sphinx/project.py index e5f05c9d3..e5df4013f 100644 --- a/sphinx/project.py +++ b/sphinx/project.py @@ -4,7 +4,7 @@ Utility function and classes for Sphinx projects. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 05f45a6d8..ab0dfdbf8 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -4,7 +4,7 @@ Utilities parsing and analyzing Python code. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -179,7 +179,7 @@ class ModuleAnalyzer: self.overloads = parser.overloads self.tags = parser.definitions self.tagorder = parser.deforders - self._parsed = True + self._analyzed = True except Exception as exc: raise PycodeError('parsing %r failed: %r' % (self.srcname, exc)) from exc diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py index e8b0832ea..65534f958 100644 --- a/sphinx/pycode/ast.py +++ b/sphinx/pycode/ast.py @@ -4,7 +4,7 @@ Helpers for AST (Abstract Syntax Tree). - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -52,6 +52,10 @@ def parse(code: str, mode: str = 'exec') -> "ast.AST": try: # type_comments parameter is available on py38+ return ast.parse(code, mode=mode, type_comments=True) # type: ignore + except SyntaxError: + # Some syntax error found. To ignore invalid type comments, retry parsing without + # type_comments parameter (refs: https://github.com/sphinx-doc/sphinx/issues/8652). + return ast.parse(code, mode=mode) except TypeError: # fallback to ast module. # typed_ast is used to parse type_comments if installed. diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index d24012111..dca59acd4 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -4,7 +4,7 @@ Utilities parsing and analyzing Python code. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import inspect diff --git a/sphinx/pygments_styles.py b/sphinx/pygments_styles.py index 248ae536e..1046826af 100644 --- a/sphinx/pygments_styles.py +++ b/sphinx/pygments_styles.py @@ -4,7 +4,7 @@ Sphinx theme specific highlighting styles. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/registry.py b/sphinx/registry.py index 0e3b95436..c6a249e74 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -4,7 +4,7 @@ Sphinx component registry. - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -63,7 +63,7 @@ class SphinxComponentRegistry: self.documenters = {} # type: Dict[str, Type[Documenter]] #: css_files; a list of tuple of filename and attributes - self.css_files = [] # type: List[Tuple[str, Dict[str, str]]] + self.css_files = [] # type: List[Tuple[str, Dict[str, Any]]] #: domains; a dict of domain name -> domain class self.domains = {} # type: Dict[str, Type[Domain]] @@ -94,7 +94,7 @@ class SphinxComponentRegistry: self.html_block_math_renderers = {} # type: Dict[str, Tuple[Callable, Callable]] #: js_files; list of JS paths or URLs - self.js_files = [] # type: List[Tuple[str, Dict[str, str]]] + self.js_files = [] # type: List[Tuple[str, Dict[str, Any]]] #: LaTeX packages; list of package names and its options self.latex_packages = [] # type: List[Tuple[str, str]] @@ -361,10 +361,10 @@ class SphinxComponentRegistry: attrgetter: Callable[[Any, str, Any], Any]) -> None: self.autodoc_attrgettrs[typ] = attrgetter - def add_css_files(self, filename: str, **attributes: str) -> None: + def add_css_files(self, filename: str, **attributes: Any) -> None: self.css_files.append((filename, attributes)) - def add_js_file(self, filename: str, **attributes: str) -> None: + def add_js_file(self, filename: str, **attributes: Any) -> None: logger.debug('[app] adding js_file: %r, %r', filename, attributes) self.js_files.append((filename, attributes)) diff --git a/sphinx/roles.py b/sphinx/roles.py index 2d3042f8f..4f9261360 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -4,7 +4,7 @@ Handlers for additional ReST roles. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index 2b7f16099..6c748aed1 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -4,7 +4,7 @@ Create a full-text search index for offline search. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import html diff --git a/sphinx/search/da.py b/sphinx/search/da.py index 57af4a7be..e164e0271 100644 --- a/sphinx/search/da.py +++ b/sphinx/search/da.py @@ -4,7 +4,7 @@ Danish search language: includes the JS Danish stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/de.py b/sphinx/search/de.py index 6c84d463a..174eb8d2f 100644 --- a/sphinx/search/de.py +++ b/sphinx/search/de.py @@ -4,7 +4,7 @@ German search language: includes the JS German stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/en.py b/sphinx/search/en.py index 23a6f9739..b33769cec 100644 --- a/sphinx/search/en.py +++ b/sphinx/search/en.py @@ -4,7 +4,7 @@ English search language: includes the JS porter stemmer. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/es.py b/sphinx/search/es.py index 1e8a21085..e87584cfc 100644 --- a/sphinx/search/es.py +++ b/sphinx/search/es.py @@ -4,7 +4,7 @@ Spanish search language: includes the JS Spanish stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/fi.py b/sphinx/search/fi.py index e98ef9805..ceb38cb5a 100644 --- a/sphinx/search/fi.py +++ b/sphinx/search/fi.py @@ -4,7 +4,7 @@ Finnish search language: includes the JS Finnish stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/fr.py b/sphinx/search/fr.py index 7cc35e2c8..46320a863 100644 --- a/sphinx/search/fr.py +++ b/sphinx/search/fr.py @@ -4,7 +4,7 @@ French search language: includes the JS French stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/hu.py b/sphinx/search/hu.py index 01ade35b7..628dde63e 100644 --- a/sphinx/search/hu.py +++ b/sphinx/search/hu.py @@ -4,7 +4,7 @@ Hungarian search language: includes the JS Hungarian stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/it.py b/sphinx/search/it.py index fa81260fb..2ecf4c2c5 100644 --- a/sphinx/search/it.py +++ b/sphinx/search/it.py @@ -4,7 +4,7 @@ Italian search language: includes the JS Italian stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py index b248eb281..6f10a4239 100644 --- a/sphinx/search/ja.py +++ b/sphinx/search/ja.py @@ -4,7 +4,7 @@ Japanese search language: includes routine to split words. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/jssplitter.py b/sphinx/search/jssplitter.py index 945579560..9b879772a 100644 --- a/sphinx/search/jssplitter.py +++ b/sphinx/search/jssplitter.py @@ -6,7 +6,7 @@ DO NOT EDIT. This is generated by utils/jssplitter_generator.py - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/nl.py b/sphinx/search/nl.py index f30ee690a..f98cdf8ad 100644 --- a/sphinx/search/nl.py +++ b/sphinx/search/nl.py @@ -4,7 +4,7 @@ Dutch search language: includes the JS porter stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/no.py b/sphinx/search/no.py index 324264bde..c78bb6981 100644 --- a/sphinx/search/no.py +++ b/sphinx/search/no.py @@ -4,7 +4,7 @@ Norwegian search language: includes the JS Norwegian stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/pt.py b/sphinx/search/pt.py index 7783f65c0..a7d99cde3 100644 --- a/sphinx/search/pt.py +++ b/sphinx/search/pt.py @@ -4,7 +4,7 @@ Portuguese search language: includes the JS Portuguese stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/ro.py b/sphinx/search/ro.py index ddb008b64..6400967c6 100644 --- a/sphinx/search/ro.py +++ b/sphinx/search/ro.py @@ -4,7 +4,7 @@ Romanian search language: includes the JS Romanian stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/ru.py b/sphinx/search/ru.py index cf979c0b4..20ce14869 100644 --- a/sphinx/search/ru.py +++ b/sphinx/search/ru.py @@ -4,7 +4,7 @@ Russian search language: includes the JS Russian stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/sv.py b/sphinx/search/sv.py index 0e7e2f37d..2ce4027ca 100644 --- a/sphinx/search/sv.py +++ b/sphinx/search/sv.py @@ -4,7 +4,7 @@ Swedish search language: includes the JS Swedish stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/tr.py b/sphinx/search/tr.py index 7788b1152..565e341fd 100644 --- a/sphinx/search/tr.py +++ b/sphinx/search/tr.py @@ -4,7 +4,7 @@ Turkish search language: includes the JS Turkish stemmer. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/search/zh.py b/sphinx/search/zh.py index 48f169aea..6948a6ad4 100644 --- a/sphinx/search/zh.py +++ b/sphinx/search/zh.py @@ -4,7 +4,7 @@ Chinese search language: includes routine to split words. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py index 3422f3d4c..0a30ff82f 100644 --- a/sphinx/setup_command.py +++ b/sphinx/setup_command.py @@ -7,14 +7,14 @@ :author: Sebastian Wiesner :contact: basti.wiesner@gmx.net - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os import sys from distutils.cmd import Command -from distutils.errors import DistutilsExecError, DistutilsOptionError +from distutils.errors import DistutilsExecError from io import StringIO from sphinx.application import Sphinx @@ -121,20 +121,6 @@ class BuildDoc(Command): return root return os.curdir - # Overriding distutils' Command._ensure_stringlike which doesn't support - # unicode, causing finalize_options to fail if invoked again. Workaround - # for https://bugs.python.org/issue19570 - def _ensure_stringlike(self, option, what, default=None): - # type: (str, str, Any) -> Any - val = getattr(self, option) - if val is None: - setattr(self, option, default) - return default - elif not isinstance(val, str): - raise DistutilsOptionError("'%s' must be a %s (got `%s`)" - % (option, what, val)) - return val - def finalize_options(self): # type: () -> None self.ensure_string_list('builder') diff --git a/sphinx/templates/graphviz/graphviz.css b/sphinx/templates/graphviz/graphviz.css index 8ab69e014..b340734c7 100644 --- a/sphinx/templates/graphviz/graphviz.css +++ b/sphinx/templates/graphviz/graphviz.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- graphviz extension. * - * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/templates/latex/latex.tex_t b/sphinx/templates/latex/latex.tex_t index 5082254e7..e288dda93 100644 --- a/sphinx/templates/latex/latex.tex_t +++ b/sphinx/templates/latex/latex.tex_t @@ -17,6 +17,9 @@ %% turn off hyperref patch of \index as sphinx.xdy xindy module takes care of %% suitable \hyperpage mark-up, working around hyperref-xindy incompatibility \PassOptionsToPackage{hyperindex=false}{hyperref} +%% memoir class requires extra handling +\makeatletter\@ifclassloaded{memoir} +{\ifdefined\memhyperindexfalse\memhyperindexfalse\fi}{}\makeatother <% endif -%> <%= passoptionstopackages %> \PassOptionsToPackage{warn}{textcomp} diff --git a/sphinx/testing/__init__.py b/sphinx/testing/__init__.py index dd9f32462..67263eff9 100644 --- a/sphinx/testing/__init__.py +++ b/sphinx/testing/__init__.py @@ -9,6 +9,6 @@ pytest_plugins = 'sphinx.testing.fixtures' - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/testing/comparer.py b/sphinx/testing/comparer.py index ddd818394..642cc8444 100644 --- a/sphinx/testing/comparer.py +++ b/sphinx/testing/comparer.py @@ -4,7 +4,7 @@ Sphinx test comparer for pytest - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import difflib diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index e6c3edd18..1cf66283f 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -4,7 +4,7 @@ Sphinx test fixtures for pytest - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -250,3 +250,15 @@ def tempdir(tmpdir: str) -> "util.path": this fixture is for compat with old test implementation. """ return util.path(tmpdir) + + +@pytest.fixture +def rollback_sysmodules(): + """Rollback sys.modules to before testing to unload modules during tests.""" + try: + sysmodules = list(sys.modules) + yield + finally: + for modname in list(sys.modules): + if modname not in sysmodules: + sys.modules.pop(modname) diff --git a/sphinx/testing/path.py b/sphinx/testing/path.py index a37839d60..efc5e8f63 100644 --- a/sphinx/testing/path.py +++ b/sphinx/testing/path.py @@ -2,7 +2,7 @@ sphinx.testing.path ~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/testing/restructuredtext.py b/sphinx/testing/restructuredtext.py index b25d7f98a..bb095b410 100644 --- a/sphinx/testing/restructuredtext.py +++ b/sphinx/testing/restructuredtext.py @@ -2,7 +2,7 @@ sphinx.testing.restructuredtext ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 208974115..7d0cde143 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -4,7 +4,7 @@ Sphinx test suite utilities - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import functools diff --git a/sphinx/texinputs/footnotehyper-sphinx.sty b/sphinx/texinputs/footnotehyper-sphinx.sty index b6692cfb8..c66e9a548 100644 --- a/sphinx/texinputs/footnotehyper-sphinx.sty +++ b/sphinx/texinputs/footnotehyper-sphinx.sty @@ -1,9 +1,9 @@ \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{footnotehyper-sphinx}% - [2017/10/27 v1.7 hyperref aware footnote.sty for sphinx (JFB)] + [2021/01/26 v1.1b hyperref aware footnote.sty for sphinx (JFB)] %% %% Package: footnotehyper-sphinx -%% Version: based on footnotehyper.sty 2017/03/07 v1.0 +%% Version: based on footnotehyper.sty 2021/01/26 v1.1b %% as available at https://www.ctan.org/pkg/footnotehyper %% License: the one applying to Sphinx %% @@ -16,7 +16,7 @@ %% 3. use of \sphinxunactivateextrasandspace from sphinx.sty, %% 4. macro definition \sphinxfootnotemark, %% 5. macro definition \sphinxlongtablepatch -%% 6. replaced an \undefined by \@undefined +%% 6. replaced some \undefined by \@undefined \DeclareOption*{\PackageWarning{footnotehyper-sphinx}{Option `\CurrentOption' is unknown}}% \ProcessOptions\relax \newbox\FNH@notes @@ -206,9 +206,20 @@ \FNH@@@1.2!3?4,\FNH@@@\relax }% \long\def\FNH@check@a #11.2!3?4,#2\FNH@@@#3{% - \ifx\relax#3\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi - \FNH@bad@makefntext@alert - {\def\FNH@prefntext{#1}\def\FNH@postfntext{#2}\FNH@check@b}% + \ifx\relax#3\FNH@bad@makefntext@alert + \else + \edef\FNH@restore@{\catcode`\noexpand\@\the\catcode`\@\relax}% + \makeatletter + \ifx\@makefntextFB\@undefined + \expandafter\@gobble\else\expandafter\@firstofone\fi + {\@ifclassloaded{memoir}% + {\ifFBFrenchFootnotes\expandafter\@gobble\fi}% + {}}% + \@secondoftwo + \scantokens{\def\FNH@prefntext{#1}\def\FNH@postfntext{#2}}% + \FNH@restore@ + \expandafter\FNH@check@b + \fi }% \def\FNH@check@b #1\relax{% \expandafter\expandafter\expandafter\FNH@check@c diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 4b637a9ec..eb5617dda 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -6,7 +6,7 @@ % \NeedsTeXFormat{LaTeX2e}[1995/12/01] -\ProvidesPackage{sphinx}[2019/09/02 v2.3.0 LaTeX package (Sphinx markup)] +\ProvidesPackage{sphinx}[2021/01/23 v3.5.0 LaTeX package (Sphinx markup)] % provides \ltx@ifundefined % (many packages load ltxcmds: graphicx does for pdftex and lualatex but @@ -239,6 +239,8 @@ \ltx@ifundefined{@removefromreset} {\RequirePackage{remreset}} {}% avoid warning +% To support hlist directive +\RequirePackage{multicol} % to make pdf with correct encoded bookmarks in Japanese % this should precede the hyperref package \ifx\kanjiskip\@undefined @@ -410,6 +412,11 @@ \DisableKeyvalOption{sphinx}{numfigreset} \DisableKeyvalOption{sphinx}{nonumfigreset} \DisableKeyvalOption{sphinx}{mathnumfig} +% To allow hyphenation of first word in narrow contexts; no option, +% customization to be done via 'preamble' key +\newcommand*\sphinxAtStartPar{\nobreak\hskip\z@skip} +% No need for the \hspace{0pt} trick (\hskip\z@skip) with luatex +\ifdefined\directlua\let\sphinxAtStartPar\@empty\fi % user interface: options can be changed midway in a document! \newcommand\sphinxsetup[1]{\setkeys{sphinx}{#1}} @@ -709,17 +716,17 @@ % Augment the sectioning commands used to get our own font family in place, % and reset some internal data items (\titleformat from titlesec package) \titleformat{\section}{\Large\py@HeaderFamily}% - {\py@TitleColor\thesection}{0.5em}{\py@TitleColor}{\py@NormalColor} + {\py@TitleColor\thesection}{0.5em}{\py@TitleColor} \titleformat{\subsection}{\large\py@HeaderFamily}% - {\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} + {\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor} \titleformat{\subsubsection}{\py@HeaderFamily}% - {\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} + {\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor} % By default paragraphs (and subsubsections) will not be numbered because % sphinxmanual.cls and sphinxhowto.cls set secnumdepth to 2 \titleformat{\paragraph}{\py@HeaderFamily}% - {\py@TitleColor\theparagraph}{0.5em}{\py@TitleColor}{\py@NormalColor} + {\py@TitleColor\theparagraph}{0.5em}{\py@TitleColor} \titleformat{\subparagraph}{\py@HeaderFamily}% - {\py@TitleColor\thesubparagraph}{0.5em}{\py@TitleColor}{\py@NormalColor} + {\py@TitleColor\thesubparagraph}{0.5em}{\py@TitleColor} %% GRAPHICS @@ -737,7 +744,7 @@ \AtBeginDocument{\spx@image@maxheight\textheight} % box scratch register -\newdimen\spx@image@box +\newbox\spx@image@box \newcommand*{\sphinxsafeincludegraphics}[2][]{% % #1 contains possibly width=, height=, but no scale= since 1.8.4 \setbox\spx@image@box\hbox{\includegraphics[#1,draft]{#2}}% @@ -844,6 +851,12 @@ %% NUMBERING OF FIGURES, TABLES, AND LITERAL BLOCKS + +% Everything is delayed to \begin{document} to allow hyperref patches into +% \newcounter to solve duplicate label problems for internal hyperlinks to +% code listings (literalblock counter). User or extension re-definitions of +% \theliteralblock, et al., thus have also to be delayed. (changed at 3.5.0) +\AtBeginDocument{% \ltx@ifundefined{c@chapter} {\newcounter{literalblock}}% {\newcounter{literalblock}[chapter]% @@ -884,62 +897,52 @@ \g@addto@macro\spx@preBthefigure{\fi}}% \fi \ifnum\spx@opt@numfigreset>1 - \AtBeginDocument{% \@addtoreset{figure}{section}% \@addtoreset{table}{section}% \@addtoreset{literalblock}{section}% \ifspx@opt@mathnumfig \@addtoreset{equation}{section}% \fi% - }% \g@addto@macro\spx@preAthefigure{\ifnum\c@section>\z@\arabic{section}.}% \g@addto@macro\spx@preBthefigure{\fi}% \fi \ifnum\spx@opt@numfigreset>2 - \AtBeginDocument{% \@addtoreset{figure}{subsection}% \@addtoreset{table}{subsection}% \@addtoreset{literalblock}{subsection}% \ifspx@opt@mathnumfig \@addtoreset{equation}{subsection}% \fi% - }% \g@addto@macro\spx@preAthefigure{\ifnum\c@subsection>\z@\arabic{subsection}.}% \g@addto@macro\spx@preBthefigure{\fi}% \fi \ifnum\spx@opt@numfigreset>3 - \AtBeginDocument{% \@addtoreset{figure}{subsubsection}% \@addtoreset{table}{subsubsection}% \@addtoreset{literalblock}{subsubsection}% \ifspx@opt@mathnumfig \@addtoreset{equation}{subsubsection}% \fi% - }% \g@addto@macro\spx@preAthefigure{\ifnum\c@subsubsection>\z@\arabic{subsubsection}.}% \g@addto@macro\spx@preBthefigure{\fi}% \fi \ifnum\spx@opt@numfigreset>4 - \AtBeginDocument{% \@addtoreset{figure}{paragraph}% \@addtoreset{table}{paragraph}% \@addtoreset{literalblock}{paragraph}% \ifspx@opt@mathnumfig \@addtoreset{equation}{paragraph}% \fi% - }% \g@addto@macro\spx@preAthefigure{\ifnum\c@subparagraph>\z@\arabic{subparagraph}.}% \g@addto@macro\spx@preBthefigure{\fi}% \fi \ifnum\spx@opt@numfigreset>5 - \AtBeginDocument{% \@addtoreset{figure}{subparagraph}% \@addtoreset{table}{subparagraph}% \@addtoreset{literalblock}{subparagraph}% \ifspx@opt@mathnumfig \@addtoreset{equation}{subparagraph}% \fi% - }% \g@addto@macro\spx@preAthefigure{\ifnum\c@subsubparagraph>\z@\arabic{subsubparagraph}.}% \g@addto@macro\spx@preBthefigure{\fi}% \fi @@ -956,7 +959,7 @@ \g@addto@macro\theequation{\arabic{equation}}% \fi \fi - +}% end of big \AtBeginDocument %% LITERAL BLOCKS % @@ -966,9 +969,9 @@ % - with possibly of a top caption, non-separable by pagebreak. % - and usable inside tables or footnotes ("footnotehyper-sphinx"). -% For extensions which use \OriginalVerbatim and compatibility with Sphinx < -% 1.5, we define and use these when (unmodified) Verbatim will be needed. But -% Sphinx >= 1.5 does not modify the \Verbatim macro anymore. +% Prior to Sphinx 1.5, \Verbatim and \endVerbatim were modified by Sphinx. +% The aliases defined here are used in sphinxVerbatim environment and can +% serve as hook-points with no need to modify \Verbatim itself. \let\OriginalVerbatim \Verbatim \let\endOriginalVerbatim\endVerbatim @@ -1863,8 +1866,8 @@ \def\sphinxstyleindexextra #1{ (\emph{#1})} \def\sphinxstyleindexpageref #1{, \pageref{#1}} \def\sphinxstyleindexpagemain#1{\textbf{#1}} -\protected\def\spxentry#1{#1}% will get \let to \sphinxstyleindexentry in index -\protected\def\spxextra#1{#1}% will get \let to \sphinxstyleindexextra in index +\def\spxentry{\@backslashchar spxentry}% let to \sphinxstyleindexentry in index +\def\spxextra{\@backslashchar spxextra}% let to \sphinxstyleindexextra in index \def\sphinxstyleindexlettergroup #1% {{\Large\sffamily#1}\nopagebreak\vspace{1mm}} \def\sphinxstyleindexlettergroupDefault #1% diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html index 26c37b10e..1d9df693b 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 ff43186da..489ec17ea 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-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 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 2f8477c31..8ca5748ac 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #}{{ warn('Now base template defindex.html is deprecated.') }} {%- extends "layout.html" %} diff --git a/sphinx/themes/basic/domainindex.html b/sphinx/themes/basic/domainindex.html index f4f742338..1ccab0286 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 b327eb620..1182fdada 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 e9db8574d..8b79dc8dc 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 fb769737f..7f16c3544 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "layout.html" %} diff --git a/sphinx/themes/basic/globaltoc.html b/sphinx/themes/basic/globaltoc.html index 1b2f9baee..a47c32cc0 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #}

    {{ _('Table of Contents') }}

    diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index 131d2c533..bd56681c0 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- block doctype -%}{%- if html5_doctype %} @@ -18,7 +18,7 @@ {%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and (sidebars != []) %} {%- set url_root = pathto('', 1) %} -{# XXX necessary? #} +{# URL root should never be #, then all links are fragments #} {%- if url_root == '#' %}{% set url_root = '' %}{% endif %} {%- if not embedded and docstitle %} {%- set titlesuffix = " — "|safe + docstitle|e %} @@ -88,7 +88,7 @@ {%- endmacro %} {%- macro script() %} - + {%- for js in script_files %} {{ js_tag(js) }} {%- endfor %} diff --git a/sphinx/themes/basic/localtoc.html b/sphinx/themes/basic/localtoc.html index 0e1c5511b..c15ad2708 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 c9c351167..5878bdeeb 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 5a2113cf0..c4391fe4d 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 cf574f8d5..453a35fb9 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 6ce202786..68f933f66 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- if pagename != "search" and builder != "singlehtml" %} diff --git a/sphinx/themes/basic/sourcelink.html b/sphinx/themes/basic/sourcelink.html index 2ff5d1b66..512611230 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 38b9fb553..3bb3ea705 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-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index 7d88f807d..61ac9d266 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for all documentation. * - * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -29,9 +29,14 @@ if (!window.console || !console.firebug) { /** * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL */ jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); }; /** diff --git a/sphinx/themes/basic/static/language_data.js_t b/sphinx/themes/basic/static/language_data.js_t index 531813877..1425b022a 100644 --- a/sphinx/themes/basic/static/language_data.js_t +++ b/sphinx/themes/basic/static/language_data.js_t @@ -5,7 +5,7 @@ * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. * - * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 261ecaa92..0dc528fb9 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for the full-text search. * - * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -379,6 +379,13 @@ var Search = { return results; }, + /** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + */ + escapeRegExp : function(string) { + return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + }, + /** * search for full-text terms in the index */ @@ -402,13 +409,14 @@ var Search = { ]; // add support for partial matches if (word.length > 2) { + var word_regex = this.escapeRegExp(word); for (var w in terms) { - if (w.match(word) && !terms[word]) { + if (w.match(word_regex) && !terms[word]) { _o.push({files: terms[w], score: Scorer.partialTerm}) } } for (var w in titleterms) { - if (w.match(word) && !titleterms[word]) { + if (w.match(word_regex) && !titleterms[word]) { _o.push({files: titleterms[w], score: Scorer.partialTitle}) } } diff --git a/sphinx/themes/basic/static/underscore-1.12.0.js b/sphinx/themes/basic/static/underscore-1.12.0.js new file mode 100644 index 000000000..3af6352e6 --- /dev/null +++ b/sphinx/themes/basic/static/underscore-1.12.0.js @@ -0,0 +1,2027 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define('underscore', factory) : + (global = global || self, (function () { + var current = global._; + var exports = global._ = factory(); + exports.noConflict = function () { global._ = current; return exports; }; + }())); +}(this, (function () { + // Underscore.js 1.12.0 + // https://underscorejs.org + // (c) 2009-2020 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + // Underscore may be freely distributed under the MIT license. + + // Current version. + var VERSION = '1.12.0'; + + // Establish the root object, `window` (`self`) in the browser, `global` + // on the server, or `this` in some virtual machines. We use `self` + // instead of `window` for `WebWorker` support. + var root = typeof self == 'object' && self.self === self && self || + typeof global == 'object' && global.global === global && global || + Function('return this')() || + {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype; + var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // Modern feature detection. + var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined', + supportsDataView = typeof DataView !== 'undefined'; + + // All **ECMAScript 5+** native function implementations that we hope to use + // are declared here. + var nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeCreate = Object.create, + nativeIsView = supportsArrayBuffer && ArrayBuffer.isView; + + // Create references to these builtin functions because we override them. + var _isNaN = isNaN, + _isFinite = isFinite; + + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + // The largest integer that can be represented exactly. + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + + // Some functions take a variable number of arguments, or a few expected + // arguments at the beginning and then a variable number of values to operate + // on. This helper accumulates all remaining arguments past the function’s + // argument length (or an explicit `startIndex`), into an array that becomes + // the last argument. Similar to ES6’s "rest parameter". + function restArguments(func, startIndex) { + startIndex = startIndex == null ? func.length - 1 : +startIndex; + return function() { + var length = Math.max(arguments.length - startIndex, 0), + rest = Array(length), + index = 0; + for (; index < length; index++) { + rest[index] = arguments[index + startIndex]; + } + switch (startIndex) { + case 0: return func.call(this, rest); + case 1: return func.call(this, arguments[0], rest); + case 2: return func.call(this, arguments[0], arguments[1], rest); + } + var args = Array(startIndex + 1); + for (index = 0; index < startIndex; index++) { + args[index] = arguments[index]; + } + args[startIndex] = rest; + return func.apply(this, args); + }; + } + + // Is a given variable an object? + function isObject(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + } + + // Is a given value equal to null? + function isNull(obj) { + return obj === null; + } + + // Is a given variable undefined? + function isUndefined(obj) { + return obj === void 0; + } + + // Is a given value a boolean? + function isBoolean(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + } + + // Is a given value a DOM element? + function isElement(obj) { + return !!(obj && obj.nodeType === 1); + } + + // Internal function for creating a `toString`-based type tester. + function tagTester(name) { + var tag = '[object ' + name + ']'; + return function(obj) { + return toString.call(obj) === tag; + }; + } + + var isString = tagTester('String'); + + var isNumber = tagTester('Number'); + + var isDate = tagTester('Date'); + + var isRegExp = tagTester('RegExp'); + + var isError = tagTester('Error'); + + var isSymbol = tagTester('Symbol'); + + var isArrayBuffer = tagTester('ArrayBuffer'); + + var isFunction = tagTester('Function'); + + // Optimize `isFunction` if appropriate. Work around some `typeof` bugs in old + // v8, IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236). + var nodelist = root.document && root.document.childNodes; + if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') { + isFunction = function(obj) { + return typeof obj == 'function' || false; + }; + } + + var isFunction$1 = isFunction; + + var hasObjectTag = tagTester('Object'); + + // In IE 10 - Edge 13, `DataView` has string tag `'[object Object]'`. + // In IE 11, the most common among them, this problem also applies to + // `Map`, `WeakMap` and `Set`. + var hasStringTagBug = ( + supportsDataView && hasObjectTag(new DataView(new ArrayBuffer(8))) + ), + isIE11 = (typeof Map !== 'undefined' && hasObjectTag(new Map)); + + var isDataView = tagTester('DataView'); + + // In IE 10 - Edge 13, we need a different heuristic + // to determine whether an object is a `DataView`. + function ie10IsDataView(obj) { + return obj != null && isFunction$1(obj.getInt8) && isArrayBuffer(obj.buffer); + } + + var isDataView$1 = (hasStringTagBug ? ie10IsDataView : isDataView); + + // Is a given value an array? + // Delegates to ECMA5's native `Array.isArray`. + var isArray = nativeIsArray || tagTester('Array'); + + // Internal function to check whether `key` is an own property name of `obj`. + function has(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + } + + var isArguments = tagTester('Arguments'); + + // Define a fallback version of the method in browsers (ahem, IE < 9), where + // there isn't any inspectable "Arguments" type. + (function() { + if (!isArguments(arguments)) { + isArguments = function(obj) { + return has(obj, 'callee'); + }; + } + }()); + + var isArguments$1 = isArguments; + + // Is a given object a finite number? + function isFinite$1(obj) { + return !isSymbol(obj) && _isFinite(obj) && !isNaN(parseFloat(obj)); + } + + // Is the given value `NaN`? + function isNaN$1(obj) { + return isNumber(obj) && _isNaN(obj); + } + + // Predicate-generating function. Often useful outside of Underscore. + function constant(value) { + return function() { + return value; + }; + } + + // Common internal logic for `isArrayLike` and `isBufferLike`. + function createSizePropertyCheck(getSizeProperty) { + return function(collection) { + var sizeProperty = getSizeProperty(collection); + return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX; + } + } + + // Internal helper to generate a function to obtain property `key` from `obj`. + function shallowProperty(key) { + return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + } + + // Internal helper to obtain the `byteLength` property of an object. + var getByteLength = shallowProperty('byteLength'); + + // Internal helper to determine whether we should spend extensive checks against + // `ArrayBuffer` et al. + var isBufferLike = createSizePropertyCheck(getByteLength); + + // Is a given value a typed array? + var typedArrayPattern = /\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/; + function isTypedArray(obj) { + // `ArrayBuffer.isView` is the most future-proof, so use it when available. + // Otherwise, fall back on the above regular expression. + return nativeIsView ? (nativeIsView(obj) && !isDataView$1(obj)) : + isBufferLike(obj) && typedArrayPattern.test(toString.call(obj)); + } + + var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant(false); + + // Internal helper to obtain the `length` property of an object. + var getLength = shallowProperty('length'); + + // Internal helper to create a simple lookup structure. + // `collectNonEnumProps` used to depend on `_.contains`, but this led to + // circular imports. `emulatedSet` is a one-off solution that only works for + // arrays of strings. + function emulatedSet(keys) { + var hash = {}; + for (var l = keys.length, i = 0; i < l; ++i) hash[keys[i]] = true; + return { + contains: function(key) { return hash[key]; }, + push: function(key) { + hash[key] = true; + return keys.push(key); + } + }; + } + + // Internal helper. Checks `keys` for the presence of keys in IE < 9 that won't + // be iterated by `for key in ...` and thus missed. Extends `keys` in place if + // needed. + function collectNonEnumProps(obj, keys) { + keys = emulatedSet(keys); + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = isFunction$1(constructor) && constructor.prototype || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (has(obj, prop) && !keys.contains(prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !keys.contains(prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. + // Delegates to **ECMAScript 5**'s native `Object.keys`. + function keys(obj) { + if (!isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) if (has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + } + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + function isEmpty(obj) { + if (obj == null) return true; + // Skip the more expensive `toString`-based type checks if `obj` has no + // `.length`. + var length = getLength(obj); + if (typeof length == 'number' && ( + isArray(obj) || isString(obj) || isArguments$1(obj) + )) return length === 0; + return getLength(keys(obj)) === 0; + } + + // Returns whether an object has a given set of `key:value` pairs. + function isMatch(object, attrs) { + var _keys = keys(attrs), length = _keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = _keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + } + + // If Underscore is called as a function, it returns a wrapped object that can + // be used OO-style. This wrapper holds altered versions of all functions added + // through `_.mixin`. Wrapped objects may be chained. + function _(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + } + + _.VERSION = VERSION; + + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxies for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.prototype.toString = function() { + return String(this._wrapped); + }; + + // Internal function to wrap or shallow-copy an ArrayBuffer, + // typed array or DataView to a new view, reusing the buffer. + function toBufferView(bufferSource) { + return new Uint8Array( + bufferSource.buffer || bufferSource, + bufferSource.byteOffset || 0, + getByteLength(bufferSource) + ); + } + + // We use this string twice, so give it a name for minification. + var tagDataView = '[object DataView]'; + + // Internal recursive comparison function for `_.isEqual`. + function eq(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a === 1 / b; + // `null` or `undefined` only equal to itself (strict comparison). + if (a == null || b == null) return false; + // `NaN`s are equivalent, but non-reflexive. + if (a !== a) return b !== b; + // Exhaust primitive checks + var type = typeof a; + if (type !== 'function' && type !== 'object' && typeof b != 'object') return false; + return deepEq(a, b, aStack, bStack); + } + + // Internal recursive comparison function for `_.isEqual`. + function deepEq(a, b, aStack, bStack) { + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className !== toString.call(b)) return false; + // Work around a bug in IE 10 - Edge 13. + if (hasStringTagBug && className == '[object Object]' && isDataView$1(a)) { + if (!isDataView$1(b)) return false; + className = tagDataView; + } + switch (className) { + // These types are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return '' + a === '' + b; + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN. + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a === +b; + case '[object Symbol]': + return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b); + case '[object ArrayBuffer]': + case tagDataView: + // Coerce to typed array so we can fall through. + return deepEq(toBufferView(a), toBufferView(b), aStack, bStack); + } + + var areArrays = className === '[object Array]'; + if (!areArrays && isTypedArray$1(a)) { + var byteLength = getByteLength(a); + if (byteLength !== getByteLength(b)) return false; + if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true; + areArrays = true; + } + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor && + isFunction$1(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] === a) return bStack[length] === b; + } + + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + + // Recursively compare objects and arrays. + if (areArrays) { + // Compare array lengths to determine if a deep comparison is necessary. + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; + } + } else { + // Deep compare objects. + var _keys = keys(a), key; + length = _keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = _keys[length]; + if (!(has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return true; + } + + // Perform a deep comparison to check if two objects are equal. + function isEqual(a, b) { + return eq(a, b); + } + + // Retrieve all the enumerable property names of an object. + function allKeys(obj) { + if (!isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + } + + // Since the regular `Object.prototype.toString` type tests don't work for + // some types in IE 11, we use a fingerprinting heuristic instead, based + // on the methods. It's not great, but it's the best we got. + // The fingerprint method lists are defined below. + function ie11fingerprint(methods) { + var length = getLength(methods); + return function(obj) { + if (obj == null) return false; + // `Map`, `WeakMap` and `Set` have no enumerable keys. + var keys = allKeys(obj); + if (getLength(keys)) return false; + for (var i = 0; i < length; i++) { + if (!isFunction$1(obj[methods[i]])) return false; + } + // If we are testing against `WeakMap`, we need to ensure that + // `obj` doesn't have a `forEach` method in order to distinguish + // it from a regular `Map`. + return methods !== weakMapMethods || !isFunction$1(obj[forEachName]); + }; + } + + // In the interest of compact minification, we write + // each string in the fingerprints only once. + var forEachName = 'forEach', + hasName = 'has', + commonInit = ['clear', 'delete'], + mapTail = ['get', hasName, 'set']; + + // `Map`, `WeakMap` and `Set` each have slightly different + // combinations of the above sublists. + var mapMethods = commonInit.concat(forEachName, mapTail), + weakMapMethods = commonInit.concat(mapTail), + setMethods = ['add'].concat(commonInit, forEachName, hasName); + + var isMap = isIE11 ? ie11fingerprint(mapMethods) : tagTester('Map'); + + var isWeakMap = isIE11 ? ie11fingerprint(weakMapMethods) : tagTester('WeakMap'); + + var isSet = isIE11 ? ie11fingerprint(setMethods) : tagTester('Set'); + + var isWeakSet = tagTester('WeakSet'); + + // Retrieve the values of an object's properties. + function values(obj) { + var _keys = keys(obj); + var length = _keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[_keys[i]]; + } + return values; + } + + // Convert an object into a list of `[key, value]` pairs. + // The opposite of `_.object` with one argument. + function pairs(obj) { + var _keys = keys(obj); + var length = _keys.length; + var pairs = Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [_keys[i], obj[_keys[i]]]; + } + return pairs; + } + + // Invert the keys and values of an object. The values must be serializable. + function invert(obj) { + var result = {}; + var _keys = keys(obj); + for (var i = 0, length = _keys.length; i < length; i++) { + result[obj[_keys[i]]] = _keys[i]; + } + return result; + } + + // Return a sorted list of the function names available on the object. + function functions(obj) { + var names = []; + for (var key in obj) { + if (isFunction$1(obj[key])) names.push(key); + } + return names.sort(); + } + + // An internal function for creating assigner functions. + function createAssigner(keysFunc, defaults) { + return function(obj) { + var length = arguments.length; + if (defaults) obj = Object(obj); + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!defaults || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + } + + // Extend a given object with all the properties in passed-in object(s). + var extend = createAssigner(allKeys); + + // Assigns a given object with all the own properties in the passed-in + // object(s). + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + var extendOwn = createAssigner(keys); + + // Fill in a given object with default properties. + var defaults = createAssigner(allKeys, true); + + // Create a naked function reference for surrogate-prototype-swapping. + function ctor() { + return function(){}; + } + + // An internal function for creating a new object that inherits from another. + function baseCreate(prototype) { + if (!isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + var Ctor = ctor(); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + } + + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + function create(prototype, props) { + var result = baseCreate(prototype); + if (props) extendOwn(result, props); + return result; + } + + // Create a (shallow-cloned) duplicate of an object. + function clone(obj) { + if (!isObject(obj)) return obj; + return isArray(obj) ? obj.slice() : extend({}, obj); + } + + // Invokes `interceptor` with the `obj` and then returns `obj`. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + function tap(obj, interceptor) { + interceptor(obj); + return obj; + } + + // Normalize a (deep) property `path` to array. + // Like `_.iteratee`, this function can be customized. + function toPath(path) { + return isArray(path) ? path : [path]; + } + _.toPath = toPath; + + // Internal wrapper for `_.toPath` to enable minification. + // Similar to `cb` for `_.iteratee`. + function toPath$1(path) { + return _.toPath(path); + } + + // Internal function to obtain a nested property in `obj` along `path`. + function deepGet(obj, path) { + var length = path.length; + for (var i = 0; i < length; i++) { + if (obj == null) return void 0; + obj = obj[path[i]]; + } + return length ? obj : void 0; + } + + // Get the value of the (deep) property on `path` from `object`. + // If any property in `path` does not exist or if the value is + // `undefined`, return `defaultValue` instead. + // The `path` is normalized through `_.toPath`. + function get(object, path, defaultValue) { + var value = deepGet(object, toPath$1(path)); + return isUndefined(value) ? defaultValue : value; + } + + // Shortcut function for checking if an object has a given property directly on + // itself (in other words, not on a prototype). Unlike the internal `has` + // function, this public version can also traverse nested properties. + function has$1(obj, path) { + path = toPath$1(path); + var length = path.length; + for (var i = 0; i < length; i++) { + var key = path[i]; + if (!has(obj, key)) return false; + obj = obj[key]; + } + return !!length; + } + + // Keep the identity function around for default iteratees. + function identity(value) { + return value; + } + + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + function matcher(attrs) { + attrs = extendOwn({}, attrs); + return function(obj) { + return isMatch(obj, attrs); + }; + } + + // Creates a function that, when passed an object, will traverse that object’s + // properties down the given `path`, specified as an array of keys or indices. + function property(path) { + path = toPath$1(path); + return function(obj) { + return deepGet(obj, path); + }; + } + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + function optimizeCb(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + // The 2-argument case is omitted because we’re not using it. + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + } + + // An internal function to generate callbacks that can be applied to each + // element in a collection, returning the desired result — either `_.identity`, + // an arbitrary callback, a property matcher, or a property accessor. + function baseIteratee(value, context, argCount) { + if (value == null) return identity; + if (isFunction$1(value)) return optimizeCb(value, context, argCount); + if (isObject(value) && !isArray(value)) return matcher(value); + return property(value); + } + + // External wrapper for our callback generator. Users may customize + // `_.iteratee` if they want additional predicate/iteratee shorthand styles. + // This abstraction hides the internal-only `argCount` argument. + function iteratee(value, context) { + return baseIteratee(value, context, Infinity); + } + _.iteratee = iteratee; + + // The function we call internally to generate a callback. It invokes + // `_.iteratee` if overridden, otherwise `baseIteratee`. + function cb(value, context, argCount) { + if (_.iteratee !== iteratee) return _.iteratee(value, context); + return baseIteratee(value, context, argCount); + } + + // Returns the results of applying the `iteratee` to each element of `obj`. + // In contrast to `_.map` it returns an object. + function mapObject(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var _keys = keys(obj), + length = _keys.length, + results = {}; + for (var index = 0; index < length; index++) { + var currentKey = _keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + } + + // Predicate-generating function. Often useful outside of Underscore. + function noop(){} + + // Generates a function for a given object that returns a given property. + function propertyOf(obj) { + if (obj == null) return noop; + return function(path) { + return get(obj, path); + }; + } + + // Run a function **n** times. + function times(n, iteratee, context) { + var accum = Array(Math.max(0, n)); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); + return accum; + } + + // Return a random integer between `min` and `max` (inclusive). + function random(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + } + + // A (possibly faster) way to get the current timestamp as an integer. + var now = Date.now || function() { + return new Date().getTime(); + }; + + // Internal helper to generate functions for escaping and unescaping strings + // to/from HTML interpolation. + function createEscaper(map) { + var escaper = function(match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped. + var source = '(?:' + keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + } + + // Internal list of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + + // Function for escaping strings to HTML interpolation. + var _escape = createEscaper(escapeMap); + + // Internal list of HTML entities for unescaping. + var unescapeMap = invert(escapeMap); + + // Function for unescaping strings from HTML interpolation. + var _unescape = createEscaper(unescapeMap); + + // By default, Underscore uses ERB-style template delimiters. Change the + // following template settings to use alternative delimiters. + var templateSettings = _.templateSettings = { + evaluate: /<%([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + escape: /<%-([\s\S]+?)%>/g + }; + + // When customizing `_.templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; + + function escapeChar(match) { + return '\\' + escapes[match]; + } + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + // NB: `oldSettings` only exists for backwards compatibility. + function template(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; + settings = defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset).replace(escapeRegExp, escapeChar); + index = offset + match.length; + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } else if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } else if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + + // Adobe VMs need the match returned to produce the correct offset. + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + 'return __p;\n'; + + var render; + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + template.source = 'function(' + argument + '){\n' + source + '}'; + + return template; + } + + // Traverses the children of `obj` along `path`. If a child is a function, it + // is invoked with its parent as context. Returns the value of the final + // child, or `fallback` if any child is undefined. + function result(obj, path, fallback) { + path = toPath$1(path); + var length = path.length; + if (!length) { + return isFunction$1(fallback) ? fallback.call(obj) : fallback; + } + for (var i = 0; i < length; i++) { + var prop = obj == null ? void 0 : obj[path[i]]; + if (prop === void 0) { + prop = fallback; + i = length; // Ensure we don't continue iterating. + } + obj = isFunction$1(prop) ? prop.call(obj) : prop; + } + return obj; + } + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + function uniqueId(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + } + + // Start chaining a wrapped Underscore object. + function chain(obj) { + var instance = _(obj); + instance._chain = true; + return instance; + } + + // Internal function to execute `sourceFunc` bound to `context` with optional + // `args`. Determines whether to execute a function as a constructor or as a + // normal function. + function executeBound(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (isObject(result)) return result; + return self; + } + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. `_` acts + // as a placeholder by default, allowing any combination of arguments to be + // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument. + var partial = restArguments(function(func, boundArgs) { + var placeholder = partial.placeholder; + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; + } + while (position < arguments.length) args.push(arguments[position++]); + return executeBound(func, bound, this, this, args); + }; + return bound; + }); + + partial.placeholder = _; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). + var bind = restArguments(function(func, context, args) { + if (!isFunction$1(func)) throw new TypeError('Bind must be called on a function'); + var bound = restArguments(function(callArgs) { + return executeBound(func, bound, context, this, args.concat(callArgs)); + }); + return bound; + }); + + // Internal helper for collection methods to determine whether a collection + // should be iterated as an array or as an object. + // Related: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 + var isArrayLike = createSizePropertyCheck(getLength); + + // Internal implementation of a recursive `flatten` function. + function flatten(input, depth, strict, output) { + output = output || []; + if (!depth && depth !== 0) { + depth = Infinity; + } else if (depth <= 0) { + return output.concat(input); + } + var idx = output.length; + for (var i = 0, length = getLength(input); i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) { + // Flatten current level of array or arguments object. + if (depth > 1) { + flatten(value, depth - 1, strict, output); + idx = output.length; + } else { + var j = 0, len = value.length; + while (j < len) output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; + } + } + return output; + } + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + var bindAll = restArguments(function(obj, keys) { + keys = flatten(keys, false, false); + var index = keys.length; + if (index < 1) throw new Error('bindAll must be passed function names'); + while (index--) { + var key = keys[index]; + obj[key] = bind(obj[key], obj); + } + return obj; + }); + + // Memoize an expensive function by storing its results. + function memoize(func, hasher) { + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!has(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; + }; + memoize.cache = {}; + return memoize; + } + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + var delay = restArguments(function(func, wait, args) { + return setTimeout(function() { + return func.apply(null, args); + }, wait); + }); + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + var defer = partial(delay, _, 1); + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + function throttle(func, wait, options) { + var timeout, context, args, result; + var previous = 0; + if (!options) options = {}; + + var later = function() { + previous = options.leading === false ? 0 : now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + + var throttled = function() { + var _now = now(); + if (!previous && options.leading === false) previous = _now; + var remaining = wait - (_now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = _now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + + throttled.cancel = function() { + clearTimeout(timeout); + previous = 0; + timeout = context = args = null; + }; + + return throttled; + } + + // When a sequence of calls of the returned function ends, the argument + // function is triggered. The end of a sequence is defined by the `wait` + // parameter. If `immediate` is passed, the argument function will be + // triggered at the beginning of the sequence instead of at the end. + function debounce(func, wait, immediate) { + var timeout, previous, args, result, context; + + var later = function() { + var passed = now() - previous; + if (wait > passed) { + timeout = setTimeout(later, wait - passed); + } else { + timeout = null; + if (!immediate) result = func.apply(context, args); + // This check is needed because `func` can recursively invoke `debounced`. + if (!timeout) args = context = null; + } + }; + + var debounced = restArguments(function(_args) { + context = this; + args = _args; + previous = now(); + if (!timeout) { + timeout = setTimeout(later, wait); + if (immediate) result = func.apply(context, args); + } + return result; + }); + + debounced.cancel = function() { + clearTimeout(timeout); + timeout = args = context = null; + }; + + return debounced; + } + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + function wrap(func, wrapper) { + return partial(wrapper, func); + } + + // Returns a negated version of the passed-in predicate. + function negate(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + } + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + function compose() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + } + + // Returns a function that will only be executed on and after the Nth call. + function after(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + } + + // Returns a function that will only be executed up to (but not including) the + // Nth call. + function before(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + } + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + var once = partial(before, 2); + + // Returns the first key on an object that passes a truth test. + function findKey(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = keys(obj), key; + for (var i = 0, length = _keys.length; i < length; i++) { + key = _keys[i]; + if (predicate(obj[key], key, obj)) return key; + } + } + + // Internal function to generate `_.findIndex` and `_.findLastIndex`. + function createPredicateIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = getLength(array); + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a truth test. + var findIndex = createPredicateIndexFinder(1); + + // Returns the last index on an array-like that passes a truth test. + var findLastIndex = createPredicateIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + function sortedIndex(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = getLength(array); + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + } + + // Internal function to generate the `_.indexOf` and `_.lastIndexOf` functions. + function createIndexFinder(dir, predicateFind, sortedIndex) { + return function(array, item, idx) { + var i = 0, length = getLength(array); + if (typeof idx == 'number') { + if (dir > 0) { + i = idx >= 0 ? idx : Math.max(idx + length, i); + } else { + length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; + } + } else if (sortedIndex && idx && length) { + idx = sortedIndex(array, item); + return array[idx] === item ? idx : -1; + } + if (item !== item) { + idx = predicateFind(slice.call(array, i, length), isNaN$1); + return idx >= 0 ? idx + i : -1; + } + for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { + if (array[idx] === item) return idx; + } + return -1; + }; + } + + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + var indexOf = createIndexFinder(1, findIndex, sortedIndex); + + // Return the position of the last occurrence of an item in an array, + // or -1 if the item is not included in the array. + var lastIndexOf = createIndexFinder(-1, findLastIndex); + + // Return the first value which passes a truth test. + function find(obj, predicate, context) { + var keyFinder = isArrayLike(obj) ? findIndex : findKey; + var key = keyFinder(obj, predicate, context); + if (key !== void 0 && key !== -1) return obj[key]; + } + + // Convenience version of a common use case of `_.find`: getting the first + // object containing specific `key:value` pairs. + function findWhere(obj, attrs) { + return find(obj, matcher(attrs)); + } + + // The cornerstone for collection functions, an `each` + // implementation, aka `forEach`. + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + function each(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); + } + } else { + var _keys = keys(obj); + for (i = 0, length = _keys.length; i < length; i++) { + iteratee(obj[_keys[i]], _keys[i], obj); + } + } + return obj; + } + + // Return the results of applying the iteratee to each element. + function map(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + } + + // Internal helper to create a reducing function, iterating left or right. + function createReduce(dir) { + // Wrap code that reassigns argument variables in a separate function than + // the one that accesses `arguments.length` to avoid a perf hit. (#1991) + var reducer = function(obj, iteratee, memo, initial) { + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length, + index = dir > 0 ? 0 : length - 1; + if (!initial) { + memo = obj[_keys ? _keys[index] : index]; + index += dir; + } + for (; index >= 0 && index < length; index += dir) { + var currentKey = _keys ? _keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + }; + + return function(obj, iteratee, memo, context) { + var initial = arguments.length >= 3; + return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial); + }; + } + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. + var reduce = createReduce(1); + + // The right-associative version of reduce, also known as `foldr`. + var reduceRight = createReduce(-1); + + // Return all the elements that pass a truth test. + function filter(obj, predicate, context) { + var results = []; + predicate = cb(predicate, context); + each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); + }); + return results; + } + + // Return all the elements for which a truth test fails. + function reject(obj, predicate, context) { + return filter(obj, negate(cb(predicate)), context); + } + + // Determine whether all of the elements pass a truth test. + function every(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; + } + + // Determine if at least one element in the object passes a truth test. + function some(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; + } + + // Determine if the array or object contains a given item (using `===`). + function contains(obj, item, fromIndex, guard) { + if (!isArrayLike(obj)) obj = values(obj); + if (typeof fromIndex != 'number' || guard) fromIndex = 0; + return indexOf(obj, item, fromIndex) >= 0; + } + + // Invoke a method (with arguments) on every item in a collection. + var invoke = restArguments(function(obj, path, args) { + var contextPath, func; + if (isFunction$1(path)) { + func = path; + } else { + path = toPath$1(path); + contextPath = path.slice(0, -1); + path = path[path.length - 1]; + } + return map(obj, function(context) { + var method = func; + if (!method) { + if (contextPath && contextPath.length) { + context = deepGet(context, contextPath); + } + if (context == null) return void 0; + method = context[path]; + } + return method == null ? method : method.apply(context, args); + }); + }); + + // Convenience version of a common use case of `_.map`: fetching a property. + function pluck(obj, key) { + return map(obj, property(key)); + } + + // Convenience version of a common use case of `_.filter`: selecting only + // objects containing specific `key:value` pairs. + function where(obj, attrs) { + return filter(obj, matcher(attrs)); + } + + // Return the maximum element (or element-based computation). + function max(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) { + obj = isArrayLike(obj) ? obj : values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value > result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = v; + lastComputed = computed; + } + }); + } + return result; + } + + // Return the minimum element (or element-based computation). + function min(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) { + obj = isArrayLike(obj) ? obj : values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value < result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = v; + lastComputed = computed; + } + }); + } + return result; + } + + // Sample **n** random values from a collection using the modern version of the + // [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `_.map`. + function sample(obj, n, guard) { + if (n == null || guard) { + if (!isArrayLike(obj)) obj = values(obj); + return obj[random(obj.length - 1)]; + } + var sample = isArrayLike(obj) ? clone(obj) : values(obj); + var length = getLength(sample); + n = Math.max(Math.min(n, length), 0); + var last = length - 1; + for (var index = 0; index < n; index++) { + var rand = random(index, last); + var temp = sample[index]; + sample[index] = sample[rand]; + sample[rand] = temp; + } + return sample.slice(0, n); + } + + // Shuffle a collection. + function shuffle(obj) { + return sample(obj, Infinity); + } + + // Sort the object's values by a criterion produced by an iteratee. + function sortBy(obj, iteratee, context) { + var index = 0; + iteratee = cb(iteratee, context); + return pluck(map(obj, function(value, key, list) { + return { + value: value, + index: index++, + criteria: iteratee(value, key, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + } + + // An internal function used for aggregate "group by" operations. + function group(behavior, partition) { + return function(obj, iteratee, context) { + var result = partition ? [[], []] : {}; + iteratee = cb(iteratee, context); + each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); + }); + return result; + }; + } + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + var groupBy = group(function(result, value, key) { + if (has(result, key)) result[key].push(value); else result[key] = [value]; + }); + + // Indexes the object's values by a criterion, similar to `_.groupBy`, but for + // when you know that your index values will be unique. + var indexBy = group(function(result, value, key) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + var countBy = group(function(result, value, key) { + if (has(result, key)) result[key]++; else result[key] = 1; + }); + + // Split a collection into two arrays: one whose elements all pass the given + // truth test, and one whose elements all do not pass the truth test. + var partition = group(function(result, value, pass) { + result[pass ? 0 : 1].push(value); + }, true); + + // Safely create a real, live array from anything iterable. + var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g; + function toArray(obj) { + if (!obj) return []; + if (isArray(obj)) return slice.call(obj); + if (isString(obj)) { + // Keep surrogate pair characters together. + return obj.match(reStrSymbol); + } + if (isArrayLike(obj)) return map(obj, identity); + return values(obj); + } + + // Return the number of elements in a collection. + function size(obj) { + if (obj == null) return 0; + return isArrayLike(obj) ? obj.length : keys(obj).length; + } + + // Internal `_.pick` helper function to determine whether `key` is an enumerable + // property name of `obj`. + function keyInObj(value, key, obj) { + return key in obj; + } + + // Return a copy of the object only containing the allowed properties. + var pick = restArguments(function(obj, keys) { + var result = {}, iteratee = keys[0]; + if (obj == null) return result; + if (isFunction$1(iteratee)) { + if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]); + keys = allKeys(obj); + } else { + iteratee = keyInObj; + keys = flatten(keys, false, false); + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; + }); + + // Return a copy of the object without the disallowed properties. + var omit = restArguments(function(obj, keys) { + var iteratee = keys[0], context; + if (isFunction$1(iteratee)) { + iteratee = negate(iteratee); + if (keys.length > 1) context = keys[1]; + } else { + keys = map(flatten(keys, false, false), String); + iteratee = function(value, key) { + return !contains(keys, key); + }; + } + return pick(obj, iteratee, context); + }); + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. + function initial(array, n, guard) { + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + } + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. The **guard** check allows it to work with `_.map`. + function first(array, n, guard) { + if (array == null || array.length < 1) return n == null || guard ? void 0 : []; + if (n == null || guard) return array[0]; + return initial(array, array.length - n); + } + + // Returns everything but the first entry of the `array`. Especially useful on + // the `arguments` object. Passing an **n** will return the rest N values in the + // `array`. + function rest(array, n, guard) { + return slice.call(array, n == null || guard ? 1 : n); + } + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + function last(array, n, guard) { + if (array == null || array.length < 1) return n == null || guard ? void 0 : []; + if (n == null || guard) return array[array.length - 1]; + return rest(array, Math.max(0, array.length - n)); + } + + // Trim out all falsy values from an array. + function compact(array) { + return filter(array, Boolean); + } + + // Flatten out an array, either recursively (by default), or up to `depth`. + // Passing `true` or `false` as `depth` means `1` or `Infinity`, respectively. + function flatten$1(array, depth) { + return flatten(array, depth, false); + } + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + var difference = restArguments(function(array, rest) { + rest = flatten(rest, true, true); + return filter(array, function(value){ + return !contains(rest, value); + }); + }); + + // Return a version of the array that does not contain the specified value(s). + var without = restArguments(function(array, otherArrays) { + return difference(array, otherArrays); + }); + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // The faster algorithm will not work with an iteratee if the iteratee + // is not a one-to-one function, so providing an iteratee will disable + // the faster algorithm. + function uniq(array, isSorted, iteratee, context) { + if (!isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; + isSorted = false; + } + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; + var seen = []; + for (var i = 0, length = getLength(array); i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted && !iteratee) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!contains(result, value)) { + result.push(value); + } + } + return result; + } + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + var union = restArguments(function(arrays) { + return uniq(flatten(arrays, true, true)); + }); + + // Produce an array that contains every item shared between all the + // passed-in arrays. + function intersection(array) { + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = getLength(array); i < length; i++) { + var item = array[i]; + if (contains(result, item)) continue; + var j; + for (j = 1; j < argsLength; j++) { + if (!contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; + } + + // Complement of zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices. + function unzip(array) { + var length = array && max(array, getLength).length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = pluck(array, index); + } + return result; + } + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + var zip = restArguments(unzip); + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. Passing by pairs is the reverse of `_.pairs`. + function object(list, values) { + var result = {}; + for (var i = 0, length = getLength(list); i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + } + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](https://docs.python.org/library/functions.html#range). + function range(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + if (!step) { + step = stop < start ? -1 : 1; + } + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + } + + // Chunk a single array into multiple arrays, each containing `count` or fewer + // items. + function chunk(array, count) { + if (count == null || count < 1) return []; + var result = []; + var i = 0, length = array.length; + while (i < length) { + result.push(slice.call(array, i, i += count)); + } + return result; + } + + // Helper function to continue chaining intermediate results. + function chainResult(instance, obj) { + return instance._chain ? _(obj).chain() : obj; + } + + // Add your own custom functions to the Underscore object. + function mixin(obj) { + each(functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return chainResult(this, func.apply(_, args)); + }; + }); + return _; + } + + // Add all mutator `Array` functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + if (obj != null) { + method.apply(obj, arguments); + if ((name === 'shift' || name === 'splice') && obj.length === 0) { + delete obj[0]; + } + } + return chainResult(this, obj); + }; + }); + + // Add all accessor `Array` functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + if (obj != null) obj = method.apply(obj, arguments); + return chainResult(this, obj); + }; + }); + + // Named Exports + + var allExports = { + __proto__: null, + VERSION: VERSION, + restArguments: restArguments, + isObject: isObject, + isNull: isNull, + isUndefined: isUndefined, + isBoolean: isBoolean, + isElement: isElement, + isString: isString, + isNumber: isNumber, + isDate: isDate, + isRegExp: isRegExp, + isError: isError, + isSymbol: isSymbol, + isArrayBuffer: isArrayBuffer, + isDataView: isDataView$1, + isArray: isArray, + isFunction: isFunction$1, + isArguments: isArguments$1, + isFinite: isFinite$1, + isNaN: isNaN$1, + isTypedArray: isTypedArray$1, + isEmpty: isEmpty, + isMatch: isMatch, + isEqual: isEqual, + isMap: isMap, + isWeakMap: isWeakMap, + isSet: isSet, + isWeakSet: isWeakSet, + keys: keys, + allKeys: allKeys, + values: values, + pairs: pairs, + invert: invert, + functions: functions, + methods: functions, + extend: extend, + extendOwn: extendOwn, + assign: extendOwn, + defaults: defaults, + create: create, + clone: clone, + tap: tap, + get: get, + has: has$1, + mapObject: mapObject, + identity: identity, + constant: constant, + noop: noop, + toPath: toPath, + property: property, + propertyOf: propertyOf, + matcher: matcher, + matches: matcher, + times: times, + random: random, + now: now, + escape: _escape, + unescape: _unescape, + templateSettings: templateSettings, + template: template, + result: result, + uniqueId: uniqueId, + chain: chain, + iteratee: iteratee, + partial: partial, + bind: bind, + bindAll: bindAll, + memoize: memoize, + delay: delay, + defer: defer, + throttle: throttle, + debounce: debounce, + wrap: wrap, + negate: negate, + compose: compose, + after: after, + before: before, + once: once, + findKey: findKey, + findIndex: findIndex, + findLastIndex: findLastIndex, + sortedIndex: sortedIndex, + indexOf: indexOf, + lastIndexOf: lastIndexOf, + find: find, + detect: find, + findWhere: findWhere, + each: each, + forEach: each, + map: map, + collect: map, + reduce: reduce, + foldl: reduce, + inject: reduce, + reduceRight: reduceRight, + foldr: reduceRight, + filter: filter, + select: filter, + reject: reject, + every: every, + all: every, + some: some, + any: some, + contains: contains, + includes: contains, + include: contains, + invoke: invoke, + pluck: pluck, + where: where, + max: max, + min: min, + shuffle: shuffle, + sample: sample, + sortBy: sortBy, + groupBy: groupBy, + indexBy: indexBy, + countBy: countBy, + partition: partition, + toArray: toArray, + size: size, + pick: pick, + omit: omit, + first: first, + head: first, + take: first, + initial: initial, + last: last, + rest: rest, + tail: rest, + drop: rest, + compact: compact, + flatten: flatten$1, + without: without, + uniq: uniq, + unique: uniq, + union: union, + intersection: intersection, + difference: difference, + unzip: unzip, + transpose: unzip, + zip: zip, + object: object, + range: range, + chunk: chunk, + mixin: mixin, + 'default': _ + }; + + // Default Export + + // Add all of the Underscore functions to the wrapper object. + var _$1 = mixin(allExports); + // Legacy Node.js API. + _$1._ = _$1; + + return _$1; + +}))); +//# sourceMappingURL=underscore.js.map diff --git a/sphinx/themes/basic/static/underscore-1.3.1.js b/sphinx/themes/basic/static/underscore-1.3.1.js deleted file mode 100644 index 208d4cd89..000000000 --- a/sphinx/themes/basic/static/underscore-1.3.1.js +++ /dev/null @@ -1,999 +0,0 @@ -// Underscore.js 1.3.1 -// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore - -(function() { - - // Baseline setup - // -------------- - - // Establish the root object, `window` in the browser, or `global` on the server. - var root = this; - - // Save the previous value of the `_` variable. - var previousUnderscore = root._; - - // Establish the object that gets returned to break out of a loop iteration. - var breaker = {}; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; - - // Create quick reference variables for speed access to core prototypes. - var slice = ArrayProto.slice, - unshift = ArrayProto.unshift, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // All **ECMAScript 5** native function implementations that we hope to use - // are declared here. - var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, - nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeBind = FuncProto.bind; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { return new wrapper(obj); }; - - // Export the Underscore object for **Node.js**, with - // backwards-compatibility for the old `require()` API. If we're in - // the browser, add `_` as a global object via a string identifier, - // for Closure Compiler "advanced" mode. - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = _; - } - exports._ = _; - } else { - root['_'] = _; - } - - // Current version. - _.VERSION = '1.3.1'; - - // Collection Functions - // -------------------- - - // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, l = obj.length; i < l; i++) { - if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; - } - } else { - for (var key in obj) { - if (_.has(obj, key)) { - if (iterator.call(context, obj[key], key, obj) === breaker) return; - } - } - } - }; - - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = _.collect = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results[results.length] = iterator.call(context, value, index, list); - }); - if (obj.length === +obj.length) results.length = obj.length; - return results; - }; - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError('Reduce of empty array with no initial value'); - return memo; - }; - - // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var reversed = _.toArray(obj).reverse(); - if (context && !initial) iterator = _.bind(iterator, context); - return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); - }; - - // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, iterator, context) { - var result; - any(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. - // Aliased as `select`. - _.filter = _.select = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); - each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - each(obj, function(value, index, list) { - if (!iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. - // Aliased as `all`. - _.every = _.all = function(obj, iterator, context) { - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); - each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) return breaker; - }); - return result; - }; - - // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. - // Aliased as `any`. - var any = _.some = _.any = function(obj, iterator, context) { - iterator || (iterator = _.identity); - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); - each(obj, function(value, index, list) { - if (result || (result = iterator.call(context, value, index, list))) return breaker; - }); - return !!result; - }; - - // Determine if a given value is included in the array or object using `===`. - // Aliased as `contains`. - _.include = _.contains = function(obj, target) { - var found = false; - if (obj == null) return found; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - found = any(obj, function(value) { - return value === target; - }); - return found; - }; - - // Invoke a method (with arguments) on every item in a collection. - _.invoke = function(obj, method) { - var args = slice.call(arguments, 2); - return _.map(obj, function(value) { - return (_.isFunction(method) ? method || value : value[method]).apply(value, args); - }); - }; - - // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); - }; - - // Return the maximum element or (element-based computation). - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); - if (!iterator && _.isEmpty(obj)) return -Infinity; - var result = {computed : -Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed >= result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); - if (!iterator && _.isEmpty(obj)) return Infinity; - var result = {computed : Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Shuffle an array. - _.shuffle = function(obj) { - var shuffled = [], rand; - each(obj, function(value, index, list) { - if (index == 0) { - shuffled[0] = value; - } else { - rand = Math.floor(Math.random() * (index + 1)); - shuffled[index] = shuffled[rand]; - shuffled[rand] = value; - } - }); - return shuffled; - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, iterator, context) { - return _.pluck(_.map(obj, function(value, index, list) { - return { - value : value, - criteria : iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }), 'value'); - }; - - // Groups the object's values by a criterion. Pass either a string attribute - // to group by, or a function that returns the criterion. - _.groupBy = function(obj, val) { - var result = {}; - var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; - each(obj, function(value, index) { - var key = iterator(value, index); - (result[key] || (result[key] = [])).push(value); - }); - return result; - }; - - // Use a comparator function to figure out at what index an object should - // be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator) { - iterator || (iterator = _.identity); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >> 1; - iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; - } - return low; - }; - - // Safely convert anything iterable into a real, live array. - _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - if (_.isArray(iterable)) return slice.call(iterable); - if (_.isArguments(iterable)) return slice.call(iterable); - return _.values(iterable); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - return _.toArray(obj).length; - }; - - // Array Functions - // --------------- - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. Aliased as `head`. The **guard** check allows it to work - // with `_.map`. - _.first = _.head = function(array, n, guard) { - return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; - }; - - // Returns everything but the last entry of the array. Especcialy useful on - // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. The **guard** check allows it to work with - // `_.map`. - _.initial = function(array, n, guard) { - return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); - }; - - // Get the last element of an array. Passing **n** will return the last N - // values in the array. The **guard** check allows it to work with `_.map`. - _.last = function(array, n, guard) { - if ((n != null) && !guard) { - return slice.call(array, Math.max(array.length - n, 0)); - } else { - return array[array.length - 1]; - } - }; - - // Returns everything but the first entry of the array. Aliased as `tail`. - // Especially useful on the arguments object. Passing an **index** will return - // the rest of the values in the array from that index onward. The **guard** - // check allows it to work with `_.map`. - _.rest = _.tail = function(array, index, guard) { - return slice.call(array, (index == null) || guard ? 1 : index); - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, function(value){ return !!value; }); - }; - - // Return a completely flattened version of an array. - _.flatten = function(array, shallow) { - return _.reduce(array, function(memo, value) { - if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); - memo[memo.length] = value; - return memo; - }, []); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - return _.difference(array, slice.call(arguments, 1)); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted, iterator) { - var initial = iterator ? _.map(array, iterator) : array; - var result = []; - _.reduce(initial, function(memo, el, i) { - if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) { - memo[memo.length] = el; - result[result.length] = array[i]; - } - return memo; - }, []); - return result; - }; - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - _.union = function() { - return _.uniq(_.flatten(arguments, true)); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. (Aliased as "intersect" for back-compat.) - _.intersection = _.intersect = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Take the difference between one array and a number of other arrays. - // Only the elements present in just the first array will remain. - _.difference = function(array) { - var rest = _.flatten(slice.call(arguments, 1)); - return _.filter(array, function(value){ return !_.include(rest, value); }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var args = slice.call(arguments); - var length = _.max(_.pluck(args, 'length')); - var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); - return results; - }; - - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i, l; - if (isSorted) { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); - for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i; - return -1; - }; - - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. - _.lastIndexOf = function(array, item) { - if (array == null) return -1; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); - var i = array.length; - while (i--) if (i in array && array[i] === item) return i; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](http://docs.python.org/library/functions.html#range). - _.range = function(start, stop, step) { - if (arguments.length <= 1) { - stop = start || 0; - start = 0; - } - step = arguments[2] || 1; - - var len = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(len); - - while(idx < len) { - range[idx++] = start; - start += step; - } - - return range; - }; - - // Function (ahem) Functions - // ------------------ - - // Reusable constructor function for prototype setting. - var ctor = function(){}; - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Binding with arguments is also known as `curry`. - // Delegates to **ECMAScript 5**'s native `Function.bind` if available. - // We check for `func.bind` first, to fail fast when `func` is undefined. - _.bind = function bind(func, context) { - var bound, args; - if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError; - args = slice.call(arguments, 2); - return bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - ctor.prototype = func.prototype; - var self = new ctor; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (Object(result) === result) return result; - return self; - }; - }; - - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length == 0) funcs = _.functions(obj); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Memoize an expensive function by storing its results. - _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); - }; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(func, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. - _.throttle = function(func, wait) { - var context, args, timeout, throttling, more; - var whenDone = _.debounce(function(){ more = throttling = false; }, wait); - return function() { - context = this; args = arguments; - var later = function() { - timeout = null; - if (more) func.apply(context, args); - whenDone(); - }; - if (!timeout) timeout = setTimeout(later, wait); - if (throttling) { - more = true; - } else { - func.apply(context, args); - } - whenDone(); - throttling = true; - }; - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. - _.debounce = function(func, wait) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - func.apply(context, args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - return memo = func.apply(this, arguments); - }; - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func].concat(slice.call(arguments, 0)); - return wrapper.apply(this, args); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = arguments; - return function() { - var args = arguments; - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - // Returns a function that will only be executed after being called N times. - _.after = function(times, func) { - if (times <= 0) return func(); - return function() { - if (--times < 1) { return func.apply(this, arguments); } - }; - }; - - // Object Functions - // ---------------- - - // Retrieve the names of an object's properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = nativeKeys || function(obj) { - if (obj !== Object(obj)) throw new TypeError('Invalid object'); - var keys = []; - for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - return _.map(obj, _.identity); - }; - - // Return a sorted list of the function names available on the object. - // Aliased as `methods` - _.functions = _.methods = function(obj) { - var names = []; - for (var key in obj) { - if (_.isFunction(obj[key])) names.push(key); - } - return names.sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (obj[prop] == null) obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - if (!_.isObject(obj)) return obj; - return _.isArray(obj) ? obj.slice() : _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Internal recursive comparison function. - function eq(a, b, stack) { - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. - if (a === b) return a !== 0 || 1 / a == 1 / b; - // A strict comparison is necessary because `null == undefined`. - if (a == null || b == null) return a === b; - // Unwrap any wrapped objects. - if (a._chain) a = a._wrapped; - if (b._chain) b = b._wrapped; - // Invoke a custom `isEqual` method if one is provided. - if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); - if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); - // Compare `[[Class]]` names. - var className = toString.call(a); - if (className != toString.call(b)) return false; - switch (className) { - // Strings, numbers, dates, and booleans are compared by value. - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return a == String(b); - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') return false; - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = stack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (stack[length] == a) return true; - } - // Add the first object to the stack of traversed objects. - stack.push(a); - var size = 0, result = true; - // Recursively compare objects and arrays. - if (className == '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - // Ensure commutative equality for sparse arrays. - if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; - } - } - } else { - // Objects with different constructors are not equivalent. - if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; - // Deep compare objects. - for (var key in a) { - if (_.has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break; - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (_.has(b, key) && !(size--)) break; - } - result = !size; - } - } - // Remove the first object from the stack of traversed objects. - stack.pop(); - return result; - } - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - return eq(a, b, []); - }; - - // Is a given array, string, or object empty? - // An "empty" object has no enumerable own-properties. - _.isEmpty = function(obj) { - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (_.has(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType == 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) == '[object Array]'; - }; - - // Is a given variable an object? - _.isObject = function(obj) { - return obj === Object(obj); - }; - - // Is a given variable an arguments object? - _.isArguments = function(obj) { - return toString.call(obj) == '[object Arguments]'; - }; - if (!_.isArguments(arguments)) { - _.isArguments = function(obj) { - return !!(obj && _.has(obj, 'callee')); - }; - } - - // Is a given value a function? - _.isFunction = function(obj) { - return toString.call(obj) == '[object Function]'; - }; - - // Is a given value a string? - _.isString = function(obj) { - return toString.call(obj) == '[object String]'; - }; - - // Is a given value a number? - _.isNumber = function(obj) { - return toString.call(obj) == '[object Number]'; - }; - - // Is the given value `NaN`? - _.isNaN = function(obj) { - // `NaN` is the only value for which `===` is not reflexive. - return obj !== obj; - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; - }; - - // Is a given value a date? - _.isDate = function(obj) { - return toString.call(obj) == '[object Date]'; - }; - - // Is the given value a regular expression? - _.isRegExp = function(obj) { - return toString.call(obj) == '[object RegExp]'; - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return obj === void 0; - }; - - // Has own property? - _.has = function(obj, key) { - return hasOwnProperty.call(obj, key); - }; - - // Utility Functions - // ----------------- - - // Run Underscore.js in *noConflict* mode, returning the `_` variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iterators. - _.identity = function(value) { - return value; - }; - - // Run a function **n** times. - _.times = function (n, iterator, context) { - for (var i = 0; i < n; i++) iterator.call(context, i); - }; - - // Escape a string for HTML interpolation. - _.escape = function(string) { - return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); - }; - - // Add your own custom functions to the Underscore object, ensuring that - // they're correctly added to the OOP wrapper as well. - _.mixin = function(obj) { - each(_.functions(obj), function(name){ - addToWrapper(name, _[name] = obj[name]); - }); - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = idCounter++; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g, - escape : /<%-([\s\S]+?)%>/g - }; - - // When customizing `templateSettings`, if you don't want to define an - // interpolation, evaluation or escaping regex, we need one that is - // guaranteed not to match. - var noMatch = /.^/; - - // Within an interpolation, evaluation, or escaping, remove HTML escaping - // that had been previously added. - var unescape = function(code) { - return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'"); - }; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(str, data) { - var c = _.templateSettings; - var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + - 'with(obj||{}){__p.push(\'' + - str.replace(/\\/g, '\\\\') - .replace(/'/g, "\\'") - .replace(c.escape || noMatch, function(match, code) { - return "',_.escape(" + unescape(code) + "),'"; - }) - .replace(c.interpolate || noMatch, function(match, code) { - return "'," + unescape(code) + ",'"; - }) - .replace(c.evaluate || noMatch, function(match, code) { - return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('"; - }) - .replace(/\r/g, '\\r') - .replace(/\n/g, '\\n') - .replace(/\t/g, '\\t') - + "');}return __p.join('');"; - var func = new Function('obj', '_', tmpl); - if (data) return func(data, _); - return function(data) { - return func.call(this, data, _); - }; - }; - - // Add a "chain" function, which will delegate to the wrapper. - _.chain = function(obj) { - return _(obj).chain(); - }; - - // The OOP Wrapper - // --------------- - - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - var wrapper = function(obj) { this._wrapped = obj; }; - - // Expose `wrapper.prototype` as `_.prototype` - _.prototype = wrapper.prototype; - - // Helper function to continue chaining intermediate results. - var result = function(obj, chain) { - return chain ? _(obj).chain() : obj; - }; - - // A method to easily add functions to the OOP wrapper. - var addToWrapper = function(name, func) { - wrapper.prototype[name] = function() { - var args = slice.call(arguments); - unshift.call(args, this._wrapped); - return result(func.apply(_, args), this._chain); - }; - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - var wrapped = this._wrapped; - method.apply(wrapped, arguments); - var length = wrapped.length; - if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0]; - return result(wrapped, this._chain); - }; - }); - - // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - return result(method.apply(this._wrapped, arguments), this._chain); - }; - }); - - // Start chaining a wrapped Underscore object. - wrapper.prototype.chain = function() { - this._chain = true; - return this; - }; - - // Extracts the result from a wrapped and chained object. - wrapper.prototype.value = function() { - return this._wrapped; - }; - -}).call(this); diff --git a/sphinx/themes/basic/static/underscore.js b/sphinx/themes/basic/static/underscore.js index 5b55f32be..166240ef2 100644 --- a/sphinx/themes/basic/static/underscore.js +++ b/sphinx/themes/basic/static/underscore.js @@ -1,31 +1,6 @@ -// Underscore.js 1.3.1 -// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore -(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== -c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, -h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= -b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== -null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= -function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= -e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= -function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, -c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; -b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, -1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; -b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; -b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), -function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ -u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= -function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= -true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); +!function(n,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define("underscore",r):(n=n||self,function(){var t=n._,e=n._=r();e.noConflict=function(){return n._=t,e}}())}(this,(function(){ +// Underscore.js 1.12.0 +// https://underscorejs.org +// (c) 2009-2020 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. +var n="1.12.0",r="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||Function("return this")()||{},t=Array.prototype,e=Object.prototype,u="undefined"!=typeof Symbol?Symbol.prototype:null,o=t.push,i=t.slice,a=e.toString,f=e.hasOwnProperty,c="undefined"!=typeof ArrayBuffer,l="undefined"!=typeof DataView,s=Array.isArray,p=Object.keys,v=Object.create,h=c&&ArrayBuffer.isView,y=isNaN,g=isFinite,d=!{toString:null}.propertyIsEnumerable("toString"),b=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],m=Math.pow(2,53)-1;function j(n,r){return r=null==r?n.length-1:+r,function(){for(var t=Math.max(arguments.length-r,0),e=Array(t),u=0;u=0&&t<=m}}function $(n){return function(r){return null==r?void 0:r[n]}}var G=$("byteLength"),H=J(G),Q=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;var X=c?function(n){return h?h(n)&&!q(n):H(n)&&Q.test(a.call(n))}:K(!1),Y=$("length");function Z(n,r){r=function(n){for(var r={},t=n.length,e=0;e":">",'"':""","'":"'","`":"`"},Kn=Ln(Cn),Jn=Ln(_n(Cn)),$n=tn.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},Gn=/(.)^/,Hn={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},Qn=/\\|'|\r|\n|\u2028|\u2029/g;function Xn(n){return"\\"+Hn[n]}var Yn=0;function Zn(n,r,t,e,u){if(!(e instanceof r))return n.apply(t,u);var o=Mn(n.prototype),i=n.apply(o,u);return _(i)?i:o}var nr=j((function(n,r){var t=nr.placeholder,e=function(){for(var u=0,o=r.length,i=Array(o),a=0;a1)er(a,r-1,t,e),u=e.length;else for(var f=0,c=a.length;f0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}}var cr=nr(fr,2);function lr(n,r,t){r=qn(r,t);for(var e,u=nn(n),o=0,i=u.length;o0?0:u-1;o>=0&&o0?a=o>=0?o:Math.max(o+f,a):f=o>=0?Math.min(o+1,f):o+f+1;else if(t&&o&&f)return e[o=t(e,u)]===u?o:-1;if(u!=u)return(o=r(i.call(e,a,f),C))>=0?o+a:-1;for(o=n>0?a:f-1;o>=0&&o0?0:i-1;for(u||(e=r[o?o[a]:a],a+=n);a>=0&&a=3;return r(n,Fn(t,u,4),e,o)}}var wr=_r(1),Ar=_r(-1);function xr(n,r,t){var e=[];return r=qn(r,t),mr(n,(function(n,t,u){r(n,t,u)&&e.push(n)})),e}function Sr(n,r,t){r=qn(r,t);for(var e=!tr(n)&&nn(n),u=(e||n).length,o=0;o=0}var Er=j((function(n,r,t){var e,u;return D(r)?u=r:(r=Nn(r),e=r.slice(0,-1),r=r[r.length-1]),jr(n,(function(n){var o=u;if(!o){if(e&&e.length&&(n=In(n,e)),null==n)return;o=n[r]}return null==o?o:o.apply(n,t)}))}));function Br(n,r){return jr(n,Rn(r))}function Nr(n,r,t){var e,u,o=-1/0,i=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;ao&&(o=e);else r=qn(r,t),mr(n,(function(n,t,e){((u=r(n,t,e))>i||u===-1/0&&o===-1/0)&&(o=n,i=u)}));return o}function Ir(n,r,t){if(null==r||t)return tr(n)||(n=jn(n)),n[Wn(n.length-1)];var e=tr(n)?En(n):jn(n),u=Y(e);r=Math.max(Math.min(r,u),0);for(var o=u-1,i=0;i1&&(e=Fn(e,r[1])),r=an(n)):(e=Pr,r=er(r,!1,!1),n=Object(n));for(var u=0,o=r.length;u1&&(t=r[1])):(r=jr(er(r,!1,!1),String),e=function(n,t){return!Mr(r,t)}),qr(n,e,t)}));function Wr(n,r,t){return i.call(n,0,Math.max(0,n.length-(null==r||t?1:r)))}function zr(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[0]:Wr(n,n.length-r)}function Lr(n,r,t){return i.call(n,null==r||t?1:r)}var Cr=j((function(n,r){return r=er(r,!0,!0),xr(n,(function(n){return!Mr(r,n)}))})),Kr=j((function(n,r){return Cr(n,r)}));function Jr(n,r,t,e){A(r)||(e=t,t=r,r=!1),null!=t&&(t=qn(t,e));for(var u=[],o=[],i=0,a=Y(n);ir?(e&&(clearTimeout(e),e=null),a=c,i=n.apply(u,o),e||(u=o=null)):e||!1===t.trailing||(e=setTimeout(f,l)),i};return c.cancel=function(){clearTimeout(e),a=0,e=u=o=null},c},debounce:function(n,r,t){var e,u,o=function(r,t){e=null,t&&(u=n.apply(r,t))},i=j((function(i){if(e&&clearTimeout(e),t){var a=!e;e=setTimeout(o,r),a&&(u=n.apply(this,i))}else e=or(o,r,this,i);return u}));return i.cancel=function(){clearTimeout(e),e=null},i},wrap:function(n,r){return nr(r,n)},negate:ar,compose:function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},after:function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},before:fr,once:cr,findKey:lr,findIndex:pr,findLastIndex:vr,sortedIndex:hr,indexOf:gr,lastIndexOf:dr,find:br,detect:br,findWhere:function(n,r){return br(n,Dn(r))},each:mr,forEach:mr,map:jr,collect:jr,reduce:wr,foldl:wr,inject:wr,reduceRight:Ar,foldr:Ar,filter:xr,select:xr,reject:function(n,r,t){return xr(n,ar(qn(r)),t)},every:Sr,all:Sr,some:Or,any:Or,contains:Mr,includes:Mr,include:Mr,invoke:Er,pluck:Br,where:function(n,r){return xr(n,Dn(r))},max:Nr,min:function(n,r,t){var e,u,o=1/0,i=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;ae||void 0===t)return 1;if(t * Humdinger * - * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 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 7df474a91..3dd748dda 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-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/nonav/layout.html b/sphinx/themes/nonav/layout.html index 256b9a228..d39800c18 100644 --- a/sphinx/themes/nonav/layout.html +++ b/sphinx/themes/nonav/layout.html @@ -4,7 +4,7 @@ Sphinx layout template for the any help system theme. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "basic/layout.html" %} diff --git a/sphinx/themes/nonav/static/nonav.css b/sphinx/themes/nonav/static/nonav.css index d37c76221..63abb9f0d 100644 --- a/sphinx/themes/nonav/static/nonav.css +++ b/sphinx/themes/nonav/static/nonav.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- nonav theme. * - * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 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 270e9570e..6e5722fd6 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-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 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 0bab3d1a2..9857f2850 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-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 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 7866a5162..066d9f668 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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 e484f8c4f..f039d9e2b 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-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t index 8eca63278..4be04ce71 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-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 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 0120f83a5..4371d8e89 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-2020 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/sphinx/theming.py b/sphinx/theming.py index 087ee7f24..30e4dffdb 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -4,7 +4,7 @@ Theming support for HTML builders. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index f854f1772..45640308f 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -4,7 +4,7 @@ Docutils transforms used by Sphinx when reading documents. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/transforms/compact_bullet_list.py b/sphinx/transforms/compact_bullet_list.py index e30efa92b..18042358e 100644 --- a/sphinx/transforms/compact_bullet_list.py +++ b/sphinx/transforms/compact_bullet_list.py @@ -4,7 +4,7 @@ Docutils transforms used by Sphinx when reading documents. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index bffcb511d..d588f0411 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -4,7 +4,7 @@ Docutils transforms used by Sphinx when reading documents. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -311,6 +311,7 @@ class Locale(SphinxTransform): refname = newf.get('refname') refs = old_foot_namerefs.get(refname, []) if not refs: + newf.parent.remove(newf) continue oldf = refs.pop(0) diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py index f9feab2a6..1c424050a 100644 --- a/sphinx/transforms/post_transforms/__init__.py +++ b/sphinx/transforms/post_transforms/__init__.py @@ -4,11 +4,11 @@ Docutils transforms used by Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -from typing import Any, Dict, List, Tuple, Type, cast +from typing import Any, Dict, List, Optional, Tuple, Type, cast from docutils import nodes from docutils.nodes import Element @@ -150,7 +150,7 @@ class ReferencesResolver(SphinxPostTransform): return newnode def warn_missing_reference(self, refdoc: str, typ: str, target: str, - node: pending_xref, domain: Domain) -> None: + node: pending_xref, domain: Optional[Domain]) -> None: warn = node.get('refwarn') if self.config.nitpicky: warn = True diff --git a/sphinx/transforms/post_transforms/code.py b/sphinx/transforms/post_transforms/code.py index d306b5c80..20df1db3c 100644 --- a/sphinx/transforms/post_transforms/code.py +++ b/sphinx/transforms/post_transforms/code.py @@ -4,7 +4,7 @@ transforms for code-blocks. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 4865aa615..2603e0458 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -4,7 +4,7 @@ Docutils transforms used by Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -20,7 +20,7 @@ from sphinx.locale import __ from sphinx.transforms import SphinxTransform from sphinx.util import epoch_to_rfc1123, logging, requests, rfc1123_to_epoch, sha1 from sphinx.util.images import get_image_extension, guess_mimetype, parse_data_uri -from sphinx.util.osutil import ensuredir, movefile +from sphinx.util.osutil import ensuredir logger = logging.getLogger(__name__) @@ -99,7 +99,7 @@ class ImageDownloader(BaseImageConverter): # append a suffix if URI does not contain suffix ext = get_image_extension(mimetype) newpath = os.path.join(self.imagedir, dirname, basename + ext) - movefile(path, newpath) + os.replace(path, newpath) self.app.env.original_image_uri.pop(path) self.app.env.original_image_uri[newpath] = node['uri'] path = newpath @@ -197,15 +197,15 @@ class ImageConverter(BaseImageConverter): def match(self, node: nodes.image) -> bool: if not self.app.builder.supported_image_types: return False + elif set(node['candidates']) & set(self.app.builder.supported_image_types): + # builder supports the image; no need to convert + return False elif self.available is None: # store the value to the class variable to share it during the build self.__class__.available = self.is_available() if not self.available: return False - elif set(node['candidates']) & set(self.app.builder.supported_image_types): - # builder supports the image; no need to convert - return False else: rule = self.get_conversion_rule(node) if rule: diff --git a/sphinx/transforms/references.py b/sphinx/transforms/references.py index b6129b0bc..cd564d9eb 100644 --- a/sphinx/transforms/references.py +++ b/sphinx/transforms/references.py @@ -4,7 +4,7 @@ Docutils transforms used by Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 9349ed0e4..2fbc182e5 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -4,7 +4,7 @@ Utility functions for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/build_phase.py b/sphinx/util/build_phase.py index d6193b400..07a5ee7cd 100644 --- a/sphinx/util/build_phase.py +++ b/sphinx/util/build_phase.py @@ -4,7 +4,7 @@ Build phase of Sphinx application. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py index 17c335b60..1549fbf75 100644 --- a/sphinx/util/cfamily.py +++ b/sphinx/util/cfamily.py @@ -4,7 +4,7 @@ Utility functions common to the C and C++ domains. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/compat.py b/sphinx/util/compat.py index 7c55c4ec7..dbe902be7 100644 --- a/sphinx/util/compat.py +++ b/sphinx/util/compat.py @@ -4,7 +4,7 @@ modules for backward compatibility - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/console.py b/sphinx/util/console.py index 579a95134..3ea5b9573 100644 --- a/sphinx/util/console.py +++ b/sphinx/util/console.py @@ -4,7 +4,7 @@ Format colored console output. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 404bb127f..3fc72340a 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -5,7 +5,7 @@ "Doc fields" are reST field lists in object descriptions that will be domain-specifically transformed to a more appealing presentation. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -295,6 +295,7 @@ class DocFieldTransformer: self.directive.domain, target, contnode=content[0], + env=self.directive.state.document.settings.env ) if _is_single_paragraph(field_body): paragraph = cast(nodes.paragraph, field_body[0]) diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py index 206986bd0..ac778af87 100644 --- a/sphinx/util/docstrings.py +++ b/sphinx/util/docstrings.py @@ -4,7 +4,7 @@ Utilities for docstring processing. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index 7831f267c..ce50c7ab1 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -4,7 +4,7 @@ Utility functions for docutils. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py index eec1ae463..466c28135 100644 --- a/sphinx/util/fileutil.py +++ b/sphinx/util/fileutil.py @@ -4,7 +4,7 @@ File utility functions for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 41407f4e1..8341dfffe 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -4,7 +4,7 @@ Builder superclass for all builders. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import gettext @@ -34,7 +34,7 @@ if False: logger = logging.getLogger(__name__) -LocaleFileInfoBase = namedtuple('CatalogInfo', 'base_dir,domain,charset') +LocaleFileInfoBase = namedtuple('LocaleFileInfoBase', 'base_dir,domain,charset') class CatalogInfo(LocaleFileInfoBase): @@ -236,7 +236,9 @@ date_format_mappings = { '%X': 'medium', # Locale’s appropriate time representation. '%y': 'YY', # Year without century as a zero-padded decimal number. '%Y': 'yyyy', # Year with century as a decimal number. - '%Z': 'zzzz', # Time zone name (no characters if no time zone exists). + '%Z': 'zzz', # Time zone name (no characters if no time zone exists). + '%z': 'ZZZ', # UTC offset in the form ±HHMM[SS[.ffffff]] + # (empty string if the object is naive). '%%': '%', } diff --git a/sphinx/util/images.py b/sphinx/util/images.py index 0ddf64908..81a321818 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -4,7 +4,7 @@ Image utility functions for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 8c596ac95..202e170c1 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -4,7 +4,7 @@ Helpers for inspecting Python modules. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -36,6 +36,10 @@ else: MethodDescriptorType = type(str.join) WrapperDescriptorType = type(dict.__dict__['fromkeys']) +if False: + # For type annotation + from typing import Type # NOQA + logger = logging.getLogger(__name__) memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE) @@ -154,11 +158,36 @@ def getall(obj: Any) -> Optional[Sequence[str]]: raise ValueError(__all__) +def getannotations(obj: Any) -> Mapping[str, Any]: + """Get __annotations__ from given *obj* safely. + + Raises AttributeError if given *obj* raises an error on accessing __attribute__. + """ + __annotations__ = safe_getattr(obj, '__annotations__', None) + if isinstance(__annotations__, Mapping): + return __annotations__ + else: + return {} + + +def getmro(obj: Any) -> Tuple["Type", ...]: + """Get __mro__ from given *obj* safely. + + Raises AttributeError if given *obj* raises an error on accessing __mro__. + """ + __mro__ = safe_getattr(obj, '__mro__', None) + if isinstance(__mro__, tuple): + return __mro__ + else: + return tuple() + + def getslots(obj: Any) -> Optional[Dict]: """Get __slots__ attribute of the class as dict. Return None if gienv *obj* does not have __slots__. Raises AttributeError if given *obj* raises an error on accessing __slots__. + Raises TypeError if given *obj* is not a class. Raises ValueError if given *obj* have invalid __slots__. """ if not inspect.isclass(obj): @@ -343,7 +372,7 @@ def iscoroutinefunction(obj: Any) -> bool: def isproperty(obj: Any) -> bool: """Check if the object is property.""" - if sys.version_info > (3, 8): + if sys.version_info >= (3, 8): from functools import cached_property # cached_property is available since py3.8 if isinstance(obj, cached_property): return True @@ -470,6 +499,19 @@ def is_builtin_class_method(obj: Any, attr_name: str) -> bool: return getattr(builtins, name, None) is cls +class DefaultValue: + """A simple wrapper for default value of the parameters of overload functions.""" + + def __init__(self, value: str) -> None: + self.value = value + + def __eq__(self, other: object) -> bool: + return self.value == other + + def __repr__(self) -> str: + return self.value + + def _should_unwrap(subject: Callable) -> bool: """Check the function should be unwrapped on getting signature.""" if (safe_getattr(subject, '__globals__', None) and @@ -675,7 +717,7 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> inspect.Signatu if defaults[i] is Parameter.empty: default = Parameter.empty else: - default = ast_unparse(defaults[i], code) + default = DefaultValue(ast_unparse(defaults[i], code)) annotation = ast_unparse(arg.annotation, code) or Parameter.empty params.append(Parameter(arg.arg, Parameter.POSITIONAL_ONLY, @@ -685,7 +727,7 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> inspect.Signatu if defaults[i + posonlyargs] is Parameter.empty: default = Parameter.empty else: - default = ast_unparse(defaults[i + posonlyargs], code) + default = DefaultValue(ast_unparse(defaults[i + posonlyargs], code)) annotation = ast_unparse(arg.annotation, code) or Parameter.empty params.append(Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD, diff --git a/sphinx/util/inventory.py b/sphinx/util/inventory.py index 6d3b5f455..b1af1bfb9 100644 --- a/sphinx/util/inventory.py +++ b/sphinx/util/inventory.py @@ -4,7 +4,7 @@ Inventory utility functions for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os diff --git a/sphinx/util/jsdump.py b/sphinx/util/jsdump.py index 0cd3c8b8c..114fd7075 100644 --- a/sphinx/util/jsdump.py +++ b/sphinx/util/jsdump.py @@ -5,7 +5,7 @@ This module implements a simple JavaScript serializer. Uses the basestring encode function from simplejson by Bob Ippolito. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 4cb3bc93f..b038fd4db 100644 --- a/sphinx/util/jsonimpl.py +++ b/sphinx/util/jsonimpl.py @@ -4,7 +4,7 @@ JSON serializer implementation wrapper. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index 0f2a8b6f0..09780723a 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -4,7 +4,7 @@ Logging utility functions for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 2d37866a6..d33ae0333 100644 --- a/sphinx/util/matching.py +++ b/sphinx/util/matching.py @@ -4,7 +4,7 @@ Pattern-matching utility functions for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/math.py b/sphinx/util/math.py index 2a9bd66d7..229e09d36 100644 --- a/sphinx/util/math.py +++ b/sphinx/util/math.py @@ -4,7 +4,7 @@ Utility functions for math. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 05e9cb7b2..d5e43e716 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -4,7 +4,7 @@ Docutils node-related utility functions for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -469,46 +469,46 @@ def _make_id(string: str) -> str: _non_id_chars = re.compile('[^a-zA-Z0-9._]+') _non_id_at_ends = re.compile('^[-0-9._]+|-+$') _non_id_translate = { - 0x00f8: u'o', # o with stroke - 0x0111: u'd', # d with stroke - 0x0127: u'h', # h with stroke - 0x0131: u'i', # dotless i - 0x0142: u'l', # l with stroke - 0x0167: u't', # t with stroke - 0x0180: u'b', # b with stroke - 0x0183: u'b', # b with topbar - 0x0188: u'c', # c with hook - 0x018c: u'd', # d with topbar - 0x0192: u'f', # f with hook - 0x0199: u'k', # k with hook - 0x019a: u'l', # l with bar - 0x019e: u'n', # n with long right leg - 0x01a5: u'p', # p with hook - 0x01ab: u't', # t with palatal hook - 0x01ad: u't', # t with hook - 0x01b4: u'y', # y with hook - 0x01b6: u'z', # z with stroke - 0x01e5: u'g', # g with stroke - 0x0225: u'z', # z with hook - 0x0234: u'l', # l with curl - 0x0235: u'n', # n with curl - 0x0236: u't', # t with curl - 0x0237: u'j', # dotless j - 0x023c: u'c', # c with stroke - 0x023f: u's', # s with swash tail - 0x0240: u'z', # z with swash tail - 0x0247: u'e', # e with stroke - 0x0249: u'j', # j with stroke - 0x024b: u'q', # q with hook tail - 0x024d: u'r', # r with stroke - 0x024f: u'y', # y with stroke + 0x00f8: 'o', # o with stroke + 0x0111: 'd', # d with stroke + 0x0127: 'h', # h with stroke + 0x0131: 'i', # dotless i + 0x0142: 'l', # l with stroke + 0x0167: 't', # t with stroke + 0x0180: 'b', # b with stroke + 0x0183: 'b', # b with topbar + 0x0188: 'c', # c with hook + 0x018c: 'd', # d with topbar + 0x0192: 'f', # f with hook + 0x0199: 'k', # k with hook + 0x019a: 'l', # l with bar + 0x019e: 'n', # n with long right leg + 0x01a5: 'p', # p with hook + 0x01ab: 't', # t with palatal hook + 0x01ad: 't', # t with hook + 0x01b4: 'y', # y with hook + 0x01b6: 'z', # z with stroke + 0x01e5: 'g', # g with stroke + 0x0225: 'z', # z with hook + 0x0234: 'l', # l with curl + 0x0235: 'n', # n with curl + 0x0236: 't', # t with curl + 0x0237: 'j', # dotless j + 0x023c: 'c', # c with stroke + 0x023f: 's', # s with swash tail + 0x0240: 'z', # z with swash tail + 0x0247: 'e', # e with stroke + 0x0249: 'j', # j with stroke + 0x024b: 'q', # q with hook tail + 0x024d: 'r', # r with stroke + 0x024f: 'y', # y with stroke } _non_id_translate_digraphs = { - 0x00df: u'sz', # ligature sz - 0x00e6: u'ae', # ae - 0x0153: u'oe', # ligature oe - 0x0238: u'db', # db digraph - 0x0239: u'qp', # qp digraph + 0x00df: 'sz', # ligature sz + 0x00e6: 'ae', # ae + 0x0153: 'oe', # ligature oe + 0x0238: 'db', # db digraph + 0x0239: 'qp', # qp digraph } diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 0390b038d..53bffd929 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -4,7 +4,7 @@ Operating system-related utility functions for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -20,7 +20,7 @@ from io import StringIO from os import path from typing import Any, Generator, Iterator, List, Optional, Tuple -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning try: # for ALT Linux (#6712) @@ -103,6 +103,9 @@ def mtimes_of_files(dirnames: List[str], suffix: str) -> Iterator[float]: def movefile(source: str, dest: str) -> None: """Move a file, removing the destination if it exists.""" + warnings.warn('sphinx.util.osutil.movefile() is deprecated for removal. ' + 'Please use os.replace() instead.', + RemovedInSphinx50Warning, stacklevel=2) if os.path.exists(dest): try: os.unlink(dest) @@ -222,14 +225,14 @@ class FileAvoidWrite: self._io.close() try: - with open(self._path) as old_f: + with open(self._path, encoding='utf-8') as old_f: old_content = old_f.read() if old_content == buf: return except OSError: pass - with open(self._path, 'w') as f: + with open(self._path, 'w', encoding='utf-8') as f: f.write(buf) def __enter__(self) -> "FileAvoidWrite": diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py index ddcdaa316..ab27a5128 100644 --- a/sphinx/util/parallel.py +++ b/sphinx/util/parallel.py @@ -4,7 +4,7 @@ Parallel building utilities. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/png.py b/sphinx/util/png.py index 3dd22d7ff..2ab5a836f 100644 --- a/sphinx/util/png.py +++ b/sphinx/util/png.py @@ -4,7 +4,7 @@ PNG image manipulation helpers. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 6474cf141..87c38f72e 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -4,7 +4,7 @@ Stuff for Python version compatibility. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/requests.py b/sphinx/util/requests.py index ca570249a..8ae435d41 100644 --- a/sphinx/util/requests.py +++ b/sphinx/util/requests.py @@ -4,7 +4,7 @@ Simple requests package loader - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/rst.py b/sphinx/util/rst.py index b67b68c4a..79ede3432 100644 --- a/sphinx/util/rst.py +++ b/sphinx/util/rst.py @@ -4,7 +4,7 @@ reST helper functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/stemmer/__init__.py b/sphinx/util/stemmer/__init__.py index 94aaa136d..6470dfe2b 100644 --- a/sphinx/util/stemmer/__init__.py +++ b/sphinx/util/stemmer/__init__.py @@ -4,7 +4,7 @@ Word stemming utilities for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/tags.py b/sphinx/util/tags.py index 1662a06f0..c50231220 100644 --- a/sphinx/util/tags.py +++ b/sphinx/util/tags.py @@ -2,7 +2,7 @@ sphinx.util.tags ~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 8785928a9..a61008602 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -4,7 +4,7 @@ Templates utility functions for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 98e763aaf..b67dcfe82 100644 --- a/sphinx/util/texescape.py +++ b/sphinx/util/texescape.py @@ -4,7 +4,7 @@ TeX escaping helper. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index 9d9355414..e85c40cdf 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -4,12 +4,13 @@ The composit types for Sphinx. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import sys import typing +from struct import Struct from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, TypeVar, Union from docutils import nodes @@ -66,7 +67,7 @@ def get_type_hints(obj: Any, globalns: Dict = None, localns: Dict = None) -> Dic from sphinx.util.inspect import safe_getattr # lazy loading try: - return typing.get_type_hints(obj, None, localns) + return typing.get_type_hints(obj, globalns, localns) except NameError: # Failed to evaluate ForwardRef (maybe TYPE_CHECKING) return safe_getattr(obj, '__annotations__', {}) @@ -94,6 +95,9 @@ def restify(cls: Optional["Type"]) -> str: return ':obj:`None`' elif cls is Ellipsis: return '...' + elif cls is Struct: + # Before Python 3.9, struct.Struct class has incorrect __module__. + return ':class:`struct.Struct`' elif inspect.isNewType(cls): return ':class:`%s`' % cls.__name__ elif cls.__module__ in ('__builtin__', 'builtins'): @@ -153,6 +157,8 @@ def _restify_py37(cls: Optional["Type"]) -> str: return ':obj:`%s`' % cls._name else: return ':obj:`%s.%s`' % (cls.__module__, cls._name) + elif isinstance(cls, ForwardRef): + return ':class:`%s`' % cls.__forward_arg__ else: # not a class (ex. TypeVar) return ':obj:`%s.%s`' % (cls.__module__, cls.__name__) @@ -303,6 +309,9 @@ def stringify(annotation: Any) -> str: return annotation.__qualname__ elif annotation is Ellipsis: return '...' + elif annotation is Struct: + # Before Python 3.9, struct.Struct class has incorrect __module__. + return 'struct.Struct' if sys.version_info >= (3, 7): # py37+ return _stringify_py37(annotation) diff --git a/sphinx/versioning.py b/sphinx/versioning.py index 7307b13d1..6e5a9eb26 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -5,7 +5,7 @@ Implements the low-level algorithms Sphinx uses for the versioning of doctrees. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import pickle diff --git a/sphinx/writers/__init__.py b/sphinx/writers/__init__.py index 97543df0d..34a5d36f3 100644 --- a/sphinx/writers/__init__.py +++ b/sphinx/writers/__init__.py @@ -4,6 +4,6 @@ Custom docutils writers. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 1fb0a9a98..d3e7e03a4 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -4,7 +4,7 @@ docutils writers handling Sphinx' custom nodes. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -22,7 +22,7 @@ from docutils.writers.html4css1 import Writer from sphinx import addnodes from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.locale import _, __, admonitionlabels from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator @@ -100,11 +100,6 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): self.docnames = [self.builder.current_docname] # for singlehtml builder self.manpages_url = self.config.manpages_url self.protect_literal_text = 0 - self.permalink_text = self.config.html_add_permalinks - # support backwards-compatible setting to a bool - if not isinstance(self.permalink_text, str): - self.permalink_text = '¶' if self.permalink_text else '' - self.permalink_text = self.encode(self.permalink_text) self.secnumber_suffix = self.config.html_secnumber_suffix self.param_separator = '' self.optional_param_level = 0 @@ -317,7 +312,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): if figure_id in self.builder.fignumbers.get(key, {}): self.body.append('') - prefix = self.builder.config.numfig_format.get(figtype) + prefix = self.config.numfig_format.get(figtype) if prefix is None: msg = __('numfig_format is not defined for %s') % figtype logger.warning(msg) @@ -335,9 +330,10 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): append_fignumber(figtype, node['ids'][0]) def add_permalink_ref(self, node: Element, title: str) -> None: - if node['ids'] and self.permalink_text and self.builder.add_permalinks: + if node['ids'] and self.config.html_permalinks and self.builder.add_permalinks: format = '%s' - self.body.append(format % (node['ids'][0], title, self.permalink_text)) + self.body.append(format % (node['ids'][0], title, + self.config.html_permalinks_icon)) def generate_targets_for_listing(self, node: Element) -> None: """Generate hyperlink targets for listings. @@ -400,6 +396,10 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): # there's a classifier. pass else: + if isinstance(node.parent.parent.parent, addnodes.glossary): + # add permalink if glossary terms + self.add_permalink_ref(node, _('Permalink to this term')) + self.body.append('') # overwritten @@ -412,7 +412,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): def depart_title(self, node: Element) -> None: close_tag = self.context[-1] - if (self.permalink_text and self.builder.add_permalinks and + if (self.config.html_permalinks and self.builder.add_permalinks and node.parent.hasattr('ids') and node.parent['ids']): # add permalink anchor if close_tag.startswith('%s' % ( _('Permalink to this headline'), - self.permalink_text)) + self.config.html_permalinks_icon)) elif isinstance(node.parent, nodes.table): self.body.append('') self.add_permalink_ref(node.parent, _('Permalink to this table')) @@ -441,14 +441,10 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): linenos = node.get('linenos', False) highlight_args = node.get('highlight_args', {}) highlight_args['force'] = node.get('force', False) - if lang is self.builder.config.highlight_language: - # only pass highlighter options for original language - opts = self.builder.config.highlight_options - else: - opts = {} + opts = self.config.highlight_options.get(lang, {}) - if linenos and self.builder.config.html_codeblock_linenos_style: - linenos = self.builder.config.html_codeblock_linenos_style + if linenos and self.config.html_codeblock_linenos_style: + linenos = self.config.html_codeblock_linenos_style highlighted = self.highlighter.highlight_block( node.rawsource, lang, opts=opts, linenos=linenos, @@ -844,3 +840,9 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): def unknown_visit(self, node: Node) -> None: raise NotImplementedError('Unknown node: ' + node.__class__.__name__) + + @property + def permalink_text(self) -> str: + warnings.warn('HTMLTranslator.permalink_text is deprecated.', + RemovedInSphinx50Warning, stacklevel=2) + return self.config.html_permalinks_icon diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index 80458f184..5666e4d02 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -4,7 +4,7 @@ Experimental docutils writers for HTML5 handling Sphinx' custom nodes. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -20,7 +20,7 @@ from docutils.writers.html5_polyglot import HTMLTranslator as BaseTranslator from sphinx import addnodes from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.locale import _, __, admonitionlabels from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator @@ -71,11 +71,6 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): self.docnames = [self.builder.current_docname] # for singlehtml builder self.manpages_url = self.config.manpages_url self.protect_literal_text = 0 - self.permalink_text = self.config.html_add_permalinks - # support backwards-compatible setting to a bool - if not isinstance(self.permalink_text, str): - self.permalink_text = '¶' if self.permalink_text else '' - self.permalink_text = self.encode(self.permalink_text) self.secnumber_suffix = self.config.html_secnumber_suffix self.param_separator = '' self.optional_param_level = 0 @@ -288,7 +283,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): if figure_id in self.builder.fignumbers.get(key, {}): self.body.append('') - prefix = self.builder.config.numfig_format.get(figtype) + prefix = self.config.numfig_format.get(figtype) if prefix is None: msg = __('numfig_format is not defined for %s') % figtype logger.warning(msg) @@ -306,9 +301,10 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): append_fignumber(figtype, node['ids'][0]) def add_permalink_ref(self, node: Element, title: str) -> None: - if node['ids'] and self.permalink_text and self.builder.add_permalinks: + if node['ids'] and self.config.html_permalinks and self.builder.add_permalinks: format = '%s' - self.body.append(format % (node['ids'][0], title, self.permalink_text)) + self.body.append(format % (node['ids'][0], title, + self.config.html_permalinks_icon)) # overwritten def visit_bullet_list(self, node: Element) -> None: @@ -351,6 +347,10 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # there's a classifier. pass else: + if isinstance(node.parent.parent.parent, addnodes.glossary): + # add permalink if glossary terms + self.add_permalink_ref(node, _('Permalink to this term')) + self.body.append('') # overwritten @@ -363,8 +363,8 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): def depart_title(self, node: Element) -> None: close_tag = self.context[-1] - if (self.permalink_text and self.builder.add_permalinks and - node.parent.hasattr('ids') and node.parent['ids']): + if (self.config.html_permalinks and self.builder.add_permalinks and + node.parent.hasattr('ids') and node.parent['ids']): # add permalink anchor if close_tag.startswith('%s' % ( _('Permalink to this headline'), - self.permalink_text)) + self.config.html_permalinks_icon)) elif isinstance(node.parent, nodes.table): self.body.append('') self.add_permalink_ref(node.parent, _('Permalink to this table')) @@ -392,14 +392,10 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): linenos = node.get('linenos', False) highlight_args = node.get('highlight_args', {}) highlight_args['force'] = node.get('force', False) - if lang is self.builder.config.highlight_language: - # only pass highlighter options for original language - opts = self.builder.config.highlight_options - else: - opts = {} + opts = self.config.highlight_options.get(lang, {}) - if linenos and self.builder.config.html_codeblock_linenos_style: - linenos = self.builder.config.html_codeblock_linenos_style + if linenos and self.config.html_codeblock_linenos_style: + linenos = self.config.html_codeblock_linenos_style highlighted = self.highlighter.highlight_block( node.rawsource, lang, opts=opts, linenos=linenos, @@ -792,3 +788,9 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): def unknown_visit(self, node: Node) -> None: raise NotImplementedError('Unknown node: ' + node.__class__.__name__) + + @property + def permalink_text(self) -> str: + warnings.warn('HTMLTranslator.permalink_text is deprecated.', + RemovedInSphinx50Warning, stacklevel=2) + return self.config.html_permalinks_icon diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index cb8e5dff3..da0d39e24 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -7,7 +7,7 @@ Much of this code is adapted from Dave Kuhlman's "docpy" writer from his docutils sandbox. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -523,7 +523,7 @@ class LaTeXTranslator(SphinxTranslator): ret = [] # latex_domain_indices can be False/True or a list of index names - indices_config = self.builder.config.latex_domain_indices + indices_config = self.config.latex_domain_indices if indices_config: for domain in self.builder.env.domains.values(): for indexcls in domain.indices: @@ -543,7 +543,7 @@ class LaTeXTranslator(SphinxTranslator): def render(self, template_name: str, variables: Dict) -> str: renderer = LaTeXRenderer(latex_engine=self.config.latex_engine) - for template_dir in self.builder.config.templates_path: + for template_dir in self.config.templates_path: template = path.join(self.builder.confdir, template_dir, template_name) if path.exists(template): @@ -815,7 +815,8 @@ class LaTeXTranslator(SphinxTranslator): pass def visit_seealso(self, node: Element) -> None: - self.body.append('\n\n\\sphinxstrong{%s:}\n\n' % admonitionlabels['seealso']) + self.body.append('\n\n\\sphinxstrong{%s:}\n\\nopagebreak\n\n' + % admonitionlabels['seealso']) def depart_seealso(self, node: Element) -> None: self.body.append("\n\n") @@ -962,7 +963,7 @@ class LaTeXTranslator(SphinxTranslator): cell = self.table.cell() context = '' if cell.width > 1: - if self.builder.config.latex_use_latex_multicolumn: + if self.config.latex_use_latex_multicolumn: if self.table.col == 0: self.body.append('\\multicolumn{%d}{|l|}{%%\n' % cell.width) else: @@ -1160,7 +1161,10 @@ class LaTeXTranslator(SphinxTranslator): # (first one is label node) pass else: - self.body.append('\n') + # the \sphinxAtStartPar is to allow hyphenation of first word of + # a paragraph in narrow contexts such as in a table cell + # added as two items (cf. line trimming in depart_entry()) + self.body.extend(['\n', '\\sphinxAtStartPar\n']) def depart_paragraph(self, node: Element) -> None: self.body.append('\n') @@ -1174,9 +1178,11 @@ class LaTeXTranslator(SphinxTranslator): self.body.append('\n\\end{center}') def visit_hlist(self, node: Element) -> None: - # for now, we don't support a more compact list format - # don't add individual itemize environments, but one for all columns self.compact_list += 1 + ncolumns = node['ncolumns'] + if self.compact_list > 1: + self.body.append('\\setlength{\\multicolsep}{0pt}\n') + self.body.append('\\begin{multicols}{' + ncolumns + '}\\raggedright\n') self.body.append('\\begin{itemize}\\setlength{\\itemsep}{0pt}' '\\setlength{\\parskip}{0pt}\n') if self.table: @@ -1184,12 +1190,17 @@ class LaTeXTranslator(SphinxTranslator): def depart_hlist(self, node: Element) -> None: self.compact_list -= 1 - self.body.append('\\end{itemize}\n') + self.body.append('\\end{itemize}\\raggedcolumns\\end{multicols}\n') def visit_hlistcol(self, node: Element) -> None: pass def depart_hlistcol(self, node: Element) -> None: + # \columnbreak would guarantee same columns as in html ouput. But + # some testing with long items showed that columns may be too uneven. + # And in case only of short items, the automatic column breaks should + # match the ones pre-computed by the hlist() directive. + # self.body.append('\\columnbreak\n') pass def latex_image_length(self, width_str: str, scale: int = 100) -> str: @@ -1204,7 +1215,6 @@ class LaTeXTranslator(SphinxTranslator): return isinstance(node.parent, nodes.TextElement) def visit_image(self, node: Element) -> None: - attrs = node.attributes pre = [] # type: List[str] # in reverse order post = [] # type: List[str] @@ -1214,27 +1224,27 @@ class LaTeXTranslator(SphinxTranslator): is_inline = self.is_inline(node.parent) else: is_inline = self.is_inline(node) - if 'width' in attrs: - if 'scale' in attrs: - w = self.latex_image_length(attrs['width'], attrs['scale']) + if 'width' in node: + if 'scale' in node: + w = self.latex_image_length(node['width'], node['scale']) else: - w = self.latex_image_length(attrs['width']) + w = self.latex_image_length(node['width']) if w: include_graphics_options.append('width=%s' % w) - if 'height' in attrs: - if 'scale' in attrs: - h = self.latex_image_length(attrs['height'], attrs['scale']) + if 'height' in node: + if 'scale' in node: + h = self.latex_image_length(node['height'], node['scale']) else: - h = self.latex_image_length(attrs['height']) + h = self.latex_image_length(node['height']) if h: include_graphics_options.append('height=%s' % h) - if 'scale' in attrs: + if 'scale' in node: if not include_graphics_options: # if no "width" nor "height", \sphinxincludegraphics will fit # to the available text width if oversized after rescaling. include_graphics_options.append('scale=%s' - % (float(attrs['scale']) / 100.0)) - if 'align' in attrs: + % (float(node['scale']) / 100.0)) + if 'align' in node: align_prepost = { # By default latex aligns the top of an image. (1, 'top'): ('', ''), @@ -1249,8 +1259,8 @@ class LaTeXTranslator(SphinxTranslator): (0, 'right'): ('{\\hspace*{\\fill}', '}'), } try: - pre.append(align_prepost[is_inline, attrs['align']][0]) - post.append(align_prepost[is_inline, attrs['align']][1]) + pre.append(align_prepost[is_inline, node['align']][0]) + post.append(align_prepost[is_inline, node['align']][1]) except KeyError: pass if self.in_parsed_literal: @@ -1542,7 +1552,7 @@ class LaTeXTranslator(SphinxTranslator): id = self.curfilestack[-1] + ':' + uri[1:] self.body.append(self.hyperlink(id)) self.body.append(r'\emph{') - if self.builder.config.latex_show_pagerefs and not \ + if self.config.latex_show_pagerefs and not \ self.in_production_list: self.context.append('}}} (%s)' % self.hyperpageref(id)) else: @@ -1566,8 +1576,7 @@ class LaTeXTranslator(SphinxTranslator): self.body.append(r'\sphinxtermref{') else: self.body.append(r'\sphinxcrossref{') - if self.builder.config.latex_show_pagerefs and not \ - self.in_production_list: + if self.config.latex_show_pagerefs and not self.in_production_list: self.context.append('}}} (%s)' % self.hyperpageref(id)) else: self.context.append('}}}') @@ -1751,11 +1760,7 @@ class LaTeXTranslator(SphinxTranslator): linenos = node.get('linenos', False) highlight_args = node.get('highlight_args', {}) highlight_args['force'] = node.get('force', False) - if lang is self.builder.config.highlight_language: - # only pass highlighter options for original language - opts = self.builder.config.highlight_options - else: - opts = {} + opts = self.config.highlight_options.get(lang, {}) hlcode = self.highlighter.highlight_block( node.rawsource, lang, opts=opts, linenos=linenos, @@ -2017,12 +2022,12 @@ class LaTeXTranslator(SphinxTranslator): else: from sphinx.util.math import wrap_displaymath self.body.append(wrap_displaymath(node.astext(), label, - self.builder.config.math_number_all)) + self.config.math_number_all)) raise nodes.SkipNode def visit_math_reference(self, node: Element) -> None: label = "equation:%s:%s" % (node['docname'], node['target']) - eqref_format = self.builder.config.math_eqref_format + eqref_format = self.config.math_eqref_format if eqref_format: try: ref = r'\ref{%s}' % label @@ -2087,7 +2092,7 @@ class LaTeXTranslator(SphinxTranslator): warnings.warn('generate_numfig_format() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) ret = [] # type: List[str] - figure = self.builder.config.numfig_format['figure'].split('%s', 1) + figure = self.config.numfig_format['figure'].split('%s', 1) if len(figure) == 1: ret.append('\\def\\fnum@figure{%s}\n' % self.escape(figure[0]).strip()) else: @@ -2098,7 +2103,7 @@ class LaTeXTranslator(SphinxTranslator): self.escape(figure[1])) ret.append('\\makeatother\n') - table = self.builder.config.numfig_format['table'].split('%s', 1) + table = self.config.numfig_format['table'].split('%s', 1) if len(table) == 1: ret.append('\\def\\fnum@table{%s}\n' % self.escape(table[0]).strip()) else: @@ -2109,7 +2114,7 @@ class LaTeXTranslator(SphinxTranslator): self.escape(table[1])) ret.append('\\makeatother\n') - codeblock = self.builder.config.numfig_format['code-block'].split('%s', 1) + codeblock = self.config.numfig_format['code-block'].split('%s', 1) if len(codeblock) == 1: pass # FIXME else: diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index c8593c6e8..9ef429ba3 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -4,7 +4,7 @@ Manual page writer, extended for Sphinx custom nodes. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -296,8 +296,7 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator): if uri.startswith('mailto:') or uri.startswith('http:') or \ uri.startswith('https:') or uri.startswith('ftp:'): # if configured, put the URL after the link - if self.builder.config.man_show_urls and \ - node.astext() != uri: + if self.config.man_show_urls and node.astext() != uri: if uri.startswith('mailto:'): uri = uri[7:] self.body.extend([ diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 5e7f98de7..6518d10da 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -4,7 +4,7 @@ Custom docutils writer for Texinfo. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -233,12 +233,12 @@ class TexinfoTranslator(SphinxTranslator): 'author': self.settings.author, # if empty, use basename of input file 'filename': self.settings.texinfo_filename, - 'release': self.escape(self.builder.config.release), - 'project': self.escape(self.builder.config.project), - 'copyright': self.escape(self.builder.config.copyright), - 'date': self.escape(self.builder.config.today or - format_date(self.builder.config.today_fmt or _('%b %d, %Y'), - language=self.builder.config.language)) + 'release': self.escape(self.config.release), + 'project': self.escape(self.config.project), + 'copyright': self.escape(self.config.copyright), + 'date': self.escape(self.config.today or + format_date(self.config.today_fmt or _('%b %d, %Y'), + language=self.config.language)) }) # title title = self.settings.title # type: str @@ -434,7 +434,7 @@ class TexinfoTranslator(SphinxTranslator): self.add_menu_entries(entries) if (node_name != 'Top' or not self.node_menus[entries[0]] or - self.builder.config.texinfo_no_detailmenu): + self.config.texinfo_no_detailmenu): self.body.append('\n@end menu\n') return @@ -484,7 +484,7 @@ class TexinfoTranslator(SphinxTranslator): ret.append('@end menu\n') return ''.join(ret) - indices_config = self.builder.config.texinfo_domain_indices + indices_config = self.config.texinfo_domain_indices if indices_config: for domain in self.builder.env.domains.values(): for indexcls in domain.indices: @@ -739,7 +739,7 @@ class TexinfoTranslator(SphinxTranslator): else: uri = self.escape_arg(uri) name = self.escape_arg(name) - show_urls = self.builder.config.texinfo_show_urls + show_urls = self.config.texinfo_show_urls if self.in_footnote: show_urls = 'inline' if not name or uri == name: @@ -1206,11 +1206,10 @@ class TexinfoTranslator(SphinxTranslator): # ignore remote images return name, ext = path.splitext(uri) - attrs = node.attributes # width and height ignored in non-tex output - width = self.tex_image_length(attrs.get('width', '')) - height = self.tex_image_length(attrs.get('height', '')) - alt = self.escape_arg(attrs.get('alt', '')) + width = self.tex_image_length(node.get('width', '')) + height = self.tex_image_length(node.get('height', '')) + alt = self.escape_arg(node.get('alt', '')) filename = "%s-figures/%s" % (self.elements['filename'][:-5], name) # type: ignore self.body.append('\n@image{%s,%s,%s,%s,%s}\n' % (filename, width, height, alt, ext[1:])) @@ -1395,9 +1394,8 @@ class TexinfoTranslator(SphinxTranslator): # use the full name of the objtype for the category try: domain = self.builder.env.get_domain(node.parent['domain']) - primary = self.builder.config.primary_domain name = domain.get_type_name(domain.object_types[objtype], - primary == domain.name) + self.config.primary_domain == domain.name) except (KeyError, ExtensionError): name = objtype # by convention, the deffn category should be capitalized like a title diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index c110f80e8..c0ebe32a2 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -4,7 +4,7 @@ Custom docutils writer for plain text. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import math diff --git a/sphinx/writers/xml.py b/sphinx/writers/xml.py index d007898ba..19fa3c1ef 100644 --- a/sphinx/writers/xml.py +++ b/sphinx/writers/xml.py @@ -4,7 +4,7 @@ Docutils-native XML and pseudo-XML writers. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/conftest.py b/tests/conftest.py index bacd13013..5580f672b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ pytest config for sphinx/tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/ext_napoleon_pep526_data_google.py b/tests/ext_napoleon_pep526_data_google.py new file mode 100644 index 000000000..95e84f470 --- /dev/null +++ b/tests/ext_napoleon_pep526_data_google.py @@ -0,0 +1,18 @@ +""" +Test module for napoleon PEP 526 compatiblity with google style +""" + +module_level_var: int = 99 +"""This is an example module level variable""" + + +class PEP526GoogleClass: + """Sample class with PEP 526 annotations and google docstring + + Attributes: + attr1: Attr1 description. + attr2: Attr2 description. + """ + + attr1: int + attr2: str diff --git a/tests/ext_napoleon_pep526_data_numpy.py b/tests/ext_napoleon_pep526_data_numpy.py new file mode 100644 index 000000000..d13ba31fb --- /dev/null +++ b/tests/ext_napoleon_pep526_data_numpy.py @@ -0,0 +1,22 @@ +""" +Test module for napoleon PEP 526 compatiblity with numpy style +""" + +module_level_var: int = 99 +"""This is an example module level variable""" + + +class PEP526NumpyClass: + """ + Sample class with PEP 526 annotations and numpy docstring + + Attributes + ---------- + attr1: + Attr1 description + + attr2: + Attr2 description + """ + attr1: int + attr2: str diff --git a/tests/js/doctools.js b/tests/js/doctools.js index 54246f635..a28f3ceb3 100644 --- a/tests/js/doctools.js +++ b/tests/js/doctools.js @@ -12,6 +12,10 @@ describe('jQuery extensions', function() { expect(jQuery.urldecode(test_encoded_string)).toEqual(test_decoded_string); }); + it('+ should result in " "', function() { + expect(jQuery.urldecode('+')).toEqual(' '); + }); + }); describe('getQueryParameters', function() { diff --git a/tests/js/searchtools.js b/tests/js/searchtools.js new file mode 100644 index 000000000..007eeb7db --- /dev/null +++ b/tests/js/searchtools.js @@ -0,0 +1,32 @@ +describe('Basic html theme search', function() { + + describe('terms search', function() { + + it('should find "C++" when in index', function() { + index = { + docnames:["index"], + filenames:["index.rst"], + terms:{'c++':0}, + titles:["<no title>"], + titleterms:{} + } + Search.setIndex(index); + searchterms = ['c++']; + excluded = []; + terms = index.terms; + titleterms = index.titleterms; + + hits = [[ + "index", + "<no title>", + "", + null, + 2, + "index.rst" + ]]; + expect(Search.performTermsSearch(searchterms, excluded, terms, titleterms)).toEqual(hits); + }); + + }); + +}); diff --git a/tests/roots/test-changes/conf.py b/tests/roots/test-changes/conf.py index 9d6d39631..ec67b9c59 100644 --- a/tests/roots/test-changes/conf.py +++ b/tests/roots/test-changes/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- project = 'Sphinx ChangesBuilder tests' -copyright = '2007-2020 by the Sphinx team, see AUTHORS' +copyright = '2007-2021 by the Sphinx team, see AUTHORS' version = '0.6' release = '0.6alpha1' diff --git a/tests/roots/test-domain-c-intersphinx/conf.py b/tests/roots/test-domain-c-intersphinx/conf.py new file mode 100644 index 000000000..c176af775 --- /dev/null +++ b/tests/roots/test-domain-c-intersphinx/conf.py @@ -0,0 +1,4 @@ +exclude_patterns = ['_build'] +extensions = [ + 'sphinx.ext.intersphinx', +] diff --git a/tests/roots/test-domain-c-intersphinx/index.rst b/tests/roots/test-domain-c-intersphinx/index.rst new file mode 100644 index 000000000..5d6d3e098 --- /dev/null +++ b/tests/roots/test-domain-c-intersphinx/index.rst @@ -0,0 +1,62 @@ +.. c:member:: void __member = _member + + - :any:`_member` + - :c:member:`_member` + - :c:var:`_member` + - :c:data:`_member` + +.. c:member:: void __var = _var + + - :any:`_var` + - :c:member:`_var` + - :c:var:`_var` + - :c:data:`_var` + +.. c:member:: void __function = _function + + - :any:`_function` + - :c:func:`_function` + - :c:type:`_function` + +.. c:member:: void __macro = _macro + + - :any:`_macro` + - :c:macro:`_macro` + +.. c:type:: _struct __struct + struct _struct __structTagged + + - :any:`_struct` + - :c:struct:`_struct` + - :c:type:`_struct` + +.. c:type:: _union __union + union _union __unionTagged + + - :any:`_union` + - :c:union:`_union` + - :c:type:`_union` + +.. c:type:: _enum __enum + enum _enum __enumTagged + + - :any:`_enum` + - :c:enum:`_enum` + - :c:type:`_enum` + +.. c:member:: void __enumerator = _enumerator + + - :any:`_enumerator` + - :c:enumerator:`_enumerator` + +.. c:type:: _type __type + + - :any:`_type` + - :c:type:`_type` + +.. c:member:: void __functionParam = _functionParam.param + + - :any:`_functionParam.param` + - :c:member:`_functionParam.param` + - :c:var:`_functionParam.param` + - :c:data:`_functionParam.param` diff --git a/tests/roots/test-domain-c/ns_lookup.rst b/tests/roots/test-domain-c/ns_lookup.rst new file mode 100644 index 000000000..87f9d68e7 --- /dev/null +++ b/tests/roots/test-domain-c/ns_lookup.rst @@ -0,0 +1,13 @@ +.. c:namespace:: ns_lookup + +.. c:var:: int i + +.. c:function:: void f(int j) + + - :c:var:`i` + - :c:var:`j` + - :c:expr:`i` + - :c:expr:`j` + +- :c:var:`i` +- :c:expr:`i` diff --git a/tests/roots/test-domain-cpp-intersphinx/conf.py b/tests/roots/test-domain-cpp-intersphinx/conf.py new file mode 100644 index 000000000..c176af775 --- /dev/null +++ b/tests/roots/test-domain-cpp-intersphinx/conf.py @@ -0,0 +1,4 @@ +exclude_patterns = ['_build'] +extensions = [ + 'sphinx.ext.intersphinx', +] diff --git a/tests/roots/test-domain-cpp-intersphinx/index.rst b/tests/roots/test-domain-cpp-intersphinx/index.rst new file mode 100644 index 000000000..9ed9493ed --- /dev/null +++ b/tests/roots/test-domain-cpp-intersphinx/index.rst @@ -0,0 +1,112 @@ +.. cpp:type:: _class __class + + - :any:`_class` + - :cpp:any:`_class` + - :cpp:class:`_class` + - :cpp:struct:`_class` + - :cpp:type:`_class` + +.. cpp:type:: _struct __struct + + - :any:`_struct` + - :cpp:any:`_struct` + - :cpp:class:`_struct` + - :cpp:struct:`_struct` + - :cpp:type:`_struct` + +.. cpp:type:: _union __union + + - :any:`_union` + - :cpp:any:`_union` + - :cpp:union:`_union` + - :cpp:type:`_union` + +.. cpp:member:: void __function = _function + + - :any:`_function` + - :cpp:any:`_function` + - :cpp:func:`_function` + - :cpp:type:`_function` + +.. cpp:member:: void __member = _member + + - :any:`_member` + - :cpp:any:`_member` + - :cpp:member:`_member` + - :cpp:var:`_member` + +.. cpp:member:: void __var = _var + + - :any:`_var` + - :cpp:any:`_var` + - :cpp:member:`_var` + - :cpp:var:`_var` + +.. cpp:type:: _type __type + + - :any:`_type` + - :cpp:any:`_type` + - :cpp:type:`_type` + +.. cpp:function:: template<_concept T> void __concept() + + - :any:`_concept` + - :cpp:any:`_concept` + - :cpp:concept:`_concept` + +.. cpp:type:: _enum __enum + + - :any:`_enum` + - :cpp:any:`_enum` + - :cpp:enum:`_enum` + - :cpp:type:`_enum` + +.. cpp:type:: _enumStruct __enumStruct + + - :any:`_enumStruct` + - :cpp:any:`_enumStruct` + - :cpp:enum:`_enumStruct` + - :cpp:type:`_enumStruct` + +.. cpp:type:: _enumClass __enumClass + + - :any:`_enumClass` + - :cpp:any:`_enumClass` + - :cpp:enum:`_enumClass` + - :cpp:type:`_enumClass` + +.. cpp:member:: void __enumerator = _enumerator + + - :any:`_enumerator` + - :cpp:any:`_enumerator` + - :cpp:enumerator:`_enumerator` + +.. cpp:member:: void __scopedEnumerator = _enumStruct::_scopedEnumerator + + - :any:`_enumStruct::_scopedEnumerator` + - :cpp:any:`_enumStruct::_scopedEnumerator` + - :cpp:enumerator:`_enumStruct::_scopedEnumerator` + +.. cpp:member:: void __enumerator2 = _enum::_enumerator + + - :any:`_enum::_enumerator` + - :cpp:any:`_enum::_enumerator` + - :cpp:enumerator:`_enum::_enumerator` + +.. cpp:member:: void __functionParam = _functionParam::param + + - :any:`_functionParam::param` + - :cpp:any:`_functionParam::param` + - :cpp:member:`_functionParam::param` + - :cpp:var:`_functionParam::param` + +.. cpp:type:: _templateParam::TParam __templateParam + + - :any:`_templateParam::TParam` + - :cpp:any:`_templateParam::TParam` + - :cpp:type:`_templateParam::TParam` + - :cpp:member:`_templateParam::TParam` + - :cpp:var:`_templateParam::TParam` + - :cpp:class:`_templateParam::TParam` + - :cpp:struct:`_templateParam::TParam` + - :cpp:union:`_templateParam::TParam` diff --git a/tests/roots/test-domain-cpp/roles-targets-ok.rst b/tests/roots/test-domain-cpp/roles-targets-ok.rst index e70b9259f..783f7b985 100644 --- a/tests/roots/test-domain-cpp/roles-targets-ok.rst +++ b/tests/roots/test-domain-cpp/roles-targets-ok.rst @@ -123,37 +123,37 @@ :class:`TParamType` :struct:`TParamType` :union:`TParamType` - :func:`TParamType` + function :member:`TParamType` :var:`TParamType` :type:`TParamType` - :concept:`TParamType` - :enum:`TParamType` - :enumerator:`TParamType` + concept + enum + enumerator :cpp:any:`TParamVar` :class:`TParamVar` :struct:`TParamVar` :union:`TParamVar` - :func:`TParamVar` + function :member:`TParamVar` :var:`TParamVar` :type:`TParamVar` - :concept:`TParamVar` - :enum:`TParamVar` - :enumerator:`TParamVar` + concept + enum + enumerator :cpp:any:`TParamTemplate` :class:`TParamTemplate` :struct:`TParamTemplate` :union:`TParamTemplate` - :func:`TParamTemplate` + function :member:`TParamTemplate` :var:`TParamTemplate` :type:`TParamTemplate` - :concept:`TParamTemplate` - :enum:`TParamTemplate` - :enumerator:`TParamTemplate` + concept + enum + enumerator .. function:: void FunctionParams(int FunctionParam) diff --git a/tests/roots/test-domain-cpp/roles-targets-warn.rst b/tests/roots/test-domain-cpp/roles-targets-warn.rst index decebe170..57083ff15 100644 --- a/tests/roots/test-domain-cpp/roles-targets-warn.rst +++ b/tests/roots/test-domain-cpp/roles-targets-warn.rst @@ -114,35 +114,35 @@ class struct union - func + :func:`TParamType` member var type - concept - enum - enumerator + :concept:`TParamType` + :enum:`TParamType` + :enumerator:`TParamType` class struct union - func + :func:`TParamVar` member var type - concept - enum - enumerator + :concept:`TParamVar` + :enum:`TParamVar` + :enumerator:`TParamVar` class struct union - func + :func:`TParamTemplate` member var type - concept - enum - enumerator + :concept:`TParamTemplate` + :enum:`TParamTemplate` + :enumerator:`TParamTemplate` .. function:: void FunctionParams(int FunctionParam) diff --git a/tests/roots/test-domain-py/abbr.rst b/tests/roots/test-domain-py/abbr.rst new file mode 100644 index 000000000..67f11578b --- /dev/null +++ b/tests/roots/test-domain-py/abbr.rst @@ -0,0 +1,10 @@ +abbrev +====== + +.. currentmodule:: module_a.submodule + +* normal: :py:meth:`module_a.submodule.ModTopLevel.mod_child_1` +* relative: :py:meth:`.ModTopLevel.mod_child_1` +* short name: :py:meth:`~module_a.submodule.ModTopLevel.mod_child_1` +* relative + short name: :py:meth:`~.ModTopLevel.mod_child_1` +* short name + relative: :py:meth:`~.ModTopLevel.mod_child_1` diff --git a/tests/roots/test-ext-autodoc/target/annotations.py b/tests/roots/test-ext-autodoc/target/annotations.py index e9ff2f604..ef600e2af 100644 --- a/tests/roots/test-ext-autodoc/target/annotations.py +++ b/tests/roots/test-ext-autodoc/target/annotations.py @@ -7,6 +7,9 @@ myint = int #: docstring variable: myint +#: docstring +variable2 = None # type: myint + def sum(x: myint, y: myint) -> myint: """docstring""" @@ -32,4 +35,7 @@ class Foo: """docstring""" #: docstring - attr: myint + attr1: myint + + def __init__(self): + self.attr2: myint = None #: docstring diff --git a/tests/roots/test-ext-autodoc/target/classes.py b/tests/roots/test-ext-autodoc/target/classes.py index 7e7d7bcd3..a3b4c6477 100644 --- a/tests/roots/test-ext-autodoc/target/classes.py +++ b/tests/roots/test-ext-autodoc/target/classes.py @@ -27,3 +27,6 @@ class Qux: class Quux(List[Union[int, float]]): """A subclass of List[Union[int, float]]""" pass + + +Alias = Foo diff --git a/tests/roots/test-ext-autodoc/target/empty_all.py b/tests/roots/test-ext-autodoc/target/empty_all.py new file mode 100644 index 000000000..c094cff70 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/empty_all.py @@ -0,0 +1,16 @@ +""" +docsting of empty_all module. +""" +__all__ = [] + + +def foo(): + """docstring""" + + +def bar(): + """docstring""" + + +def baz(): + """docstring""" diff --git a/tests/roots/test-ext-autodoc/target/genericalias.py b/tests/roots/test-ext-autodoc/target/genericalias.py index d37bcb7fa..9909efca1 100644 --- a/tests/roots/test-ext-autodoc/target/genericalias.py +++ b/tests/roots/test-ext-autodoc/target/genericalias.py @@ -4,3 +4,8 @@ from typing import Callable, List T = List[int] C = Callable[[int], None] # a generic alias not having a doccomment + + +class Class: + #: A list of int + T = List[int] diff --git a/tests/roots/test-ext-autodoc/target/hide_value.py b/tests/roots/test-ext-autodoc/target/hide_value.py new file mode 100644 index 000000000..1d53aabe9 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/hide_value.py @@ -0,0 +1,19 @@ +#: docstring +#: +#: :meta hide-value: +SENTINEL1 = object() + +#: :meta hide-value: +SENTINEL2 = object() + + +class Foo: + """docstring""" + + #: docstring + #: + #: :meta hide-value: + SENTINEL1 = object() + + #: :meta hide-value: + SENTINEL2 = object() diff --git a/tests/roots/test-ext-autodoc/target/instance_variable.py b/tests/roots/test-ext-autodoc/target/instance_variable.py new file mode 100644 index 000000000..ae86d1edb --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/instance_variable.py @@ -0,0 +1,10 @@ +class Foo: + def __init__(self): + self.attr1 = None #: docstring foo + self.attr2 = None #: docstring foo + + +class Bar(Foo): + def __init__(self): + self.attr2 = None #: docstring bar + self.attr3 = None #: docstring bar diff --git a/tests/roots/test-ext-autodoc/target/need_mocks.py b/tests/roots/test-ext-autodoc/target/need_mocks.py index 275ce8d5f..a29954184 100644 --- a/tests/roots/test-ext-autodoc/target/need_mocks.py +++ b/tests/roots/test-ext-autodoc/target/need_mocks.py @@ -9,7 +9,7 @@ import sphinx.missing_module4 # NOQA from sphinx.missing_module4 import missing_name2 # NOQA -@missing_name +@missing_name(int) def decoratedFunction(): """decoratedFunction docstring""" return None @@ -28,4 +28,9 @@ class TestAutodoc(object): return None +class Inherited(missing_module.Class): + """docstring""" + pass + + sphinx.missing_module4.missing_function(len(missing_name2)) diff --git a/tests/roots/test-ext-autodoc/target/overload.py b/tests/roots/test-ext-autodoc/target/overload.py index cc4e509f2..1b395ee5b 100644 --- a/tests/roots/test-ext-autodoc/target/overload.py +++ b/tests/roots/test-ext-autodoc/target/overload.py @@ -2,21 +2,21 @@ from typing import Any, overload @overload -def sum(x: int, y: int) -> int: +def sum(x: int, y: int = 0) -> int: ... @overload -def sum(x: "float", y: "float") -> "float": +def sum(x: "float", y: "float" = 0.0) -> "float": ... @overload -def sum(x: str, y: str) -> str: +def sum(x: str, y: str = ...) -> str: ... -def sum(x, y): +def sum(x, y=None): """docstring""" return x + y @@ -25,18 +25,18 @@ class Math: """docstring""" @overload - def sum(self, x: int, y: int) -> int: + def sum(self, x: int, y: int = 0) -> int: ... @overload - def sum(self, x: "float", y: "float") -> "float": + def sum(self, x: "float", y: "float" = 0.0) -> "float": ... @overload - def sum(self, x: str, y: str) -> str: + def sum(self, x: str, y: str = ...) -> str: ... - def sum(self, x, y): + def sum(self, x, y=None): """docstring""" return x + y diff --git a/tests/roots/test-ext-autodoc/target/private.py b/tests/roots/test-ext-autodoc/target/private.py index a39ce085e..02d174863 100644 --- a/tests/roots/test-ext-autodoc/target/private.py +++ b/tests/roots/test-ext-autodoc/target/private.py @@ -9,3 +9,7 @@ def _public_function(name): :meta public: """ + + +PRIVATE_CONSTANT = None #: :meta private: +_PUBLIC_CONSTANT = None #: :meta public: diff --git a/tests/roots/test-ext-autodoc/target/slots.py b/tests/roots/test-ext-autodoc/target/slots.py index 144f97c95..32822fd38 100644 --- a/tests/roots/test-ext-autodoc/target/slots.py +++ b/tests/roots/test-ext-autodoc/target/slots.py @@ -1,8 +1,12 @@ class Foo: + """docstring""" + __slots__ = ['attr'] class Bar: + """docstring""" + __slots__ = {'attr1': 'docstring of attr1', 'attr2': 'docstring of attr2', 'attr3': None} @@ -12,4 +16,6 @@ class Bar: class Baz: + """docstring""" + __slots__ = 'attr' diff --git a/tests/roots/test-ext-autodoc/target/typed_vars.py b/tests/roots/test-ext-autodoc/target/typed_vars.py index ba9657f18..f909b80f1 100644 --- a/tests/roots/test-ext-autodoc/target/typed_vars.py +++ b/tests/roots/test-ext-autodoc/target/typed_vars.py @@ -29,3 +29,6 @@ class Class: class Derived(Class): attr7: int + + +Alias = Derived diff --git a/tests/roots/test-ext-autodoc/target/typevar.py b/tests/roots/test-ext-autodoc/target/typevar.py index ce531e8f3..864fea20c 100644 --- a/tests/roots/test-ext-autodoc/target/typevar.py +++ b/tests/roots/test-ext-autodoc/target/typevar.py @@ -19,7 +19,8 @@ T6 = NewType("T6", int) class Class: - # TODO: TypeVar + #: T1 + T1 = TypeVar("T1") #: T6 T6 = NewType("T6", int) diff --git a/tests/roots/test-ext-math/index.rst b/tests/roots/test-ext-math/index.rst index 4237b73ff..221284aeb 100644 --- a/tests/roots/test-ext-math/index.rst +++ b/tests/roots/test-ext-math/index.rst @@ -6,6 +6,7 @@ Test Math math page + nomath .. math:: a^2+b^2=c^2 diff --git a/tests/roots/test-ext-math/nomath.rst b/tests/roots/test-ext-math/nomath.rst new file mode 100644 index 000000000..e69de29bb diff --git a/tests/roots/test-highlight_options/conf.py b/tests/roots/test-highlight_options/conf.py new file mode 100644 index 000000000..90997d444 --- /dev/null +++ b/tests/roots/test-highlight_options/conf.py @@ -0,0 +1,4 @@ +highlight_options = { + 'default': {'default_option': True}, + 'python': {'python_option': True} +} diff --git a/tests/roots/test-highlight_options/index.rst b/tests/roots/test-highlight_options/index.rst new file mode 100644 index 000000000..389041ace --- /dev/null +++ b/tests/roots/test-highlight_options/index.rst @@ -0,0 +1,14 @@ +test-highlight_options +====================== + +.. code-block:: + + blah blah blah + +.. code-block:: python + + blah blah blah + +.. code-block:: java + + blah blah blah diff --git a/tests/roots/test-html_assets/conf.py b/tests/roots/test-html_assets/conf.py index ec53011c2..7f94bbbce 100644 --- a/tests/roots/test-html_assets/conf.py +++ b/tests/roots/test-html_assets/conf.py @@ -4,7 +4,9 @@ version = '1.4.4' html_static_path = ['static', 'subdir'] html_extra_path = ['extra', 'subdir'] html_css_files = ['css/style.css', - ('https://example.com/custom.css', {'title': 'title', 'media': 'print'})] + ('https://example.com/custom.css', + {'title': 'title', 'media': 'print', 'priority': 400})] html_js_files = ['js/custom.js', - ('https://example.com/script.js', {'async': 'async'})] + ('https://example.com/script.js', + {'async': 'async', 'priority': 400})] exclude_patterns = ['**/_build', '**/.htpasswd'] diff --git a/tests/roots/test-latex-equations/expects/latex-equations.tex b/tests/roots/test-latex-equations/expects/latex-equations.tex index ce07a0128..5374a6721 100644 --- a/tests/roots/test-latex-equations/expects/latex-equations.tex +++ b/tests/roots/test-latex-equations/expects/latex-equations.tex @@ -1,13 +1,18 @@ + +\sphinxAtStartPar Equation without a label. \begin{equation*} \begin{split}E = mc^2\end{split} \end{equation*} +\sphinxAtStartPar Equation with label. \begin{equation}\label{equation:equations:test} \begin{split}E = hv\end{split} \end{equation} +\sphinxAtStartPar Second equation without label. \begin{equation*} \begin{split}c^2 = a^2 + b^2\end{split} \end{equation*} +\sphinxAtStartPar Equation with label \eqref{equation:equations:test} is important. diff --git a/tests/roots/test-latex-table/complex.rst b/tests/roots/test-latex-table/complex.rst index f3f927a3e..fa84f8266 100644 --- a/tests/roots/test-latex-table/complex.rst +++ b/tests/roots/test-latex-table/complex.rst @@ -11,9 +11,9 @@ grid table +---------+ +---------+ | cell2-1 | | cell2-3 | + +---------+---------+ -| | cell3-2 | +| | cell3-2-par1 | +---------+ | -| cell4-1 | | +| cell4-1 | cell3-2-par2 | +---------+---------+---------+ | cell5-1 | +---------+---------+---------+ diff --git a/tests/roots/test-latex-table/expects/complex_spanning_cell.tex b/tests/roots/test-latex-table/expects/complex_spanning_cell.tex index 5d524c257..966f39a95 100644 --- a/tests/roots/test-latex-table/expects/complex_spanning_cell.tex +++ b/tests/roots/test-latex-table/expects/complex_spanning_cell.tex @@ -1,10 +1,13 @@ \label{\detokenize{complex:complex-spanning-cell}} +\sphinxAtStartPar table having … \begin{itemize} \item {} +\sphinxAtStartPar consecutive multirow at top of row (1\sphinxhyphen{}1 and 1\sphinxhyphen{}2) \item {} +\sphinxAtStartPar consecutive multirow at end of row (1\sphinxhyphen{}4 and 1\sphinxhyphen{}5) \end{itemize} @@ -16,26 +19,31 @@ consecutive multirow at end of row (1\sphinxhyphen{}4 and 1\sphinxhyphen{}5) \hline \sphinxmultirow{3}{1}{% \begin{varwidth}[t]{\sphinxcolwidth{1}{5}} +\sphinxAtStartPar cell1\sphinxhyphen{}1 \par \vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}% }% &\sphinxmultirow{3}{2}{% \begin{varwidth}[t]{\sphinxcolwidth{1}{5}} +\sphinxAtStartPar cell1\sphinxhyphen{}2 \par \vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}% }% & +\sphinxAtStartPar cell1\sphinxhyphen{}3 &\sphinxmultirow{3}{4}{% \begin{varwidth}[t]{\sphinxcolwidth{1}{5}} +\sphinxAtStartPar cell1\sphinxhyphen{}4 \par \vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}% }% &\sphinxmultirow{2}{5}{% \begin{varwidth}[t]{\sphinxcolwidth{1}{5}} +\sphinxAtStartPar cell1\sphinxhyphen{}5 \par \vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}% @@ -43,12 +51,14 @@ cell1\sphinxhyphen{}5 \\ \cline{3-3}\sphinxtablestrut{1}&\sphinxtablestrut{2}&\sphinxmultirow{2}{6}{% \begin{varwidth}[t]{\sphinxcolwidth{1}{5}} +\sphinxAtStartPar cell2\sphinxhyphen{}3 \par \vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}% }% &\sphinxtablestrut{4}&\sphinxtablestrut{5}\\ \cline{5-5}\sphinxtablestrut{1}&\sphinxtablestrut{2}&\sphinxtablestrut{6}&\sphinxtablestrut{4}& +\sphinxAtStartPar cell3\sphinxhyphen{}5 \\ \hline diff --git a/tests/roots/test-latex-table/expects/gridtable.tex b/tests/roots/test-latex-table/expects/gridtable.tex index 28b0b086b..a71c9e202 100644 --- a/tests/roots/test-latex-table/expects/gridtable.tex +++ b/tests/roots/test-latex-table/expects/gridtable.tex @@ -5,46 +5,60 @@ \begin{tabulary}{\linewidth}[t]{|T|T|T|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 &\sphinxstyletheadfamily +\sphinxAtStartPar header3 \\ \hline +\sphinxAtStartPar cell1\sphinxhyphen{}1 &\sphinxmultirow{2}{5}{% \begin{varwidth}[t]{\sphinxcolwidth{1}{3}} +\sphinxAtStartPar cell1\sphinxhyphen{}2 \par \vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}% }% & +\sphinxAtStartPar cell1\sphinxhyphen{}3 \\ \cline{1-1}\cline{3-3}\sphinxmultirow{2}{7}{% \begin{varwidth}[t]{\sphinxcolwidth{1}{3}} +\sphinxAtStartPar cell2\sphinxhyphen{}1 \par \vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}% }% &\sphinxtablestrut{5}& +\sphinxAtStartPar cell2\sphinxhyphen{}3 \\ \cline{2-3}\sphinxtablestrut{7}&\sphinxstartmulticolumn{2}% \sphinxmultirow{2}{9}{% \begin{varwidth}[t]{\sphinxcolwidth{2}{3}} -cell3\sphinxhyphen{}2 +\sphinxAtStartPar +cell3\sphinxhyphen{}2\sphinxhyphen{}par1 + +\sphinxAtStartPar +cell3\sphinxhyphen{}2\sphinxhyphen{}par2 \par \vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}% }% \sphinxstopmulticolumn \\ \cline{1-1} +\sphinxAtStartPar cell4\sphinxhyphen{}1 &\multicolumn{2}{l|}{\sphinxtablestrut{9}}\\ \hline\sphinxstartmulticolumn{3}% \begin{varwidth}[t]{\sphinxcolwidth{3}{3}} +\sphinxAtStartPar cell5\sphinxhyphen{}1 \par \vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}% diff --git a/tests/roots/test-latex-table/expects/longtable.tex b/tests/roots/test-latex-table/expects/longtable.tex index 9febfcef5..e2138ad58 100644 --- a/tests/roots/test-latex-table/expects/longtable.tex +++ b/tests/roots/test-latex-table/expects/longtable.tex @@ -3,8 +3,10 @@ \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|l|l|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -14,8 +16,10 @@ header2 {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} \textendash{} continued from previous page}}}\\ \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -27,18 +31,24 @@ header2 \endlastfoot +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/longtable_having_align.tex b/tests/roots/test-latex-table/expects/longtable_having_align.tex index 1969e19d2..764ef55f3 100644 --- a/tests/roots/test-latex-table/expects/longtable_having_align.tex +++ b/tests/roots/test-latex-table/expects/longtable_having_align.tex @@ -3,8 +3,10 @@ \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[r]{|l|l|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -14,8 +16,10 @@ header2 {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} \textendash{} continued from previous page}}}\\ \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -27,18 +31,24 @@ header2 \endlastfoot +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/longtable_having_caption.tex b/tests/roots/test-latex-table/expects/longtable_having_caption.tex index f0041e9ec..0ca5506be 100644 --- a/tests/roots/test-latex-table/expects/longtable_having_caption.tex +++ b/tests/roots/test-latex-table/expects/longtable_having_caption.tex @@ -5,8 +5,10 @@ \caption{caption for longtable\strut}\label{\detokenize{longtable:id1}}\\*[\sphinxlongtablecapskipadjust] \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -16,8 +18,10 @@ header2 {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} \textendash{} continued from previous page}}}\\ \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -29,18 +33,24 @@ header2 \endlastfoot +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/longtable_having_problematic_cell.tex b/tests/roots/test-latex-table/expects/longtable_having_problematic_cell.tex index 050527b69..9551a0a3b 100644 --- a/tests/roots/test-latex-table/expects/longtable_having_problematic_cell.tex +++ b/tests/roots/test-latex-table/expects/longtable_having_problematic_cell.tex @@ -3,8 +3,10 @@ \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|*{2}{\X{1}{2}|}} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -14,8 +16,10 @@ header2 {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} \textendash{} continued from previous page}}}\\ \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -28,23 +32,30 @@ header2 \endlastfoot \begin{itemize} \item {} +\sphinxAtStartPar item1 \item {} +\sphinxAtStartPar item2 \end{itemize} & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/longtable_having_stub_columns_and_problematic_cell.tex b/tests/roots/test-latex-table/expects/longtable_having_stub_columns_and_problematic_cell.tex index 68e74c5f4..e54f8acec 100644 --- a/tests/roots/test-latex-table/expects/longtable_having_stub_columns_and_problematic_cell.tex +++ b/tests/roots/test-latex-table/expects/longtable_having_stub_columns_and_problematic_cell.tex @@ -3,10 +3,13 @@ \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|*{3}{\X{1}{3}|}} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 &\sphinxstyletheadfamily +\sphinxAtStartPar header3 \\ \hline @@ -16,10 +19,13 @@ header3 {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} \textendash{} continued from previous page}}}\\ \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 &\sphinxstyletheadfamily +\sphinxAtStartPar header3 \\ \hline @@ -32,22 +38,29 @@ header3 \endlastfoot \sphinxstyletheadfamily \begin{itemize} \item {} +\sphinxAtStartPar instub1\sphinxhyphen{}1a \item {} +\sphinxAtStartPar instub1\sphinxhyphen{}1b \end{itemize} &\sphinxstyletheadfamily +\sphinxAtStartPar instub1\sphinxhyphen{}2 & +\sphinxAtStartPar notinstub1\sphinxhyphen{}3 \\ \hline\sphinxstyletheadfamily +\sphinxAtStartPar cell2\sphinxhyphen{}1 &\sphinxstyletheadfamily +\sphinxAtStartPar cell2\sphinxhyphen{}2 & +\sphinxAtStartPar cell2\sphinxhyphen{}3 \\ \hline diff --git a/tests/roots/test-latex-table/expects/longtable_having_verbatim.tex b/tests/roots/test-latex-table/expects/longtable_having_verbatim.tex index c7213b906..a0e7ecfcd 100644 --- a/tests/roots/test-latex-table/expects/longtable_having_verbatim.tex +++ b/tests/roots/test-latex-table/expects/longtable_having_verbatim.tex @@ -3,8 +3,10 @@ \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|*{2}{\X{1}{2}|}} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -14,8 +16,10 @@ header2 {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} \textendash{} continued from previous page}}}\\ \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -31,16 +35,21 @@ header2 \PYG{n}{hello} \PYG{n}{world} \end{sphinxVerbatimintable} & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/longtable_having_widths.tex b/tests/roots/test-latex-table/expects/longtable_having_widths.tex index 884fd9f8a..cdd0e7a2b 100644 --- a/tests/roots/test-latex-table/expects/longtable_having_widths.tex +++ b/tests/roots/test-latex-table/expects/longtable_having_widths.tex @@ -3,8 +3,10 @@ \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|\X{30}{100}|\X{70}{100}|} \hline\noalign{\phantomsection\label{\detokenize{longtable:namedlongtable}}\label{\detokenize{longtable:mylongtable}}}% \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -14,8 +16,10 @@ header2 {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} \textendash{} continued from previous page}}}\\ \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -27,21 +31,28 @@ header2 \endlastfoot +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline \end{longtable}\sphinxatlongtableend\end{savenotes} +\sphinxAtStartPar See {\hyperref[\detokenize{longtable:mylongtable}]{\sphinxcrossref{mylongtable}}}, same as {\hyperref[\detokenize{longtable:namedlongtable}]{\sphinxcrossref{\DUrole{std,std-ref}{this one}}}}. diff --git a/tests/roots/test-latex-table/expects/longtable_having_widths_and_problematic_cell.tex b/tests/roots/test-latex-table/expects/longtable_having_widths_and_problematic_cell.tex index 17c5ec4cc..ea868ffe4 100644 --- a/tests/roots/test-latex-table/expects/longtable_having_widths_and_problematic_cell.tex +++ b/tests/roots/test-latex-table/expects/longtable_having_widths_and_problematic_cell.tex @@ -3,8 +3,10 @@ \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|\X{30}{100}|\X{70}{100}|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -14,8 +16,10 @@ header2 {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} \textendash{} continued from previous page}}}\\ \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -28,23 +32,30 @@ header2 \endlastfoot \begin{itemize} \item {} +\sphinxAtStartPar item1 \item {} +\sphinxAtStartPar item2 \end{itemize} & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/longtable_with_tabularcolumn.tex b/tests/roots/test-latex-table/expects/longtable_with_tabularcolumn.tex index 2fbbbc4ef..426086de5 100644 --- a/tests/roots/test-latex-table/expects/longtable_with_tabularcolumn.tex +++ b/tests/roots/test-latex-table/expects/longtable_with_tabularcolumn.tex @@ -3,8 +3,10 @@ \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|c|c|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -14,8 +16,10 @@ header2 {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} \textendash{} continued from previous page}}}\\ \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -27,18 +31,24 @@ header2 \endlastfoot +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/simple_table.tex b/tests/roots/test-latex-table/expects/simple_table.tex index 8044a6cc4..a06bfb1cf 100644 --- a/tests/roots/test-latex-table/expects/simple_table.tex +++ b/tests/roots/test-latex-table/expects/simple_table.tex @@ -5,23 +5,31 @@ \begin{tabulary}{\linewidth}[t]{|T|T|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/table_having_caption.tex b/tests/roots/test-latex-table/expects/table_having_caption.tex index d4423a05d..33a5f1d8f 100644 --- a/tests/roots/test-latex-table/expects/table_having_caption.tex +++ b/tests/roots/test-latex-table/expects/table_having_caption.tex @@ -9,23 +9,31 @@ \begin{tabulary}{\linewidth}[t]{|T|T|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/table_having_problematic_cell.tex b/tests/roots/test-latex-table/expects/table_having_problematic_cell.tex index 7a9b0f293..c5c57e2f7 100644 --- a/tests/roots/test-latex-table/expects/table_having_problematic_cell.tex +++ b/tests/roots/test-latex-table/expects/table_having_problematic_cell.tex @@ -5,29 +5,38 @@ \begin{tabular}[t]{|*{2}{\X{1}{2}|}} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline\begin{itemize} \item {} +\sphinxAtStartPar item1 \item {} +\sphinxAtStartPar item2 \end{itemize} & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/table_having_stub_columns_and_problematic_cell.tex b/tests/roots/test-latex-table/expects/table_having_stub_columns_and_problematic_cell.tex index 700fc4663..13c48a213 100644 --- a/tests/roots/test-latex-table/expects/table_having_stub_columns_and_problematic_cell.tex +++ b/tests/roots/test-latex-table/expects/table_having_stub_columns_and_problematic_cell.tex @@ -5,30 +5,40 @@ \begin{tabular}[t]{|*{3}{\X{1}{3}|}} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 &\sphinxstyletheadfamily +\sphinxAtStartPar header3 \\ \hline\sphinxstyletheadfamily \begin{itemize} \item {} +\sphinxAtStartPar instub1\sphinxhyphen{}1a \item {} +\sphinxAtStartPar instub1\sphinxhyphen{}1b \end{itemize} &\sphinxstyletheadfamily +\sphinxAtStartPar instub1\sphinxhyphen{}2 & +\sphinxAtStartPar notinstub1\sphinxhyphen{}3 \\ \hline\sphinxstyletheadfamily +\sphinxAtStartPar cell2\sphinxhyphen{}1 &\sphinxstyletheadfamily +\sphinxAtStartPar cell2\sphinxhyphen{}2 & +\sphinxAtStartPar cell2\sphinxhyphen{}3 \\ \hline diff --git a/tests/roots/test-latex-table/expects/table_having_threeparagraphs_cell_in_first_col.tex b/tests/roots/test-latex-table/expects/table_having_threeparagraphs_cell_in_first_col.tex index 6d3e81021..c1a440558 100644 --- a/tests/roots/test-latex-table/expects/table_having_threeparagraphs_cell_in_first_col.tex +++ b/tests/roots/test-latex-table/expects/table_having_threeparagraphs_cell_in_first_col.tex @@ -5,13 +5,17 @@ \begin{tabulary}{\linewidth}[t]{|T|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 \\ \hline +\sphinxAtStartPar cell1\sphinxhyphen{}1\sphinxhyphen{}par1 +\sphinxAtStartPar cell1\sphinxhyphen{}1\sphinxhyphen{}par2 +\sphinxAtStartPar cell1\sphinxhyphen{}1\sphinxhyphen{}par3 \\ \hline diff --git a/tests/roots/test-latex-table/expects/table_having_verbatim.tex b/tests/roots/test-latex-table/expects/table_having_verbatim.tex index f66bb8001..23faac55e 100644 --- a/tests/roots/test-latex-table/expects/table_having_verbatim.tex +++ b/tests/roots/test-latex-table/expects/table_having_verbatim.tex @@ -5,8 +5,10 @@ \begin{tabular}[t]{|*{2}{\X{1}{2}|}} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline @@ -14,16 +16,21 @@ header2 \PYG{n}{hello} \PYG{n}{world} \end{sphinxVerbatimintable} & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/table_having_widths.tex b/tests/roots/test-latex-table/expects/table_having_widths.tex index 094596bec..d01a40576 100644 --- a/tests/roots/test-latex-table/expects/table_having_widths.tex +++ b/tests/roots/test-latex-table/expects/table_having_widths.tex @@ -6,23 +6,31 @@ \begin{tabular}[t]{|\X{30}{100}|\X{70}{100}|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline @@ -30,4 +38,5 @@ cell3\sphinxhyphen{}2 \par \sphinxattableend\end{savenotes} +\sphinxAtStartPar See {\hyperref[\detokenize{tabular:mytabular}]{\sphinxcrossref{\DUrole{std,std-ref}{this}}}}, same as {\hyperref[\detokenize{tabular:namedtabular}]{\sphinxcrossref{namedtabular}}}. diff --git a/tests/roots/test-latex-table/expects/table_having_widths_and_problematic_cell.tex b/tests/roots/test-latex-table/expects/table_having_widths_and_problematic_cell.tex index a636b022e..ca6b697e5 100644 --- a/tests/roots/test-latex-table/expects/table_having_widths_and_problematic_cell.tex +++ b/tests/roots/test-latex-table/expects/table_having_widths_and_problematic_cell.tex @@ -5,29 +5,38 @@ \begin{tabular}[t]{|\X{30}{100}|\X{70}{100}|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline\begin{itemize} \item {} +\sphinxAtStartPar item1 \item {} +\sphinxAtStartPar item2 \end{itemize} & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/tabular_having_widths.tex b/tests/roots/test-latex-table/expects/tabular_having_widths.tex index 5ee1542d4..596ba4868 100644 --- a/tests/roots/test-latex-table/expects/tabular_having_widths.tex +++ b/tests/roots/test-latex-table/expects/tabular_having_widths.tex @@ -5,23 +5,31 @@ \begin{tabular}[t]{|\X{30}{100}|\X{70}{100}|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/tabularcolumn.tex b/tests/roots/test-latex-table/expects/tabularcolumn.tex index 02e9af440..c020e0cb4 100644 --- a/tests/roots/test-latex-table/expects/tabularcolumn.tex +++ b/tests/roots/test-latex-table/expects/tabularcolumn.tex @@ -5,23 +5,31 @@ \begin{tabulary}{\linewidth}[t]{|c|c|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/roots/test-latex-table/expects/tabulary_having_widths.tex b/tests/roots/test-latex-table/expects/tabulary_having_widths.tex index 06d347fa3..0b42fb0cf 100644 --- a/tests/roots/test-latex-table/expects/tabulary_having_widths.tex +++ b/tests/roots/test-latex-table/expects/tabulary_having_widths.tex @@ -5,23 +5,31 @@ \begin{tabulary}{\linewidth}[t]{|T|T|} \hline \sphinxstyletheadfamily +\sphinxAtStartPar header1 &\sphinxstyletheadfamily +\sphinxAtStartPar header2 \\ \hline +\sphinxAtStartPar cell1\sphinxhyphen{}1 & +\sphinxAtStartPar cell1\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell2\sphinxhyphen{}1 & +\sphinxAtStartPar cell2\sphinxhyphen{}2 \\ \hline +\sphinxAtStartPar cell3\sphinxhyphen{}1 & +\sphinxAtStartPar cell3\sphinxhyphen{}2 \\ \hline diff --git a/tests/test_api_translator.py b/tests/test_api_translator.py index 47895a675..fddcd9d65 100644 --- a/tests/test_api_translator.py +++ b/tests/test_api_translator.py @@ -4,7 +4,7 @@ Test the Sphinx API for translator. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_application.py b/tests/test_application.py index a089a4bc0..94ddd04cf 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -4,7 +4,7 @@ Test the Sphinx class. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_build.py b/tests/test_build.py index 9dcf78165..62de3ea5f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -4,7 +4,7 @@ Test all builders. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -36,7 +36,11 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir): if not srcdir.exists(): (rootdir / 'test-root').copytree(srcdir) except UnicodeEncodeError: + # Now Python 3.7+ follows PEP-540 and uses utf-8 encoding for filesystem by default. + # So this error handling will be no longer used (after dropping python 3.6 support). srcdir = basedir / 'all' + if not srcdir.exists(): + (rootdir / 'test-root').copytree(srcdir) else: # add a doc with a non-ASCII file name to the source dir (srcdir / (test_name + '.txt')).write_text(dedent(""" diff --git a/tests/test_build_changes.py b/tests/test_build_changes.py index 3aedd03f5..c6cbd497d 100644 --- a/tests/test_build_changes.py +++ b/tests/test_build_changes.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- """ test_build_changes ~~~~~~~~~~~~~~~~~~ Test the ChangesBuilder class. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_build_dirhtml.py b/tests/test_build_dirhtml.py index e89e6888d..086b8426b 100644 --- a/tests/test_build_dirhtml.py +++ b/tests/test_build_dirhtml.py @@ -4,7 +4,7 @@ Test dirhtml builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_build_epub.py b/tests/test_build_epub.py index 470414915..982f4c1b9 100644 --- a/tests/test_build_epub.py +++ b/tests/test_build_epub.py @@ -4,7 +4,7 @@ Test the HTML builder and check output against XPath. - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 08fda9644..92ec384b5 100644 --- a/tests/test_build_gettext.py +++ b/tests/test_build_gettext.py @@ -4,7 +4,7 @@ Test the build process with gettext builder with the test root. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_build_html.py b/tests/test_build_html.py index aba9f63b5..8d02f5bed 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -4,7 +4,7 @@ Test the HTML builder and check output against XPath. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -12,6 +12,7 @@ import os import re from distutils.version import LooseVersion from itertools import chain, cycle +from unittest.mock import ANY, call, patch import pygments import pytest @@ -253,6 +254,7 @@ def test_html4_output(app, status, warning): (".//p[@class='centered']/strong", 'LICENSE'), # a glossary (".//dl/dt[@id='term-boson']", 'boson'), + (".//dl/dt[@id='term-boson']/a", '¶'), # a production list (".//pre/strong", 'try_stmt'), (".//pre/a[@href='#grammar-token-try1_stmt']/code/span", 'try1_stmt'), @@ -662,7 +664,7 @@ def test_numfig_without_numbered_toctree_warn(app, warning): warnings = warning.getvalue() assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings - assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings + assert 'index.rst:55: WARNING: Failed to create a cross reference. Any number is not assigned: index' in warnings assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings @@ -770,7 +772,7 @@ def test_numfig_with_numbered_toctree_warn(app, warning): app.build() warnings = warning.getvalue() assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings - assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings + assert 'index.rst:55: WARNING: Failed to create a cross reference. Any number is not assigned: index' in warnings assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings @@ -875,7 +877,7 @@ def test_numfig_with_prefix_warn(app, warning): app.build() warnings = warning.getvalue() assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings - assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings + assert 'index.rst:55: WARNING: Failed to create a cross reference. Any number is not assigned: index' in warnings assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings @@ -981,7 +983,7 @@ def test_numfig_with_secnum_depth_warn(app, warning): app.build() warnings = warning.getvalue() assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings - assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings + assert 'index.rst:55: WARNING: Failed to create a cross reference. Any number is not assigned: index' in warnings assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings @@ -1230,6 +1232,35 @@ def test_html_assets(app): '' in content) +@pytest.mark.sphinx('html', testroot='html_assets') +def test_assets_order(app): + app.add_css_file('normal.css') + app.add_css_file('early.css', priority=100) + app.add_css_file('late.css', priority=750) + app.add_css_file('lazy.css', priority=900) + app.add_js_file('normal.js') + app.add_js_file('early.js', priority=100) + app.add_js_file('late.js', priority=750) + app.add_js_file('lazy.js', priority=900) + + app.builder.build_all() + content = (app.outdir / 'index.html').read_text() + + # css_files + expected = ['_static/pygments.css', '_static/alabaster.css', '_static/early.css', + 'https://example.com/custom.css', '_static/normal.css', '_static/late.css', + '_static/css/style.css', '_static/lazy.css'] + pattern = '.*'.join('href="%s"' % f for f in expected) + assert re.search(pattern, content, re.S) + + # js_files + expected = ['_static/early.js', '_static/jquery.js', '_static/underscore.js', + '_static/doctools.js', 'https://example.com/script.js', '_static/normal.js', + '_static/late.js', '_static/js/custom.js', '_static/lazy.js'] + pattern = '.*'.join('src="%s"' % f for f in expected) + assert re.search(pattern, content, re.S) + + @pytest.mark.sphinx('html', testroot='basic', confoverrides={'html_copy_source': False}) def test_html_copy_source(app): app.builder.build_all() @@ -1604,3 +1635,56 @@ def test_html_codeblock_linenos_style_inline(app): assert '1' in content else: assert '1 ' in content + + +@pytest.mark.sphinx('html', testroot='highlight_options') +def test_highlight_options(app): + subject = app.builder.highlighter + with patch.object(subject, 'highlight_block', wraps=subject.highlight_block) as highlight: + app.build() + + call_args = highlight.call_args_list + assert len(call_args) == 3 + assert call_args[0] == call(ANY, 'default', force=False, linenos=False, + location=ANY, opts={'default_option': True}) + assert call_args[1] == call(ANY, 'python', force=False, linenos=False, + location=ANY, opts={'python_option': True}) + assert call_args[2] == call(ANY, 'java', force=False, linenos=False, + location=ANY, opts={}) + + +@pytest.mark.sphinx('html', testroot='highlight_options', + confoverrides={'highlight_options': {'default_option': True}}) +def test_highlight_options_old(app): + subject = app.builder.highlighter + with patch.object(subject, 'highlight_block', wraps=subject.highlight_block) as highlight: + app.build() + + call_args = highlight.call_args_list + assert len(call_args) == 3 + assert call_args[0] == call(ANY, 'default', force=False, linenos=False, + location=ANY, opts={'default_option': True}) + assert call_args[1] == call(ANY, 'python', force=False, linenos=False, + location=ANY, opts={}) + assert call_args[2] == call(ANY, 'java', force=False, linenos=False, + location=ANY, opts={}) + + +@pytest.mark.sphinx('html', testroot='basic', + confoverrides={'html_permalinks': False}) +def test_html_permalink_disable(app): + app.build() + content = (app.outdir / 'index.html').read_text() + + assert '

    The basic Sphinx documentation for testing

    ' in content + + +@pytest.mark.sphinx('html', testroot='basic', + confoverrides={'html_permalinks_icon': '[PERMALINK]'}) +def test_html_permalink_icon(app): + app.build() + content = (app.outdir / 'index.html').read_text() + + assert ('

    The basic Sphinx documentation for testing[PERMALINK]

    ' in content) diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 69ffa36e0..a67871f60 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -4,7 +4,7 @@ Test the build process with LaTeX builder with the test root. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -730,13 +730,14 @@ def test_footnote(app, status, warning): '\\end{footnote}') in result assert '\\begin{footnote}[3]\\sphinxAtStartFootnote\nnamed\n%\n\\end{footnote}' in result assert '\\sphinxcite{footnote:bar}' in result - assert ('\\bibitem[bar]{footnote:bar}\ncite\n') in result + assert ('\\bibitem[bar]{footnote:bar}\n\\sphinxAtStartPar\ncite\n') in result assert '\\sphinxcaption{Table caption \\sphinxfootnotemark[4]' in result assert ('\\hline%\n\\begin{footnotetext}[4]\\sphinxAtStartFootnote\n' 'footnote in table caption\n%\n\\end{footnotetext}\\ignorespaces %\n' '\\begin{footnotetext}[5]\\sphinxAtStartFootnote\n' - 'footnote in table header\n%\n\\end{footnotetext}\\ignorespaces \n' - 'VIDIOC\\_CROPCAP\n&\n') in result + 'footnote in table header\n%\n\\end{footnotetext}\\ignorespaces ' + '\n\\sphinxAtStartPar\n' + 'VIDIOC\\_CROPCAP\n&\n\\sphinxAtStartPar\n') in result assert ('Information about VIDIOC\\_CROPCAP %\n' '\\begin{footnote}[6]\\sphinxAtStartFootnote\n' 'footnote in table not in header\n%\n\\end{footnote}\n\\\\\n\\hline\n' @@ -776,7 +777,7 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning): assert ('This is a reference to the code\\sphinxhyphen{}block in the footnote:\n' '{\\hyperref[\\detokenize{index:codeblockinfootnote}]' '{\\sphinxcrossref{\\DUrole{std,std-ref}{I am in a footnote}}}}') in result - assert ('&\nThis is one more footnote with some code in it %\n' + assert ('&\n\\sphinxAtStartPar\nThis is one more footnote with some code in it %\n' '\\begin{footnote}[11]\\sphinxAtStartFootnote\n' 'Third footnote in longtable\n') in result assert ('\\end{sphinxVerbatim}\n%\n\\end{footnote}.\n') in result @@ -816,13 +817,15 @@ def test_latex_show_urls_is_inline(app, status, warning): assert ('\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde} ' '(http://sphinx\\sphinxhyphen{}doc.org/\\textasciitilde{}test/)') in result assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{URL in term} ' - '(http://sphinx\\sphinxhyphen{}doc.org/)}] \\leavevmode\nDescription' in result) + '(http://sphinx\\sphinxhyphen{}doc.org/)}] ' + '\\leavevmode\n\\sphinxAtStartPar\nDescription' in result) assert ('\\item[{Footnote in term \\sphinxfootnotemark[6]}] ' '\\leavevmode%\n\\begin{footnotetext}[6]\\sphinxAtStartFootnote\n' - 'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces \n' - 'Description') in result + 'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces ' + '\n\\sphinxAtStartPar\nDescription') in result assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist} ' - '(http://sphinx\\sphinxhyphen{}doc.org/)}] \\leavevmode\nDescription') in result + '(http://sphinx\\sphinxhyphen{}doc.org/)}] ' + '\\leavevmode\n\\sphinxAtStartPar\nDescription') in result assert '\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result assert ('\\sphinxhref{mailto:sphinx-dev@googlegroups.com}' '{sphinx\\sphinxhyphen{}dev@googlegroups.com}') in result @@ -867,16 +870,16 @@ def test_latex_show_urls_is_footnote(app, status, warning): '{URL in term}\\sphinxfootnotemark[9]}] ' '\\leavevmode%\n\\begin{footnotetext}[9]\\sphinxAtStartFootnote\n' '\\sphinxnolinkurl{http://sphinx-doc.org/}\n%\n' - '\\end{footnotetext}\\ignorespaces \nDescription') in result + '\\end{footnotetext}\\ignorespaces \n\\sphinxAtStartPar\nDescription') in result assert ('\\item[{Footnote in term \\sphinxfootnotemark[11]}] ' '\\leavevmode%\n\\begin{footnotetext}[11]\\sphinxAtStartFootnote\n' - 'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces \n' - 'Description') in result + 'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces ' + '\n\\sphinxAtStartPar\nDescription') in result assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist}' '\\sphinxfootnotemark[10]}] ' '\\leavevmode%\n\\begin{footnotetext}[10]\\sphinxAtStartFootnote\n' '\\sphinxnolinkurl{http://sphinx-doc.org/}\n%\n' - '\\end{footnotetext}\\ignorespaces \nDescription') in result + '\\end{footnotetext}\\ignorespaces \n\\sphinxAtStartPar\nDescription') in result assert ('\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result) assert ('\\sphinxhref{mailto:sphinx-dev@googlegroups.com}' '{sphinx\\sphinxhyphen{}dev@googlegroups.com}\n') in result @@ -913,13 +916,13 @@ def test_latex_show_urls_is_no(app, status, warning): 'Footnote inside footnote\n%\n\\end{footnotetext}\\ignorespaces') in result assert '\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde}' in result assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{URL in term}}] ' - '\\leavevmode\nDescription') in result + '\\leavevmode\n\\sphinxAtStartPar\nDescription') in result assert ('\\item[{Footnote in term \\sphinxfootnotemark[6]}] ' '\\leavevmode%\n\\begin{footnotetext}[6]\\sphinxAtStartFootnote\n' - 'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces \n' - 'Description') in result + 'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces ' + '\n\\sphinxAtStartPar\nDescription') in result assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist}}] ' - '\\leavevmode\nDescription') in result + '\\leavevmode\n\\sphinxAtStartPar\nDescription') in result assert ('\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result) assert ('\\sphinxhref{mailto:sphinx-dev@googlegroups.com}' '{sphinx\\sphinxhyphen{}dev@googlegroups.com}\n') in result @@ -1354,7 +1357,7 @@ def test_latex_index(app, status, warning): '\\index{equation@\\spxentry{equation}}equation:\n' in result) assert ('\n\\index{Einstein@\\spxentry{Einstein}}' '\\index{relativity@\\spxentry{relativity}}' - '\\ignorespaces \nand') in result + '\\ignorespaces \n\\sphinxAtStartPar\nand') in result assert ('\n\\index{main \\sphinxleftcurlybrace{}@\\spxentry{' 'main \\sphinxleftcurlybrace{}}}\\ignorespaces ' in result) @@ -1403,7 +1406,7 @@ def test_latex_thebibliography(app, status, warning): result = (app.outdir / 'python.tex').read_text() print(result) assert ('\\begin{sphinxthebibliography}{AuthorYe}\n' - '\\bibitem[AuthorYear]{index:authoryear}\n' + '\\bibitem[AuthorYear]{index:authoryear}\n\\sphinxAtStartPar\n' 'Author, Title, Year\n' '\\end{sphinxthebibliography}\n' in result) assert '\\sphinxcite{index:authoryear}' in result @@ -1447,7 +1450,8 @@ def test_latex_labels(app, status, warning): r'\end{figure}' in result) assert (r'\caption{labeled figure}' '\\label{\\detokenize{index:figure3}}\n' - '\\begin{sphinxlegend}\nwith a legend\n\\end{sphinxlegend}\n' + '\\begin{sphinxlegend}\n\\sphinxAtStartPar\n' + 'with a legend\n\\end{sphinxlegend}\n' r'\end{figure}' in result) # code-blocks diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index c09c81fe0..60b62435c 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -4,23 +4,34 @@ Test the build process with manpage builder with the test root. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import http.server import json +import re import textwrap +import time +import wsgiref.handlers +from datetime import datetime +from typing import Dict +from unittest import mock import pytest import requests -from .utils import CERT_FILE, http_server, https_server, modify_env +from sphinx.builders.linkcheck import CheckExternalLinksBuilder, RateLimit +from sphinx.util.console import strip_colors + +from .utils import CERT_FILE, http_server, https_server + +ts_re = re.compile(r".*\[(?P.*)\].*") @pytest.mark.sphinx('linkcheck', testroot='linkcheck', freshenv=True) def test_defaults(app): - app.builder.build_all() + app.build() assert (app.outdir / 'output.txt').exists() content = (app.outdir / 'output.txt').read_text() @@ -41,7 +52,7 @@ def test_defaults(app): @pytest.mark.sphinx('linkcheck', testroot='linkcheck', freshenv=True) def test_defaults_json(app): - app.builder.build_all() + app.build() assert (app.outdir / 'output.json').exists() content = (app.outdir / 'output.json').read_text() @@ -102,7 +113,7 @@ def test_defaults_json(app): 'path/to/notfound'] }) def test_anchors_ignored(app): - app.builder.build_all() + app.build() assert (app.outdir / 'output.txt').exists() content = (app.outdir / 'output.txt').read_text() @@ -118,7 +129,7 @@ def test_raises_for_invalid_status(app): self.send_error(500, "Internal Server Error") with http_server(InternalServerErrorHandler): - app.builder.build_all() + app.build() content = (app.outdir / 'output.txt').read_text() assert content == ( "index.rst:1: [broken] http://localhost:7777/#anchor: " @@ -146,7 +157,7 @@ class HeadersDumperHandler(http.server.BaseHTTPRequestHandler): ]}) def test_auth_header_uses_first_match(app, capsys): with http_server(HeadersDumperHandler): - app.builder.build_all() + app.build() stdout, stderr = capsys.readouterr() auth = requests.auth._basic_auth_str('user1', 'password') assert "Authorization: %s\n" % auth in stdout @@ -157,7 +168,7 @@ def test_auth_header_uses_first_match(app, capsys): confoverrides={'linkcheck_auth': [(r'^$', ('user1', 'password'))]}) def test_auth_header_no_match(app, capsys): with http_server(HeadersDumperHandler): - app.builder.build_all() + app.build() stdout, stderr = capsys.readouterr() assert "Authorization" not in stdout @@ -174,7 +185,7 @@ def test_auth_header_no_match(app, capsys): }}) def test_linkcheck_request_headers(app, capsys): with http_server(HeadersDumperHandler): - app.builder.build_all() + app.build() stdout, _stderr = capsys.readouterr() assert "Accept: text/html\n" in stdout @@ -190,7 +201,7 @@ def test_linkcheck_request_headers(app, capsys): }}) def test_linkcheck_request_headers_no_slash(app, capsys): with http_server(HeadersDumperHandler): - app.builder.build_all() + app.build() stdout, _stderr = capsys.readouterr() assert "Accept: application/json\n" in stdout @@ -206,7 +217,7 @@ def test_linkcheck_request_headers_no_slash(app, capsys): }}) def test_linkcheck_request_headers_default(app, capsys): with http_server(HeadersDumperHandler): - app.builder.build_all() + app.build() stdout, _stderr = capsys.readouterr() assert "Accepts: application/json\n" not in stdout @@ -240,7 +251,7 @@ def make_redirect_handler(*, support_head): @pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) def test_follows_redirects_on_HEAD(app, capsys): with http_server(make_redirect_handler(support_head=True)): - app.builder.build_all() + app.build() stdout, stderr = capsys.readouterr() content = (app.outdir / 'output.txt').read_text() assert content == ( @@ -258,7 +269,7 @@ def test_follows_redirects_on_HEAD(app, capsys): @pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) def test_follows_redirects_on_GET(app, capsys): with http_server(make_redirect_handler(support_head=False)): - app.builder.build_all() + app.build() stdout, stderr = capsys.readouterr() content = (app.outdir / 'output.txt').read_text() assert content == ( @@ -288,7 +299,7 @@ class OKHandler(http.server.BaseHTTPRequestHandler): def test_invalid_ssl(app): # Link indicates SSL should be used (https) but the server does not handle it. with http_server(OKHandler): - app.builder.build_all() + app.build() with open(app.outdir / 'output.json') as fp: content = json.load(fp) @@ -302,7 +313,7 @@ def test_invalid_ssl(app): @pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-https', freshenv=True) def test_connect_to_selfsigned_fails(app): with https_server(OKHandler): - app.builder.build_all() + app.build() with open(app.outdir / 'output.json') as fp: content = json.load(fp) @@ -317,7 +328,7 @@ def test_connect_to_selfsigned_fails(app): def test_connect_to_selfsigned_with_tls_verify_false(app): app.config.tls_verify = False with https_server(OKHandler): - app.builder.build_all() + app.build() with open(app.outdir / 'output.json') as fp: content = json.load(fp) @@ -335,7 +346,7 @@ def test_connect_to_selfsigned_with_tls_verify_false(app): def test_connect_to_selfsigned_with_tls_cacerts(app): app.config.tls_cacerts = CERT_FILE with https_server(OKHandler): - app.builder.build_all() + app.build() with open(app.outdir / 'output.json') as fp: content = json.load(fp) @@ -350,9 +361,10 @@ def test_connect_to_selfsigned_with_tls_cacerts(app): @pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-https', freshenv=True) -def test_connect_to_selfsigned_with_requests_env_var(app): - with modify_env(REQUESTS_CA_BUNDLE=CERT_FILE), https_server(OKHandler): - app.builder.build_all() +def test_connect_to_selfsigned_with_requests_env_var(monkeypatch, app): + monkeypatch.setenv("REQUESTS_CA_BUNDLE", CERT_FILE) + with https_server(OKHandler): + app.build() with open(app.outdir / 'output.json') as fp: content = json.load(fp) @@ -370,7 +382,7 @@ def test_connect_to_selfsigned_with_requests_env_var(app): def test_connect_to_selfsigned_nonexistent_cert_file(app): app.config.tls_cacerts = "does/not/exist" with https_server(OKHandler): - app.builder.build_all() + app.build() with open(app.outdir / 'output.json') as fp: content = json.load(fp) @@ -382,3 +394,182 @@ def test_connect_to_selfsigned_nonexistent_cert_file(app): "uri": "https://localhost:7777/", "info": "Could not find a suitable TLS CA certificate bundle, invalid path: does/not/exist", } + + +@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) +def test_TooManyRedirects_on_HEAD(app): + class InfiniteRedirectOnHeadHandler(http.server.BaseHTTPRequestHandler): + def do_HEAD(self): + self.send_response(302, "Found") + self.send_header("Location", "http://localhost:7777/") + self.end_headers() + + def do_GET(self): + self.send_response(200, "OK") + self.end_headers() + self.wfile.write(b"ok\n") + + with http_server(InfiniteRedirectOnHeadHandler): + app.build() + + with open(app.outdir / 'output.json') as fp: + content = json.load(fp) + assert content == { + "code": 0, + "status": "working", + "filename": "index.rst", + "lineno": 1, + "uri": "http://localhost:7777/", + "info": "", + } + + +def make_retry_after_handler(responses): + class RetryAfterHandler(http.server.BaseHTTPRequestHandler): + def do_HEAD(self): + status, retry_after = responses.pop(0) + self.send_response(status) + if retry_after: + self.send_header('Retry-After', retry_after) + self.end_headers() + + def log_date_time_string(self): + """Strip date and time from logged messages for assertions.""" + return "" + + return RetryAfterHandler + + +@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) +def test_too_many_requests_retry_after_int_delay(app, capsys, status): + with http_server(make_retry_after_handler([(429, "0"), (200, None)])), \ + mock.patch("sphinx.builders.linkcheck.DEFAULT_DELAY", 0), \ + mock.patch("sphinx.builders.linkcheck.QUEUE_POLL_SECS", 0.01): + app.build() + content = (app.outdir / 'output.json').read_text() + assert json.loads(content) == { + "filename": "index.rst", + "lineno": 1, + "status": "working", + "code": 0, + "uri": "http://localhost:7777/", + "info": "", + } + rate_limit_log = "-rate limited- http://localhost:7777/ | sleeping...\n" + assert rate_limit_log in strip_colors(status.getvalue()) + _stdout, stderr = capsys.readouterr() + assert stderr == textwrap.dedent( + """\ + 127.0.0.1 - - [] "HEAD / HTTP/1.1" 429 - + 127.0.0.1 - - [] "HEAD / HTTP/1.1" 200 - + """ + ) + + +@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) +def test_too_many_requests_retry_after_HTTP_date(app, capsys): + now = datetime.now().timetuple() + retry_after = wsgiref.handlers.format_date_time(time.mktime(now)) + with http_server(make_retry_after_handler([(429, retry_after), (200, None)])): + app.build() + content = (app.outdir / 'output.json').read_text() + assert json.loads(content) == { + "filename": "index.rst", + "lineno": 1, + "status": "working", + "code": 0, + "uri": "http://localhost:7777/", + "info": "", + } + _stdout, stderr = capsys.readouterr() + assert stderr == textwrap.dedent( + """\ + 127.0.0.1 - - [] "HEAD / HTTP/1.1" 429 - + 127.0.0.1 - - [] "HEAD / HTTP/1.1" 200 - + """ + ) + + +@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) +def test_too_many_requests_retry_after_without_header(app, capsys): + with http_server(make_retry_after_handler([(429, None), (200, None)])),\ + mock.patch("sphinx.builders.linkcheck.DEFAULT_DELAY", 0): + app.build() + content = (app.outdir / 'output.json').read_text() + assert json.loads(content) == { + "filename": "index.rst", + "lineno": 1, + "status": "working", + "code": 0, + "uri": "http://localhost:7777/", + "info": "", + } + _stdout, stderr = capsys.readouterr() + assert stderr == textwrap.dedent( + """\ + 127.0.0.1 - - [] "HEAD / HTTP/1.1" 429 - + 127.0.0.1 - - [] "HEAD / HTTP/1.1" 200 - + """ + ) + + +@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) +def test_too_many_requests_user_timeout(app, capsys): + app.config.linkcheck_rate_limit_timeout = 0.0 + with http_server(make_retry_after_handler([(429, None)])): + app.build() + content = (app.outdir / 'output.json').read_text() + assert json.loads(content) == { + "filename": "index.rst", + "lineno": 1, + "status": "broken", + "code": 0, + "uri": "http://localhost:7777/", + "info": "429 Client Error: Too Many Requests for url: http://localhost:7777/", + } + + +class FakeResponse: + headers = {} # type: Dict[str, str] + url = "http://localhost/" + + +def test_limit_rate_default_sleep(app): + checker = CheckExternalLinksBuilder(app) + checker.rate_limits = {} + with mock.patch('time.time', return_value=0.0): + next_check = checker.limit_rate(FakeResponse()) + assert next_check == 60.0 + + +def test_limit_rate_user_max_delay(app): + app.config.linkcheck_rate_limit_timeout = 0.0 + checker = CheckExternalLinksBuilder(app) + checker.rate_limits = {} + next_check = checker.limit_rate(FakeResponse()) + assert next_check is None + + +def test_limit_rate_doubles_previous_wait_time(app): + checker = CheckExternalLinksBuilder(app) + checker.rate_limits = {"localhost": RateLimit(60.0, 0.0)} + with mock.patch('time.time', return_value=0.0): + next_check = checker.limit_rate(FakeResponse()) + assert next_check == 120.0 + + +def test_limit_rate_clips_wait_time_to_max_time(app): + checker = CheckExternalLinksBuilder(app) + app.config.linkcheck_rate_limit_timeout = 90.0 + checker.rate_limits = {"localhost": RateLimit(60.0, 0.0)} + with mock.patch('time.time', return_value=0.0): + next_check = checker.limit_rate(FakeResponse()) + assert next_check == 90.0 + + +def test_limit_rate_bails_out_after_waiting_max_time(app): + checker = CheckExternalLinksBuilder(app) + app.config.linkcheck_rate_limit_timeout = 90.0 + checker.rate_limits = {"localhost": RateLimit(90.0, 0.0)} + next_check = checker.limit_rate(FakeResponse()) + assert next_check is None diff --git a/tests/test_build_manpage.py b/tests/test_build_manpage.py index d4b1a320e..a017abc69 100644 --- a/tests/test_build_manpage.py +++ b/tests/test_build_manpage.py @@ -4,7 +4,7 @@ Test the build process with manpage builder with the test root. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py index a18c8c6bf..546ccaabf 100644 --- a/tests/test_build_texinfo.py +++ b/tests/test_build_texinfo.py @@ -4,7 +4,7 @@ Test the build process with Texinfo builder with the test root. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_build_text.py b/tests/test_build_text.py index 0ada5bb70..e3d8ff111 100644 --- a/tests/test_build_text.py +++ b/tests/test_build_text.py @@ -4,7 +4,7 @@ Test the build process with Text builder with the test root. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_builder.py b/tests/test_builder.py index 4a37db84d..ed3f8cdaa 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -4,7 +4,7 @@ Test the Builder class. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import pytest diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index e603a5812..986979fe2 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -4,7 +4,7 @@ Test the base build process. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 64face9fc..9a0b617d5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,7 +5,7 @@ Test the sphinx.config.Config class and its handling in the Application class. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_correct_year.py b/tests/test_correct_year.py index 7dc3ea89d..6afcce2b5 100644 --- a/tests/test_correct_year.py +++ b/tests/test_correct_year.py @@ -4,7 +4,7 @@ Test copyright year adjustment - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import pytest diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index eda331645..0ae11baf3 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -4,7 +4,7 @@ Test the code-block directive. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -250,6 +250,14 @@ def test_LiteralIncludeReader_dedent(literal_inc_path): " pass\n" "\n") + # dedent: None + options = {'lines': '9-11', 'dedent': None} + reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) + content, lines = reader.read() + assert content == ("def baz():\n" + " pass\n" + "\n") + @pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_tabwidth(testroot): diff --git a/tests/test_directive_only.py b/tests/test_directive_only.py index 3de22c71f..72cbc6bd7 100644 --- a/tests/test_directive_only.py +++ b/tests/test_directive_only.py @@ -4,7 +4,7 @@ Test the only directive with the test root. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_directive_other.py b/tests/test_directive_other.py index 52e4a937c..09877208c 100644 --- a/tests/test_directive_other.py +++ b/tests/test_directive_other.py @@ -4,7 +4,7 @@ Test the other directives. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_directive_patch.py b/tests/test_directive_patch.py index 8572ff672..7dc568b1d 100644 --- a/tests/test_directive_patch.py +++ b/tests/test_directive_patch.py @@ -4,7 +4,7 @@ Test the patched directives. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 02a7fc62b..9f12fd004 100644 --- a/tests/test_docutilsconf.py +++ b/tests/test_docutilsconf.py @@ -4,7 +4,7 @@ Test docutils.conf support for several writers. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 28e74e7bf..2cfcf74fa 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -4,9 +4,11 @@ Tests the C Domain - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ + +import zlib from xml.etree import ElementTree import pytest @@ -14,6 +16,7 @@ import pytest from sphinx import addnodes from sphinx.addnodes import desc from sphinx.domains.c import DefinitionError, DefinitionParser, Symbol, _id_prefix, _max_id +from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping from sphinx.testing import restructuredtext from sphinx.testing.util import assert_node @@ -595,6 +598,13 @@ def test_build_function_param_target(app, warning): ] +@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) +def test_build_ns_lookup(app, warning): + app.builder.build_all() + ws = filter_warnings(warning, "ns_lookup") + assert len(ws) == 0 + + def _get_obj(app, queryName): domain = app.env.get_domain('c') for name, dispname, objectType, docname, anchor, prio in domain.get_objects(): @@ -642,3 +652,52 @@ def test_noindexentry(app): assert_node(doctree, (addnodes.index, desc, addnodes.index, desc)) assert_node(doctree[0], addnodes.index, entries=[('single', 'f (C function)', 'c.f', '', None)]) assert_node(doctree[2], addnodes.index, entries=[]) + + +@pytest.mark.sphinx(testroot='domain-c-intersphinx', confoverrides={'nitpicky': True}) +def test_intersphinx(tempdir, app, status, warning): + origSource = """\ +.. c:member:: int _member +.. c:var:: int _var +.. c:function:: void _function() +.. c:macro:: _macro +.. c:struct:: _struct +.. c:union:: _union +.. c:enum:: _enum + + .. c:enumerator:: _enumerator + +.. c:type:: _type +.. c:function:: void _functionParam(int param) +""" # noqa + inv_file = tempdir / 'inventory' + inv_file.write_bytes(b'''\ +# Sphinx inventory version 2 +# Project: C Intersphinx Test +# Version: +# The remainder of this file is compressed using zlib. +''' + zlib.compress(b'''\ +_enum c:enum 1 index.html#c.$ - +_enum._enumerator c:enumerator 1 index.html#c.$ - +_enumerator c:enumerator 1 index.html#c._enum.$ - +_function c:function 1 index.html#c.$ - +_functionParam c:function 1 index.html#c.$ - +_functionParam.param c:functionParam 1 index.html#c._functionParam - +_macro c:macro 1 index.html#c.$ - +_member c:member 1 index.html#c.$ - +_struct c:struct 1 index.html#c.$ - +_type c:type 1 index.html#c.$ - +_union c:union 1 index.html#c.$ - +_var c:member 1 index.html#c.$ - +''')) # noqa + app.config.intersphinx_mapping = { + 'https://localhost/intersphinx/c/': inv_file, + } + app.config.intersphinx_cache_limit = 0 + # load the inventory and check if it's done correctly + normalize_intersphinx_mapping(app, app.config) + load_mappings(app) + + app.builder.build_all() + ws = filter_warnings(warning, "index") + assert len(ws) == 0 diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index ff2a015ee..690093f5c 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -4,11 +4,12 @@ Tests the C++ Domain - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re +import zlib import pytest @@ -17,6 +18,7 @@ from sphinx import addnodes from sphinx.addnodes import desc from sphinx.domains.cpp import (DefinitionError, DefinitionParser, NoOldIdError, Symbol, _id_prefix, _max_id) +from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping from sphinx.testing import restructuredtext from sphinx.testing.util import assert_node from sphinx.util import docutils @@ -1049,8 +1051,8 @@ def test_build_domain_cpp_misuse_of_roles(app, status, warning): ('concept', ['concept']), ('enum', ['type', 'enum']), ('enumerator', ['enumerator']), - ('tParam', ['class', 'struct', 'union', 'func', 'member', 'var', 'type', 'concept', 'enum', 'enumerator', 'functionParam']), ('functionParam', ['member', 'var']), + ('templateParam', ['class', 'struct', 'union', 'member', 'var', 'type']), ] warn = [] for targetType, roles in ok: @@ -1058,6 +1060,9 @@ def test_build_domain_cpp_misuse_of_roles(app, status, warning): for r in allRoles: if r not in roles: warn.append("WARNING: cpp:{} targets a {} (".format(r, txtTargetType)) + if targetType == 'templateParam': + warn.append("WARNING: cpp:{} targets a {} (".format(r, txtTargetType)) + warn.append("WARNING: cpp:{} targets a {} (".format(r, txtTargetType)) warn = list(sorted(warn)) for w in ws: assert "targets a" in w @@ -1250,3 +1255,66 @@ def test_mix_decl_duplicate(app, warning): assert "index.rst:3: WARNING: Duplicate C++ declaration, also defined at index:1." in ws[2] assert "Declaration is '.. cpp:struct:: A'." in ws[3] assert ws[4] == "" + + +@pytest.mark.sphinx(testroot='domain-cpp-intersphinx', confoverrides={'nitpicky': True}) +def test_intersphinx(tempdir, app, status, warning): + origSource = """\ +.. cpp:class:: _class +.. cpp:struct:: _struct +.. cpp:union:: _union +.. cpp:function:: void _function() +.. cpp:member:: int _member +.. cpp:var:: int _var +.. cpp:type:: _type +.. cpp:concept:: template _concept +.. cpp:enum:: _enum + + .. cpp:enumerator:: _enumerator + +.. cpp:enum-struct:: _enumStruct + + .. cpp:enumerator:: _scopedEnumerator + +.. cpp:enum-class:: _enumClass +.. cpp:function:: void _functionParam(int param) +.. cpp:function:: template void _templateParam() +""" # noqa + inv_file = tempdir / 'inventory' + inv_file.write_bytes(b'''\ +# Sphinx inventory version 2 +# Project: C Intersphinx Test +# Version: +# The remainder of this file is compressed using zlib. +''' + zlib.compress(b'''\ +_class cpp:class 1 index.html#_CPPv46$ - +_concept cpp:concept 1 index.html#_CPPv4I0E8$ - +_concept::T cpp:templateParam 1 index.html#_CPPv4I0E8_concept - +_enum cpp:enum 1 index.html#_CPPv45$ - +_enum::_enumerator cpp:enumerator 1 index.html#_CPPv4N5_enum11_enumeratorE - +_enumClass cpp:enum 1 index.html#_CPPv410$ - +_enumStruct cpp:enum 1 index.html#_CPPv411$ - +_enumStruct::_scopedEnumerator cpp:enumerator 1 index.html#_CPPv4N11_enumStruct17_scopedEnumeratorE - +_enumerator cpp:enumerator 1 index.html#_CPPv4N5_enum11_enumeratorE - +_function cpp:function 1 index.html#_CPPv49_functionv - +_functionParam cpp:function 1 index.html#_CPPv414_functionParami - +_functionParam::param cpp:functionParam 1 index.html#_CPPv414_functionParami - +_member cpp:member 1 index.html#_CPPv47$ - +_struct cpp:class 1 index.html#_CPPv47$ - +_templateParam cpp:function 1 index.html#_CPPv4I0E14_templateParamvv - +_templateParam::TParam cpp:templateParam 1 index.html#_CPPv4I0E14_templateParamvv - +_type cpp:type 1 index.html#_CPPv45$ - +_union cpp:union 1 index.html#_CPPv46$ - +_var cpp:member 1 index.html#_CPPv44$ - +''')) # noqa + app.config.intersphinx_mapping = { + 'https://localhost/intersphinx/cpp/': inv_file, + } + app.config.intersphinx_cache_limit = 0 + # load the inventory and check if it's done correctly + normalize_intersphinx_mapping(app, app.config) + load_mappings(app) + + app.builder.build_all() + ws = filter_warnings(warning, "index") + assert len(ws) == 0 diff --git a/tests/test_domain_js.py b/tests/test_domain_js.py index 58c54c766..1fb865d4b 100644 --- a/tests/test_domain_js.py +++ b/tests/test_domain_js.py @@ -4,7 +4,7 @@ Tests the JavaScript Domain - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 2dc97bed9..fe117659a 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -4,10 +4,11 @@ Tests the Python Domain - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +import re import sys from unittest.mock import Mock @@ -132,6 +133,29 @@ def test_domain_py_xrefs(app, status, warning): assert len(refnodes) == 2 +@pytest.mark.sphinx('html', testroot='domain-py') +def test_domain_py_xrefs_abbreviations(app, status, warning): + app.builder.build_all() + + content = (app.outdir / 'abbr.html').read_text() + assert re.search(r'normal: <.*>module_a.submodule.ModTopLevel.mod_child_1\(\)' + r'<.*>', + content) + assert re.search(r'relative: <.*>ModTopLevel.mod_child_1\(\)<.*>', + content) + assert re.search(r'short name: <.*>mod_child_1\(\)<.*>', + content) + assert re.search(r'relative \+ short name: <.*>mod_child_1\(\)<.*>', + content) + assert re.search(r'short name \+ relative: <.*>mod_child_1\(\)<.*>', + content) + + @pytest.mark.sphinx('dummy', testroot='domain-py') def test_domain_py_objects(app, status, warning): app.builder.build_all() @@ -774,6 +798,53 @@ def test_pydecoratormethod_signature(app): assert domain.objects['deco'] == ('index', 'deco', 'method') +def test_info_field_list(app): + text = (".. py:module:: example\n" + ".. py:class:: Class\n" + "\n" + " :param str name: blah blah\n" + " :param age: blah blah\n" + " :type age: int\n") + doctree = restructuredtext.parse(app, text) + print(doctree) + + assert_node(doctree, (nodes.target, + addnodes.index, + addnodes.index, + [desc, ([desc_signature, ([desc_annotation, "class "], + [desc_addname, "example."], + [desc_name, "Class"])], + [desc_content, nodes.field_list, nodes.field])])) + assert_node(doctree[3][1][0][0], + ([nodes.field_name, "Parameters"], + [nodes.field_body, nodes.bullet_list, ([nodes.list_item, nodes.paragraph], + [nodes.list_item, nodes.paragraph])])) + + # :param str name: + assert_node(doctree[3][1][0][0][1][0][0][0], + ([addnodes.literal_strong, "name"], + " (", + [pending_xref, addnodes.literal_emphasis, "str"], + ")", + " -- ", + "blah blah")) + assert_node(doctree[3][1][0][0][1][0][0][0][2], pending_xref, + refdomain="py", reftype="class", reftarget="str", + **{"py:module": "example", "py:class": "Class"}) + + # :param age: + :type age: + assert_node(doctree[3][1][0][0][1][0][1][0], + ([addnodes.literal_strong, "age"], + " (", + [pending_xref, addnodes.literal_emphasis, "int"], + ")", + " -- ", + "blah blah")) + assert_node(doctree[3][1][0][0][1][0][1][0][2], pending_xref, + refdomain="py", reftype="class", reftarget="int", + **{"py:module": "example", "py:class": "Class"}) + + @pytest.mark.sphinx(freshenv=True) def test_module_index(app): text = (".. py:module:: docutils\n" diff --git a/tests/test_domain_rst.py b/tests/test_domain_rst.py index 93401e6c9..ed542ed35 100644 --- a/tests/test_domain_rst.py +++ b/tests/test_domain_rst.py @@ -4,7 +4,7 @@ Tests the reStructuredText domain. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 7ee1d6c07..cf32e7964 100644 --- a/tests/test_domain_std.py +++ b/tests/test_domain_std.py @@ -4,7 +4,7 @@ Tests the std domain - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -91,6 +91,28 @@ def test_get_full_qualified_name(): assert domain.get_full_qualified_name(node) == 'ls.-l' +def test_cmd_option_with_optional_value(app): + text = ".. option:: -j[=N]" + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (index, + [desc, ([desc_signature, ([desc_name, '-j'], + [desc_addname, '[=N]'])], + [desc_content, ()])])) + objects = list(app.env.get_domain("std").get_objects()) + assert ('-j', '-j', 'cmdoption', 'index', 'cmdoption-j', 1) in objects + + +def test_cmd_option_starting_with_bracket(app): + text = ".. option:: [enable=]PATTERN" + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (index, + [desc, ([desc_signature, ([desc_name, '[enable'], + [desc_addname, '=]PATTERN'])], + [desc_content, ()])])) + objects = list(app.env.get_domain("std").get_objects()) + assert ('[enable', '[enable', 'cmdoption', 'index', 'cmdoption-arg-enable', 1) in objects + + def test_glossary(app): text = (".. glossary::\n" "\n" @@ -383,6 +405,22 @@ def test_productionlist(app, status, warning): assert "A ::= B C D E F G" in text +def test_productionlist2(app): + text = (".. productionlist:: P2\n" + " A: `:A` `A`\n" + " B: `P1:B` `~P1:B`\n") + doctree = restructuredtext.parse(app, text) + refnodes = list(doctree.traverse(pending_xref)) + assert_node(refnodes[0], pending_xref, reftarget="A") + assert_node(refnodes[1], pending_xref, reftarget="P2:A") + assert_node(refnodes[2], pending_xref, reftarget="P1:B") + assert_node(refnodes[3], pending_xref, reftarget="P1:B") + assert_node(refnodes[0], [pending_xref, nodes.literal, "A"]) + assert_node(refnodes[1], [pending_xref, nodes.literal, "A"]) + assert_node(refnodes[2], [pending_xref, nodes.literal, "P1:B"]) + assert_node(refnodes[3], [pending_xref, nodes.literal, "B"]) + + def test_disabled_docref(app): text = (":doc:`index`\n" ":doc:`!index`\n") @@ -390,3 +428,13 @@ def test_disabled_docref(app): assert_node(doctree, ([nodes.paragraph, ([pending_xref, nodes.inline, "index"], "\n", [nodes.inline, "index"])],)) + + +def test_labeled_rubric(app): + text = (".. _label:\n" + ".. rubric:: blah *blah* blah\n") + restructuredtext.parse(app, text) + + domain = app.env.get_domain("std") + assert 'label' in domain.labels + assert domain.labels['label'] == ('index', 'label', 'blah blah blah') diff --git a/tests/test_environment.py b/tests/test_environment.py index 0cc1247c0..9791c2d5b 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -4,7 +4,7 @@ Test the BuildEnvironment class. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os @@ -138,6 +138,11 @@ def test_env_relfn2path(app): assert relfn == '../logo.jpg' assert absfn == app.srcdir.parent / 'logo.jpg' + # relative path traversal + relfn, absfn = app.env.relfn2path('subdir/../logo.jpg', 'index') + assert relfn == 'logo.jpg' + assert absfn == app.srcdir / 'logo.jpg' + # omit docname (w/ current docname) app.env.temp_data['docname'] = 'subdir/document' relfn, absfn = app.env.relfn2path('images/logo.jpg') diff --git a/tests/test_environment_indexentries.py b/tests/test_environment_indexentries.py index 075022be5..1b549bacb 100644 --- a/tests/test_environment_indexentries.py +++ b/tests/test_environment_indexentries.py @@ -4,7 +4,7 @@ Test the sphinx.environment.managers.indexentries. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -23,7 +23,7 @@ def test_create_single_index(app): ".. index:: Sphinx\n" ".. index:: Ель\n" ".. index:: ёлка\n" - ".. index:: ‏תירבע‎\n" + ".. index:: ‏עברית‎\n" ".. index:: 9-symbol\n" ".. index:: &-symbol\n" ".. index:: £100\n") @@ -41,7 +41,9 @@ def test_create_single_index(app): assert index[4] == ('Е', [('ёлка', [[('', '#index-6')], [], None]), ('Ель', [[('', '#index-5')], [], None])]) - assert index[5] == ('ת', [('‏תירבע‎', [[('', '#index-7')], [], None])]) + # Here the word starts with U+200F RIGHT-TO-LEFT MARK, which should be + # ignored when getting the first letter. + assert index[5] == ('ע', [('‏עברית‎', [[('', '#index-7')], [], None])]) @pytest.mark.sphinx('dummy', freshenv=True) diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py index db79c358f..41b3f727c 100644 --- a/tests/test_environment_toctree.py +++ b/tests/test_environment_toctree.py @@ -4,7 +4,7 @@ Test the sphinx.environment.managers.toctree. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_events.py b/tests/test_events.py index 4fbe03a17..bfff7a30f 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -4,7 +4,7 @@ Test the EventManager class. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_apidoc.py b/tests/test_ext_apidoc.py index e19a1d7ba..d6c45c268 100644 --- a/tests/test_ext_apidoc.py +++ b/tests/test_ext_apidoc.py @@ -4,7 +4,7 @@ Test the sphinx.apidoc module. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -216,6 +216,8 @@ def test_trailing_underscore(make_app, apidoc): def test_excludes(apidoc): outdir = apidoc.outdir assert (outdir / 'conf.py').isfile() + assert (outdir / 'a.rst').isfile() + assert (outdir / 'a.b.rst').isfile() assert (outdir / 'a.b.c.rst').isfile() # generated because not empty assert not (outdir / 'a.b.e.rst').isfile() # skipped because of empty after excludes assert (outdir / 'a.b.x.rst').isfile() @@ -231,6 +233,8 @@ def test_excludes_subpackage_should_be_skipped(apidoc): """Subpackage exclusion should work.""" outdir = apidoc.outdir assert (outdir / 'conf.py').isfile() + assert (outdir / 'a.rst').isfile() + assert (outdir / 'a.b.rst').isfile() assert (outdir / 'a.b.c.rst').isfile() # generated because not empty assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because 'b/e' subpackage is skipped @@ -244,6 +248,8 @@ def test_excludes_module_should_be_skipped(apidoc): """Module exclusion should work.""" outdir = apidoc.outdir assert (outdir / 'conf.py').isfile() + assert (outdir / 'a.rst').isfile() + assert (outdir / 'a.b.rst').isfile() assert (outdir / 'a.b.c.rst').isfile() # generated because not empty assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes @@ -257,6 +263,8 @@ def test_excludes_module_should_not_be_skipped(apidoc): """Module should be included if no excludes are used.""" outdir = apidoc.outdir assert (outdir / 'conf.py').isfile() + assert (outdir / 'a.rst').isfile() + assert (outdir / 'a.b.rst').isfile() assert (outdir / 'a.b.c.rst').isfile() # generated because not empty assert (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 9b80d8669..d555359cf 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -5,7 +5,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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -367,11 +367,6 @@ def test_get_doc(app): """Döcstring""" assert getdocl('function', f) == ['Döcstring'] - # already-unicode docstrings must be taken literally - def f(): - """Döcstring""" - assert getdocl('function', f) == ['Döcstring'] - # verify that method docstrings get extracted in both normal case # and in case of bound method posing as a function class J: # NOQA @@ -695,6 +690,7 @@ def test_autodoc_special_members(app): actual = do_autodoc(app, 'class', 'target.Class', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Class(arg)', + ' .. py:attribute:: Class.__annotations__', ' .. py:attribute:: Class.__dict__', ' .. py:method:: Class.__init__(arg)', ' .. py:attribute:: Class.__module__', @@ -806,7 +802,7 @@ def test_autodoc_inner_class(app): ' .. py:attribute:: Outer.factory', ' :module: target', '', - ' alias of :class:`builtins.dict`' + ' alias of :class:`dict`' ] actual = do_autodoc(app, 'class', 'target.Outer.Inner', options) @@ -1177,6 +1173,8 @@ def test_slots(app): '.. py:class:: Bar()', ' :module: target.slots', '', + ' docstring', + '', '', ' .. py:attribute:: Bar.attr1', ' :module: target.slots', @@ -1197,6 +1195,8 @@ def test_slots(app): '.. py:class:: Baz()', ' :module: target.slots', '', + ' docstring', + '', '', ' .. py:attribute:: Baz.attr', ' :module: target.slots', @@ -1205,6 +1205,8 @@ def test_slots(app): '.. py:class:: Foo()', ' :module: target.slots', '', + ' docstring', + '', '', ' .. py:attribute:: Foo.attr', ' :module: target.slots', @@ -1566,6 +1568,11 @@ def test_autodoc_typed_instance_variables(app): '.. py:module:: target.typed_vars', '', '', + '.. py:attribute:: Alias', + ' :module: target.typed_vars', + '', + ' alias of :class:`target.typed_vars.Derived`', + '', '.. py:class:: Class()', ' :module: target.typed_vars', '', @@ -1675,9 +1682,31 @@ def test_autodoc_typed_inherited_instance_variables(app): '', ' .. py:attribute:: Derived.attr3', ' :module: target.typed_vars', + ' :type: int', ' :value: 0', '', '', + ' .. py:attribute:: Derived.attr4', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr4', + '', + '', + ' .. py:attribute:: Derived.attr5', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr5', + '', + '', + ' .. py:attribute:: Derived.attr6', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr6', + '', + '', ' .. py:attribute:: Derived.attr7', ' :module: target.typed_vars', ' :type: int', @@ -1701,10 +1730,19 @@ def test_autodoc_GenericAlias(app): '.. py:module:: target.genericalias', '', '', + '.. py:class:: Class()', + ' :module: target.genericalias', + '', + '', + ' .. py:attribute:: Class.T', + ' :module: target.genericalias', + '', + ' alias of :class:`List`\\ [:class:`int`]', + '', '.. py:attribute:: T', ' :module: target.genericalias', '', - ' alias of :class:`typing.List`', + ' alias of :class:`List`\\ [:class:`int`]', ] else: assert list(actual) == [ @@ -1712,12 +1750,25 @@ def test_autodoc_GenericAlias(app): '.. py:module:: target.genericalias', '', '', + '.. py:class:: Class()', + ' :module: target.genericalias', + '', + '', + ' .. py:attribute:: Class.T', + ' :module: target.genericalias', + '', + ' A list of int', + '', + ' alias of List[int]', + '', + '', '.. py:data:: T', ' :module: target.genericalias', '', ' A list of int', '', ' alias of List[int]', + '', ] @@ -1735,6 +1786,14 @@ def test_autodoc_TypeVar(app): ' :module: target.typevar', '', '', + ' .. py:attribute:: Class.T1', + ' :module: target.typevar', + '', + ' T1', + '', + " alias of TypeVar('T1')", + '', + '', ' .. py:attribute:: Class.T6', ' :module: target.typevar', '', @@ -1750,6 +1809,7 @@ def test_autodoc_TypeVar(app): '', " alias of TypeVar('T1')", '', + '', '.. py:data:: T3', ' :module: target.typevar', '', @@ -1757,6 +1817,7 @@ def test_autodoc_TypeVar(app): '', " alias of TypeVar('T3', int, str)", '', + '', '.. py:data:: T4', ' :module: target.typevar', '', @@ -1764,6 +1825,7 @@ def test_autodoc_TypeVar(app): '', " alias of TypeVar('T4', covariant=True)", '', + '', '.. py:data:: T5', ' :module: target.typevar', '', @@ -1771,6 +1833,7 @@ def test_autodoc_TypeVar(app): '', " alias of TypeVar('T5', contravariant=True)", '', + '', '.. py:data:: T6', ' :module: target.typevar', '', @@ -2015,17 +2078,17 @@ def test_overload(app): ' docstring', '', '', - ' .. py:method:: Math.sum(x: int, y: int) -> int', - ' Math.sum(x: float, y: float) -> float', - ' Math.sum(x: str, y: str) -> str', + ' .. py:method:: Math.sum(x: int, y: int = 0) -> int', + ' Math.sum(x: float, y: float = 0.0) -> float', + ' Math.sum(x: str, y: str = None) -> str', ' :module: target.overload', '', ' docstring', '', '', - '.. py:function:: sum(x: int, y: int) -> int', - ' sum(x: float, y: float) -> float', - ' sum(x: str, y: str) -> str', + '.. py:function:: sum(x: int, y: int = 0) -> int', + ' sum(x: float, y: float = 0.0) -> float', + ' sum(x: str, y: str = None) -> str', ' :module: target.overload', '', ' docstring', @@ -2172,3 +2235,49 @@ def test_name_mangling(app): ' name of Foo', '', ] + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.') +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_hide_value(app): + options = {'members': True} + actual = do_autodoc(app, 'module', 'target.hide_value', options) + assert list(actual) == [ + '', + '.. py:module:: target.hide_value', + '', + '', + '.. py:class:: Foo()', + ' :module: target.hide_value', + '', + ' docstring', + '', + '', + ' .. py:attribute:: Foo.SENTINEL1', + ' :module: target.hide_value', + '', + ' docstring', + '', + ' :meta hide-value:', + '', + '', + ' .. py:attribute:: Foo.SENTINEL2', + ' :module: target.hide_value', + '', + ' :meta hide-value:', + '', + '', + '.. py:data:: SENTINEL1', + ' :module: target.hide_value', + '', + ' docstring', + '', + ' :meta hide-value:', + '', + '', + '.. py:data:: SENTINEL2', + ' :module: target.hide_value', + '', + ' :meta hide-value:', + '', + ] diff --git a/tests/test_ext_autodoc_autoattribute.py b/tests/test_ext_autodoc_autoattribute.py index 28c48d081..48e897b2e 100644 --- a/tests/test_ext_autodoc_autoattribute.py +++ b/tests/test_ext_autodoc_autoattribute.py @@ -5,7 +5,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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -57,6 +57,19 @@ def test_autoattribute_typed_variable(app): ] +@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.') +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_typed_variable_in_alias(app): + actual = do_autodoc(app, 'attribute', 'target.typed_vars.Alias.attr2') + assert list(actual) == [ + '', + '.. py:attribute:: Alias.attr2', + ' :module: target.typed_vars', + ' :type: int', + '', + ] + + @pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autoattribute_instance_variable(app): @@ -72,6 +85,82 @@ def test_autoattribute_instance_variable(app): ] +@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.') +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_instance_variable_in_alias(app): + actual = do_autodoc(app, 'attribute', 'target.typed_vars.Alias.attr4') + assert list(actual) == [ + '', + '.. py:attribute:: Alias.attr4', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr4', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_slots_variable_list(app): + actual = do_autodoc(app, 'attribute', 'target.slots.Foo.attr') + assert list(actual) == [ + '', + '.. py:attribute:: Foo.attr', + ' :module: target.slots', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_slots_variable_dict(app): + actual = do_autodoc(app, 'attribute', 'target.slots.Bar.attr1') + assert list(actual) == [ + '', + '.. py:attribute:: Bar.attr1', + ' :module: target.slots', + '', + ' docstring of attr1', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_slots_variable_str(app): + actual = do_autodoc(app, 'attribute', 'target.slots.Baz.attr') + assert list(actual) == [ + '', + '.. py:attribute:: Baz.attr', + ' :module: target.slots', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_GenericAlias(app): + actual = do_autodoc(app, 'attribute', 'target.genericalias.Class.T') + if sys.version_info < (3, 7): + assert list(actual) == [ + '', + '.. py:attribute:: Class.T', + ' :module: target.genericalias', + ' :value: typing.List[int]', + '', + ' A list of int', + '', + ] + else: + assert list(actual) == [ + '', + '.. py:attribute:: Class.T', + ' :module: target.genericalias', + '', + ' A list of int', + '', + ' alias of List[int]', + '', + ] + + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autoattribute_NewType(app): actual = do_autodoc(app, 'attribute', 'target.typevar.Class.T6') @@ -85,3 +174,44 @@ def test_autoattribute_NewType(app): ' alias of :class:`int`', '', ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_TypeVar(app): + actual = do_autodoc(app, 'attribute', 'target.typevar.Class.T1') + assert list(actual) == [ + '', + '.. py:attribute:: Class.T1', + ' :module: target.typevar', + '', + ' T1', + '', + " alias of TypeVar('T1')", + '', + ] + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.') +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_hide_value(app): + actual = do_autodoc(app, 'attribute', 'target.hide_value.Foo.SENTINEL1') + assert list(actual) == [ + '', + '.. py:attribute:: Foo.SENTINEL1', + ' :module: target.hide_value', + '', + ' docstring', + '', + ' :meta hide-value:', + '', + ] + + actual = do_autodoc(app, 'attribute', 'target.hide_value.Foo.SENTINEL2') + assert list(actual) == [ + '', + '.. py:attribute:: Foo.SENTINEL2', + ' :module: target.hide_value', + '', + ' :meta hide-value:', + '', + ] diff --git a/tests/test_ext_autodoc_autoclass.py b/tests/test_ext_autodoc_autoclass.py index 17c7f8944..488b72263 100644 --- a/tests/test_ext_autodoc_autoclass.py +++ b/tests/test_ext_autodoc_autoclass.py @@ -5,7 +5,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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -51,6 +51,61 @@ def test_classes(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_instance_variable(app): + options = {'members': True} + actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options) + assert list(actual) == [ + '', + '.. py:class:: Bar()', + ' :module: target.instance_variable', + '', + '', + ' .. py:attribute:: Bar.attr2', + ' :module: target.instance_variable', + '', + ' docstring bar', + '', + '', + ' .. py:attribute:: Bar.attr3', + ' :module: target.instance_variable', + '', + ' docstring bar', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_inherited_instance_variable(app): + options = {'members': True, + 'inherited-members': True} + actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options) + assert list(actual) == [ + '', + '.. py:class:: Bar()', + ' :module: target.instance_variable', + '', + '', + ' .. py:attribute:: Bar.attr1', + ' :module: target.instance_variable', + '', + ' docstring foo', + '', + '', + ' .. py:attribute:: Bar.attr2', + ' :module: target.instance_variable', + '', + ' docstring bar', + '', + '', + ' .. py:attribute:: Bar.attr3', + ' :module: target.instance_variable', + '', + ' docstring bar', + '', + ] + + def test_decorators(app): actual = do_autodoc(app, 'class', 'target.decorator.Baz') assert list(actual) == [ @@ -77,6 +132,32 @@ def test_decorators(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_slots_attribute(app): + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.slots.Bar', options) + assert list(actual) == [ + '', + '.. py:class:: Bar()', + ' :module: target.slots', + '', + ' docstring', + '', + '', + ' .. py:attribute:: Bar.attr1', + ' :module: target.slots', + '', + ' docstring of attr1', + '', + '', + ' .. py:attribute:: Bar.attr2', + ' :module: target.slots', + '', + ' docstring of instance attr2', + '', + ] + + @pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_show_inheritance_for_subclass_of_generic_type(app): @@ -92,3 +173,21 @@ def test_show_inheritance_for_subclass_of_generic_type(app): ' A subclass of List[Union[int, float]]', '', ] + + +def test_class_alias(app): + def autodoc_process_docstring(*args): + """A handler always raises an error. + This confirms this handler is never called for class aliases. + """ + raise + + app.connect('autodoc-process-docstring', autodoc_process_docstring) + actual = do_autodoc(app, 'class', 'target.classes.Alias') + assert list(actual) == [ + '', + '.. py:attribute:: Alias', + ' :module: target.classes', + '', + ' alias of :class:`target.classes.Foo`', + ] diff --git a/tests/test_ext_autodoc_autodata.py b/tests/test_ext_autodoc_autodata.py index 9c765676f..907d70aa1 100644 --- a/tests/test_ext_autodoc_autodata.py +++ b/tests/test_ext_autodoc_autodata.py @@ -5,7 +5,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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -75,6 +75,32 @@ def test_autodata_type_comment(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodata_GenericAlias(app): + actual = do_autodoc(app, 'data', 'target.genericalias.T') + if sys.version_info < (3, 7): + assert list(actual) == [ + '', + '.. py:data:: T', + ' :module: target.genericalias', + ' :value: typing.List[int]', + '', + ' A list of int', + '', + ] + else: + assert list(actual) == [ + '', + '.. py:data:: T', + ' :module: target.genericalias', + '', + ' A list of int', + '', + ' alias of List[int]', + '', + ] + + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodata_NewType(app): actual = do_autodoc(app, 'data', 'target.typevar.T6') @@ -88,3 +114,44 @@ def test_autodata_NewType(app): ' alias of :class:`int`', '', ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodata_TypeVar(app): + actual = do_autodoc(app, 'data', 'target.typevar.T1') + assert list(actual) == [ + '', + '.. py:data:: T1', + ' :module: target.typevar', + '', + ' T1', + '', + " alias of TypeVar('T1')", + '', + ] + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.') +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodata_hide_value(app): + actual = do_autodoc(app, 'data', 'target.hide_value.SENTINEL1') + assert list(actual) == [ + '', + '.. py:data:: SENTINEL1', + ' :module: target.hide_value', + '', + ' docstring', + '', + ' :meta hide-value:', + '', + ] + + actual = do_autodoc(app, 'data', 'target.hide_value.SENTINEL2') + assert list(actual) == [ + '', + '.. py:data:: SENTINEL2', + ' :module: target.hide_value', + '', + ' :meta hide-value:', + '', + ] diff --git a/tests/test_ext_autodoc_autofunction.py b/tests/test_ext_autodoc_autofunction.py index 4010e7cee..aa0476687 100644 --- a/tests/test_ext_autodoc_autofunction.py +++ b/tests/test_ext_autodoc_autofunction.py @@ -5,7 +5,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-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_autodoc_automodule.py b/tests/test_ext_autodoc_automodule.py new file mode 100644 index 000000000..df57724b3 --- /dev/null +++ b/tests/test_ext_autodoc_automodule.py @@ -0,0 +1,44 @@ +""" + test_ext_autodoc_autocmodule + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + 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-2021 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import sys + +import pytest + +from .test_ext_autodoc import do_autodoc + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_empty_all(app): + options = {'members': True} + actual = do_autodoc(app, 'module', 'target.empty_all', options) + assert list(actual) == [ + '', + '.. py:module:: target.empty_all', + '', + 'docsting of empty_all module.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc', + confoverrides={'autodoc_mock_imports': ['missing_module', + 'missing_package1', + 'missing_package2', + 'missing_package3', + 'sphinx.missing_module4']}) +@pytest.mark.usefixtures("rollback_sysmodules") +def test_subclass_of_mocked_object(app): + sys.modules.pop('target', None) # unload target module to clear the module cache + + options = {'members': True} + actual = do_autodoc(app, 'module', 'target.need_mocks', options) + assert '.. py:class:: Inherited(*args: Any, **kwargs: Any)' in actual diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index d76ed25e3..bae684397 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -4,7 +4,7 @@ Test the autodoc extension. This tests mainly for config variables - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -429,7 +429,10 @@ def test_autoclass_content_and_docstring_signature_both(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') +@pytest.mark.usefixtures("rollback_sysmodules") def test_mocked_module_imports(app, warning): + sys.modules.pop('target', None) # unload target module to clear the module cache + # no autodoc_mock_imports options = {"members": 'TestAutodoc,decoratedFunction,func'} actual = do_autodoc(app, 'module', 'target.need_mocks', options) @@ -644,13 +647,13 @@ def test_autodoc_typehints_none_for_overload(app): ' docstring', '', '', - ' .. py:method:: Math.sum(x, y)', + ' .. py:method:: Math.sum(x, y=None)', ' :module: target.overload', '', ' docstring', '', '', - '.. py:function:: sum(x, y)', + '.. py:function:: sum(x, y=None)', ' :module: target.overload', '', ' docstring', @@ -707,7 +710,14 @@ def test_autodoc_type_aliases(app): ' docstring', '', '', - ' .. py:attribute:: Foo.attr', + ' .. py:attribute:: Foo.attr1', + ' :module: target.annotations', + ' :type: int', + '', + ' docstring', + '', + '', + ' .. py:attribute:: Foo.attr2', ' :module: target.annotations', ' :type: int', '', @@ -733,6 +743,14 @@ def test_autodoc_type_aliases(app): '', ' docstring', '', + '', + '.. py:data:: variable2', + ' :module: target.annotations', + ' :type: int', + ' :value: None', + '', + ' docstring', + '', ] # define aliases @@ -749,7 +767,14 @@ def test_autodoc_type_aliases(app): ' docstring', '', '', - ' .. py:attribute:: Foo.attr', + ' .. py:attribute:: Foo.attr1', + ' :module: target.annotations', + ' :type: myint', + '', + ' docstring', + '', + '', + ' .. py:attribute:: Foo.attr2', ' :module: target.annotations', ' :type: myint', '', @@ -775,6 +800,14 @@ def test_autodoc_type_aliases(app): '', ' docstring', '', + '', + '.. py:data:: variable2', + ' :module: target.annotations', + ' :type: myint', + ' :value: None', + '', + ' docstring', + '', ] diff --git a/tests/test_ext_autodoc_events.py b/tests/test_ext_autodoc_events.py index ad6a81129..561ac49de 100644 --- a/tests/test_ext_autodoc_events.py +++ b/tests/test_ext_autodoc_events.py @@ -4,7 +4,7 @@ Test the autodoc extension. This tests mainly for autodoc events - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -34,6 +34,23 @@ def test_process_docstring(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_process_docstring_for_nondatadescriptor(app): + def on_process_docstring(app, what, name, obj, options, lines): + raise + + app.connect('autodoc-process-docstring', on_process_docstring) + + actual = do_autodoc(app, 'attribute', 'target.AttCls.a1') + assert list(actual) == [ + '', + '.. py:attribute:: AttCls.a1', + ' :module: target', + ' :value: hello world', + '', + ] + + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_cut_lines(app): app.connect('autodoc-process-docstring', diff --git a/tests/test_ext_autodoc_mock.py b/tests/test_ext_autodoc_mock.py index a29170f75..497bd8a6b 100644 --- a/tests/test_ext_autodoc_mock.py +++ b/tests/test_ext_autodoc_mock.py @@ -4,7 +4,7 @@ Test the autodoc extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -15,7 +15,7 @@ from typing import TypeVar import pytest -from sphinx.ext.autodoc.mock import _MockModule, _MockObject, mock +from sphinx.ext.autodoc.mock import _MockModule, _MockObject, ismock, mock, undecorate def test_MockModule(): @@ -115,17 +115,38 @@ def test_mock_decorator(): @mock.function_deco def func(): - """docstring""" + pass class Foo: @mock.method_deco def meth(self): - """docstring""" + pass @mock.class_deco class Bar: - """docstring""" + pass - assert func.__doc__ == "docstring" - assert Foo.meth.__doc__ == "docstring" - assert Bar.__doc__ == "docstring" + @mock.funcion_deco(Foo) + class Baz: + pass + + assert undecorate(func).__name__ == "func" + assert undecorate(Foo.meth).__name__ == "meth" + assert undecorate(Bar).__name__ == "Bar" + assert undecorate(Baz).__name__ == "Baz" + + +def test_ismock(): + with mock(['sphinx.unknown']): + mod1 = import_module('sphinx.unknown') + mod2 = import_module('sphinx.application') + + class Inherited(mod1.Class): + pass + + assert ismock(mod1) is True + assert ismock(mod1.Class) is True + assert ismock(Inherited) is False + + assert ismock(mod2) is False + assert ismock(mod2.Sphinx) is False diff --git a/tests/test_ext_autodoc_private_members.py b/tests/test_ext_autodoc_private_members.py index 6bce5ce78..0de74b898 100644 --- a/tests/test_ext_autodoc_private_members.py +++ b/tests/test_ext_autodoc_private_members.py @@ -4,7 +4,7 @@ Test the autodoc extension. This tests mainly for private-members option. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -23,6 +23,13 @@ def test_private_field(app): '.. py:module:: target.private', '', '', + '.. py:data:: _PUBLIC_CONSTANT', + ' :module: target.private', + ' :value: None', + '', + ' :meta public:', + '', + '', '.. py:function:: _public_function(name)', ' :module: target.private', '', @@ -44,6 +51,20 @@ def test_private_field_and_private_members(app): '.. py:module:: target.private', '', '', + '.. py:data:: PRIVATE_CONSTANT', + ' :module: target.private', + ' :value: None', + '', + ' :meta private:', + '', + '', + '.. py:data:: _PUBLIC_CONSTANT', + ' :module: target.private', + ' :value: None', + '', + ' :meta public:', + '', + '', '.. py:function:: _public_function(name)', ' :module: target.private', '', @@ -66,13 +87,20 @@ def test_private_field_and_private_members(app): def test_private_members(app): app.config.autoclass_content = 'class' options = {"members": None, - "private-members": "_public_function"} + "private-members": "_PUBLIC_CONSTANT,_public_function"} actual = do_autodoc(app, 'module', 'target.private', options) assert list(actual) == [ '', '.. py:module:: target.private', '', '', + '.. py:data:: _PUBLIC_CONSTANT', + ' :module: target.private', + ' :value: None', + '', + ' :meta public:', + '', + '', '.. py:function:: _public_function(name)', ' :module: target.private', '', diff --git a/tests/test_ext_autosectionlabel.py b/tests/test_ext_autosectionlabel.py index 310435d8e..0afe13b41 100644 --- a/tests/test_ext_autosectionlabel.py +++ b/tests/test_ext_autosectionlabel.py @@ -4,7 +4,7 @@ Test sphinx.ext.autosectionlabel extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index 3832bc963..da7c53ec9 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -4,7 +4,7 @@ Test the autosummary extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -97,6 +97,9 @@ def test_extract_summary(capsys): doc = ['Blabla, i.e. bla.'] assert extract_summary(doc, document) == ' '.join(doc) + doc = ['Blabla, et al. bla.'] + assert extract_summary(doc, document) == ' '.join(doc) + # literal doc = ['blah blah::'] assert extract_summary(doc, document) == 'blah blah.' @@ -375,7 +378,10 @@ def test_autosummary_generate_overwrite2(app_params, make_app): @pytest.mark.sphinx('dummy', testroot='ext-autosummary-recursive') +@pytest.mark.usefixtures("rollback_sysmodules") def test_autosummary_recursive(app, status, warning): + sys.modules.pop('package', None) # unload target module to clear the module cache + app.build() # autosummary having :recursive: option @@ -399,6 +405,20 @@ def test_autosummary_recursive(app, status, warning): assert 'package.package.module' in content +@pytest.mark.sphinx('dummy', testroot='ext-autosummary-recursive', + srcdir='test_autosummary_recursive_skips_mocked_modules', + confoverrides={'autosummary_mock_imports': ['package.package']}) +@pytest.mark.usefixtures("rollback_sysmodules") +def test_autosummary_recursive_skips_mocked_modules(app, status, warning): + sys.modules.pop('package', None) # unload target module to clear the module cache + app.build() + + assert (app.srcdir / 'generated' / 'package.rst').exists() + assert (app.srcdir / 'generated' / 'package.module.rst').exists() + assert (app.srcdir / 'generated' / 'package.package.rst').exists() is False + assert (app.srcdir / 'generated' / 'package.package.module.rst').exists() is False + + @pytest.mark.sphinx('dummy', testroot='ext-autosummary-filename-map') def test_autosummary_filename_map(app, status, warning): app.build() diff --git a/tests/test_ext_coverage.py b/tests/test_ext_coverage.py index 033a1c1b1..6172c502d 100644 --- a/tests/test_ext_coverage.py +++ b/tests/test_ext_coverage.py @@ -4,7 +4,7 @@ Test the coverage builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 0ba53e791..729067b4d 100644 --- a/tests/test_ext_doctest.py +++ b/tests/test_ext_doctest.py @@ -4,7 +4,7 @@ Test the doctest extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import os diff --git a/tests/test_ext_duration.py b/tests/test_ext_duration.py index debe7fa56..681530cef 100644 --- a/tests/test_ext_duration.py +++ b/tests/test_ext_duration.py @@ -4,7 +4,7 @@ Test sphinx.ext.duration extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 index 41dbe4f60..5c13a8f97 100644 --- a/tests/test_ext_githubpages.py +++ b/tests/test_ext_githubpages.py @@ -4,7 +4,7 @@ Test sphinx.ext.githubpages extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_graphviz.py b/tests/test_ext_graphviz.py index a8de950ce..a19261e96 100644 --- a/tests/test_ext_graphviz.py +++ b/tests/test_ext_graphviz.py @@ -4,7 +4,7 @@ Test sphinx.ext.graphviz extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_ifconfig.py b/tests/test_ext_ifconfig.py index 232ddf0d8..eea2386c3 100644 --- a/tests/test_ext_ifconfig.py +++ b/tests/test_ext_ifconfig.py @@ -4,7 +4,7 @@ Test sphinx.ext.ifconfig extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_imgconverter.py b/tests/test_ext_imgconverter.py index 075446b0a..5f6f236cf 100644 --- a/tests/test_ext_imgconverter.py +++ b/tests/test_ext_imgconverter.py @@ -4,7 +4,7 @@ Test sphinx.ext.imgconverter extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py index 4eff0e35c..eada88e91 100644 --- a/tests/test_ext_inheritance_diagram.py +++ b/tests/test_ext_inheritance_diagram.py @@ -4,7 +4,7 @@ Test sphinx.ext.inheritance_diagram extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index 718bc9422..a87677525 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -4,7 +4,7 @@ Test the intersphinx extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -46,7 +46,7 @@ def reference_check(app, *args, **kwds): @mock.patch('sphinx.ext.intersphinx._read_from_url') def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status, warning): intersphinx_setup(app) - _read_from_url().readline.return_value = '# Sphinx inventory version 2'.encode() + _read_from_url().readline.return_value = b'# Sphinx inventory version 2' # same uri and inv, not redirected _read_from_url().url = 'http://hostname/' + INVENTORY_FILENAME diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py index 4df7d47c7..10c8c4866 100644 --- a/tests/test_ext_math.py +++ b/tests/test_ext_math.py @@ -4,7 +4,7 @@ Test math extensions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -15,6 +15,7 @@ import warnings import pytest from docutils import nodes +from sphinx.ext.mathjax import MATHJAX_URL from sphinx.testing.util import assert_node @@ -224,6 +225,18 @@ def test_mathjax_config(app, status, warning): '' in content) +@pytest.mark.sphinx('html', testroot='ext-math', + confoverrides={'extensions': ['sphinx.ext.mathjax']}) +def test_mathjax_is_installed_only_if_document_having_math(app, status, warning): + app.builder.build_all() + + content = (app.outdir / 'index.html').read_text() + assert MATHJAX_URL in content + + content = (app.outdir / 'nomath.html').read_text() + assert MATHJAX_URL not in content + + @pytest.mark.sphinx('html', testroot='basic', confoverrides={'extensions': ['sphinx.ext.mathjax']}) def test_mathjax_is_not_installed_if_no_equations(app, status, warning): diff --git a/tests/test_ext_napoleon.py b/tests/test_ext_napoleon.py index c271a7c9a..d6334b9eb 100644 --- a/tests/test_ext_napoleon.py +++ b/tests/test_ext_napoleon.py @@ -5,7 +5,7 @@ Tests for :mod:`sphinx.ext.napoleon.__init__` module. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 220a394d4..362e6fb8e 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -5,11 +5,12 @@ Tests for :mod:`sphinx.ext.napoleon.docstring` module. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re +import sys from collections import namedtuple from contextlib import contextmanager from inspect import cleandoc @@ -23,6 +24,10 @@ from sphinx.ext.napoleon.docstring import (GoogleDocstring, NumpyDocstring, _convert_numpy_type_spec, _recombine_set_tokens, _token_type, _tokenize_type_spec) +if sys.version_info >= (3, 6): + from .ext_napoleon_pep526_data_google import PEP526GoogleClass + from .ext_napoleon_pep526_data_numpy import PEP526NumpyClass + class NamedtupleSubclass(namedtuple('NamedtupleSubclass', ('attr1', 'attr2'))): """Sample namedtuple subclass @@ -298,6 +303,34 @@ class GoogleDocstringTest(BaseDocstringTest): """ Single line summary + Receive: + arg1 (list(int)): Description + arg2 (list[int]): Description + """, + """ + Single line summary + + :Receives: * **arg1** (*list(int)*) -- Description + * **arg2** (*list[int]*) -- Description + """ + ), ( + """ + Single line summary + + Receives: + arg1 (list(int)): Description + arg2 (list[int]): Description + """, + """ + Single line summary + + :Receives: * **arg1** (*list(int)*) -- Description + * **arg2** (*list[int]*) -- Description + """ + ), ( + """ + Single line summary + Yield: str:Extended description of yielded value @@ -1039,10 +1072,27 @@ You should listen to me! Sooper Warning: Stop hitting yourself! """, """:Warns: **Stop hitting yourself!** +"""), + ("""\ +Params Style: + arg1 (int): Description of arg1 + arg2 (str): Description of arg2 + +""", """\ +:Params Style: * **arg1** (*int*) -- Description of arg1 + * **arg2** (*str*) -- Description of arg2 +"""), + ("""\ +Returns Style: + description of custom section + +""", """:Returns Style: description of custom section """)) testConfig = Config(napoleon_custom_sections=['Really Important Details', - ('Sooper Warning', 'warns')]) + ('Sooper Warning', 'warns'), + ('Params Style', 'params_style'), + ('Returns Style', 'returns_style')]) for docstring, expected in docstrings: actual = str(GoogleDocstring(docstring, testConfig)) @@ -1089,6 +1139,55 @@ Do as you please :keyword gotham_is_yours: shall interfere. :kwtype gotham_is_yours: None +""" + self.assertEqual(expected, actual) + + def test_pep526_annotations(self): + if sys.version_info >= (3, 6): + # Test class attributes annotations + config = Config( + napoleon_attr_annotations=True + ) + actual = str(GoogleDocstring(cleandoc(PEP526GoogleClass.__doc__), config, app=None, what="class", + obj=PEP526GoogleClass)) + expected = """\ +Sample class with PEP 526 annotations and google docstring + +.. attribute:: attr1 + + Attr1 description. + + :type: int + +.. attribute:: attr2 + + Attr2 description. + + :type: str +""" + self.assertEqual(expected, actual) + + def test_preprocess_types(self): + docstring = """\ +Do as you please + +Yield: + str:Extended +""" + actual = str(GoogleDocstring(docstring)) + expected = """\ +Do as you please + +:Yields: *str* -- Extended +""" + self.assertEqual(expected, actual) + + config = Config(napoleon_preprocess_types=True) + actual = str(GoogleDocstring(docstring, config)) + expected = """\ +Do as you please + +:Yields: :class:`str` -- Extended """ self.assertEqual(expected, actual) @@ -1233,6 +1332,48 @@ class NumpyDocstringTest(BaseDocstringTest): """ Single line summary + Receive + ------- + arg1:str + Extended + description of arg1 + arg2 : int + Extended + description of arg2 + """, + """ + Single line summary + + :Receives: * **arg1** (:class:`str`) -- Extended + description of arg1 + * **arg2** (:class:`int`) -- Extended + description of arg2 + """ + ), ( + """ + Single line summary + + Receives + -------- + arg1:str + Extended + description of arg1 + arg2 : int + Extended + description of arg2 + """, + """ + Single line summary + + :Receives: * **arg1** (:class:`str`) -- Extended + description of arg1 + * **arg2** (:class:`int`) -- Extended + description of arg2 + """ + ), ( + """ + Single line summary + Yield ----- str @@ -2402,3 +2543,29 @@ class TestNumpyDocstring: actual = numpy_docstring._escape_args_and_kwargs(name) assert actual == expected + + def test_pep526_annotations(self): + if sys.version_info >= (3, 6): + # test class attributes annotations + config = Config( + napoleon_attr_annotations=True + ) + actual = str(NumpyDocstring(cleandoc(PEP526NumpyClass.__doc__), config, app=None, what="class", + obj=PEP526NumpyClass)) + expected = """\ +Sample class with PEP 526 annotations and numpy docstring + +.. attribute:: attr1 + + Attr1 description + + :type: int + +.. attribute:: attr2 + + Attr2 description + + :type: str +""" + print(actual) + assert expected == actual diff --git a/tests/test_ext_napoleon_iterators.py b/tests/test_ext_napoleon_iterators.py index 456287e7c..45b558794 100644 --- a/tests/test_ext_napoleon_iterators.py +++ b/tests/test_ext_napoleon_iterators.py @@ -5,7 +5,7 @@ Tests for :mod:`sphinx.ext.napoleon.iterators` module. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_ext_todo.py b/tests/test_ext_todo.py index 7b4fdeabe..b6fb2549c 100644 --- a/tests/test_ext_todo.py +++ b/tests/test_ext_todo.py @@ -4,7 +4,7 @@ Test sphinx.ext.todo extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 3d9ea27d7..d75fb7196 100644 --- a/tests/test_ext_viewcode.py +++ b/tests/test_ext_viewcode.py @@ -4,7 +4,7 @@ Test sphinx.ext.viewcode extension. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -49,6 +49,27 @@ def test_viewcode(app, status, warning): ' """
    \n') in result +@pytest.mark.sphinx('epub', testroot='ext-viewcode') +def test_viewcode_epub_default(app, status, warning): + app.builder.build_all() + + assert not (app.outdir / '_modules/spam/mod1.xhtml').exists() + + result = (app.outdir / 'index.xhtml').read_text() + assert result.count('href="_modules/spam/mod1.xhtml#func1"') == 0 + + +@pytest.mark.sphinx('epub', testroot='ext-viewcode', + confoverrides={'viewcode_enable_epub': True}) +def test_viewcode_epub_enabled(app, status, warning): + app.builder.build_all() + + assert (app.outdir / '_modules/spam/mod1.xhtml').exists() + + result = (app.outdir / 'index.xhtml').read_text() + assert result.count('href="_modules/spam/mod1.xhtml#func1"') == 2 + + @pytest.mark.sphinx(testroot='ext-viewcode', tags=['test_linkcode']) def test_linkcode(app, status, warning): app.builder.build(['objects']) diff --git a/tests/test_highlighting.py b/tests/test_highlighting.py index 19ca8bde9..a51e25688 100644 --- a/tests/test_highlighting.py +++ b/tests/test_highlighting.py @@ -4,7 +4,7 @@ Test the Pygments highlighting bridge. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 d96733e4a..00f599595 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -5,7 +5,7 @@ Test message patching for internationalization purposes. Runs the text builder in the test root. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_locale.py b/tests/test_locale.py index a744a5ff7..5d3dbf17f 100644 --- a/tests/test_locale.py +++ b/tests/test_locale.py @@ -4,7 +4,7 @@ Test locale. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_markup.py b/tests/test_markup.py index a2bcb2dc1..f8fff1c2d 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -4,7 +4,7 @@ Test various Sphinx-specific markup extensions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -158,7 +158,8 @@ def get_verifier(verify, verify_re): ':pep:`8`', ('

    PEP 8

    '), - ('\\index{Python Enhancement Proposals@\\spxentry{Python Enhancement Proposals}' + ('\\sphinxAtStartPar\n' + '\\index{Python Enhancement Proposals@\\spxentry{Python Enhancement Proposals}' '!PEP 8@\\spxentry{PEP 8}}\\sphinxhref{http://www.python.org/dev/peps/pep-0008}' '{\\sphinxstylestrong{PEP 8}}') ), @@ -169,7 +170,8 @@ def get_verifier(verify, verify_re): ('

    ' 'PEP 8#id1

    '), - ('\\index{Python Enhancement Proposals@\\spxentry{Python Enhancement Proposals}' + ('\\sphinxAtStartPar\n' + '\\index{Python Enhancement Proposals@\\spxentry{Python Enhancement Proposals}' '!PEP 8\\#id1@\\spxentry{PEP 8\\#id1}}\\sphinxhref' '{http://www.python.org/dev/peps/pep-0008\\#id1}' '{\\sphinxstylestrong{PEP 8\\#id1}}') @@ -180,7 +182,8 @@ def get_verifier(verify, verify_re): ':rfc:`2324`', ('

    RFC 2324

    '), - ('\\index{RFC@\\spxentry{RFC}!RFC 2324@\\spxentry{RFC 2324}}' + ('\\sphinxAtStartPar\n' + '\\index{RFC@\\spxentry{RFC}!RFC 2324@\\spxentry{RFC 2324}}' '\\sphinxhref{http://tools.ietf.org/html/rfc2324.html}' '{\\sphinxstylestrong{RFC 2324}}') ), @@ -191,7 +194,8 @@ def get_verifier(verify, verify_re): ('

    ' 'RFC 2324#id1

    '), - ('\\index{RFC@\\spxentry{RFC}!RFC 2324\\#id1@\\spxentry{RFC 2324\\#id1}}' + ('\\sphinxAtStartPar\n' + '\\index{RFC@\\spxentry{RFC}!RFC 2324\\#id1@\\spxentry{RFC 2324\\#id1}}' '\\sphinxhref{http://tools.ietf.org/html/rfc2324.html\\#id1}' '{\\sphinxstylestrong{RFC 2324\\#id1}}') ), @@ -201,14 +205,14 @@ def get_verifier(verify, verify_re): '``code sample``', ('

    ' 'code   sample

    '), - r'\\sphinxcode{\\sphinxupquote{code sample}}', + r'\\sphinxAtStartPar\n\\sphinxcode{\\sphinxupquote{code sample}}', ), ( # interpolation of arrows in menuselection 'verify', ':menuselection:`a --> b`', ('

    a \N{TRIANGULAR BULLET} b

    '), - '\\sphinxmenuselection{a \\(\\rightarrow\\) b}', + '\\sphinxAtStartPar\n\\sphinxmenuselection{a \\(\\rightarrow\\) b}', ), ( # interpolation of ampersands in menuselection @@ -216,7 +220,9 @@ def get_verifier(verify, verify_re): ':menuselection:`&Foo -&&- &Bar`', ('

    Foo ' '-&- Bar

    '), - r'\sphinxmenuselection{\sphinxaccelerator{F}oo \sphinxhyphen{}\&\sphinxhyphen{} \sphinxaccelerator{B}ar}', + ('\\sphinxAtStartPar\n' + r'\sphinxmenuselection{\sphinxaccelerator{F}oo \sphinxhyphen{}' + r'\&\sphinxhyphen{} \sphinxaccelerator{B}ar}'), ), ( # interpolation of ampersands in guilabel @@ -224,38 +230,51 @@ def get_verifier(verify, verify_re): ':guilabel:`&Foo -&&- &Bar`', ('

    Foo ' '-&- Bar

    '), - r'\sphinxguilabel{\sphinxaccelerator{F}oo \sphinxhyphen{}\&\sphinxhyphen{} \sphinxaccelerator{B}ar}', + ('\\sphinxAtStartPar\n' + r'\sphinxguilabel{\sphinxaccelerator{F}oo \sphinxhyphen{}\&\sphinxhyphen{} \sphinxaccelerator{B}ar}'), ), ( # no ampersands in guilabel 'verify', ':guilabel:`Foo`', '

    Foo

    ', - r'\sphinxguilabel{Foo}', + '\\sphinxAtStartPar\n\\sphinxguilabel{Foo}', ), ( # kbd role 'verify', ':kbd:`space`', '

    space

    ', - '\\sphinxkeyboard{\\sphinxupquote{space}}', + '\\sphinxAtStartPar\n\\sphinxkeyboard{\\sphinxupquote{space}}', ), ( # kbd role 'verify', ':kbd:`Control+X`', - ('

    ' + ('

    ' 'Control' '+' 'X' '

    '), - '\\sphinxkeyboard{\\sphinxupquote{Control+X}}', + '\\sphinxAtStartPar\n\\sphinxkeyboard{\\sphinxupquote{Control+X}}', + ), + ( + # kbd role + 'verify', + ':kbd:`Alt+^`', + ('

    ' + 'Alt' + '+' + '^' + '

    '), + ('\\sphinxAtStartPar\n' + '\\sphinxkeyboard{\\sphinxupquote{Alt+\\textasciicircum{}}}'), ), ( # kbd role 'verify', ':kbd:`M-x M-s`', - ('

    ' + ('

    ' 'M' '-' 'x' @@ -264,7 +283,24 @@ def get_verifier(verify, verify_re): '-' 's' '

    '), - '\\sphinxkeyboard{\\sphinxupquote{M\\sphinxhyphen{}x M\\sphinxhyphen{}s}}', + ('\\sphinxAtStartPar\n' + '\\sphinxkeyboard{\\sphinxupquote{M\\sphinxhyphen{}x M\\sphinxhyphen{}s}}'), + ), + ( + # kbd role + 'verify', + ':kbd:`-`', + '

    -

    ', + ('\\sphinxAtStartPar\n' + '\\sphinxkeyboard{\\sphinxupquote{\\sphinxhyphen{}}}'), + ), + ( + # kbd role + 'verify', + ':kbd:`Caps Lock`', + '

    Caps Lock

    ', + ('\\sphinxAtStartPar\n' + '\\sphinxkeyboard{\\sphinxupquote{Caps Lock}}'), ), ( # non-interpolation of dashes in option role @@ -272,14 +308,15 @@ def get_verifier(verify, verify_re): ':option:`--with-option`', ('

    ' '--with-option

    $'), - r'\\sphinxcode{\\sphinxupquote{\\sphinxhyphen{}\\sphinxhyphen{}with\\sphinxhyphen{}option}}$', + (r'\\sphinxAtStartPar\n' + r'\\sphinxcode{\\sphinxupquote{\\sphinxhyphen{}\\sphinxhyphen{}with\\sphinxhyphen{}option}}$'), ), ( # verify smarty-pants quotes 'verify', '"John"', '

    “John”

    ', - "“John”", + "\\sphinxAtStartPar\n“John”", ), ( # ... but not in literal text @@ -287,21 +324,21 @@ def get_verifier(verify, verify_re): '``"John"``', ('

    ' '"John"

    '), - '\\sphinxcode{\\sphinxupquote{"John"}}', + '\\sphinxAtStartPar\n\\sphinxcode{\\sphinxupquote{"John"}}', ), ( # verify classes for inline roles 'verify', ':manpage:`mp(1)`', '

    mp(1)

    ', - '\\sphinxstyleliteralemphasis{\\sphinxupquote{mp(1)}}', + '\\sphinxAtStartPar\n\\sphinxstyleliteralemphasis{\\sphinxupquote{mp(1)}}', ), ( # correct escaping in normal mode 'verify', 'Γ\\\\∞$', None, - 'Γ\\textbackslash{}\\(\\infty\\)\\$', + '\\sphinxAtStartPar\nΓ\\textbackslash{}\\(\\infty\\)\\$', ), ( # in verbatim code fragments @@ -317,7 +354,7 @@ def get_verifier(verify, verify_re): 'verify_re', '`test `_', None, - r'\\sphinxhref{https://www.google.com/~me/}{test}.*', + r'\\sphinxAtStartPar\n\\sphinxhref{https://www.google.com/~me/}{test}.*', ), ( # description list: simple @@ -338,8 +375,12 @@ def get_verifier(verify, verify_re): # glossary (description list): multiple terms 'verify', '.. glossary::\n\n term1\n term2\n description', - ('
    \n
    term1
    ' - '
    term2
    description
    \n
    '), + ('
    \n' + '
    term1
    ' + '
    term2
    ' + '
    description
    \n
    '), None, ), ]) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index e345488e6..465386430 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -4,7 +4,7 @@ Test our handling of metadata in files with bibliographic metadata. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_parser.py b/tests/test_parser.py index f76e004d6..df22750e3 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -4,7 +4,7 @@ Tests parsers module. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_project.py b/tests/test_project.py index 906a52bfe..03fa1d49f 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -4,7 +4,7 @@ Tests project module. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_pycode.py b/tests/test_pycode.py index 3d69e53cb..bbcc42a52 100644 --- a/tests/test_pycode.py +++ b/tests/test_pycode.py @@ -4,7 +4,7 @@ Test pycode. - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_pycode_ast.py b/tests/test_pycode_ast.py index bbff64dd0..e80062351 100644 --- a/tests/test_pycode_ast.py +++ b/tests/test_pycode_ast.py @@ -4,7 +4,7 @@ Test pycode.ast - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_pycode_parser.py b/tests/test_pycode_parser.py index 71847f04f..9169315fc 100644 --- a/tests/test_pycode_parser.py +++ b/tests/test_pycode_parser.py @@ -4,7 +4,7 @@ Test pycode.parser. - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 26f1d0d9e..11086f5f6 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -4,7 +4,7 @@ Test the sphinx.quickstart module. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_roles.py b/tests/test_roles.py index d1ed7f439..553a50853 100644 --- a/tests/test_roles.py +++ b/tests/test_roles.py @@ -4,7 +4,7 @@ Test sphinx.roles - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_search.py b/tests/test_search.py index 5f8e46302..1ceb6d55b 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -4,7 +4,7 @@ Test the search index builder. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 14a687ada..52dad14fd 100644 --- a/tests/test_setup_command.py +++ b/tests/test_setup_command.py @@ -4,7 +4,7 @@ Test setup_command for distutils. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_smartquotes.py b/tests/test_smartquotes.py index 6276e6a74..0cc0681dc 100644 --- a/tests/test_smartquotes.py +++ b/tests/test_smartquotes.py @@ -4,7 +4,7 @@ Test smart quotes. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 f2c1d563b..3a7c58216 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -4,7 +4,7 @@ Test templating. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 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 f1cb5a3e8..0dded0725 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -4,7 +4,7 @@ Test the Theme class. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -81,7 +81,7 @@ def test_js_source(app, status, warning): jquery_src = (app.outdir / '_static' / 'jquery-{v}.js'.format(v=v)).read_text() assert 'jQuery JavaScript Library v{v}'.format(v=v) in jquery_src, msg - v = '1.3.1' + v = '1.12.0' msg = 'underscore.js version does not match to {v}'.format(v=v) underscore_min = (app.outdir / '_static' / 'underscore.js').read_text() assert 'Underscore.js {v}'.format(v=v) in underscore_min, msg diff --git a/tests/test_toctree.py b/tests/test_toctree.py index 38886769a..7a9f7a578 100644 --- a/tests/test_toctree.py +++ b/tests/test_toctree.py @@ -4,7 +4,7 @@ Test the HTML builder and check output against XPath. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re diff --git a/tests/test_transforms_post_transforms_code.py b/tests/test_transforms_post_transforms_code.py index 2e5da05ae..449cba78a 100644 --- a/tests/test_transforms_post_transforms_code.py +++ b/tests/test_transforms_post_transforms_code.py @@ -2,7 +2,7 @@ test_transforms_post_transforms_code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util.py b/tests/test_util.py index 2d03ed89a..9b25aac58 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -4,7 +4,7 @@ Tests util functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_docstrings.py b/tests/test_util_docstrings.py index e6ae32306..543feca2a 100644 --- a/tests/test_util_docstrings.py +++ b/tests/test_util_docstrings.py @@ -4,7 +4,7 @@ Test sphinx.util.docstrings. - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_docutils.py b/tests/test_util_docutils.py index 41be9b8d1..9c4e373cf 100644 --- a/tests/test_util_docutils.py +++ b/tests/test_util_docutils.py @@ -4,7 +4,7 @@ Tests util.utils functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_fileutil.py b/tests/test_util_fileutil.py index e9c80d5b6..fd2f186c2 100644 --- a/tests/test_util_fileutil.py +++ b/tests/test_util_fileutil.py @@ -4,7 +4,7 @@ Tests sphinx.util.fileutil functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index 7e3a7e2c9..180350e86 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -4,7 +4,7 @@ Test i18n util. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -87,6 +87,12 @@ def test_format_date(): assert i18n.format_date(format, date=datet) == 'Feb 7, 2016, 5:11:17 AM' assert i18n.format_date(format, date=date) == 'Feb 7, 2016' + # timezone + format = '%Z' + assert i18n.format_date(format, date=datet) == 'UTC' + format = '%z' + assert i18n.format_date(format, date=datet) == '+0000' + @pytest.mark.xfail(os.name != 'posix', reason="Path separators don't match on windows") def test_get_filename_for_language(app): diff --git a/tests/test_util_images.py b/tests/test_util_images.py index dd2044dff..bf71f4c49 100644 --- a/tests/test_util_images.py +++ b/tests/test_util_images.py @@ -4,7 +4,7 @@ Test images util. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 17070f4d6..863f44921 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -4,7 +4,7 @@ Tests util.inspect functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_inventory.py b/tests/test_util_inventory.py index 2183313e1..f12ae5011 100644 --- a/tests/test_util_inventory.py +++ b/tests/test_util_inventory.py @@ -4,7 +4,7 @@ Test inventory util functions. - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ @@ -14,20 +14,20 @@ from io import BytesIO from sphinx.ext.intersphinx import InventoryFile -inventory_v1 = '''\ +inventory_v1 = b'''\ # Sphinx inventory version 1 # Project: foo # Version: 1.0 module mod foo.html module.cls class foo.html -'''.encode() +''' -inventory_v2 = '''\ +inventory_v2 = b'''\ # Sphinx inventory version 2 # Project: foo # Version: 2.0 # The remainder of this file is compressed with zlib. -'''.encode() + zlib.compress('''\ +''' + zlib.compress(b'''\ module1 py:module 0 foo.html#module-module1 Long Module desc module2 py:module 0 foo.html#module-$ - module1.func py:function 1 sub/foo.html#$ - @@ -47,16 +47,16 @@ foo.bar js:class 1 index.html#foo.bar - foo.bar.baz js:method 1 index.html#foo.bar.baz - foo.bar.qux js:data 1 index.html#foo.bar.qux - a term including:colon std:term -1 glossary.html#term-a-term-including-colon - -'''.encode()) +''') -inventory_v2_not_having_version = '''\ +inventory_v2_not_having_version = b'''\ # Sphinx inventory version 2 # Project: foo # Version: # The remainder of this file is compressed with zlib. -'''.encode() + zlib.compress('''\ +''' + zlib.compress(b'''\ module1 py:module 0 foo.html#module-module1 Long Module desc -'''.encode()) +''') def test_read_inventory_v1(): diff --git a/tests/test_util_logging.py b/tests/test_util_logging.py index 36034cd92..aedbe9e64 100644 --- a/tests/test_util_logging.py +++ b/tests/test_util_logging.py @@ -4,7 +4,7 @@ Test logging util. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_matching.py b/tests/test_util_matching.py index 9d90f5a90..008dc6267 100644 --- a/tests/test_util_matching.py +++ b/tests/test_util_matching.py @@ -4,7 +4,7 @@ Tests sphinx.util.matching functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from sphinx.util.matching import Matcher, compile_matchers diff --git a/tests/test_util_nodes.py b/tests/test_util_nodes.py index dd41022f3..cb2ae70a8 100644 --- a/tests/test_util_nodes.py +++ b/tests/test_util_nodes.py @@ -4,7 +4,7 @@ Tests uti.nodes functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from textwrap import dedent diff --git a/tests/test_util_pycompat.py b/tests/test_util_pycompat.py index 67e61bb58..0e395fa7a 100644 --- a/tests/test_util_pycompat.py +++ b/tests/test_util_pycompat.py @@ -4,7 +4,7 @@ Tests sphinx.util.pycompat functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_rst.py b/tests/test_util_rst.py index 58cf46fe8..7e14eb3f7 100644 --- a/tests/test_util_rst.py +++ b/tests/test_util_rst.py @@ -4,7 +4,7 @@ Tests sphinx.util.rst functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_template.py b/tests/test_util_template.py index 58ebd7fbb..bc325ac9d 100644 --- a/tests/test_util_template.py +++ b/tests/test_util_template.py @@ -4,7 +4,7 @@ Tests sphinx.util.template functions. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index 882c652cc..9da505814 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -4,12 +4,13 @@ Tests util.typing functions. - :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import sys from numbers import Integral +from struct import Struct from typing import (Any, Callable, Dict, Generator, List, NewType, Optional, Tuple, TypeVar, Union) @@ -43,6 +44,7 @@ def test_restify(): assert restify(str) == ":class:`str`" assert restify(None) == ":obj:`None`" assert restify(Integral) == ":class:`numbers.Integral`" + assert restify(Struct) == ":class:`struct.Struct`" assert restify(Any) == ":obj:`Any`" @@ -109,6 +111,12 @@ def test_restify_type_hints_alias(): assert restify(MyTuple) == ":class:`Tuple`\\ [:class:`str`, :class:`str`]" # type: ignore +@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.') +def test_restify_type_ForwardRef(): + from typing import ForwardRef # type: ignore + assert restify(ForwardRef("myint")) == ":class:`myint`" + + def test_restify_broken_type_hints(): assert restify(BrokenType) == ':class:`tests.test_util_typing.BrokenType`' @@ -118,6 +126,7 @@ def test_stringify(): assert stringify(str) == "str" assert stringify(None) == "None" assert stringify(Integral) == "numbers.Integral" + assert restify(Struct) == ":class:`struct.Struct`" assert stringify(Any) == "Any" diff --git a/tests/test_versioning.py b/tests/test_versioning.py index e5f5474cf..33fb045ce 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -4,7 +4,7 @@ Test the versioning implementation. - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_writer_latex.py b/tests/test_writer_latex.py index 160b14fa1..c3f6a622b 100644 --- a/tests/test_writer_latex.py +++ b/tests/test_writer_latex.py @@ -4,7 +4,7 @@ Test the LaTeX writer - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/utils.py b/tests/utils.py index eb2c40c52..9430c9beb 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,5 @@ import contextlib import http.server -import os import pathlib import ssl import threading @@ -48,18 +47,3 @@ def create_server(thread_class): http_server = create_server(HttpServerThread) https_server = create_server(HttpsServerThread) - - -@contextlib.contextmanager -def modify_env(**env): - original_env = os.environ.copy() - for k, v in env.items(): - os.environ[k] = v - try: - yield - finally: - for k in env: - try: - os.environ[k] = original_env[k] - except KeyError: - os.unsetenv(k) diff --git a/tox.ini b/tox.ini index dbb705a3a..21a0faec3 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ deps = extras = test setenv = - PYTHONWARNINGS = all,ignore::ImportWarning:importlib._bootstrap_external,ignore::DeprecationWarning:site,ignore::DeprecationWarning:distutils + PYTHONWARNINGS = all,ignore::ImportWarning:importlib._bootstrap_external,ignore::DeprecationWarning:site,ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:pip._vendor.packaging.version PYTEST_ADDOPTS = {env:PYTEST_ADDOPTS:} --color yes commands= python -X dev -m pytest --durations 25 {posargs} diff --git a/utils/release-checklist b/utils/release-checklist index dad83c850..477ddcbbe 100644 --- a/utils/release-checklist +++ b/utils/release-checklist @@ -4,7 +4,7 @@ Release checklist for stable releases ------------------- -* open https://github.com/sphinx-doc/sphinx/actions?query=branch:X.Y.x and all tests has passed +* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:X.Y.x" and all tests has passed * Run ``git fetch; git status`` and check nothing changed * ``python utils/bump_version.py X.Y.Z`` * Check diff by ``git diff`` @@ -18,17 +18,17 @@ for stable releases * ``python utils/bump_version.py --in-develop X.Y.Zb0`` (ex. 1.5.3b0) * Check diff by ``git diff`` * ``git commit -am 'Bump version'`` -* ``git push origin X.Y --tags`` -* ``git checkout master`` -* ``git merge X.Y`` -* ``git push origin master`` +* ``git push origin X.Y.x --tags`` +* ``git checkout X.x`` +* ``git merge X.Y.x`` +* ``git push origin X.x`` * Add new version/milestone to tracker categories * Write announcement and send to sphinx-dev, sphinx-users and python-announce for first beta releases ----------------------- -* open https://github.com/sphinx-doc/sphinx/actions?query=branch:master and all tests has passed +* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:master" and all tests has passed * Run ``git fetch; git status`` and check nothing changed * Run ``python setup.py extract_messages`` * Run ``(cd sphinx/locale; tx push -s)`` @@ -43,10 +43,10 @@ for first beta releases * ``python utils/bump_version.py --in-develop X.Y.0b2`` (ex. 1.6.0b2) * Check diff by ``git diff`` * ``git commit -am 'Bump version'`` -* ``git checkout -b X.Y`` -* ``git push origin X.Y --tags`` +* ``git checkout -b X.x`` +* ``git push origin X.x --tags`` * ``git checkout master`` -* ``git merge X.Y`` +* ``git merge X.x`` * ``python utils/bump_version.py --in-develop A.B.0b0`` (ex. 1.7.0b0) * Check diff by ``git diff`` * ``git commit -am 'Bump version'`` @@ -58,7 +58,7 @@ for first beta releases for other beta releases ----------------------- -* open https://github.com/sphinx-doc/sphinx/actions?query=branch:X.x and all tests has passed +* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:X.x" and all tests has passed * Run ``git fetch; git status`` and check nothing changed * ``python utils/bump_version.py X.Y.0bN`` * Check diff by ``git diff`` @@ -71,9 +71,9 @@ for other beta releases * ``python utils/bump_version.py --in-develop X.Y.0bM`` (ex. 1.6.0b3) * Check diff by `git diff`` * ``git commit -am 'Bump version'`` -* ``git push origin X.Y --tags`` +* ``git push origin X.x --tags`` * ``git checkout master`` -* ``git merge X.Y`` +* ``git merge X.x`` * ``git push origin master`` * Add new version/milestone to tracker categories * Write announcement and send to sphinx-dev, sphinx-users and python-announce @@ -81,7 +81,7 @@ for other beta releases for major releases ------------------ -* open https://github.com/sphinx-doc/sphinx/actions?query=branch:X.x and all tests has passed +* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:X.x" and all tests has passed * Run ``git fetch; git status`` and check nothing changed * Run ``(cd sphinx/locale; tx pull -a -f)`` * Run ``python setup.py compile_catalog`` @@ -99,9 +99,9 @@ for major releases * ``python utils/bump_version.py --in-develop X.Y.1b0`` (ex. 1.6.1b0) * Check diff by ``git diff`` * ``git commit -am 'Bump version'`` -* ``git push origin X.Y --tags`` +* ``git push origin X.x --tags`` * ``git checkout master`` -* ``git merge X.Y`` +* ``git merge X.x`` * ``git push origin master`` * open https://github.com/sphinx-doc/sphinx/settings/branches and make ``A.B`` branch *not* protected * ``git checkout A.B`` (checkout old stable)