diff options
261 files changed, 6378 insertions, 6971 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d1fd7dc6e..3cdda918c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,16 +4,12 @@ on: [push, pull_request] jobs: ubuntu: - runs-on: ${{ matrix.os }} + runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: - name: [py35, py36, py37, py38, py39] - os: [ubuntu-16.04] + name: [py36, py37, py38, py39, py310-dev] include: - - name: py35 - python: 3.5 - docutils: du12 - name: py36 python: 3.6 docutils: du13 @@ -29,8 +25,7 @@ jobs: coverage: "--cov ./ --cov-append --cov-config setup.cfg" - name: py310-dev python: 3.10-dev - docutils: du17 - os: ubuntu-latest # required + docutils: du16 env: PYTEST_ADDOPTS: ${{ matrix.coverage }} @@ -1,3 +1,139 @@ +Release 4.0.0 (in development) +============================== + +Dependencies +------------ + +* Drop python 3.5 support +* Drop docutils 0.12 and 0.13 support +* LaTeX: add ``tex-gyre`` font dependency + +Incompatible changes +-------------------- + +* #8539: autodoc: info-field-list is generated into the class description when + ``autodoc_typehints='description'`` and ``autoclass_content='class'`` set +* domain: The ``Index`` class becomes subclasses of ``abc.ABC`` to indicate + methods that must be overrided in the concrete classes +* #4826: py domain: The structure of python objects is changed. A boolean value + is added to indicate that the python object is canonical one +* #7425: MathJax: The MathJax was changed from 2 to 3. Users using a custom + MathJax configuration may have to set the old MathJax path or update their + configuration for version 3. See :mod:`sphinx.ext.mathjax`. +* #7784: i18n: The msgid for alt text of image is changed +* #5560: napoleon: :confval:`napoleon_use_param` also affect "other parameters" + section +* #7996: manpage: Make a section directory on build manpage by default (see + :confval:`man_make_section_directory`) +* #7849: html: Change the default setting of + :confval:`html_codeblock_linenos_style` to ``'inline'`` +* #8380: html search: search results are wrapped with ``<p>`` instead of + ``<div>`` +* html theme: Move a script tag for documentation_options.js in + basic/layout.html to ``script_files`` variable +* html theme: Move CSS tags in basic/layout.html to ``css_files`` variable +* #8915: html theme: Emit a warning for sphinx_rtd_theme-0.2.4 or older +* #8508: LaTeX: uplatex becomes a default setting of latex_engine for Japanese + documents +* #5977: py domain: ``:var:``, ``:cvar:`` and ``:ivar:`` fields do not create + cross-references +* #4550: The ``align`` attribute of ``figure`` and ``table`` nodes becomes + ``None`` by default instead of ``'default'`` +* #8769: LaTeX refactoring: split sphinx.sty into multiple files and rename + some auxiliary files created in ``latex`` build output repertory +* #8937: Use explicit title instead of <no title> +* #8487: The :file: option for csv-table directive now recognizes an absolute + path as a relative path from source directory + +Deprecated +---------- + +* :confval:`html_codeblock_linenos_style` +* ``favicon`` and ``logo`` variable in HTML templates +* ``sphinx.directives.patches.CSVTable`` +* ``sphinx.directives.patches.ListTable`` +* ``sphinx.directives.patches.RSTTable`` +* ``sphinx.ext.autodoc.directive.DocumenterBridge.filename_set`` +* ``sphinx.ext.autodoc.directive.DocumenterBridge.warn()`` +* ``sphinx.registry.SphinxComponentRegistry.get_source_input()`` +* ``sphinx.registry.SphinxComponentRegistry.source_inputs`` +* ``sphinx.transforms.FigureAligner`` +* ``sphinx.util.pycompat.convert_with_2to3()`` +* ``sphinx.util.pycompat.execfile_()`` +* ``sphinx.util.smartypants`` +* ``sphinx.util.typing.DirectiveOption`` + +Features added +-------------- + +* #8924: autodoc: Support ``bound`` argument for TypeVar +* #7383: autodoc: Support typehints for properties +* #5603: autodoc: Allow to refer to a python class using its canonical name + when the class has two different names; a canonical name and an alias name +* #8539: autodoc: Add :confval:`autodoc_typehints_description_target` to control + the behavior of ``autodoc_typehints=description`` +* #8841: autodoc: :confval:`autodoc_docstring_signature` will continue to look + for multiple signature lines without backslash character +* #7549: autosummary: Enable :confval:`autosummary_generate` by default +* #4826: py domain: Add ``:canonical:`` option to python directives to describe + the location where the object is defined +* #7199: py domain: Add :confval:`python_use_unqualified_type_names` to suppress + the module name of the python reference if it can be resolved (experimental) +* #7068: py domain: Add :rst:dir:`py:property` directive to describe a property +* #7784: i18n: The alt text for image is translated by default (without + :confval:`gettext_additional_targets` setting) +* #2018: html: :confval:`html_favicon` and :confval:`html_logo` now accept URL + for the image +* #8070: html search: Support searching for 2characters word +* #9036: html theme: Allow to inherite the search page +* #8938: imgconverter: Show the error of the command availability check +* #7830: Add debug logs for change detection of sources and templates +* #8201: Emit a warning if toctree contains duplicated entries +* #8326: ``master_doc`` is now renamed to :confval:`root_doc` +* #8942: C++, add support for the C++20 spaceship operator, ``<=>``. +* #7199: A new node, ``sphinx.addnodes.pending_xref_condition`` has been added. + It can be used to choose appropriate content of the reference by conditions. + +Bugs fixed +---------- + +* #8917: autodoc: Raises a warning if function has wrong __globals__ value +* #8415: autodoc: a TypeVar imported from other module is not resolved (in + Python 3.7 or above) +* #8992: autodoc: Failed to resolve types.TracebackType type annotation +* #8905: html: html_add_permalinks=None and html_add_permalinks="" are ignored +* #8380: html search: Paragraphs in search results are not identified as ``<p>`` +* #8915: html theme: The translation of sphinx_rtd_theme does not work +* #8342: Emit a warning if a unknown domain is given for directive or role (ex. + ``:unknown:doc:``) +* #7241: LaTeX: No wrapping for ``cpp:enumerator`` +* #8711: LaTeX: backticks in code-blocks trigger latexpdf build warning (and font + change) with late TeXLive 2019 +* #8253: LaTeX: Figures with no size defined get overscaled (compared to images + with size explicitly set in pixels) (fixed for ``'pdflatex'/'lualatex'`` only) +* #8881: LaTeX: The depth of bookmarks panel in PDF is not enough for navigation +* #8874: LaTeX: the fix to two minor Pygments LaTeXFormatter output issues ignore + Pygments style +* #8925: LaTeX: 3.5.0 ``verbatimmaxunderfull`` setting does not work as + expected +* #8980: LaTeX: missing line break in ``\pysigline`` +* #8995: LaTeX: legacy ``\pysiglinewithargsret`` does not compute correctly + available horizontal space and should use a ragged right style +* #9009: LaTeX: "release" value with underscore leads to invalid LaTeX +* #8911: C++: remove the longest matching prefix in + :confval:`cpp_index_common_prefix` instead of the first that matches. +* C, properly reject function declarations when a keyword is used + as parameter name. +* #8933: viewcode: Failed to create back-links on parallel build +* #8960: C and C++, fix rendering of (member) function pointer types in + function parameter lists. +* C++, fix linking of names in array declarators, pointer to member + (function) declarators, and in the argument to ``sizeof...``. +* C, fix linking of names in array declarators. + +Testing +-------- + Release 3.5.4 (in development) ============================== @@ -16,6 +152,8 @@ Features added Bugs fixed ---------- +* #8870: The style of toctree captions has been changed with docutils-0.17 + Testing -------- @@ -100,6 +238,9 @@ Features added * #8775: autodoc: Support type union operator (PEP-604) in Python 3.10 or above * #8297: autodoc: Allow to extend :confval:`autodoc_default_options` via directive options +* #759: autodoc: Add a new configuration :confval:`autodoc_preserve_defaults` as + an experimental feature. It preserves the default argument values of + functions in source code and keep them not evaluated for readability. * #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()` @@ -12,19 +12,20 @@ interesting examples. Documentation using the alabaster theme --------------------------------------- +* `AIOHTTP <https://docs.aiohttp.org/>`__ * `Alabaster <https://alabaster.readthedocs.io/>`__ * `Blinker <https://pythonhosted.org/blinker/>`__ * `Calibre <https://manual.calibre-ebook.com/>`__ -* `Click <http://click.pocoo.org/>`__ (customized) +* `Click <https://click.palletsprojects.com/>`__ (customized) * `coala <https://docs.coala.io/>`__ (customized) * `CodePy <https://documen.tician.de/codepy/>`__ * `Eve <https://docs.python-eve.org/>`__ (Python REST API framework) * `Fabric <https://docs.fabfile.org/>`__ * `Fityk <https://fityk.nieto.pl/>`__ -* `Flask <http://flask.pocoo.org/docs/>`__ +* `Flask <https://flask.palletsprojects.com/>`__ * `Flask-OpenID <https://pythonhosted.org/Flask-OpenID/>`__ * `Invoke <https://docs.pyinvoke.org/>`__ -* `Jinja <http://jinja.pocoo.org/docs/>`__ +* `Jinja <https://jinja.palletsprojects.com/>`__ * `Lino <http://www.lino-framework.org/>`__ (customized) * `marbl <https://getmarbl.readthedocs.io/>`__ * `MDAnalysis <https://www.mdanalysis.org/docs/>`__ (customized) @@ -41,7 +42,8 @@ Documentation using the alabaster theme * `Spyder <https://docs.spyder-ide.org/>`__ (customized) * `Tablib <http://docs.python-tablib.org/>`__ * `urllib3 <https://urllib3.readthedocs.io/>`__ (customized) -* `Werkzeug <http://werkzeug.pocoo.org/docs/>`__ (customized) +* `Werkzeug <https://werkzeug.palletsprojects.com/>`__ +* `Write the Docs <https://writethedocs-www.readthedocs.io/>`__ Documentation using the classic theme ------------------------------------- @@ -132,7 +134,7 @@ Documentation using the sphinxdoc theme Documentation using the nature theme ------------------------------------ -* `Alembic <http://alembic.zzzcomputing.com/>`__ +* `Alembic <https://alembic.sqlalchemy.org/>`__ * `Cython <http://docs.cython.org/>`__ * `easybuild <https://easybuild.readthedocs.io/>`__ * `jsFiddle <http://doc.jsfiddle.net/>`__ @@ -141,6 +143,7 @@ Documentation using the nature theme * `MapServer <https://mapserver.org/>`__ (customized) * `Pandas <https://pandas.pydata.org/pandas-docs/stable/>`__ * `pyglet <https://pyglet.readthedocs.io/>`__ (customized) +* `PyWavelets <https://pywavelets.readthedocs.io/>`__ * `Setuptools <https://setuptools.readthedocs.io/>`__ * `Spring Python <https://docs.spring.io/spring-python/1.2.x/sphinx/html/>`__ * `StatsModels <https://www.statsmodels.org/>`__ (customized) @@ -156,6 +159,7 @@ Documentation using another builtin theme * `PyPubSub <https://pypubsub.readthedocs.io/>`__ (bizstyle) * `Pylons <https://docs.pylonsproject.org/projects/pylons-webframework/>`__ (pyramid) * `Pyramid web framework <https://docs.pylonsproject.org/projects/pyramid/>`__ (pyramid) +* `RxDock <https://www.rxdock.org/documentation/html/devel/>`__ * `Sphinx <http://www.sphinx-doc.org/>`__ (sphinx13) :-) * `Valence <https://docs.valence.desire2learn.com/>`__ (haiku, customized) @@ -219,6 +223,7 @@ Documentation using sphinx_rtd_theme * `Mailman <http://docs.list.org/>`__ * `MathJax <https://docs.mathjax.org/>`__ * `MDTraj <http://mdtraj.org/latest/>`__ (customized) +* `Mesa 3D <https://docs.mesa3d.org/>`__ * `micca - MICrobial Community Analysis <https://micca.readthedocs.io/>`__ * `MicroPython <https://docs.micropython.org/>`__ * `Minds <https://www.minds.org/docs/>`__ (customized) @@ -227,6 +232,7 @@ Documentation using sphinx_rtd_theme * `mod_wsgi <https://modwsgi.readthedocs.io/>`__ * `MoinMoin <https://moin-20.readthedocs.io/>`__ * `Mopidy <https://docs.mopidy.com/>`__ +* `mpi4py <https://mpi4py.readthedocs.io/>`__ * `MyHDL <http://docs.myhdl.org/>`__ * `Nextflow <https://www.nextflow.io/docs/latest/index.html>`__ * `NICOS <https://forge.frm2.tum.de/nicos/doc/nicos-master/>`__ (customized) @@ -247,6 +253,7 @@ Documentation using sphinx_rtd_theme * `PyVISA <https://pyvisa.readthedocs.io/>`__ * `pyvista <https://docs.pyvista.org/>`__ * `Read The Docs <https://docs.readthedocs.io/>`__ +* `ROCm Platform <https://rocm-documentation.readthedocs.io/>`__ * `Free your information from their silos (French) <http://redaction-technique.org/>`__ (customized) * `Releases Sphinx extension <https://releases.readthedocs.io/>`__ * `Qtile <http://docs.qtile.org/>`__ @@ -254,7 +261,7 @@ Documentation using sphinx_rtd_theme * `QuTiP <http://qutip.org/docs/latest/>`__ * `Satchmo <http://docs.satchmoproject.com/>`__ * `Scapy <https://scapy.readthedocs.io/>`__ -* `SimGrid <http://simgrid.gforge.inria.fr/simgrid/latest/doc/>`__ +* `SimGrid <https://simgrid.org/doc/latest/>`__ * `SimPy <https://simpy.readthedocs.io/>`__ * `six <https://six.readthedocs.io/>`__ * `SlamData <https://newdocs.slamdata.com>`__ @@ -283,7 +290,6 @@ Documentation using sphinx_rtd_theme * `Web Application Attack and Audit Framework (w3af) <http://docs.w3af.org/>`__ * `Weblate <https://docs.weblate.org/>`__ * `x265 <https://x265.readthedocs.io/>`__ -* `ZeroNet <https://zeronet.readthedocs.io/>`__ * `Zulip <https://zulip.readthedocs.io/>`__ Documentation using sphinx_bootstrap_theme @@ -319,6 +325,7 @@ Documentation using a custom theme or integrated in a website * `Doctrine <https://www.doctrine-project.org/>`__ * `Enterprise Toolkit for Acrobat products <https://www.adobe.com/devnet-docs/acrobatetk/>`__ * `FreeFEM <https://doc.freefem.org/introduction/>`__ +* `fmt <https://fmt.dev/>`__ * `Gameduino <http://excamera.com/sphinx/gameduino/>`__ * `gensim <https://radimrehurek.com/gensim/>`__ * `GeoServer <http://docs.geoserver.org/>`__ @@ -328,6 +335,7 @@ Documentation using a custom theme or integrated in a website * `H2O.ai <http://docs.h2o.ai/>`__ * `Heka <https://hekad.readthedocs.io/>`__ * `Istihza (Turkish Python documentation project) <https://belgeler.yazbel.com/python-istihza/>`__ +* `JupyterHub <https://jupyterhub.readthedocs.io/>`__ * `Kombu <http://docs.kombu.me/>`__ * `Lasso <http://lassoguide.com/>`__ * `Mako <http://docs.makotemplates.org/>`__ diff --git a/MANIFEST.in b/MANIFEST.in index 1114ca19f..7c2f852a8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -15,6 +15,7 @@ include sphinx-quickstart.py include sphinx-apidoc.py include tox.ini include sphinx/locale/.tx/config +include sphinx/py.typed recursive-include sphinx/templates * recursive-include sphinx/texinputs * diff --git a/bindep.txt b/bindep.txt index dfee52c28..0c599b9f2 100644 --- a/bindep.txt +++ b/bindep.txt @@ -12,10 +12,12 @@ texlive-luatex85 [platform:rpm] texlive-anyfontsize [platform:rpm] texlive-ctablestack [platform:rpm] texlive-gnu-freefont [platform:rpm] +texlive-tex-gyre [platform:rpm] latexmk [platform:rpm] texlive-latex-recommended [platform:dpkg] texlive-fonts-recommended [platform:dpkg] +tex-gyre [platform:dpkg] texlive-latex-extra [platform:dpkg] texlive-luatex [platform:dpkg] latexmk [platform:dpkg] diff --git a/doc/_static/conf.py.txt b/doc/_static/conf.py.txt index 5420e2717..844451fd8 100644 --- a/doc/_static/conf.py.txt +++ b/doc/_static/conf.py.txt @@ -43,7 +43,7 @@ source_suffix = '.rst' # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +root_doc = 'index' # General information about the project. project = u'test' @@ -252,7 +252,7 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'test.tex', u'test Documentation', + (root_doc, 'test.tex', u'test Documentation', u'test', 'manual'), ] @@ -283,7 +283,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'test', u'test Documentation', + (root_doc, 'test', u'test Documentation', [author], 1) ] @@ -298,7 +298,7 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'test', u'test Documentation', + (root_doc, 'test', u'test Documentation', author, 'test', 'One line description of project.', 'Miscellaneous'), ] diff --git a/doc/changes.rst b/doc/changes.rst index 829c7f7ed..3fe902404 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -10,7 +10,6 @@ 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 a3d2f5f12..3eb01b5ea 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -9,7 +9,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram'] -master_doc = 'contents' +root_doc = 'contents' templates_path = ['_templates'] exclude_patterns = ['_build'] @@ -59,27 +59,14 @@ latex_documents = [('contents', 'sphinx.tex', 'Sphinx Documentation', latex_logo = '_static/sphinx.png' latex_elements = { 'fontenc': r'\usepackage[LGR,X2,T1]{fontenc}', - 'fontpkg': r''' -\usepackage[sc]{mathpazo} -\usepackage[scaled]{helvet} -\usepackage{courier} -\substitutefont{LGR}{\rmdefault}{cmr} -\substitutefont{LGR}{\sfdefault}{cmss} -\substitutefont{LGR}{\ttdefault}{cmtt} -\substitutefont{X2}{\rmdefault}{cmr} -\substitutefont{X2}{\sfdefault}{cmss} -\substitutefont{X2}{\ttdefault}{cmtt} -''', '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{tocdepth}{3}% depth of what main TOC shows (3=subsubsection) \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''' \IfFileExists{\jobname.ind} @@ -91,6 +78,7 @@ latex_show_urls = 'footnote' latex_use_xindy = True autodoc_member_order = 'groupwise' +autosummary_generate = False todo_include_todos = True extlinks = {'duref': ('http://docutils.sourceforge.net/docs/ref/rst/' 'restructuredtext.html#%s', ''), diff --git a/doc/development/tutorials/autodoc_ext.rst b/doc/development/tutorials/autodoc_ext.rst new file mode 100644 index 000000000..d8905710c --- /dev/null +++ b/doc/development/tutorials/autodoc_ext.rst @@ -0,0 +1,142 @@ +.. _autodoc_ext_tutorial: + +Developing autodoc extension for IntEnum +======================================== + +The objective of this tutorial is to create an extension that adds +support for new type for autodoc. This autodoc extension will format +the ``IntEnum`` class from Python standard library. (module ``enum``) + +Overview +-------- + +We want the extension that will create auto-documentation for IntEnum. +``IntEnum`` is the integer enum class from standard library ``enum`` module. + +Currently this class has no special auto documentation behavior. + +We want to add following to autodoc: + +* A new ``autointenum`` directive that will document the ``IntEnum`` class. +* The generated documentation will have all the enum possible values + with names. +* The ``autointenum`` directive will have an option ``:hex:`` which will + cause the integers be printed in hexadecimal form. + + +Prerequisites +------------- + +We need the same setup as in :doc:`the previous extensions <todo>`. This time, +we will be putting out extension in a file called :file:`autodoc_intenum.py`. +The :file:`my_enums.py` will contain the sample enums we will document. + +Here is an example of the folder structure you might obtain: + +.. code-block:: text + + └── source + ├── _ext + │ └── autodoc_intenum.py + ├── conf.py + ├── index.rst + └── my_enums.py + + +Writing the extension +--------------------- + +Start with ``setup`` function for the extension. + +.. literalinclude:: examples/autodoc_intenum.py + :language: python + :linenos: + :pyobject: setup + + +The :meth:`~Sphinx.setup_extension` method will pull the autodoc extension +because our new extension depends on autodoc. :meth:`~Sphinx.add_autodocumenter` +is the method that registers our new auto documenter class. + +We want to import certain objects from the autodoc extension: + +.. literalinclude:: examples/autodoc_intenum.py + :language: python + :linenos: + :lines: 1-7 + + +There are several different documenter classes such as ``MethodDocumenter`` +or ``AttributeDocumenter`` available in the autodoc extension but +our new class is the subclass of ``ClassDocumenter`` which a +documenter class used by autodoc to document classes. + +This is the definition of our new the auto-documenter class: + +.. literalinclude:: examples/autodoc_intenum.py + :language: python + :linenos: + :pyobject: IntEnumDocumenter + + +Important attributes of the new class: + +**objtype** + This attribute determines the ``auto`` directive name. In + this case the auto directive will be ``autointenum``. + +**directivetype** + This attribute sets the generated directive name. In + this example the generated directive will be ``.. :py:class::``. + +**priority** + the larger the number the higher is the priority. We want our + documenter be higher priority than the parent. + +**option_spec** + option specifications. We copy the parent class options and + add a new option *hex*. + + +Overridden members: + +**can_document_member** + This member is important to override. It should + return *True* when the passed object can be documented by this class. + +**add_directive_header** + This method generates the directive header. We add + **:final:** directive option. Remember to call **super** or no directive + will be generated. + +**add_content** + This method generates the body of the class documentation. + After calling the super method we generate lines for enum description. + + +Using the extension +------------------- + +You can now use the new autodoc directive to document any ``IntEnum``. + +For example, you have the following ``IntEnum``: + +.. code-block:: python + :caption: my_enums.py + + class Colors(IntEnum): + """Colors enumerator""" + NONE = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + +This will be the documentation file with auto-documentation directive: + +.. code-block:: rst + :caption: index.rst + + .. autointenum:: my_enums.Colors + + diff --git a/doc/development/tutorials/examples/autodoc_intenum.py b/doc/development/tutorials/examples/autodoc_intenum.py new file mode 100644 index 000000000..7fb85d066 --- /dev/null +++ b/doc/development/tutorials/examples/autodoc_intenum.py @@ -0,0 +1,52 @@ +from enum import IntEnum +from typing import Any, Optional + +from docutils.statemachine import StringList + +from sphinx.application import Sphinx +from sphinx.ext.autodoc import ClassDocumenter, bool_option + + +class IntEnumDocumenter(ClassDocumenter): + objtype = 'intenum' + directivetype = 'class' + priority = 10 + ClassDocumenter.priority + option_spec = dict(ClassDocumenter.option_spec) + option_spec['hex'] = bool_option + + @classmethod + def can_document_member(cls, + member: Any, membername: str, + isattr: bool, parent: Any) -> bool: + return isinstance(member, IntEnum) + + def add_directive_header(self, sig: str) -> None: + super().add_directive_header(sig) + self.add_line(' :final:', self.get_sourcename()) + + def add_content(self, + more_content: Optional[StringList], + no_docstring: bool = False + ) -> None: + + super().add_content(more_content, no_docstring) + + source_name = self.get_sourcename() + enum_object: IntEnum = self.object + use_hex = self.options.hex + self.add_line('', source_name) + + for enum_value in enum_object: + the_value_name = enum_value.name + the_value_value = enum_value.value + if use_hex: + the_value_value = hex(the_value_value) + + self.add_line( + f"**{the_value_name}**: {the_value_value}", source_name) + self.add_line('', source_name) + + +def setup(app: Sphinx) -> None: + app.setup_extension('sphinx.ext.autodoc') # Require autodoc extension + app.add_autodocumenter(IntEnumDocumenter) diff --git a/doc/development/tutorials/index.rst b/doc/development/tutorials/index.rst index be126b3ca..a7eee4899 100644 --- a/doc/development/tutorials/index.rst +++ b/doc/development/tutorials/index.rst @@ -13,3 +13,5 @@ Refer to the following tutorials to get started with extension development. helloworld todo recipe + autodoc_ext + diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index 4585df949..41318e9d6 100644 --- a/doc/extdev/appapi.rst +++ b/doc/extdev/appapi.rst @@ -167,26 +167,33 @@ type for that event:: 4. event.env-before-read-docs(app, env, docnames) for docname in docnames: - 5. event.env-purge-doc(app, env, docname) + 5. event.env-purge-doc(app, env, docname) + if doc changed and not removed: 6. source-read(app, docname, source) - 7. run source parsers: text -> docutils.document (parsers can be added with the app.add_source_parser() API) - 8. apply transforms (by priority): docutils.document -> docutils.document - - event.doctree-read(app, doctree) is called in the middly of transforms, + 7. run source parsers: text -> docutils.document + - parsers can be added with the app.add_source_parser() API + 8. apply transforms based on priority: docutils.document -> docutils.document + - event.doctree-read(app, doctree) is called in the middle of transforms, transforms come before/after this event depending on their priority. - 9. (if running in parallel mode, for each process) event.env-merged-info(app, env, docnames, other) + + 9. event.env-merge-info(app, env, docnames, other) + - if running in parallel mode, this event will be emitted for each process + 10. event.env-updated(app, env) 11. event.env-get-updated(app, env) 12. event.env-check-consistency(app, env) # The updated-docs list can be builder dependent, but generally includes all new/changed documents, # plus any output from `env-get-updated`, and then all "parent" documents in the ToC tree - # For builders that output a single page, they are first joined into a single doctree before post-transforms/doctree-resolved + # For builders that output a single page, they are first joined into a single doctree before post-transforms + # or the doctree-resolved event is emitted for docname in updated-docs: 13. apply post-transforms (by priority): docutils.document -> docutils.document 14. event.doctree-resolved(app, doctree, docname) - - (for any reference node that fails to resolve) event.missing-reference(env, node, contnode) - - (for any reference node that fails to resolve) event.warn-missing-reference(domain, node) + - In the event that any reference nodes fail to resolve, the following may emit: + - event.missing-reference(env, node, contnode) + - event.warn-missing-reference(domain, node) 15. Generate output files 16. event.build-finished(app, exception) diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index ccf1e79da..9e17b9fb4 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -22,6 +22,71 @@ The following is a list of deprecated interfaces. - (will be) Removed - Alternatives + * - ``favicon`` variable in HTML templates + - 4.0 + - TBD + - ``favicon_url`` + + * - ``logo`` variable in HTML templates + - 4.0 + - TBD + - ``logo_url`` + + * - ``sphinx.directives.patches.ListTable`` + - 4.0 + - 6.0 + - ``docutils.parsers.rst.diretives.tables.ListSVTable`` + + * - ``sphinx.directives.patches.RSTTable`` + - 4.0 + - 6.0 + - ``docutils.parsers.rst.diretives.tables.RSTTable`` + + * - ``sphinx.ext.autodoc.directive.DocumenterBridge.filename_set`` + - 4.0 + - 6.0 + - ``sphinx.ext.autodoc.directive.DocumenterBridge.record_dependencies`` + + * - ``sphinx.ext.autodoc.directive.DocumenterBridge.warn()`` + - 4.0 + - 6.0 + - :ref:`logging-api` + + * - ``sphinx.registry.SphinxComponentRegistry.get_source_input()`` + - 4.0 + - 6.0 + - N/A + + * - ``sphinx.registry.SphinxComponentRegistry.source_inputs`` + - 4.0 + - 6.0 + - N/A + + * - ``sphinx.transforms.FigureAligner`` + - 4.0 + - 6.0 + - N/A + + * - ``sphinx.util.pycompat.convert_with_2to3()`` + - 4.0 + - 6.0 + - N/A + + * - ``sphinx.util.pycompat.execfile_()`` + - 4.0 + - 6.0 + - N/A + + * - ``sphinx.util.smartypants`` + - 4.0 + - 6.0 + - ``docutils.utils.smartyquotes`` + + * - ``sphinx.util.typing.DirectiveOption`` + - 4.0 + - 6.0 + - N/A + * - pending_xref node for viewcode extension - 3.5 - 5.0 diff --git a/doc/extdev/nodes.rst b/doc/extdev/nodes.rst index 5d8272eae..3976de4c7 100644 --- a/doc/extdev/nodes.rst +++ b/doc/extdev/nodes.rst @@ -37,8 +37,8 @@ New inline nodes .. autoclass:: index .. autoclass:: pending_xref +.. autoclass:: pending_xref_condition .. autoclass:: literal_emphasis -.. autoclass:: abbreviation .. autoclass:: download_reference Special nodes diff --git a/doc/latex.rst b/doc/latex.rst index 446731964..ff1af9286 100644 --- a/doc/latex.rst +++ b/doc/latex.rst @@ -139,57 +139,33 @@ Keys that you may want to override include: ``babel``, not ``polyglossia``. ``'fontpkg'`` - Font package inclusion. The default of ``'\\usepackage{times}'`` uses Times - for text, Helvetica for sans serif and Courier for monospace. - - In order to support occasional Cyrillic (физика частиц) or Greek - letters (Σωματιδιακή φυσική) in a document whose language is - English or a Latin European one, the default set-up is enhanced (only for - ``'pdflatex'`` engine) to do: - - .. code-block:: latex - - \substitutefont{LGR}{\rmdefault}{cmr} - \substitutefont{LGR}{\sfdefault}{cmss} - \substitutefont{LGR}{\ttdefault}{cmtt} - \substitutefont{X2}{\rmdefault}{cmr} - \substitutefont{X2}{\sfdefault}{cmss} - \substitutefont{X2}{\ttdefault}{cmtt} - - This is activated only under the condition that the ``'fontenc'`` key is - configured to load the ``LGR`` (Greek) and/or ``X2`` (Cyrillic) - pdflatex-font encodings (if the :confval:`language` is set to a Cyrillic - language, this ``'fontpkg'`` key must be used as "times" package has no - direct support for it; then keep only ``LGR`` lines from the above, if - support is needed for Greek in the text). - - The ``\substitutefont`` command is from the eponymous LaTeX package, which - is loaded by Sphinx if needed (on Ubuntu Xenial it is part of - ``texlive-latex-extra`` which is a Sphinx requirement). - - Only if the document actually does contain Unicode Greek letters (in text) - or Cyrillic letters, will the above default set-up cause additional - requirements for the PDF build. On Ubuntu Xenial, these are the - ``texlive-lang-greek``, ``texlive-lang-cyrillic``, and (with the above - choice of fonts) the ``cm-super`` (or ``cm-super-minimal``) packages. - - For ``'xelatex'`` and ``'lualatex'``, the default is to use the FreeFont - family: this OpenType font family supports both Cyrillic and Greek scripts - and is available as separate Ubuntu Xenial package ``fonts-freefont-otf``. - It is not necessary to install the much larger ``texlive-fonts-extra`` - package. - - ``'platex'`` (Japanese documents) engine supports individual Cyrillic and - Greek letters with no need of extra user set-up. - - Default: ``'\\usepackage{times}'`` (or ``''`` when using a Cyrillic script) + Font package inclusion. The default is:: + + r"""\usepackage{tgtermes} + \usepackage{tgheros} + \renewcommand\ttdefault{txtt} + """ + + For ``'xelatex'`` and ``'lualatex'`` however the default is to use + the GNU FreeFont. .. versionchanged:: 1.2 Defaults to ``''`` when the :confval:`language` uses the Cyrillic script. .. versionchanged:: 2.0 - Added support for individual Greek and Cyrillic letters: + Incorporates some font substitution commands to help support occasional + Greek or Cyrillic in a document using ``'pdflatex'`` engine. + + .. versionchanged:: 4.0.0 + - The font substitution commands added at ``2.0`` have been moved + to the ``'fontsubstitution'`` key, as their presence here made + it complicated for user to customize the value of ``'fontpkg'``. + - The default font setting has changed: it still uses Times and + Helvetica clones for serif and sans serif, but via better, more + complete TeX fonts and associated LaTeX packages. The + monospace font has been changed to better match the Times clone. + ``'fncychap'`` Inclusion of the "fncychap" package (which makes fancy chapter titles), @@ -320,37 +296,28 @@ Keys that don't need to be overridden unless in special cases are: .. versionadded:: 1.2 ``'fontenc'`` - "fontenc" package inclusion. + Customize this from its default ``'\\usepackage[T1]{fontenc}'`` to: - If ``'pdflatex'`` is the :confval:`latex_engine`, one can add ``LGR`` - for support of Greek letters in the document, and also ``X2`` (or - ``T2A``) for Cyrillic letters, like this: + - ``'\\usepackage[X2,T1]{fontenc}'`` if you need occasional + Cyrillic letters (физика частиц), - .. code-block:: latex + - ``'\\usepackage[LGR,T1]{fontenc}'`` if you need occasional + Greek letters (Σωματιδιακή φυσική). - r'\usepackage[LGR,X2,T1]{fontenc}' + Use ``[LGR,X2,T1]`` rather if both are needed. .. attention:: - If Greek is main language, do not use this key. Since Sphinx 2.2.1, - ``xelatex`` will be used automatically as :confval:`latex_engine`. - Formerly, Sphinx did not support producing PDF via LaTeX with Greek as - main language. - - Prior to 2.0, Unicode Greek letters were escaped to use LaTeX math - mark-up. This is not the case anymore, and the above must be used - (only in case of ``'pdflatex'`` engine) if the source contains such - Unicode Greek. + - Do not use this key for a :confval:`latex_engine` other than + ``'pdflatex'``. - On Ubuntu xenial, packages ``texlive-lang-greek`` and ``cm-super`` - (for the latter, only if the ``'fontpkg'`` setting is left to its - default) are needed for ``LGR`` to work. In place of ``cm-super`` - one can install smaller ``cm-super-minimal``, but it requires the - LaTeX document to execute ``\usepackage[10pt]{type1ec}`` before - loading ``fontenc``. Thus, use this key with this extra at its - start if needed. + - If Greek is main language, do not use this key. Since Sphinx 2.2.1, + ``xelatex`` will be used automatically as :confval:`latex_engine`. - Default: ``'\\usepackage[T1]{fontenc}'`` + - The TeX installation may need some extra packages. For example, + on Ubuntu xenial, packages ``texlive-lang-greek`` and ``cm-super`` + are needed for ``LGR`` to work. And ``texlive-lang-cyrillic`` and + ``cm-super`` are needed for support of Cyrillic. .. versionchanged:: 1.5 Defaults to ``'\\usepackage{fontspec}'`` when @@ -367,32 +334,37 @@ Keys that don't need to be overridden unless in special cases are: .. versionchanged:: 2.0 Detection of ``LGR``, ``T2A``, ``X2`` to trigger support of - occasional Greek or Cyrillic (``'pdflatex'`` only, as this support - is provided natively by ``'platex'`` and only requires suitable - font with ``'xelatex'/'lualatex'``). + occasional Greek or Cyrillic letters (``'pdflatex'``). .. versionchanged:: 2.3.0 - ``'xelatex'`` also executes + ``'xelatex'`` executes ``\defaultfontfeatures[\rmfamily,\sffamily]{}`` in order to avoid contractions of ``--`` into en-dash or transforms of straight quotes into curly ones in PDF (in non-literal text paragraphs) despite :confval:`smartquotes` being set to ``False``. -``'textgreek'`` - This is needed for ``pdflatex`` to support Unicode input of Greek - letters such as φύσις. Expert users may want to load the ``textalpha`` - package with its option ``normalize-symbols``. +``'fontsubstitution'`` + Ignored if ``'fontenc'`` was not configured to use ``LGR`` or ``X2`` (or + ``T2A``). In case ``'fontpkg'`` key is configured for usage with some + TeX fonts known to be available in the ``LGR`` or ``X2`` encodings, set + this one to be the empty string. Else leave to its default. - .. hint:: + Ignored with :confval:`latex_engine` other than ``'pdflatex'``. - Unicode Greek (but no further Unicode symbols) in :rst:dir:`math` - can be supported by ``'pdflatex'`` from setting this key to - ``r'\usepackage{textalpha,alphabeta}'``. Then ``:math:`α``` (U+03B1) - will render as :math:`\alpha`. For wider Unicode support in math - input, see the discussion of :confval:`latex_engine`. + .. versionadded:: 4.0.0 - With ``'platex'`` (Japanese), ``'xelatex'`` or ``'lualatex'``, this - key is ignored. +``'textgreek'`` + For the support of occasional Greek letters. + + It is ignored with ``'platex'``, ``'xelatex'`` or ``'lualatex'`` as + :confval:`latex_engine` and defaults to either the empty string or + to ``'\\usepackage{textalpha}'`` for ``'pdflatex'`` depending on + whether the ``'fontenc'`` key was used with ``LGR`` or not. Only + expert LaTeX users may want to customize this key. + + It can also be used as ``r'\usepackage{textalpha,alphabeta}'`` to let + ``'pdflatex'`` support Greek Unicode input in :rst:dir:`math` context. + For example ``:math:`α``` (U+03B1) will render as :math:`\alpha`. Default: ``'\\usepackage{textalpha}'`` or ``''`` if ``fontenc`` does not include the ``LGR`` option. @@ -517,19 +489,25 @@ Keys that don't need to be overridden unless in special cases are: Default: ``'\\printindex'`` ``'fvset'`` - Customization of ``fancyvrb`` LaTeX package. The default value of - ``'\\fvset{fontsize=\\small}'`` is used to adjust for the large character - width of the monospace font, used in code-blocks. You may need to modify - this if you use custom fonts. + Customization of ``fancyvrb`` LaTeX package. + + The default value is ``'\\fvset{fontsize=auto}'`` which means that the + font size will adjust correctly if a code-block ends up in a footnote. + You may need to modify this if you use custom fonts: + ``'\\fvset{fontsize=\\small}'`` if the monospace font is Courier-like. - Default: ``'\\fvset{fontsize=\\small}'`` + Default: ``'\\fvset{fontsize=auto}'`` .. versionadded:: 1.8 .. versionchanged:: 2.0 - Due to new default font choice for ``'xelatex'`` and ``'lualatex'`` - (FreeFont), Sphinx does ``\\fvset{fontsize=\\small}`` also with these - engines (and not ``\\fvset{fontsize=auto}``). + For ``'xelatex'`` and ``'lualatex'`` defaults to + ``'\\fvset{fontsize=\\small}'`` as this + is adapted to the relative widths of the FreeFont family. + + .. versionchanged:: 4.0.0 + Changed default for ``'pdflatex'``. Previously it was using + ``'\\fvset{fontsize=\\small}'``. Keys that are set by other options and therefore should not be overridden are: @@ -600,6 +578,17 @@ e.g ``'sphinxsetup': "verbatimwrapslines=false"``. If setting the boolean key to ``true``, ``=true`` is optional. Spaces around the commas and equal signs are ignored, spaces inside LaTeX macros may be significant. +Do not use quotes to enclose values, whether numerical or strings. + +``bookmarksdepth`` + Controls the depth of the collapsable bookmarks panel in the PDF. + May be either a number (e.g. ``3``) or a LaTeX sectioning name (e.g. + ``subsubsection``, i.e. without backslash). + For details, refer to the ``hyperref`` LaTeX docs. + + Default: ``5`` + + .. versionadded:: 4.0.0 .. _latexsphinxsetuphmargin: @@ -917,9 +906,36 @@ macros may be significant. LaTeX macros and environments ----------------------------- -Here are some macros from the package file :file:`sphinx.sty` and class files -:file:`sphinxhowto.cls`, :file:`sphinxmanual.cls`, which have public names -thus allowing redefinitions. Check the respective files for the defaults. +The "LaTeX package" file :file:`sphinx.sty` loads various components +providing support macros (aka commands), and environments, which are used in +the mark-up produced on output from the ``latex`` builder, before conversion +to ``pdf`` via the LaTeX toolchain. Also the "LaTeX class" files +:file:`sphinxhowto.cls` and :file:`sphinxmanual.cls` define or customize some +environments. All of these files can be found in the latex build repertory. + +Some of these provide facilities not available from pre-existing LaTeX +packages and work around LaTeX limitations with lists, table cells, verbatim +rendering, footnotes, etc... + +Others simply define macros with public names to make overwriting their +defaults easy via user-added contents to the preamble. We will survey most of +those public names here, but defaults have to be looked at in their respective +definition files. + +.. hint:: + + Sphinx LaTeX support code is split across multiple smaller-sized files. + Rather than adding code to the preamble via + `latex_elements <latex_elements_confval_>`_\ [``'preamble'``] it is + also possible to replace entirely one of the component files of Sphinx + LaTeX code with a custom version, simply by including a modified copy in + the project source and adding the filename to the + :confval:`latex_additional_files` list. Check the LaTeX build repertory + for the filenames and contents. + +.. versionchanged:: 4.0.0 + split of :file:`sphinx.sty` into multiple smaller units, to facilitate + customization of many aspects simultaneously. .. _latex-macros: diff --git a/doc/man/sphinx-apidoc.rst b/doc/man/sphinx-apidoc.rst index 725d2f169..3ce5b4523 100644 --- a/doc/man/sphinx-apidoc.rst +++ b/doc/man/sphinx-apidoc.rst @@ -145,7 +145,7 @@ These options are used when :option:`--full` is specified: * ``module.rst_t`` * ``package.rst_t`` * ``toc.rst_t`` - * ``master_doc.rst_t`` + * ``root_doc.rst_t`` * ``conf.py_t`` * ``Makefile_t`` * ``Makefile.new_t`` diff --git a/doc/man/sphinx-build.rst b/doc/man/sphinx-build.rst index 1ef0e07dc..ca16b265a 100644 --- a/doc/man/sphinx-build.rst +++ b/doc/man/sphinx-build.rst @@ -19,13 +19,12 @@ files, including ``conf.py``. :program:`sphinx-build` can create documentation in different formats. A format is selected by specifying the builder name on the command line; it defaults to HTML. Builders can also perform other tasks related to -documentation processing. +documentation processing. For a list of available builders, refer to +:option:`sphinx-build -b`. By default, everything that is outdated is built. Output only for selected files can be built by specifying individual filenames. -For a list of available options, refer to :option:`sphinx-build -b`. - Options ------- diff --git a/doc/man/sphinx-quickstart.rst b/doc/man/sphinx-quickstart.rst index 520a420ce..16fc66560 100644 --- a/doc/man/sphinx-quickstart.rst +++ b/doc/man/sphinx-quickstart.rst @@ -72,7 +72,7 @@ Options .. option:: --master=MASTER - Master document name. (see :confval:`master_doc`). + Master document name. (see :confval:`root_doc`). .. rubric:: Extension Options @@ -149,7 +149,7 @@ Options sphinx project files generated by quickstart. Following Jinja2 template files are allowed: - * ``master_doc.rst_t`` + * ``root_doc.rst_t`` * ``conf.py_t`` * ``Makefile_t`` * ``Makefile.new_t`` diff --git a/doc/templating.rst b/doc/templating.rst index 548f8b8d9..61b714bfa 100644 --- a/doc/templating.rst +++ b/doc/templating.rst @@ -274,7 +274,19 @@ in the future. .. data:: favicon - The path to the HTML favicon in the static path, or ``''``. + The path to the HTML favicon in the static path, or URL to the favicon, or + ``''``. + + .. deprecated:: 4.0 + + Recommend to use ``favicon_url`` instead. + +.. data:: favicon_url + + The relative path to the HTML favicon image from the current document, or + URL to the favicon, or ``''``. + + .. versionadded:: 4.0 .. data:: file_suffix @@ -297,11 +309,35 @@ in the future. .. data:: logo - The path to the HTML logo image in the static path, or ``''``. + The path to the HTML logo image in the static path, or URL to the logo, or + ``''``. + + .. deprecated:: 4.0 + + Recommend to use ``logo_url`` instead. + +.. data:: logo_url + + The relative path to the HTML logo image from the current document, or URL + to the logo, or ``''``. + + .. versionadded:: 4.0 .. data:: master_doc - The value of :confval:`master_doc`, for usage with :func:`pathto`. + Same as :data:`root_doc`. + + .. versionchanged:: 4.0 + + Renamed to ``root_doc``. + +.. data:: root_doc + + The value of :confval:`root_doc`, for usage with :func:`pathto`. + + .. versionchanged:: 4.0 + + Renamed from ``master_doc``. .. data:: pagename diff --git a/doc/usage/advanced/intl.rst b/doc/usage/advanced/intl.rst index 67d5e10e5..03b77fb6b 100644 --- a/doc/usage/advanced/intl.rst +++ b/doc/usage/advanced/intl.rst @@ -306,7 +306,7 @@ Contributing to Sphinx reference translation The recommended way for new contributors to translate Sphinx reference is to join the translation team on Transifex. -There is `sphinx translation page`_ for Sphinx (master) documentation. +There is a `sphinx translation page`_ for Sphinx (master) documentation. 1. Login to transifex_ service. 2. Go to `sphinx translation page`_. @@ -314,6 +314,8 @@ There is `sphinx translation page`_ for Sphinx (master) documentation. 4. Wait acceptance by transifex sphinx translation maintainers. 5. (After acceptance) Translate on transifex. +Detail is here: https://docs.transifex.com/getting-started-1/translators + .. rubric:: Footnotes .. [1] See the `GNU gettext utilities diff --git a/doc/usage/builders/index.rst b/doc/usage/builders/index.rst index c45a8062f..74853fee9 100644 --- a/doc/usage/builders/index.rst +++ b/doc/usage/builders/index.rst @@ -179,6 +179,7 @@ The builder's "name" must be given to the **-b** command-line option of * ``texlive-latex-recommended`` * ``texlive-fonts-recommended`` + * ``tex-gyre`` (if :confval:`latex_engine` is ``'pdflatex'``) * ``texlive-latex-extra`` * ``latexmk`` (this is a Sphinx requirement on GNU/Linux and MacOS X for functioning of ``make latexpdf``) @@ -186,17 +187,14 @@ The builder's "name" must be given to the **-b** command-line option of Additional packages are needed in some circumstances (see the discussion of the ``'fontpkg'`` key of :confval:`latex_elements` for more information): - * to support occasional Cyrillic letters or words, and a fortiori if - :confval:`language` is set to a Cyrillic language, the package - ``texlive-lang-cyrillic`` is required, and, with unmodified ``'fontpkg'``, - also ``cm-super`` or ``cm-super-minimal``, - * to support occasional Greek letters or words (in text, not in - :rst:dir:`math` directive contents), ``texlive-lang-greek`` is required, - and, with unmodified ``'fontpkg'``, also ``cm-super`` or - ``cm-super-minimal``, - * for ``'xelatex'`` or ``'lualatex'`` (see :confval:`latex_engine`), - ``texlive-xetex`` resp. ``texlive-luatex``, and, if leaving unchanged - ``'fontpkg'``, ``fonts-freefont-otf``. + * ``texlive-lang-cyrillic`` for Cyrillic (even individual letters), and, + ``cm-super`` or ``cm-super-minimal`` (if default fonts), + * ``texlive-lang-greek`` for Greek (even individual letters), and, + ``cm-super`` or ``cm-super-minimal`` (if default fonts), + * ``texlive-xetex`` if :confval:`latex_engine` is ``'xelatex'``, + * ``texlive-luatex`` if :confval:`latex_engine` is ``'lualatex'``, + * ``fonts-freefont-otf`` if :confval:`latex_engine` is ``'xelatex'`` + or ``'lualatex'``. The testing of Sphinx LaTeX is done on Ubuntu xenial whose TeX distribution is based on a TeXLive 2015 snapshot dated March 2016. @@ -207,6 +205,9 @@ The builder's "name" must be given to the **-b** command-line option of .. versionchanged:: 2.0 Formerly, testing had been done on Ubuntu trusty (TeXLive 2013). + .. versionchanged:: 4.0.0 + TeX Gyre fonts dependency for the default LaTeX font configuration. + .. note:: Since 1.6, ``make latexpdf`` uses ``latexmk`` (not on Windows). This diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index c2250eb7d..989ce8737 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -183,11 +183,20 @@ General configuration .. confval:: master_doc - The document name of the "master" document, that is, the document that + Same as :confval:`root_doc`. + + .. versionchanged:: 4.0 + Renamed ``master_doc`` to ``master_doc``. + +.. confval:: root_doc + + The document name of the "root" document, that is, the document that contains the root :rst:dir:`toctree` directive. Default is ``'index'``. .. versionchanged:: 2.0 The default is changed to ``'index'`` from ``'contents'``. + .. versionchanged:: 4.0 + Renamed ``master_doc`` from ``master_doc``. .. confval:: exclude_patterns @@ -479,11 +488,10 @@ General configuration .. confval:: smartquotes_action - This string, for use with Docutils ``0.14`` or later, customizes the Smart - Quotes transform. See the file :file:`smartquotes.py` at the `Docutils - repository`__ for details. The default ``'qDe'`` educates normal **q**\ - uote characters ``"``, ``'``, em- and en-**D**\ ashes ``---``, ``--``, and - **e**\ llipses ``...``. + This string customizes the Smart Quotes transform. See the file + :file:`smartquotes.py` at the `Docutils repository`__ for details. The + default ``'qDe'`` educates normal **q**\ uote characters ``"``, ``'``, + em- and en-**D**\ ashes ``---``, ``--``, and **e**\ llipses ``...``. .. versionadded:: 1.6.6 @@ -834,13 +842,16 @@ documentation on :ref:`intl` for details. :literal-block: literal blocks (``::`` annotation and ``code-block`` directive) :doctest-block: doctest block :raw: raw content - :image: image/figure uri and alt + :image: image/figure uri For example: ``gettext_additional_targets = ['literal-block', 'image']``. The default is ``[]``. .. versionadded:: 1.3 + .. versionchanged:: 4.0 + + The alt text for image is translated by default. .. confval:: figure_language_filename @@ -970,10 +981,15 @@ that use Sphinx's HTMLWriter class. The style of line numbers for code-blocks. - * ``'table'`` -- display line numbers using ``<table>`` tag (default) - * ``'inline'`` -- display line numbers using ``<span>`` tag + * ``'table'`` -- display line numbers using ``<table>`` tag + * ``'inline'`` -- display line numbers using ``<span>`` tag (default) .. versionadded:: 3.2 + .. versionchanged:: 4.0 + + It defaults to ``'inline'``. + + .. deprecated:: 4.0 .. confval:: html_context @@ -986,26 +1002,32 @@ that use Sphinx's HTMLWriter class. .. confval:: html_logo If given, this must be the name of an image file (path relative to the - :term:`configuration directory`) that is the logo of the docs. It is placed - at the top of the sidebar; its width should therefore not exceed 200 pixels. - Default: ``None``. + :term:`configuration directory`) that is the logo of the docs, or URL that + points an image file for the logo. It is placed at the top of the sidebar; + its width should therefore not exceed 200 pixels. Default: ``None``. .. versionadded:: 0.4.1 The image file will be copied to the ``_static`` directory of the output HTML, but only if the file does not already exist there. + .. versionchanged:: 4.0 + Also accepts the URL for the logo file. + .. confval:: html_favicon If given, this must be the name of an image file (path relative to the - :term:`configuration directory`) that is the favicon of the docs. Modern - browsers use this as the icon for tabs, windows and bookmarks. It should - be a Windows-style icon file (``.ico``), which is 16x16 or 32x32 - pixels large. Default: ``None``. + :term:`configuration directory`) that is the favicon of the docs, or URL that + points an image file for the favicon. Modern browsers use this as the icon + for tabs, windows and bookmarks. It should be a Windows-style icon file + (``.ico``), which is 16x16 or 32x32 pixels large. Default: ``None``. .. versionadded:: 0.4 The image file will be copied to the ``_static`` directory of the output HTML, but only if the file does not already exist there. + .. versionchanged:: 4.0 + Also accepts the URL for the favicon. + .. confval:: html_css_files A list of CSS files. The entry must be a *filename* string or a tuple @@ -1479,8 +1501,7 @@ that use Sphinx's HTMLWriter class. .. confval:: html_experimental_html5_writer - Output is processed with HTML5 writer. This feature needs docutils 0.13 or - newer. Default is ``False``. + Output is processed with HTML5 writer. Default is ``False``. .. versionadded:: 1.6 @@ -1957,8 +1978,8 @@ These options influence LaTeX output. * ``'pdflatex'`` -- PDFLaTeX (default) * ``'xelatex'`` -- XeLaTeX * ``'lualatex'`` -- LuaLaTeX - * ``'platex'`` -- pLaTeX (default if :confval:`language` is ``'ja'``) - * ``'uplatex'`` -- upLaTeX (experimental) + * ``'platex'`` -- pLaTeX + * ``'uplatex'`` -- upLaTeX (default if :confval:`language` is ``'ja'``) ``'pdflatex'``\ 's support for Unicode characters is limited. @@ -1988,6 +2009,10 @@ These options influence LaTeX output. Add ``uplatex`` support. + .. versionchanged:: 4.0 + + ``uplatex`` becomes the default setting of Japanese documents. + Contrarily to :ref:`MathJaX math rendering in HTML output <math-support>`, LaTeX requires some extra configuration to support Unicode literals in :rst:dir:`math`: the only comprehensive solution (as far as we know) is to @@ -2007,8 +2032,8 @@ These options influence LaTeX output. *startdocname* String that specifies the :term:`document name` of the LaTeX file's master document. All documents referenced by the *startdoc* document in TOC trees - will be included in the LaTeX file. (If you want to use the default master - document for your LaTeX build, provide your :confval:`master_doc` here.) + will be included in the LaTeX file. (If you want to use the default root + document for your LaTeX build, provide your :confval:`root_doc` here.) *targetname* File name of the LaTeX file in the output directory. @@ -2277,7 +2302,7 @@ These options influence manual page output. String that specifies the :term:`document name` of the manual page's master document. All documents referenced by the *startdoc* document in TOC trees will be included in the manual file. (If you want to use the default - master document for your manual pages build, use your :confval:`master_doc` + root document for your manual pages build, use your :confval:`root_doc` here.) *name* @@ -2307,10 +2332,12 @@ These options influence manual page output. .. confval:: man_make_section_directory - If true, make a section directory on build man page. Default is False. + If true, make a section directory on build man page. Default is True. .. versionadded:: 3.3 + .. versionchanged:: 4.0 + The default is changed to ``False`` from ``True``. .. _texinfo-options: @@ -2331,7 +2358,7 @@ These options influence Texinfo output. master document. All documents referenced by the *startdoc* document in TOC trees will be included in the Texinfo file. (If you want to use the default master document for your Texinfo build, provide your - :confval:`master_doc` here.) + :confval:`root_doc` here.) *targetname* File name (no extension) of the Texinfo file in the output directory. @@ -2687,6 +2714,17 @@ Options for the C++ domain .. versionadded:: 1.5 +Options for the Python domain +----------------------------- + +.. confval:: python_use_unqualified_type_names + + If true, suppress the module name of the python reference if it can be + resolved. The default is ``False``. + + .. versionadded:: 4.0 + + .. note:: This configuration is still in experimental Example of configuration file ============================= diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index a07042781..1bfa8086c 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -89,33 +89,96 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, Boil the noodle *time* minutes. - **Options and advanced usage** + .. rubric:: Options + + .. rst:directive:option:: members + :type: no value or comma separated list + + If set, autodoc will generate document for the members of the target + module, class or exception. - * If you want to automatically document members, there's a ``members`` - option:: + For example:: .. automodule:: noodle :members: - will document all module members (recursively), and :: + will document all module members (recursively), and :: .. autoclass:: Noodle :members: - will document all non-private member functions and properties (that is, - those whose name doesn't start with ``_``). + will document all class member methods and properties. - For modules, ``__all__`` will be respected when looking for members unless - you give the ``ignore-module-all`` flag option. Without - ``ignore-module-all``, the order of the members will also be the order in - ``__all__``. + By default, autodoc will not generate document for the members that are + private, not having docstrings, inherited from super class, or special + members. - You can also give an explicit list of members; only these will then be - documented:: + For modules, ``__all__`` will be respected when looking for members unless + you give the ``ignore-module-all`` flag option. Without + ``ignore-module-all``, the order of the members will also be the order in + ``__all__``. + + You can also give an explicit list of members; only these will then be + documented:: .. autoclass:: Noodle :members: eat, slurp + .. rst:directive:option:: undoc-members + :type: no value + + If set, autodoc will also generate document for the members not having + docstrings:: + + .. automodule:: noodle + :members: + :undoc-members: + + .. rst:directive:option:: private-members + :type: no value or comma separated list + + If set, autodoc will also generate document for the private members + (that is, those named like ``_private`` or ``__private``):: + + .. automodule:: noodle + :members: + :private-members: + + It can also take an explicit list of member names to be documented as + arguments:: + + .. automodule:: noodle + :members: + :private-members: _spicy, _garlickly + + .. versionadded:: 1.1 + .. versionchanged:: 3.2 + The option can now take arguments. + + .. rst:directive:option:: special-members + :type: no value or comma separated list + + If set, autodoc will also generate document for the special members + (that is, those named like ``__special__``):: + + .. autoclass:: my.Class + :members: + :special-members: + + It can also take an explicit list of member names to be documented as + arguments:: + + .. autoclass:: my.Class + :members: + :special-members: __init__, __name__ + + .. versionadded:: 1.1 + + .. versionchanged:: 1.2 + The option can now take arguments + + **Options and advanced usage** + * If you want to make the ``members`` option (or other options described below) the default, see :confval:`autodoc_default_options`. @@ -139,31 +202,6 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. versionchanged:: 3.5 The default options can be overridden or extended temporarily. - * Members without docstrings will be left out, unless you give the - ``undoc-members`` flag option:: - - .. automodule:: noodle - :members: - :undoc-members: - - * "Private" members (that is, those named like ``_private`` or ``__private``) - will be included if the ``private-members`` flag option is given:: - - .. automodule:: noodle - :members: - :private-members: - - It can also take an explicit list of member names to be documented as - arguments:: - - .. automodule:: noodle - :members: - :private-members: _spicy, _garlickly - - .. versionadded:: 1.1 - .. versionchanged:: 3.2 - The option can now take arguments. - * autodoc considers a member private if its docstring contains ``:meta private:`` in its :ref:`info-field-lists`. For example: @@ -203,21 +241,6 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. versionadded:: 3.5 - * Python "special" members (that is, those named like ``__special__``) will - be included if the ``special-members`` flag option is given:: - - .. autoclass:: my.Class - :members: - :private-members: - :special-members: - - would document both "private" and "special" members of the class. - - .. versionadded:: 1.1 - - .. versionchanged:: 1.2 - The option can now take arguments, i.e. the special members to document. - * For classes and exceptions, members inherited from base classes will be left out when documenting all members, unless you give the ``inherited-members`` option, in addition to ``members``:: @@ -506,15 +529,19 @@ There are also config values that you can set: looks like a signature, use the line as the signature and remove it from the docstring content. - If the signature line ends with backslash, autodoc considers the function has - multiple signatures and look at the next line of the docstring. It is useful - for overloaded function. + autodoc will continue to look for multiple signature lines, + stopping at the first line that does not look like a signature. + This is useful for declaring overloaded function signatures. .. versionadded:: 1.1 .. versionchanged:: 3.1 Support overloaded signatures + .. versionchanged:: 4.0 + + Overloaded signatures do not need to be separated by a backslash + .. confval:: autodoc_mock_imports This value contains a list of modules to be mocked up. This is useful when @@ -548,6 +575,19 @@ There are also config values that you can set: New option ``'description'`` is added. +.. confval:: autodoc_typehints_description_target + + This value controls whether the types of undocumented parameters and return + values are documented when ``autodoc_typehints`` is set to ``description``. + + The default value is ``"all"``, meaning that types are documented for all + parameters and return values, whether they are documented or not. + + When set to ``"documented"``, types will only be documented for a parameter + or a return value that is already documented by the docstring. + + .. versionadded:: 4.0 + .. confval:: autodoc_type_aliases A dictionary for users defined `type aliases`__ that maps a type name to the @@ -586,6 +626,16 @@ There are also config values that you can set: .. __: https://mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases .. versionadded:: 3.3 +.. confval:: autodoc_preserve_defaults + + If True, the default argument values of functions will be not evaluated on + generating document. It preserves them as is in the source code. + + .. versionadded:: 4.0 + + Added as an experimental feature. This will be integrated into autodoc core + in the future. + .. confval:: autodoc_warningiserror This value controls the behavior of :option:`sphinx-build -W` during @@ -597,7 +647,7 @@ There are also config values that you can set: This value controls the docstrings inheritance. If set to True the docstring for classes or methods, if not explicitly set, - is inherited form parents. + is inherited from parents. The default is ``True``. diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst index 03ea7548e..28f207de7 100644 --- a/doc/usage/extensions/autosummary.rst +++ b/doc/usage/extensions/autosummary.rst @@ -19,11 +19,13 @@ The :mod:`sphinx.ext.autosummary` extension does this in two parts: that contain links to the documented items, and short summary blurbs extracted from their docstrings. -2. Optionally, the convenience script :program:`sphinx-autogen` or the new - :confval:`autosummary_generate` config value can be used to generate short - "stub" files for the entries listed in the :rst:dir:`autosummary` directives. - These files by default contain only the corresponding - :mod:`sphinx.ext.autodoc` directive, but can be customized with templates. +2. A :rst:dir:`autosummary` directive also generates short "stub" files for the + entries listed in its content. These files by default contain only the + corresponding :mod:`sphinx.ext.autodoc` directive, but can be customized with + templates. + + The :program:`sphinx-autogen` script is also able to generate "stub" files + from command line. .. rst:directive:: autosummary @@ -161,7 +163,7 @@ also use these config values: .. confval:: autosummary_generate Boolean indicating whether to scan all found documents for autosummary - directives, and to generate stub pages for each. It is disabled by default. + directives, and to generate stub pages for each. It is enabled by default. Can also be a list of documents for which stub pages should be generated. @@ -173,6 +175,10 @@ also use these config values: Emits :event:`autodoc-skip-member` event as :mod:`~sphinx.ext.autodoc` does. + .. versionchanged:: 4.0 + + Enabled by default. + .. confval:: autosummary_generate_overwrite If true, autosummary overwrites existing files by generated stub pages. diff --git a/doc/usage/extensions/math.rst b/doc/usage/extensions/math.rst index 780e57ee2..655364767 100644 --- a/doc/usage/extensions/math.rst +++ b/doc/usage/extensions/math.rst @@ -140,6 +140,12 @@ are built: .. module:: sphinx.ext.mathjax :synopsis: Render math using JavaScript via MathJax. +.. warning:: + Version 4.0 changes the version of MathJax used to version 3. You may need to + override ``mathjax_path`` to + ``https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS-MML_HTMLorMML`` + or update your configuration options for version 3. + .. versionadded:: 1.1 This extension puts math as-is into the HTML files. The JavaScript package @@ -161,14 +167,14 @@ Sphinx but is set to automatically include it from a third-party site. MathJax. The default is the ``https://`` URL that loads the JS files from the - `cdnjs`__ Content Delivery Network. See the `MathJax Getting Started + `jsdelivr`__ Content Delivery Network. See the `MathJax Getting Started page`__ for details. If you want MathJax to be available offline or without including resources from a third-party site, you have to download it and set this value to a different path. - __ https://cdnjs.com + __ https://www.jsdelivr.com/ - __ https://docs.mathjax.org/en/latest/start.html + __ https://www.mathjax.org/#gettingstarted The path can be absolute or relative; if it is relative, it is relative to the ``_static`` directory of the built docs. diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst index 46ef6a51e..f0384ea9d 100644 --- a/doc/usage/installation.rst +++ b/doc/usage/installation.rst @@ -12,7 +12,7 @@ Installing Sphinx Overview -------- -Sphinx is written in `Python`__ and supports Python 3.5+. It builds upon the +Sphinx is written in `Python`__ and supports Python 3.6+. It builds upon the shoulders of many third-party libraries such as `Docutils`__ and `Jinja`__, which are installed when Sphinx is installed. @@ -107,7 +107,30 @@ Anaconda Windows ------- -.. todo:: Could we start packaging this? +Sphinx can be install using `Chocolatey`__ or +:ref:`installed manually <windows-other-method>`. + +__ https://chocolatey.org/ + +Chocolatey +~~~~~~~~~~ + +:: + + $ choco install sphinx + +You would need to `install Chocolatey +<https://chocolatey.org/install/>`_ +before running this. + +For more information, refer to the `chocolatey page`__. + +__ https://chocolatey.org/packages/sphinx/ + +.. _windows-other-method: + +Other Methods +~~~~~~~~~~~~~ Most Windows users do not have Python installed by default, so we begin with the installation of Python itself. To check if you already have Python @@ -183,7 +206,7 @@ Please choose one for your purpose. When using docker images, please use ``docker run`` command to invoke sphinx commands. For example, you can use following command to create a Sphinx project:: - $ docker run --rm -v /path/to/document:/docs sphinxdoc/sphinx sphinx-quickstart + $ docker run -it --rm -v /path/to/document:/docs sphinxdoc/sphinx sphinx-quickstart And you can following command this to build HTML document:: diff --git a/doc/usage/quickstart.rst b/doc/usage/quickstart.rst index 1d7e540a6..3cd6ac993 100644 --- a/doc/usage/quickstart.rst +++ b/doc/usage/quickstart.rst @@ -13,7 +13,7 @@ Sphinx focuses on documentation, in particular handwritten documentation, however, Sphinx can also be used to generate blogs, homepages and even books. Much of Sphinx's power comes from the richness of its default plain-text markup format, :doc:`reStructuredText </usage/restructuredtext/index>`, along with -it's :doc:`significant extensibility capabilities </development/index>`. +its :doc:`significant extensibility capabilities </development/index>`. The goal of this document is to give you a quick taste of what Sphinx is and how you might use it. When you're done here, you can check out the diff --git a/doc/usage/restructuredtext/basics.rst b/doc/usage/restructuredtext/basics.rst index 8f596ed9a..03b690f44 100644 --- a/doc/usage/restructuredtext/basics.rst +++ b/doc/usage/restructuredtext/basics.rst @@ -288,7 +288,7 @@ Roles ----- A role or "custom interpreted text role" (:duref:`ref <roles>`) is an inline -piece of explicit markup. It signifies that that the enclosed text should be +piece of explicit markup. It signifies that the enclosed text should be interpreted in a specific way. Sphinx uses this to provide semantic markup and cross-referencing of identifiers, as described in the appropriate section. The general syntax is ``:rolename:`content```. diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst index e8b88c21b..995804e37 100644 --- a/doc/usage/restructuredtext/directives.rst +++ b/doc/usage/restructuredtext/directives.rst @@ -197,9 +197,9 @@ tables of contents. The ``toctree`` directive is the central element. <metadata>` to let a document be built, but notify Sphinx that it is not reachable via a toctree. - The "master document" (selected by :confval:`master_doc`) is the "root" of - the TOC tree hierarchy. It can be used as the documentation's main page, or - as a "full table of contents" if you don't give a ``maxdepth`` option. + The "root document" (selected by :confval:`root_doc`) is the "root" of the TOC + tree hierarchy. It can be used as the documentation's main page, or as a + "full table of contents" if you don't give a ``maxdepth`` option. .. versionchanged:: 0.3 Added "globbing" option. @@ -404,10 +404,15 @@ Showing code examples single: sourcecode There are multiple ways to show syntax-highlighted literal code blocks in -Sphinx: using :ref:`reST doctest blocks <rst-doctest-blocks>`; using :ref:`reST -literal blocks <rst-literal-blocks>`, optionally in combination with the -:rst:dir:`highlight` directive; using the :rst:dir:`code-block` directive; and -using the :rst:dir:`literalinclude` directive. Doctest blocks can only be used +Sphinx: + +* using :ref:`reST doctest blocks <rst-doctest-blocks>`; +* using :ref:`reST literal blocks <rst-literal-blocks>`, optionally in + combination with the :rst:dir:`highlight` directive; +* using the :rst:dir:`code-block` directive; +* and using the :rst:dir:`literalinclude` directive. + +Doctest blocks can only be used to show interactive Python sessions, while the remaining three can be used for other languages. Of these three, literal blocks are useful when an entire document, or at least large sections of it, use code blocks with the same diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 53e1fc5d3..65a32b6c8 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -202,6 +202,14 @@ The following directives are provided for module and class contents: .. versionadded:: 2.1 + .. rst:directive:option:: canonical + :type: full qualified name including module name + + Describe the location where the object is defined if the object is + imported from other modules + + .. versionadded:: 4.0 + .. rst:directive:: .. py:data:: name Describes global data in a module, including both variables and values used @@ -220,6 +228,14 @@ The following directives are provided for module and class contents: .. versionadded:: 2.4 + .. rst:directive:option:: canonical + :type: full qualified name including module name + + Describe the location where the object is defined if the object is + imported from other modules + + .. versionadded:: 4.0 + .. rst:directive:: .. py:exception:: name Describes an exception class. The signature can, but need not include @@ -259,6 +275,14 @@ The following directives are provided for module and class contents: .. rubric:: options + .. rst:directive:option:: canonical + :type: full qualified name including module name + + Describe the location where the object is defined if the object is + imported from other modules + + .. versionadded:: 4.0 + .. rst:directive:option:: final :type: no value @@ -284,6 +308,30 @@ The following directives are provided for module and class contents: .. versionadded:: 2.4 + .. rst:directive:option:: canonical + :type: full qualified name including module name + + Describe the location where the object is defined if the object is + imported from other modules + + .. versionadded:: 4.0 + +.. rst:directive:: .. py:property:: name + + Describes an object property. + + .. versionadded:: 4.0 + + .. rubric:: options + + .. rst:directive:option:: abstractmethod + :type: no value + + Indicate the property is abstract. + + .. rst:directive:option:: type: type of the property + :type: text + .. rst:directive:: .. py:method:: name(parameters) Describes an object method. The parameters should not include the ``self`` @@ -307,6 +355,14 @@ The following directives are provided for module and class contents: .. versionadded:: 2.1 + .. rst:directive:option:: canonical + :type: full qualified name including module name + + Describe the location where the object is defined if the object is + imported from other modules + + .. versionadded:: 4.0 + .. rst:directive:option:: classmethod :type: no value @@ -328,6 +384,10 @@ The following directives are provided for module and class contents: .. versionadded:: 2.1 + .. deprecated:: 4.0 + + Use :rst:dir:`py:property` instead. + .. rst:directive:option:: staticmethod :type: no value @@ -544,6 +604,8 @@ a matching identifier is found: Reference a data attribute of an object. + .. note:: The role is also able to refer to property. + .. rst:role:: py:exc Reference an exception. A dotted name may be used. @@ -1848,7 +1910,7 @@ currently Ada_, CoffeeScript_, Erlang_, HTTP_, Lasso_, MATLAB_, PHP_, and Ruby_ domains. Also available are domains for `Chapel`_, `Common Lisp`_, dqn_, Go_, Jinja_, Operation_, and Scala_. -.. _sphinx-contrib: https://bitbucket.org/birkenfeld/sphinx-contrib/ +.. _sphinx-contrib: https://github.com/sphinx-contrib .. _Ada: https://pypi.org/project/sphinxcontrib-adadomain/ .. _Chapel: https://pypi.org/project/sphinxcontrib-chapeldomain/ diff --git a/doc/usage/theming.rst b/doc/usage/theming.rst index fb06e8741..fc135a095 100644 --- a/doc/usage/theming.rst +++ b/doc/usage/theming.rst @@ -334,38 +334,15 @@ These themes are: Third Party Themes ~~~~~~~~~~~~~~~~~~ -.. cssclass:: longtable - -+--------------------+--------------------+ -| **Theme overview** | | -+--------------------+--------------------+ -| |sphinx_rtd_theme| | | -| | | -| *sphinx_rtd_theme* | | -+--------------------+--------------------+ - -.. |sphinx_rtd_theme| image:: /_static/themes/sphinx_rtd_theme.png - -There are many third-party themes available. Some of these are general use, -while others are specific to an individual project. A section of third-party -themes is listed below. Many more can be found on PyPI__, GitHub__, GitLab__ and -sphinx-themes.org__. - -.. cssclass:: clear - -**sphinx_rtd_theme** - `Read the Docs Sphinx Theme`_. - This is a mobile-friendly sphinx theme that was made for readthedocs.org. - View a working demo over on readthedocs.org. You can get install and options - information at `Read the Docs Sphinx Theme`_ page. - - .. _Read the Docs Sphinx Theme: https://pypi.org/project/sphinx_rtd_theme/ - - .. versionchanged:: 1.4 - **sphinx_rtd_theme** has become optional. +There are many third-party themes available for Sphinx. Some of these are for +general use, while others are specific to an individual project. +sphinx-themes.org__ is a gallery that showcases various themes for Sphinx, +with demo documentation rendered under each theme. Themes can also be found +on PyPI__ (using the classifier ``Framework :: Sphinx :: Theme``), GitHub__ +and GitLab__. +.. __: https://sphinx-themes.org/ .. __: https://pypi.org/search/?q=&o=&c=Framework+%3A%3A+Sphinx+%3A%3A+Theme -.. __: https://github.com/search?utf8=%E2%9C%93&q=sphinx+theme&type= +.. __: https://github.com/search?utf8=%E2%9C%93&q=sphinx+theme .. __: https://gitlab.com/explore?name=sphinx+theme -.. __: https://sphinx-themes.org/ @@ -45,7 +45,7 @@ paths = line_length = 95 [mypy] -python_version = 3.5 +python_version = 3.6 disallow_incomplete_defs = True show_column_numbers = True show_error_context = True @@ -61,7 +61,6 @@ filterwarnings = ignore::DeprecationWarning:docutils.io ignore::DeprecationWarning:pyximport.pyximport ignore::ImportWarning:importlib._bootstrap - ignore::PendingDeprecationWarning:sphinx.util.pycompat markers = apidoc setup_command @@ -10,8 +10,8 @@ import sphinx with open('README.rst') as f: long_desc = f.read() -if sys.version_info < (3, 5): - print('ERROR: Sphinx requires at least Python 3.5 to run.') +if sys.version_info < (3, 6): + print('ERROR: Sphinx requires at least Python 3.6 to run.') sys.exit(1) install_requires = [ @@ -23,7 +23,7 @@ install_requires = [ 'sphinxcontrib-qthelp', 'Jinja2>=2.3', 'Pygments>=2.0', - 'docutils>=0.12', + 'docutils>=0.14', 'snowballstemmer>=1.1', 'babel>=1.3', 'alabaster>=0.7,<0.8', @@ -200,7 +200,6 @@ setup( 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', @@ -242,7 +241,7 @@ setup( 'build_sphinx = sphinx.setup_command:BuildDoc', ], }, - python_requires=">=3.5", + python_requires=">=3.6", install_requires=install_requires, extras_require=extras_require, cmdclass=cmdclass, diff --git a/sphinx/__init__.py b/sphinx/__init__.py index e72c9f81c..c44e7db13 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -19,11 +19,6 @@ from subprocess import PIPE from .deprecation import RemovedInNextVersionWarning -if False: - # For type annotation - from typing import Any # NOQA - - # by default, all DeprecationWarning under sphinx package will be emit. # Users can avoid this by using environment variable: PYTHONWARNINGS= if 'PYTHONWARNINGS' not in os.environ: @@ -32,8 +27,8 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '3.5.4+' -__released__ = '3.5.4' # used when Sphinx builds its own docs +__version__ = '4.0.0+' +__released__ = '4.0.0' # used when Sphinx builds its own docs #: Version info for better programmatic use. #: @@ -43,7 +38,7 @@ __released__ = '3.5.4' # used when Sphinx builds its own docs #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. -version_info = (3, 5, 4, 'beta', 0) +version_info = (4, 0, 0, 'beta', 0) package_dir = path.abspath(path.dirname(__file__)) @@ -57,8 +52,8 @@ if __version__.endswith('+'): try: ret = subprocess.run(['git', 'show', '-s', '--pretty=format:%h'], cwd=package_dir, - stdout=PIPE, stderr=PIPE) + stdout=PIPE, stderr=PIPE, encoding='ascii') if ret.stdout: - __display_version__ += '/' + ret.stdout.decode('ascii').strip() + __display_version__ += '/' + ret.stdout.strip() except Exception: pass diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 5f371e46b..5645ac91b 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -8,16 +8,12 @@ :license: BSD, see LICENSE for details. """ -import warnings -from typing import Any, Dict, List, Sequence +from typing import TYPE_CHECKING, Any, Dict, List, Sequence from docutils import nodes -from docutils.nodes import Element, Node +from docutils.nodes import Element -from sphinx.deprecation import RemovedInSphinx40Warning - -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -108,7 +104,7 @@ class toctree(nodes.General, nodes.Element, translatable): self['caption'] = translated_message def extract_original_messages(self) -> List[str]: - messages = [] # type: List[str] + messages: List[str] = [] # toctree entries messages.extend(self.get('rawentries', [])) @@ -213,7 +209,7 @@ class desc_content(nodes.General, nodes.Element): class desc_sig_element(nodes.inline): """Common parent class of nodes for inline text of a signature.""" - classes = [] # type: List[str] + classes: List[str] = [] def __init__(self, rawsource: str = '', text: str = '', *children: Element, **attributes: Any) -> None: @@ -342,6 +338,54 @@ class pending_xref(nodes.Inline, nodes.Element): """ +class pending_xref_condition(nodes.Inline, nodes.TextElement): + """Node for cross-references that are used to choose appropriate + content of the reference by conditions on the resolving phase. + + When the :py:class:`pending_xref` node contains one or more + **pending_xref_condition** nodes, the cross-reference resolver + should choose the content of the reference using defined conditions + in ``condition`` attribute of each pending_xref_condition nodes:: + + <pending_xref refdomain="py" reftarget="io.StringIO ...> + <pending_xref_condition condition="resolved"> + <literal> + StringIO + <pending_xref_condition condition="*"> + <literal> + io.StringIO + + After the processing of cross-reference resolver, one of the content node + under pending_xref_condition node is chosen by its condition and to be + removed all of pending_xref_condition nodes:: + + # When resolved the cross-reference successfully + <reference> + <literal> + StringIO + + # When resolution is failed + <reference> + <literal> + io.StringIO + + .. note:: This node is only allowed to be placed under pending_xref node. + It is not allows to place it under other nodes. In addition, + pending_xref node must contain only pending_xref_condition + nodes if it contains one or more pending_xref_condition nodes. + + The pending_xref_condition node should have **condition** attribute. + Domains can be store their individual conditions into the attribute to + filter contents on resolving phase. As a reserved condition name, + ``condition="*"`` is used for the fallback of resolution failure. + Additionally, as a recommended condition name, ``condition="resolved"`` + is used for the representation of resolstion success in the intersphinx + module. + + .. versionadded:: 4.0 + """ + + class number_reference(nodes.reference): """Node for number references, similar to pending_xref.""" @@ -362,20 +406,6 @@ class literal_strong(nodes.strong, not_smartquotable): """ -class abbreviation(nodes.abbreviation): - """Node for abbreviations with explanations. - - .. deprecated:: 2.0 - """ - - def __init__(self, rawsource: str = '', text: str = '', - *children: Node, **attributes: Any) -> None: - warnings.warn("abbrevition node for Sphinx was replaced by docutils'.", - RemovedInSphinx40Warning, stacklevel=2) - - super().__init__(rawsource, text, *children, **attributes) - - class manpage(nodes.Inline, nodes.FixedTextElement): """Node for references to manpages.""" diff --git a/sphinx/application.py b/sphinx/application.py index c7b293828..4735beffd 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -14,11 +14,10 @@ import os import pickle import platform import sys -import warnings from collections import deque from io import StringIO from os import path -from typing import IO, Any, Callable, Dict, List, Optional, Tuple, Union +from typing import IO, TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Type, Union from docutils import nodes from docutils.nodes import Element, TextElement @@ -30,14 +29,13 @@ from pygments.lexer import Lexer import sphinx from sphinx import locale, package_dir from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain, Index from sphinx.environment import BuildEnvironment from sphinx.environment.collectors import EnvironmentCollector from sphinx.errors import ApplicationError, ConfigError, VersionRequirementError from sphinx.events import EventManager from sphinx.extension import Extension -from sphinx.highlighting import lexer_classes, lexers +from sphinx.highlighting import lexer_classes from sphinx.locale import __ from sphinx.project import Project from sphinx.registry import SphinxComponentRegistry @@ -52,10 +50,7 @@ from sphinx.util.osutil import abspath, ensuredir, relpath from sphinx.util.tags import Tags from sphinx.util.typing import RoleFunction, TitleGetter -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from docutils.nodes import Node # NOQA from sphinx.builders import Builder @@ -135,6 +130,9 @@ class Sphinx: :ivar outdir: Directory for storing build documents. """ + warningiserror: bool + _warncount: int + def __init__(self, srcdir: str, confdir: Optional[str], outdir: str, doctreedir: str, buildername: str, confoverrides: Dict = None, status: IO = sys.stdout, warning: IO = sys.stderr, @@ -142,12 +140,12 @@ class Sphinx: verbosity: int = 0, parallel: int = 0, keep_going: bool = False) -> None: self.phase = BuildPhase.INITIALIZATION self.verbosity = verbosity - self.extensions = {} # type: Dict[str, Extension] - self.builder = None # type: Builder - self.env = None # type: BuildEnvironment - self.project = None # type: Project + self.extensions: Dict[str, Extension] = {} + self.builder: Builder = None + self.env: BuildEnvironment = None + self.project: Project = None self.registry = SphinxComponentRegistry() - self.html_themes = {} # type: Dict[str, str] + self.html_themes: Dict[str, str] = {} # validate provided directories self.srcdir = abspath(srcdir) @@ -175,14 +173,14 @@ class Sphinx: self.parallel = parallel if status is None: - self._status = StringIO() # type: IO + self._status: IO = StringIO() self.quiet = True else: self._status = status self.quiet = False if warning is None: - self._warning = StringIO() # type: IO + self._warning: IO = StringIO() else: self._warning = warning self._warncount = 0 @@ -197,7 +195,7 @@ class Sphinx: # keep last few messages for traceback # This will be filled by sphinx.util.logging.LastMessagesWriter - self.messagelog = deque(maxlen=10) # type: deque + self.messagelog: deque = deque(maxlen=10) # say hello to the world logger.info(bold(__('Running Sphinx v%s') % sphinx.__display_version__)) @@ -294,7 +292,7 @@ class Sphinx: if catalog.domain == 'sphinx' and catalog.is_outdated(): catalog.write_mo(self.config.language) - locale_dirs = list(repo.locale_dirs) # type: List[Optional[str]] + locale_dirs: List[Optional[str]] = list(repo.locale_dirs) locale_dirs += [None] locale_dirs += [path.join(package_dir, 'locale')] @@ -445,7 +443,7 @@ class Sphinx: self.events.disconnect(listener_id) def emit(self, event: str, *args: Any, - allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> List: + allowed_exceptions: Tuple[Type[Exception], ...] = ()) -> List: """Emit *event* and pass *arguments* to the callback functions. Return the return values of all callbacks as a list. Do not emit core @@ -462,7 +460,7 @@ class Sphinx: return self.events.emit(event, *args, allowed_exceptions=allowed_exceptions) def emit_firstresult(self, event: str, *args: Any, - allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> Any: + allowed_exceptions: Tuple[Type[Exception], ...] = ()) -> Any: """Emit *event* and pass *arguments* to the callback functions. Return the result of the first callback that doesn't return ``None``. @@ -481,7 +479,7 @@ class Sphinx: # registering addon parts - def add_builder(self, builder: "Type[Builder]", override: bool = False) -> None: + def add_builder(self, builder: Type["Builder"], override: bool = False) -> None: """Register a new builder. :param builder: A builder class @@ -528,8 +526,7 @@ class Sphinx: ``'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 ())) + logger.debug('[app] adding config value: %r', (name, default, rebuild, types)) if rebuild in (False, True): rebuild = 'env' if rebuild else '' self.config.add(name, default, rebuild, types) @@ -544,7 +541,7 @@ class Sphinx: logger.debug('[app] adding event: %r', name) self.events.add(name) - def set_translator(self, name: str, translator_class: "Type[nodes.NodeVisitor]", + def set_translator(self, name: str, translator_class: Type[nodes.NodeVisitor], override: bool = False) -> None: """Register or override a Docutils translator class. @@ -563,7 +560,7 @@ class Sphinx: """ self.registry.add_translator(name, translator_class, override=override) - def add_node(self, node: "Type[Element]", override: bool = False, + def add_node(self, node: Type[Element], override: bool = False, **kwargs: Tuple[Callable, Optional[Callable]]) -> None: """Register a Docutils node class. @@ -607,7 +604,7 @@ class Sphinx: docutils.register_node(node) self.registry.add_translation_handlers(node, **kwargs) - def add_enumerable_node(self, node: "Type[Element]", figtype: str, + def add_enumerable_node(self, node: Type[Element], figtype: str, title_getter: TitleGetter = None, override: bool = False, **kwargs: Tuple[Callable, Callable]) -> None: """Register a Docutils node class as a numfig target. @@ -636,7 +633,7 @@ class Sphinx: self.registry.add_enumerable_node(node, figtype, title_getter, override=override) self.add_node(node, override=override, **kwargs) - def add_directive(self, name: str, cls: "Type[Directive]", override: bool = False) -> None: + def add_directive(self, name: str, cls: Type[Directive], override: bool = False) -> None: """Register a Docutils directive. :param name: The name of directive @@ -726,7 +723,7 @@ class Sphinx: role = roles.GenericRole(name, nodeclass) docutils.register_role(name, role) - def add_domain(self, domain: "Type[Domain]", override: bool = False) -> None: + def add_domain(self, domain: Type[Domain], override: bool = False) -> None: """Register a domain. :param domain: A domain class @@ -740,7 +737,7 @@ class Sphinx: self.registry.add_domain(domain, override=override) def add_directive_to_domain(self, domain: str, name: str, - cls: "Type[Directive]", override: bool = False) -> None: + cls: Type[Directive], override: bool = False) -> None: """Register a Docutils directive in a domain. Like :meth:`add_directive`, but the directive is added to the domain @@ -777,7 +774,7 @@ class Sphinx: """ self.registry.add_role_to_domain(domain, name, role, override=override) - def add_index_to_domain(self, domain: str, index: "Type[Index]", override: bool = False + def add_index_to_domain(self, domain: str, index: Type[Index], override: bool = False ) -> None: """Register a custom index for a domain. @@ -795,7 +792,7 @@ class Sphinx: self.registry.add_index_to_domain(domain, index) def add_object_type(self, directivename: str, rolename: str, indextemplate: str = '', - parse_node: Callable = None, ref_nodeclass: "Type[TextElement]" = None, + parse_node: Callable = None, ref_nodeclass: Type[TextElement] = None, objname: str = '', doc_field_types: List = [], override: bool = False ) -> None: """Register a new object type. @@ -862,7 +859,7 @@ class Sphinx: override=override) def add_crossref_type(self, directivename: str, rolename: str, indextemplate: str = '', - ref_nodeclass: "Type[TextElement]" = None, objname: str = '', + ref_nodeclass: Type[TextElement] = None, objname: str = '', override: bool = False) -> None: """Register a new crossref object type. @@ -900,7 +897,7 @@ class Sphinx: indextemplate, ref_nodeclass, objname, override=override) - def add_transform(self, transform: "Type[Transform]") -> None: + def add_transform(self, transform: Type[Transform]) -> None: """Register a Docutils transform to be applied after parsing. Add the standard docutils :class:`Transform` subclass *transform* to @@ -935,7 +932,7 @@ class Sphinx: """ # NOQA self.registry.add_transform(transform) - def add_post_transform(self, transform: "Type[Transform]") -> None: + def add_post_transform(self, transform: Type[Transform]) -> None: """Register a Docutils transform to be applied before writing. Add the standard docutils :class:`Transform` subclass *transform* to @@ -946,13 +943,6 @@ class Sphinx: """ self.registry.add_post_transform(transform) - 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, priority: int = 500, **kwargs: Any) -> None: """Register a JavaScript file to include in the HTML output. @@ -1032,6 +1022,8 @@ class Sphinx: * - Priority - Main purpose in Sphinx + * - 200 + - default priority for built-in CSS files * - 500 - default priority for extensions * - 800 @@ -1061,24 +1053,6 @@ class Sphinx: if hasattr(self.builder, 'add_css_file'): self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore - def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None - ) -> None: - """An alias of :meth:`add_css_file`.""" - warnings.warn('The app.add_stylesheet() is deprecated. ' - 'Please use app.add_css_file() instead.', - RemovedInSphinx40Warning, stacklevel=2) - - attributes = {} # type: Dict[str, Any] - if alternate: - attributes['rel'] = 'alternate stylesheet' - else: - attributes['rel'] = 'stylesheet' - - if title: - attributes['title'] = title - - self.add_css_file(filename, **attributes) - def add_latex_package(self, packagename: str, options: str = None, after_hyperref: bool = False) -> None: r"""Register a package to include in the LaTeX source code. @@ -1102,7 +1076,7 @@ class Sphinx: """ self.registry.add_latex_package(packagename, options, after_hyperref) - def add_lexer(self, alias: str, lexer: Union[Lexer, "Type[Lexer]"]) -> None: + def add_lexer(self, alias: str, lexer: Type[Lexer]) -> None: """Register a new lexer for source code. Use *lexer* to highlight code blocks with the given language *alias*. @@ -1113,13 +1087,7 @@ class Sphinx: still supported until Sphinx-3.x. """ logger.debug('[app] adding lexer: %r', (alias, lexer)) - if isinstance(lexer, Lexer): - warnings.warn('app.add_lexer() API changed; ' - 'Please give lexer class instead of instance', - RemovedInSphinx40Warning, stacklevel=2) - lexers[alias] = lexer - else: - lexer_classes[alias] = lexer + lexer_classes[alias] = lexer def add_autodocumenter(self, cls: Any, override: bool = False) -> None: """Register a new documenter class for the autodoc extension. @@ -1133,7 +1101,7 @@ class Sphinx: If *override* is True, the given *cls* is forcedly installed even if a documenter having the same name is already installed. - .. todo:: Add real docs for Documenter and subclassing + See :ref:`autodoc_ext_tutorial`. .. versionadded:: 0.6 .. versionchanged:: 2.2 @@ -1144,7 +1112,7 @@ class Sphinx: self.registry.add_documenter(cls.objtype, cls) self.add_directive('auto' + cls.objtype, AutodocDirective, override=override) - def add_autodoc_attrgetter(self, typ: "Type", getter: Callable[[Any, str, Any], Any] + def add_autodoc_attrgetter(self, typ: Type, getter: Callable[[Any, str, Any], Any] ) -> None: """Register a new ``getattr``-like function for the autodoc extension. @@ -1188,7 +1156,7 @@ class Sphinx: """ self.registry.add_source_suffix(suffix, filetype, override=override) - def add_source_parser(self, parser: "Type[Parser]", override: bool = False) -> None: + def add_source_parser(self, parser: Type[Parser], override: bool = False) -> None: """Register a parser class. If *override* is True, the given *parser* is forcedly installed even if @@ -1203,7 +1171,7 @@ class Sphinx: """ self.registry.add_source_parser(parser, override=override) - def add_env_collector(self, collector: "Type[EnvironmentCollector]") -> None: + def add_env_collector(self, collector: Type[EnvironmentCollector]) -> None: """Register an environment collector class. Refer to :ref:`collector-api`. diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 58030bb6c..bedc65b61 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -11,7 +11,7 @@ import pickle import time from os import path -from typing import Any, Dict, Iterable, List, Sequence, Set, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Sequence, Set, Tuple, Type, Union from docutils import nodes from docutils.nodes import Node @@ -40,10 +40,7 @@ try: except ImportError: multiprocessing = None -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -66,7 +63,7 @@ class Builder: #: default translator class for the builder. This can be overridden by #: :py:meth:`app.set_translator()`. - default_translator_class = None # type: Type[nodes.NodeVisitor] + default_translator_class: Type[nodes.NodeVisitor] = None # doctree versioning method versioning_method = 'none' versioning_compare = False @@ -77,7 +74,7 @@ class Builder: #: The list of MIME types of image formats supported by the builder. #: Image files are searched in the order in which they appear here. - supported_image_types = [] # type: List[str] + supported_image_types: List[str] = [] #: The builder supports remote images or not. supported_remote_images = False #: The builder supports data URIs or not. @@ -90,18 +87,18 @@ class Builder: self.doctreedir = app.doctreedir ensuredir(self.doctreedir) - self.app = app # type: Sphinx - self.env = None # type: BuildEnvironment - self.events = app.events # type: EventManager - self.config = app.config # type: Config - self.tags = app.tags # type: Tags + self.app: Sphinx = app + self.env: BuildEnvironment = None + self.events: EventManager = app.events + self.config: Config = app.config + self.tags: Tags = app.tags self.tags.add(self.format) self.tags.add(self.name) self.tags.add("format_%s" % self.format) self.tags.add("builder_%s" % self.name) # images that need to be copied over (source -> dest) - self.images = {} # type: Dict[str, str] + self.images: Dict[str, str] = {} # basename of images directory self.imagedir = "" # relative path to image directory from current docname (used at writing docs) @@ -109,7 +106,7 @@ class Builder: # these get set later self.parallel_ok = False - self.finish_tasks = None # type: Any + self.finish_tasks: Any = None def set_environment(self, env: BuildEnvironment) -> None: """Store BuildEnvironment object.""" @@ -117,7 +114,7 @@ class Builder: self.env.set_versioning_method(self.versioning_method, self.versioning_compare) - def get_translator_class(self, *args: Any) -> "Type[nodes.NodeVisitor]": + def get_translator_class(self, *args: Any) -> Type[nodes.NodeVisitor]: """Return a class of translator.""" return self.app.registry.get_translator_class(self) @@ -264,8 +261,7 @@ class Builder: # relative to the source directory and without source_suffix. dirlen = len(self.srcdir) + 1 to_write = [] - suffixes = None # type: Tuple[str] - suffixes = tuple(self.config.source_suffix) # type: ignore + suffixes: Tuple[str] = tuple(self.config.source_suffix) # type: ignore for filename in filenames: filename = path.normpath(path.abspath(filename)) if not filename.startswith(self.srcdir): @@ -416,9 +412,9 @@ class Builder: else: self._read_serial(docnames) - if self.config.master_doc not in self.env.all_docs: - raise SphinxError('master file %s not found' % - self.env.doc2path(self.config.master_doc)) + if self.config.root_doc not in self.env.all_docs: + raise SphinxError('root file %s not found' % + self.env.doc2path(self.config.root_doc)) for retval in self.events.emit('env-updated', self.env): if retval is not None: @@ -520,7 +516,7 @@ class Builder: for tocdocname in self.env.files_to_rebuild.get(docname, set()): if tocdocname in self.env.found_docs: docnames.add(tocdocname) - docnames.add(self.config.master_doc) + docnames.add(self.config.root_doc) with progress_message(__('preparing documents')): self.prepare_writing(docnames) diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index 7df3f8df5..103033478 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -11,10 +11,8 @@ import html import os import re -import warnings -from collections import namedtuple from os import path -from typing import Any, Dict, List, Set, Tuple +from typing import Any, Dict, List, NamedTuple, Set, Tuple from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile from docutils import nodes @@ -23,7 +21,6 @@ from docutils.utils import smartquotes from sphinx import addnodes from sphinx.builders.html import BuildInfo, StandaloneHTMLBuilder -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import __ from sphinx.util import logging, status_iterator from sphinx.util.fileutil import copy_asset_file @@ -84,10 +81,30 @@ VECTOR_GRAPHICS_EXTENSIONS = ('.svg',) REFURI_RE = re.compile("([^#:]*#)(.*)") -ManifestItem = namedtuple('ManifestItem', ['href', 'id', 'media_type']) -Spine = namedtuple('Spine', ['idref', 'linear']) -Guide = namedtuple('Guide', ['type', 'title', 'uri']) -NavPoint = namedtuple('NavPoint', ['navpoint', 'playorder', 'text', 'refuri', 'children']) +class ManifestItem(NamedTuple): + href: str + id: str + media_type: str + + +class Spine(NamedTuple): + idref: str + linear: bool + + +class Guide(NamedTuple): + type: str + title: str + uri: str + + +class NavPoint(NamedTuple): + navpoint: str + playorder: int + text: str + refuri: str + children: List[Any] # mypy does not support recursive types + # https://github.com/python/mypy/issues/7069 def sphinx_smarty_pants(t: str, language: str = 'en') -> str: @@ -148,9 +165,9 @@ class EpubBuilder(StandaloneHTMLBuilder): self.link_suffix = '.xhtml' self.playorder = 0 self.tocid = 0 - self.id_cache = {} # type: Dict[str, str] + self.id_cache: Dict[str, str] = {} self.use_index = self.get_builder_config('use_index', 'epub') - self.refnodes = [] # type: List[Dict[str, Any]] + self.refnodes: List[Dict[str, Any]] = [] def create_build_info(self) -> BuildInfo: return BuildInfo(self.config, self.tags, ['html', 'epub']) @@ -168,18 +185,6 @@ class EpubBuilder(StandaloneHTMLBuilder): self.id_cache[name] = id return id - def esc(self, name: str) -> str: - """Replace all characters not allowed in text an attribute values.""" - warnings.warn( - '%s.esc() is deprecated. Use html.escape() instead.' % self.__class__.__name__, - RemovedInSphinx40Warning, stacklevel=2) - name = name.replace('&', '&') - name = name.replace('<', '<') - name = name.replace('>', '>') - name = name.replace('"', '"') - name = name.replace('\'', ''') - return name - def get_refnodes(self, doctree: Node, result: List[Dict[str, Any]]) -> List[Dict[str, Any]]: # NOQA """Collect section titles, their depth in the toc and the refuri.""" # XXX: is there a better way than checking the attribute @@ -204,7 +209,7 @@ class EpubBuilder(StandaloneHTMLBuilder): return result def check_refnodes(self, nodes: List[Dict[str, Any]]) -> None: - appeared = set() # type: Set[str] + appeared: Set[str] = set() for node in nodes: if node['refuri'] in appeared: logger.warning( @@ -217,14 +222,14 @@ class EpubBuilder(StandaloneHTMLBuilder): appeared.add(node['refuri']) def get_toc(self) -> None: - """Get the total table of contents, containing the master_doc + """Get the total table of contents, containing the root_doc and pre and post files not managed by sphinx. """ - doctree = self.env.get_and_resolve_doctree(self.config.master_doc, + doctree = self.env.get_and_resolve_doctree(self.config.root_doc, self, prune_toctrees=False, includehidden=True) self.refnodes = self.get_refnodes(doctree, []) - master_dir = path.dirname(self.config.master_doc) + master_dir = path.dirname(self.config.root_doc) if master_dir: master_dir += '/' # XXX or os.sep? for item in self.refnodes: @@ -232,13 +237,13 @@ class EpubBuilder(StandaloneHTMLBuilder): self.toc_add_files(self.refnodes) def toc_add_files(self, refnodes: List[Dict[str, Any]]) -> None: - """Add the master_doc, pre and post files to a list of refnodes. + """Add the root_doc, pre and post files to a list of refnodes. """ refnodes.insert(0, { 'level': 1, - 'refuri': html.escape(self.config.master_doc + self.out_suffix), + 'refuri': html.escape(self.config.root_doc + self.out_suffix), 'text': ssp(html.escape( - self.env.titles[self.config.master_doc].astext())) + self.env.titles[self.config.root_doc].astext())) }) for file, text in reversed(self.config.epub_pre_files): refnodes.insert(0, { @@ -265,7 +270,7 @@ class EpubBuilder(StandaloneHTMLBuilder): """ def update_node_id(node: Element) -> None: """Update IDs of given *node*.""" - new_ids = [] + new_ids: List[str] = [] for node_id in node['ids']: new_id = self.fix_fragment('', node_id) if new_id not in new_ids: @@ -283,7 +288,7 @@ class EpubBuilder(StandaloneHTMLBuilder): for target in tree.traverse(nodes.target): update_node_id(target) - next_node = target.next_node(ascend=True) # type: Node + next_node: Node = target.next_node(ascend=True) if isinstance(next_node, nodes.Element): update_node_id(next_node) @@ -461,36 +466,23 @@ class EpubBuilder(StandaloneHTMLBuilder): addctx['doctype'] = self.doctype super().handle_page(pagename, addctx, templatename, outfilename, event_arg) - def build_mimetype(self, outdir: str = None, outname: str = 'mimetype') -> None: + def build_mimetype(self) -> None: """Write the metainfo file mimetype.""" - if outdir: - warnings.warn('The arguments of EpubBuilder.build_mimetype() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - else: - outdir = self.outdir + logger.info(__('writing mimetype file...')) + copy_asset_file(path.join(self.template_dir, 'mimetype'), self.outdir) - logger.info(__('writing %s file...'), outname) - copy_asset_file(path.join(self.template_dir, 'mimetype'), - path.join(outdir, outname)) - - def build_container(self, outdir: str = None, outname: str = 'META-INF/container.xml') -> None: # NOQA + def build_container(self, outname: str = 'META-INF/container.xml') -> None: # NOQA """Write the metainfo file META-INF/container.xml.""" - if outdir: - warnings.warn('The arguments of EpubBuilder.build_container() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - else: - outdir = self.outdir - - logger.info(__('writing %s file...'), outname) - filename = path.join(outdir, outname) - ensuredir(path.dirname(filename)) - copy_asset_file(path.join(self.template_dir, 'container.xml'), filename) + logger.info(__('writing META-INF/container.xml file...')) + outdir = path.join(self.outdir, 'META-INF') + ensuredir(outdir) + copy_asset_file(path.join(self.template_dir, 'container.xml'), outdir) def content_metadata(self) -> Dict[str, Any]: """Create a dictionary with all metadata for the content.opf file properly escaped. """ - metadata = {} # type: Dict[str, Any] + metadata: Dict[str, Any] = {} metadata['title'] = html.escape(self.config.epub_title) metadata['author'] = html.escape(self.config.epub_author) metadata['uid'] = html.escape(self.config.epub_uid) @@ -505,24 +497,18 @@ class EpubBuilder(StandaloneHTMLBuilder): metadata['guides'] = [] return metadata - def build_content(self, outdir: str = None, outname: str = 'content.opf') -> None: + def build_content(self) -> None: """Write the metainfo file content.opf It contains bibliographic data, a file list and the spine (the reading order). """ - if outdir: - warnings.warn('The arguments of EpubBuilder.build_content() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - else: - outdir = self.outdir - - logger.info(__('writing %s file...'), outname) + logger.info(__('writing content.opf file...')) metadata = self.content_metadata() # files - if not outdir.endswith(os.sep): - outdir += os.sep - olen = len(outdir) - self.files = [] # type: List[str] + if not self.outdir.endswith(os.sep): + self.outdir += os.sep + olen = len(self.outdir) + self.files: List[str] = [] self.ignored_files = ['.buildinfo', 'mimetype', 'content.opf', 'toc.ncx', 'META-INF/container.xml', 'Thumbs.db', 'ehthumbs.db', '.DS_Store', @@ -530,7 +516,7 @@ class EpubBuilder(StandaloneHTMLBuilder): self.config.epub_exclude_files if not self.use_index: self.ignored_files.append('genindex' + self.out_suffix) - for root, dirs, files in os.walk(outdir): + for root, dirs, files in os.walk(self.outdir): dirs.sort() for fn in sorted(files): filename = path.join(root, fn)[olen:] @@ -620,9 +606,7 @@ class EpubBuilder(StandaloneHTMLBuilder): html.escape(self.refnodes[0]['refuri']))) # write the project file - copy_asset_file(path.join(self.template_dir, 'content.opf_t'), - path.join(outdir, outname), - metadata) + copy_asset_file(path.join(self.template_dir, 'content.opf_t'), self.outdir, metadata) def new_navpoint(self, node: Dict[str, Any], level: int, incr: bool = True) -> NavPoint: """Create a new entry in the toc from the node at given level.""" @@ -639,8 +623,8 @@ class EpubBuilder(StandaloneHTMLBuilder): Subelements of a node are nested inside the navpoint. For nested nodes the parent node is reinserted in the subnav. """ - navstack = [] # type: List[NavPoint] - navstack.append(NavPoint('dummy', '', '', '', [])) + navstack: List[NavPoint] = [] + navstack.append(NavPoint('dummy', 0, '', '', [])) level = 0 lastnode = None for node in nodes: @@ -681,25 +665,19 @@ class EpubBuilder(StandaloneHTMLBuilder): """Create a dictionary with all metadata for the toc.ncx file properly escaped. """ - metadata = {} # type: Dict[str, Any] + metadata: Dict[str, Any] = {} metadata['uid'] = self.config.epub_uid metadata['title'] = html.escape(self.config.epub_title) metadata['level'] = level metadata['navpoints'] = navpoints return metadata - def build_toc(self, outdir: str = None, outname: str = 'toc.ncx') -> None: + def build_toc(self) -> None: """Write the metainfo file toc.ncx.""" - if outdir: - warnings.warn('The arguments of EpubBuilder.build_toc() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - else: - outdir = self.outdir - - logger.info(__('writing %s file...'), outname) + logger.info(__('writing toc.ncx file...')) if self.config.epub_tocscope == 'default': - doctree = self.env.get_and_resolve_doctree(self.config.master_doc, + doctree = self.env.get_and_resolve_doctree(self.config.root_doc, self, prune_toctrees=False, includehidden=False) refnodes = self.get_refnodes(doctree, []) @@ -711,28 +689,21 @@ class EpubBuilder(StandaloneHTMLBuilder): navpoints = self.build_navpoints(refnodes) level = max(item['level'] for item in self.refnodes) level = min(level, self.config.epub_tocdepth) - copy_asset_file(path.join(self.template_dir, 'toc.ncx_t'), - path.join(outdir, outname), + copy_asset_file(path.join(self.template_dir, 'toc.ncx_t'), self.outdir, self.toc_metadata(level, navpoints)) - def build_epub(self, outdir: str = None, outname: str = None) -> None: + def build_epub(self) -> None: """Write the epub file. It is a zip file with the mimetype file stored uncompressed as the first entry. """ - if outdir: - warnings.warn('The arguments of EpubBuilder.build_epub() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - else: - outdir = self.outdir - outname = self.config.epub_basename + '.epub' - + outname = self.config.epub_basename + '.epub' logger.info(__('writing %s file...'), outname) - epub_filename = path.join(outdir, outname) + epub_filename = path.join(self.outdir, outname) with ZipFile(epub_filename, 'w', ZIP_DEFLATED) as epub: - epub.write(path.join(outdir, 'mimetype'), 'mimetype', ZIP_STORED) + epub.write(path.join(self.outdir, 'mimetype'), 'mimetype', ZIP_STORED) for filename in ['META-INF/container.xml', 'content.opf', 'toc.ncx']: - epub.write(path.join(outdir, filename), filename, ZIP_DEFLATED) + epub.write(path.join(self.outdir, filename), filename, ZIP_DEFLATED) for filename in self.files: - epub.write(path.join(outdir, filename), filename, ZIP_DEFLATED) + epub.write(path.join(self.outdir, filename), filename, ZIP_DEFLATED) diff --git a/sphinx/builders/applehelp.py b/sphinx/builders/applehelp.py deleted file mode 100644 index bfc8c8dc8..000000000 --- a/sphinx/builders/applehelp.py +++ /dev/null @@ -1,45 +0,0 @@ -""" - sphinx.builders.applehelp - ~~~~~~~~~~~~~~~~~~~~~~~~~ - - Build Apple help books. - - :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import warnings -from typing import Any, Dict - -from sphinxcontrib.applehelp import (AppleHelpBuilder, AppleHelpCodeSigningFailed, - AppleHelpIndexerFailed) - -from sphinx.application import Sphinx -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias - -deprecated_alias('sphinx.builders.applehelp', - { - 'AppleHelpCodeSigningFailed': AppleHelpCodeSigningFailed, - 'AppleHelpIndexerFailed': AppleHelpIndexerFailed, - 'AppleHelpBuilder': AppleHelpBuilder, - }, - RemovedInSphinx40Warning, - { - 'AppleHelpCodeSigningFailed': - 'sphinxcontrib.applehelp.AppleHelpCodeSigningFailed', - 'AppleHelpIndexerFailed': - 'sphinxcontrib.applehelp.AppleHelpIndexerFailed', - 'AppleHelpBuilder': 'sphinxcontrib.applehelp.AppleHelpBuilder', - }) - - -def setup(app: Sphinx) -> Dict[str, Any]: - warnings.warn('sphinx.builders.applehelp has been moved to sphinxcontrib-applehelp.', - RemovedInSphinx40Warning, stacklevel=2) - app.setup_extension('sphinxcontrib.applehelp') - - return { - 'version': 'builtin', - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index 87dd03fb8..5e51499f9 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -51,9 +51,9 @@ class ChangesBuilder(Builder): def write(self, *ignored: Any) -> None: version = self.config.version domain = cast(ChangeSetDomain, self.env.get_domain('changeset')) - libchanges = {} # type: Dict[str, List[Tuple[str, str, int]]] - apichanges = [] # type: List[Tuple[str, str, int]] - otherchanges = {} # type: Dict[Tuple[str, str], List[Tuple[str, str, int]]] + libchanges: Dict[str, List[Tuple[str, str, int]]] = {} + apichanges: List[Tuple[str, str, int]] = [] + otherchanges: Dict[Tuple[str, str], List[Tuple[str, str, int]]] = {} changesets = domain.get_changesets_for(version) if not changesets: diff --git a/sphinx/builders/devhelp.py b/sphinx/builders/devhelp.py deleted file mode 100644 index 9625b62f4..000000000 --- a/sphinx/builders/devhelp.py +++ /dev/null @@ -1,40 +0,0 @@ -""" - sphinx.builders.devhelp - ~~~~~~~~~~~~~~~~~~~~~~~ - - Build HTML documentation and Devhelp_ support files. - - .. _Devhelp: https://wiki.gnome.org/Apps/Devhelp - - :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import warnings -from typing import Any, Dict - -from sphinxcontrib.devhelp import DevhelpBuilder - -from sphinx.application import Sphinx -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias - -deprecated_alias('sphinx.builders.devhelp', - { - 'DevhelpBuilder': DevhelpBuilder, - }, - RemovedInSphinx40Warning, - { - 'DevhelpBuilder': 'sphinxcontrib.devhelp.DevhelpBuilder' - }) - - -def setup(app: Sphinx) -> Dict[str, Any]: - warnings.warn('sphinx.builders.devhelp has been moved to sphinxcontrib-devhelp.', - RemovedInSphinx40Warning) - app.setup_extension('sphinxcontrib.devhelp') - - return { - 'version': 'builtin', - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index d1cf64eb3..be69e87ef 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -10,16 +10,13 @@ """ import html -import warnings -from collections import namedtuple from os import path -from typing import Any, Dict, List, Set, Tuple +from typing import Any, Dict, List, NamedTuple, Set, Tuple from sphinx import package_dir from sphinx.application import Sphinx from sphinx.builders import _epub_base from sphinx.config import ENUM, Config -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import __ from sphinx.util import logging, xmlname_checker from sphinx.util.fileutil import copy_asset_file @@ -29,7 +26,12 @@ from sphinx.util.osutil import make_filename logger = logging.getLogger(__name__) -NavPoint = namedtuple('NavPoint', ['text', 'refuri', 'children']) +class NavPoint(NamedTuple): + text: str + refuri: str + children: List[Any] # mypy does not support recursive types + # https://github.com/python/mypy/issues/7069 + # writing modes PAGE_PROGRESSION_DIRECTIONS = { @@ -81,10 +83,6 @@ class Epub3Builder(_epub_base.EpubBuilder): self.build_toc() self.build_epub() - def validate_config_value(self) -> None: - warnings.warn('Epub3Builder.validate_config_value() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - def content_metadata(self) -> Dict: """Create a dictionary with all metadata for the content.opf file properly escaped. @@ -120,7 +118,7 @@ class Epub3Builder(_epub_base.EpubBuilder): The difference from build_navpoints method is templates which are used when generating navigation documents. """ - navstack = [] # type: List[NavPoint] + navstack: List[NavPoint] = [] navstack.append(NavPoint('', '', [])) level = 0 for node in navnodes: @@ -156,25 +154,19 @@ class Epub3Builder(_epub_base.EpubBuilder): """Create a dictionary with all metadata for the nav.xhtml file properly escaped. """ - metadata = {} # type: Dict + metadata: Dict = {} metadata['lang'] = html.escape(self.config.epub_language) metadata['toc_locale'] = html.escape(self.guide_titles['toc']) metadata['navlist'] = navlist return metadata - def build_navigation_doc(self, outdir: str = None, outname: str = 'nav.xhtml') -> None: + def build_navigation_doc(self) -> None: """Write the metainfo file nav.xhtml.""" - if outdir: - warnings.warn('The arguments of Epub3Builder.build_navigation_doc() ' - 'is deprecated.', RemovedInSphinx40Warning, stacklevel=2) - else: - outdir = self.outdir - - logger.info(__('writing %s file...'), outname) + logger.info(__('writing nav.xhtml file...')) if self.config.epub_tocscope == 'default': doctree = self.env.get_and_resolve_doctree( - self.config.master_doc, self, + self.config.root_doc, self, prune_toctrees=False, includehidden=False) refnodes = self.get_refnodes(doctree, []) self.toc_add_files(refnodes) @@ -182,13 +174,12 @@ class Epub3Builder(_epub_base.EpubBuilder): # 'includehidden' refnodes = self.refnodes navlist = self.build_navlist(refnodes) - copy_asset_file(path.join(self.template_dir, 'nav.xhtml_t'), - path.join(outdir, outname), + copy_asset_file(path.join(self.template_dir, 'nav.xhtml_t'), self.outdir, self.navigation_doc_metadata(navlist)) # Add nav.xhtml to epub file - if outname not in self.files: - self.files.append(outname) + if 'nav.xhtml' not in self.files: + self.files.append('nav.xhtml') def validate_config_values(app: Sphinx) -> None: @@ -232,7 +223,7 @@ def validate_config_values(app: Sphinx) -> None: def convert_epub_css_files(app: Sphinx, config: Config) -> None: """This converts string styled epub_css_files to tuple styled one.""" - epub_css_files = [] # type: List[Tuple[str, Dict]] + epub_css_files: List[Tuple[str, Dict]] = [] for entry in config.epub_css_files: if isinstance(entry, str): epub_css_files.append((entry, {})) diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index 75c95c0bc..be178ca24 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -13,7 +13,7 @@ from collections import OrderedDict, defaultdict from datetime import datetime, timedelta, tzinfo from os import getenv, path, walk from time import time -from typing import Any, Dict, Generator, Iterable, List, Set, Tuple, Union +from typing import Any, DefaultDict, Dict, Generator, Iterable, List, Set, Tuple, Union from uuid import uuid4 from docutils import nodes @@ -33,33 +33,8 @@ from sphinx.util.osutil import canon_path, ensuredir, relpath from sphinx.util.tags import Tags from sphinx.util.template import SphinxRenderer -if False: - # For type annotation - from typing import DefaultDict # for python3.5.1 - logger = logging.getLogger(__name__) -POHEADER = r""" -# SOME DESCRIPTIVE TITLE. -# Copyright (C) %(copyright)s -# This file is distributed under the same license as the %(project)s package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: %(project)s %(version)s\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: %(ctime)s\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -"""[1:] # RemovedInSphinx40Warning - class Message: """An entry of translatable message.""" @@ -73,10 +48,10 @@ class Catalog: """Catalog of translatable messages.""" def __init__(self) -> None: - self.messages = [] # type: List[str] - # retain insertion order, a la OrderedDict - self.metadata = OrderedDict() # type: Dict[str, List[Tuple[str, int, str]]] - # msgid -> file, line, uid + self.messages: List[str] = [] # retain insertion order, a la OrderedDict + + # msgid -> file, line, uid + self.metadata: Dict[str, List[Tuple[str, int, str]]] = OrderedDict() def add(self, msg: str, origin: Union[Element, "MsgOrigin"]) -> None: if not hasattr(origin, 'uid'): @@ -146,8 +121,7 @@ class I18nBuilder(Builder): """ name = 'i18n' versioning_method = 'text' - versioning_compare = None # type: bool - # be set by `gettext_uuid` + versioning_compare: bool = None # be set by `gettext_uuid` use_message_catalog = False def init(self) -> None: @@ -155,7 +129,7 @@ class I18nBuilder(Builder): self.env.set_versioning_method(self.versioning_method, self.env.config.gettext_uuid) self.tags = I18nTags() - self.catalogs = defaultdict(Catalog) # type: DefaultDict[str, Catalog] + self.catalogs: DefaultDict[str, Catalog] = defaultdict(Catalog) def get_target_uri(self, docname: str, typ: str = None) -> str: return '' diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index c799b0176..a78d54a16 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -13,9 +13,9 @@ import os import posixpath import re import sys -import warnings +from datetime import datetime from os import path -from typing import IO, Any, Dict, Iterable, Iterator, List, Set, Tuple +from typing import IO, Any, Dict, Iterable, Iterator, List, Set, Tuple, Type from urllib.parse import quote from docutils import nodes @@ -29,7 +29,6 @@ from sphinx import __display_version__, package_dir from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import ENUM, Config -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain, Index, IndexEntry from sphinx.environment.adapters.asset import ImageAdapter from sphinx.environment.adapters.indexentries import IndexEntries @@ -39,7 +38,7 @@ from sphinx.highlighting import PygmentsBridge from sphinx.locale import _, __ from sphinx.search import js_index from sphinx.theming import HTMLThemeFactory -from sphinx.util import logging, md5, progress_message, status_iterator +from sphinx.util import isurl, logging, md5, progress_message, status_iterator from sphinx.util.docutils import is_html5_writer_available, new_document from sphinx.util.fileutil import copy_asset from sphinx.util.i18n import format_date @@ -49,11 +48,6 @@ from sphinx.util.osutil import copyfile, ensuredir, os_path, relative_uri from sphinx.util.tags import Tags from sphinx.writers.html import HTMLTranslator, HTMLWriter -if False: - # For type annotation - from typing import Type # for python3.5.1 - - # HTML5 Writer is available or not if is_html5_writer_available(): from sphinx.writers.html5 import HTML5Translator @@ -88,9 +82,9 @@ class Stylesheet(str): its filename (str). """ - attributes = None # type: Dict[str, str] - filename = None # type: str - priority = None # type: int + attributes: Dict[str, str] = None + filename: str = None + priority: int = None def __new__(cls, filename: str, *args: str, priority: int = 500, **attributes: Any ) -> "Stylesheet": @@ -114,9 +108,9 @@ class JavaScript(str): its filename (str). """ - attributes = None # type: Dict[str, str] - filename = None # type: str - priority = None # type: int + attributes: Dict[str, str] = None + filename: str = None + priority: int = None def __new__(cls, filename: str, priority: int = 500, **attributes: str) -> "JavaScript": self = str.__new__(cls, filename) @@ -185,7 +179,7 @@ class StandaloneHTMLBuilder(Builder): allow_parallel = True out_suffix = '.html' link_suffix = '.html' # defaults to matching out_suffix - indexer_format = js_index # type: Any + indexer_format: Any = js_index indexer_dumps_unicode = True # create links to original images from images [True/False] html_scaled_image_link = True @@ -201,26 +195,26 @@ class StandaloneHTMLBuilder(Builder): use_index = False download_support = True # enable download role - imgpath = None # type: str - domain_indices = [] # type: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] # NOQA + imgpath: str = None + domain_indices: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] = [] # NOQA def __init__(self, app: Sphinx) -> None: super().__init__(app) # CSS files - self.css_files = [] # type: List[Dict[str, str]] + self.css_files: List[Dict[str, str]] = [] # JS files - self.script_files = [] # type: List[JavaScript] + self.script_files: List[JavaScript] = [] def init(self) -> None: self.build_info = self.create_build_info() # basename of images directory self.imagedir = '_images' # section numbers for headings in the currently visited document - self.secnumbers = {} # type: Dict[str, Tuple[int, ...]] + self.secnumbers: Dict[str, Tuple[int, ...]] = {} # currently written docname - self.current_docname = None # type: str + self.current_docname: str = None self.init_templates() self.init_highlighter() @@ -256,6 +250,14 @@ class StandaloneHTMLBuilder(Builder): return jsfile return None + def _get_style_filename(self) -> str: + if self.config.html_style is not None: + return self.config.html_style + elif self.theme: + return self.theme.get_config('theme', 'stylesheet') + else: + return 'default.css' + def get_theme_config(self) -> Tuple[str, Dict]: return self.config.html_theme, self.config.html_theme_options @@ -291,6 +293,9 @@ class StandaloneHTMLBuilder(Builder): self.dark_highlighter = None def init_css_files(self) -> None: + self.add_css_file('pygments.css', priority=200) + self.add_css_file(self._get_style_filename(), priority=200) + for filename, attrs in self.app.registry.css_files: self.add_css_file(filename, **attrs) @@ -305,6 +310,8 @@ class StandaloneHTMLBuilder(Builder): self.css_files.append(Stylesheet(filename, **kwargs)) # type: ignore def init_js_files(self) -> None: + self.add_js_file('documentation_options.js', id="documentation_options", + data_url_root='', priority=200) self.add_js_file('jquery.js', priority=200) self.add_js_file('underscore.js', priority=200) self.add_js_file('doctools.js', priority=200) @@ -326,7 +333,7 @@ class StandaloneHTMLBuilder(Builder): self.script_files.append(JavaScript(filename, **kwargs)) @property - def default_translator_class(self) -> "Type[nodes.NodeVisitor]": # type: ignore + def default_translator_class(self) -> Type[nodes.NodeVisitor]: # type: ignore if not html5_ready or self.config.html4_writer: return HTMLTranslator else: @@ -358,6 +365,7 @@ class StandaloneHTMLBuilder(Builder): buildinfo = BuildInfo.load(fp) if self.build_info != buildinfo: + logger.debug('[build target] did not match: build_info ') yield from self.env.found_docs return except ValueError as exc: @@ -372,6 +380,7 @@ class StandaloneHTMLBuilder(Builder): template_mtime = 0 for docname in self.env.found_docs: if docname not in self.env.all_docs: + logger.debug('[build target] did not in env: %r', docname) yield docname continue targetname = self.get_outfilename(docname) @@ -383,6 +392,14 @@ class StandaloneHTMLBuilder(Builder): srcmtime = max(path.getmtime(self.env.doc2path(docname)), template_mtime) if srcmtime > targetmtime: + logger.debug( + '[build target] targetname %r(%s), template(%s), docname %r(%s)', + targetname, + datetime.utcfromtimestamp(targetmtime), + datetime.utcfromtimestamp(template_mtime), + docname, + datetime.utcfromtimestamp(path.getmtime(self.env.doc2path(docname))), + ) yield docname except OSError: # source doesn't exist anymore @@ -419,10 +436,10 @@ class StandaloneHTMLBuilder(Builder): self.load_indexer(docnames) self.docwriter = HTMLWriter(self) - self.docsettings = OptionParser( + self.docsettings: Any = OptionParser( defaults=self.env.settings, components=(self.docwriter,), - read_config_files=True).get_default_values() # type: Any + read_config_files=True).get_default_values() self.docsettings.compact_lists = bool(self.config.html_compact_lists) # determine the additional indices to include @@ -431,8 +448,7 @@ class StandaloneHTMLBuilder(Builder): indices_config = self.config.html_domain_indices if indices_config: for domain_name in sorted(self.env.domains): - domain = None # type: Domain - domain = self.env.domains[domain_name] + domain: Domain = self.env.domains[domain_name] for indexcls in domain.indices: indexname = '%s-%s' % (domain.name, indexcls.name) if isinstance(indices_config, list): @@ -457,7 +473,7 @@ class StandaloneHTMLBuilder(Builder): self.relations = self.env.collect_relations() - rellinks = [] # type: List[Tuple[str, str, str, str]] + rellinks: List[Tuple[str, str, str, str]] = [] if self.use_index: rellinks.append(('genindex', _('General Index'), 'I', _('index'))) for indexname, indexcls, content, collapse in self.domain_indices: @@ -470,13 +486,6 @@ class StandaloneHTMLBuilder(Builder): 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: - stylename = self.theme.get_config('theme', 'stylesheet') - else: - stylename = 'default.css' - self.globalcontext = { 'embedded': self.embedded, 'project': self.config.project, @@ -484,7 +493,8 @@ class StandaloneHTMLBuilder(Builder): 'version': self.config.version, 'last_updated': self.last_updated, 'copyright': self.config.copyright, - 'master_doc': self.config.master_doc, + 'master_doc': self.config.root_doc, + 'root_doc': self.config.root_doc, 'use_opensearch': self.config.html_use_opensearch, 'docstitle': self.config.html_title, 'shorttitle': self.config.html_short_title, @@ -499,7 +509,7 @@ class StandaloneHTMLBuilder(Builder): 'language': self.config.language, 'css_files': self.css_files, 'sphinx_version': __display_version__, - 'style': stylename, + 'style': self._get_style_filename(), 'rellinks': rellinks, 'builder': self.name, 'parents': [], @@ -786,12 +796,12 @@ class StandaloneHTMLBuilder(Builder): excluded, context=context, renderer=self.templates, onerror=onerror) def copy_html_logo(self) -> None: - if self.config.html_logo: + if self.config.html_logo and not isurl(self.config.html_logo): copy_asset(path.join(self.confdir, self.config.html_logo), path.join(self.outdir, '_static')) def copy_html_favicon(self) -> None: - if self.config.html_favicon: + if self.config.html_favicon and not isurl(self.config.html_favicon): copy_asset(path.join(self.confdir, self.config.html_favicon), path.join(self.outdir, '_static')) @@ -889,21 +899,11 @@ class StandaloneHTMLBuilder(Builder): # only index pages with title if self.indexer is not None and title: filename = self.env.doc2path(pagename, base=None) - try: - metadata = self.env.metadata.get(pagename, {}) - if 'nosearch' in metadata: - self.indexer.feed(pagename, filename, '', new_document('')) - else: - self.indexer.feed(pagename, filename, title, doctree) - except TypeError: - # fallback for old search-adapters - self.indexer.feed(pagename, title, doctree) # type: ignore - indexer_name = self.indexer.__class__.__name__ - warnings.warn( - 'The %s.feed() method signature is deprecated. Update to ' - '%s.feed(docname, filename, title, doctree).' % ( - indexer_name, indexer_name), - RemovedInSphinx40Warning, stacklevel=2) + metadata = self.env.metadata.get(pagename, {}) + if 'nosearch' in metadata: + self.indexer.feed(pagename, filename, '', new_document('')) + else: + self.indexer.feed(pagename, filename, title, doctree) def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any) -> str: if 'includehidden' not in kwargs: @@ -1108,7 +1108,7 @@ class StandaloneHTMLBuilder(Builder): def convert_html_css_files(app: Sphinx, config: Config) -> None: """This converts string styled html_css_files to tuple styled one.""" - html_css_files = [] # type: List[Tuple[str, Dict]] + html_css_files: List[Tuple[str, Dict]] = [] for entry in config.html_css_files: if isinstance(entry, str): html_css_files.append((entry, {})) @@ -1125,7 +1125,7 @@ def convert_html_css_files(app: Sphinx, config: Config) -> None: def convert_html_js_files(app: Sphinx, config: Config) -> None: """This converts string styled html_js_files to tuple styled one.""" - html_js_files = [] # type: List[Tuple[str, Dict]] + html_js_files: List[Tuple[str, Dict]] = [] for entry in config.html_js_files: if isinstance(entry, str): html_js_files.append((entry, {})) @@ -1140,7 +1140,7 @@ def convert_html_js_files(app: Sphinx, config: Config) -> None: config.html_js_files = html_js_files # type: ignore -def setup_js_tag_helper(app: Sphinx, pagename: str, templatexname: str, +def setup_js_tag_helper(app: Sphinx, pagename: str, templatename: str, context: Dict, doctree: Node) -> None: """Set up js_tag() template helper. @@ -1157,6 +1157,8 @@ def setup_js_tag_helper(app: Sphinx, pagename: str, templatexname: str, if value is not None: if key == 'body': body = value + elif key == 'data_url_root': + attrs.append('data-url_root="%s"' % pathto('', resource=True)) else: attrs.append('%s="%s"' % (key, html.escape(value, True))) if js.filename: @@ -1169,6 +1171,26 @@ def setup_js_tag_helper(app: Sphinx, pagename: str, templatexname: str, context['js_tag'] = js_tag +def setup_resource_paths(app: Sphinx, pagename: str, templatename: str, + context: Dict, doctree: Node) -> None: + """Set up relative resource paths.""" + pathto = context.get('pathto') + + # favicon_url + favicon = context.get('favicon') + if favicon and not isurl(favicon): + context['favicon_url'] = pathto('_static/' + favicon, resource=True) + else: + context['favicon_url'] = favicon + + # logo_url + logo = context.get('logo') + if logo and not isurl(logo): + context['logo_url'] = pathto('_static/' + logo, resource=True) + else: + context['logo_url'] = logo + + def validate_math_renderer(app: Sphinx) -> None: if app.builder.format != 'html': return @@ -1209,27 +1231,47 @@ def validate_html_static_path(app: Sphinx, config: Config) -> None: def validate_html_logo(app: Sphinx, config: Config) -> None: """Check html_logo setting.""" - if config.html_logo and not path.isfile(path.join(app.confdir, config.html_logo)): + if (config.html_logo and + not path.isfile(path.join(app.confdir, config.html_logo)) and + not isurl(config.html_logo)): logger.warning(__('logo file %r does not exist'), config.html_logo) config.html_logo = None # type: ignore def validate_html_favicon(app: Sphinx, config: Config) -> None: """Check html_favicon setting.""" - if config.html_favicon and not path.isfile(path.join(app.confdir, config.html_favicon)): + if (config.html_favicon and + not path.isfile(path.join(app.confdir, config.html_favicon)) and + not isurl(config.html_favicon)): logger.warning(__('favicon file %r does not exist'), config.html_favicon) config.html_favicon = None # type: ignore +class _stable_repr_object(): + + def __repr__(self): + return '<object>' + + +UNSET = _stable_repr_object() + + 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 + html_add_permalinks = config.html_add_permalinks + if html_add_permalinks is UNSET: + return + + # RemovedInSphinx60Warning + logger.warning(__('html_add_permalinks has been deprecated since v3.5.0. ' + 'Please use html_permalinks and html_permalinks_icon instead.')) + if not html_add_permalinks: + config.html_permalinks = False # type: ignore[attr-defined] + return + config.html_permalinks_icon = html.escape( # type: ignore[attr-defined] + html_add_permalinks + ) # for compatibility import sphinxcontrib.serializinghtml # NOQA @@ -1261,7 +1303,7 @@ 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', None, 'html') + app.add_config_value('html_add_permalinks', UNSET, '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') @@ -1283,7 +1325,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('html_search_scorer', '', None) app.add_config_value('html_scaled_image_link', True, 'html') app.add_config_value('html_baseurl', '', 'html') - app.add_config_value('html_codeblock_linenos_style', 'table', 'html', + app.add_config_value('html_codeblock_linenos_style', 'inline', 'html', # RemovedInSphinx60Warning # NOQA ENUM('table', 'inline')) app.add_config_value('html_math_renderer', None, 'env') app.add_config_value('html4_writer', False, 'html') @@ -1302,6 +1344,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('config-inited', validate_html_favicon, priority=800) app.connect('builder-inited', validate_math_renderer) app.connect('html-page-context', setup_js_tag_helper) + app.connect('html-page-context', setup_resource_paths) # load default math renderer app.setup_extension('sphinx.ext.mathjax') diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py deleted file mode 100644 index 08719712c..000000000 --- a/sphinx/builders/htmlhelp.py +++ /dev/null @@ -1,47 +0,0 @@ -""" - sphinx.builders.htmlhelp - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Build HTML help support files. - Parts adapted from Python's Doc/tools/prechm.py. - - :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import warnings -from typing import Any, Dict - -from sphinxcontrib.htmlhelp import (HTMLHelpBuilder, chm_htmlescape, chm_locales, - default_htmlhelp_basename) - -from sphinx.application import Sphinx -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias - -deprecated_alias('sphinx.builders.htmlhelp', - { - 'chm_locales': chm_locales, - 'chm_htmlescape': chm_htmlescape, - 'HTMLHelpBuilder': HTMLHelpBuilder, - 'default_htmlhelp_basename': default_htmlhelp_basename, - }, - RemovedInSphinx40Warning, - { - 'chm_locales': 'sphinxcontrib.htmlhelp.chm_locales', - 'chm_htmlescape': 'sphinxcontrib.htmlhelp.chm_htmlescape', - 'HTMLHelpBuilder': 'sphinxcontrib.htmlhelp.HTMLHelpBuilder', - 'default_htmlhelp_basename': - 'sphinxcontrib.htmlhelp.default_htmlhelp_basename', - }) - - -def setup(app: Sphinx) -> Dict[str, Any]: - warnings.warn('sphinx.builders.htmlhelp has been moved to sphinxcontrib-htmlhelp.', - RemovedInSphinx40Warning, stacklevel=2) - app.setup_extension('sphinxcontrib.htmlhelp') - - return { - 'version': 'builtin', - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 5c7a7412a..a37a35e61 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -24,7 +24,7 @@ from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTING from sphinx.builders.latex.theming import Theme, ThemeFactory from sphinx.builders.latex.util import ExtBabel from sphinx.config import ENUM, Config -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.environment.adapters.asset import ImageAdapter from sphinx.errors import NoUri, SphinxError from sphinx.locale import _, __ @@ -122,10 +122,10 @@ class LaTeXBuilder(Builder): default_translator_class = LaTeXTranslator def init(self) -> None: - self.babel = None # type: ExtBabel - self.context = {} # type: Dict[str, Any] - self.docnames = [] # type: Iterable[str] - self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]] + self.babel: ExtBabel = None + self.context: Dict[str, Any] = {} + self.docnames: Iterable[str] = {} + self.document_data: List[Tuple[str, str, str, str, str, bool]] = [] self.themes = ThemeFactory(self.app) texescape.init() @@ -153,7 +153,7 @@ class LaTeXBuilder(Builder): 'will be written')) return # assign subdirs to titles - self.titles = [] # type: List[Tuple[str, str]] + self.titles: List[Tuple[str, str]] = [] for entry in preliminary_document_data: docname = entry[0] if docname not in self.env.all_docs: @@ -216,14 +216,18 @@ class LaTeXBuilder(Builder): if not self.babel.uses_cyrillic(): if 'X2' in self.context['fontenc']: self.context['substitutefont'] = '\\usepackage{substitutefont}' - self.context['textcyrillic'] = '\\usepackage[Xtwo]{sphinxcyrillic}' + self.context['textcyrillic'] = ('\\usepackage[Xtwo]' + '{sphinxpackagecyrillic}') elif 'T2A' in self.context['fontenc']: self.context['substitutefont'] = '\\usepackage{substitutefont}' - self.context['textcyrillic'] = '\\usepackage[TtwoA]{sphinxcyrillic}' + self.context['textcyrillic'] = ('\\usepackage[TtwoA]' + '{sphinxpackagecyrillic}') if 'LGR' in self.context['fontenc']: self.context['substitutefont'] = '\\usepackage{substitutefont}' else: self.context['textgreek'] = '' + if self.context['substitutefont'] == '': + self.context['fontsubstitution'] = '' # 'babel' key is public and user setting must be obeyed if self.context['babel']: @@ -252,16 +256,16 @@ class LaTeXBuilder(Builder): with open(stylesheet, 'w') as f: f.write('\\NeedsTeXFormat{LaTeX2e}[1995/12/01]\n') f.write('\\ProvidesPackage{sphinxhighlight}' - '[2016/05/29 stylesheet for highlighting with pygments]\n\n') + '[2016/05/29 stylesheet for highlighting with pygments]\n') + f.write('% Its contents depend on pygments_style configuration variable.\n\n') f.write(highlighter.get_stylesheet()) def write(self, *ignored: Any) -> None: docwriter = LaTeXWriter(self) - docsettings = OptionParser( + docsettings: Any = OptionParser( defaults=self.env.settings, components=(docwriter,), - read_config_files=True).get_default_values() # type: Any - patch_settings(docsettings) + read_config_files=True).get_default_values() self.init_document_data() self.write_stylesheet() @@ -352,7 +356,7 @@ class LaTeXBuilder(Builder): for pendingnode in largetree.traverse(addnodes.pending_xref): docname = pendingnode['refdocname'] sectname = pendingnode['refsectname'] - newnodes = [nodes.emphasis(sectname, sectname)] # type: List[Node] + newnodes: List[Node] = [nodes.emphasis(sectname, sectname)] for subdir, title in self.titles: if docname.startswith(subdir): newnodes.append(nodes.Text(_(' (in '), _(' (in '))) @@ -364,10 +368,6 @@ class LaTeXBuilder(Builder): pendingnode.replace_self(newnodes) return largetree - def apply_transforms(self, doctree: nodes.document) -> None: - warnings.warn('LaTeXBuilder.apply_transforms() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - def finish(self) -> None: self.copy_image_files() self.write_message_catalog() @@ -462,44 +462,6 @@ class LaTeXBuilder(Builder): return self.app.registry.latex_packages_after_hyperref -def patch_settings(settings: Any) -> Any: - """Make settings object to show deprecation messages.""" - - class Values(type(settings)): # type: ignore - @property - def author(self) -> str: - warnings.warn('settings.author is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - return self._author - - @property - def title(self) -> str: - warnings.warn('settings.title is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - return self._title - - @property - def contentsname(self) -> str: - warnings.warn('settings.contentsname is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - return self._contentsname - - @property - def docname(self) -> str: - warnings.warn('settings.docname is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - return self._docname - - @property - def docclass(self) -> str: - warnings.warn('settings.docclass is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - return self._docclass - - # dynamic subclassing - settings.__class__ = Values - - def validate_config_values(app: Sphinx, config: Config) -> None: for key in list(config.latex_elements): if key not in DEFAULT_SETTINGS: @@ -525,7 +487,7 @@ def install_packages_for_ja(app: Sphinx) -> None: def default_latex_engine(config: Config) -> str: """ Better default latex_engine settings for specific languages. """ if config.language == 'ja': - return 'platex' + return 'uplatex' elif (config.language or '').startswith('zh'): return 'xelatex' elif config.language == 'el': @@ -556,7 +518,7 @@ def default_latex_documents(config: Config) -> List[Tuple[str, str, str, str, st """ Better default latex_documents settings. """ project = texescape.escape(config.project, config.latex_engine) author = texescape.escape(config.author, config.latex_engine) - return [(config.master_doc, + return [(config.root_doc, make_filename_from_project(config.project) + '.tex', texescape.escape_abbr(project), texescape.escape_abbr(author), diff --git a/sphinx/builders/latex/constants.py b/sphinx/builders/latex/constants.py index 0b20c7cef..f5e69225d 100644 --- a/sphinx/builders/latex/constants.py +++ b/sphinx/builders/latex/constants.py @@ -11,7 +11,12 @@ from typing import Any, Dict PDFLATEX_DEFAULT_FONTPKG = r''' -\usepackage{times} +\usepackage{tgtermes} +\usepackage{tgheros} +\renewcommand{\ttdefault}{txtt} +''' + +PDFLATEX_DEFAULT_FONTSUBSTITUTION = r''' \expandafter\ifx\csname T@LGR\endcsname\relax \else % LGR was declared as font encoding @@ -66,7 +71,7 @@ XELATEX_GREEK_DEFAULT_FONTPKG = (XELATEX_DEFAULT_FONTPKG + LUALATEX_DEFAULT_FONTPKG = XELATEX_DEFAULT_FONTPKG -DEFAULT_SETTINGS = { +DEFAULT_SETTINGS: Dict[str, Any] = { 'latex_engine': 'pdflatex', 'papersize': '', 'pointsize': '', @@ -76,7 +81,7 @@ DEFAULT_SETTINGS = { 'maxlistdepth': '', 'sphinxpkgoptions': '', 'sphinxsetup': '', - 'fvset': '\\fvset{fontsize=\\small}', + 'fvset': '\\fvset{fontsize=auto}', 'passoptionstopackages': '', 'geometry': '\\usepackage{geometry}', 'inputenc': '', @@ -88,6 +93,7 @@ DEFAULT_SETTINGS = { 'babel': '\\usepackage{babel}', 'polyglossia': '', 'fontpkg': PDFLATEX_DEFAULT_FONTPKG, + 'fontsubstitution': PDFLATEX_DEFAULT_FONTSUBSTITUTION, 'substitutefont': '', 'textcyrillic': '', 'textgreek': '\\usepackage{textalpha}', @@ -115,9 +121,9 @@ DEFAULT_SETTINGS = { 'figure_align': 'htbp', 'tocdepth': '', 'secnumdepth': '', -} # type: Dict[str, Any] +} -ADDITIONAL_SETTINGS = { +ADDITIONAL_SETTINGS: Dict[Any, Dict[str, Any]] = { 'pdflatex': { 'inputenc': '\\usepackage[utf8]{inputenc}', 'utf8extra': ('\\ifdefined\\DeclareUnicodeCharacter\n' @@ -142,6 +148,8 @@ ADDITIONAL_SETTINGS = { 'fontenc': ('\\usepackage{fontspec}\n' '\\defaultfontfeatures[\\rmfamily,\\sffamily,\\ttfamily]{}'), 'fontpkg': XELATEX_DEFAULT_FONTPKG, + 'fvset': '\\fvset{fontsize=\\small}', + 'fontsubstitution': '', 'textgreek': '', 'utf8extra': ('\\catcode`^^^^00a0\\active\\protected\\def^^^^00a0' '{\\leavevmode\\nobreak\\ }'), @@ -153,6 +161,8 @@ ADDITIONAL_SETTINGS = { 'fontenc': ('\\usepackage{fontspec}\n' '\\defaultfontfeatures[\\rmfamily,\\sffamily,\\ttfamily]{}'), 'fontpkg': LUALATEX_DEFAULT_FONTPKG, + 'fvset': '\\fvset{fontsize=\\small}', + 'fontsubstitution': '', 'textgreek': '', 'utf8extra': ('\\catcode`^^^^00a0\\active\\protected\\def^^^^00a0' '{\\leavevmode\\nobreak\\ }'), @@ -161,7 +171,8 @@ ADDITIONAL_SETTINGS = { 'latex_engine': 'platex', 'babel': '', 'classoptions': ',dvipdfmx', - 'fontpkg': '\\usepackage{times}', + 'fontpkg': PDFLATEX_DEFAULT_FONTPKG, + 'fontsubstitution': '', 'textgreek': '', 'fncychap': '', 'geometry': '\\usepackage[dvipdfm]{geometry}', @@ -170,7 +181,8 @@ ADDITIONAL_SETTINGS = { 'latex_engine': 'uplatex', 'babel': '', 'classoptions': ',dvipdfmx', - 'fontpkg': '\\usepackage{times}', + 'fontpkg': PDFLATEX_DEFAULT_FONTPKG, + 'fontsubstitution': '', 'textgreek': '', 'fncychap': '', 'geometry': '\\usepackage[dvipdfm]{geometry}', @@ -190,7 +202,7 @@ ADDITIONAL_SETTINGS = { ('xelatex', 'el'): { 'fontpkg': XELATEX_GREEK_DEFAULT_FONTPKG, }, -} # type: Dict[Any, Dict[str, Any]] +} SHORTHANDOFF = r''' diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py index 5af79e8a2..d5c53a58b 100644 --- a/sphinx/builders/latex/theming.py +++ b/sphinx/builders/latex/theming.py @@ -106,7 +106,7 @@ class ThemeFactory: """A factory class for LaTeX Themes.""" def __init__(self, app: Sphinx) -> None: - self.themes = {} # type: Dict[str, Theme] + self.themes: Dict[str, Theme] = {} self.theme_paths = [path.join(app.srcdir, p) for p in app.config.latex_theme_path] self.config = app.config self.load_builtin_themes(app.config) diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py index 0a74eded4..a07393690 100644 --- a/sphinx/builders/latex/transforms.py +++ b/sphinx/builders/latex/transforms.py @@ -33,7 +33,7 @@ class FootnoteDocnameUpdater(SphinxTransform): def apply(self, **kwargs: Any) -> None: matcher = NodeMatcher(*self.TARGET_NODES) - for node in self.document.traverse(matcher): # type: nodes.Element + for node in self.document.traverse(matcher): # type: Element node['docname'] = self.env.docname @@ -65,7 +65,7 @@ class ShowUrlsTransform(SphinxPostTransform): def run(self, **kwargs: Any) -> None: try: # replace id_prefix temporarily - settings = self.document.settings # type: Any + settings: Any = self.document.settings id_prefix = settings.id_prefix settings.id_prefix = 'show_urls' @@ -157,9 +157,9 @@ class FootnoteCollector(nodes.NodeVisitor): """Collect footnotes and footnote references on the document""" def __init__(self, document: nodes.document) -> None: - self.auto_footnotes = [] # type: List[nodes.footnote] - self.used_footnote_numbers = set() # type: Set[str] - self.footnote_refs = [] # type: List[nodes.footnote_reference] + self.auto_footnotes: List[nodes.footnote] = [] + self.used_footnote_numbers: Set[str] = set() + self.footnote_refs: List[nodes.footnote_reference] = [] super().__init__(document) def unknown_visit(self, node: Node) -> None: @@ -358,11 +358,11 @@ class LaTeXFootnoteTransform(SphinxPostTransform): class LaTeXFootnoteVisitor(nodes.NodeVisitor): def __init__(self, document: nodes.document, footnotes: List[nodes.footnote]) -> None: - self.appeared = set() # type: Set[Tuple[str, str]] - self.footnotes = footnotes # type: List[nodes.footnote] - self.pendings = [] # type: List[nodes.footnote] - self.table_footnotes = [] # type: List[nodes.footnote] - self.restricted = None # type: nodes.Element + self.appeared: Set[Tuple[str, str]] = set() + self.footnotes: List[nodes.footnote] = footnotes + self.pendings: List[nodes.footnote] = [] + self.table_footnotes: List[nodes.footnote] = [] + self.restricted: Element = None super().__init__(document) def unknown_visit(self, node: Node) -> None: diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index bd81bcee6..05e12c173 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -9,7 +9,6 @@ """ import json -import queue import re import socket import time @@ -18,6 +17,7 @@ from datetime import datetime, timezone from email.utils import parsedate_to_datetime from html.parser import HTMLParser from os import path +from queue import PriorityQueue, Queue from threading import Thread from typing import (Any, Dict, Generator, List, NamedTuple, Optional, Pattern, Set, Tuple, Union, cast) @@ -120,16 +120,16 @@ class CheckExternalLinksBuilder(DummyBuilder): '%(outdir)s/output.txt') def init(self) -> None: - self.hyperlinks = {} # type: Dict[str, Hyperlink] - self._good = set() # type: Set[str] - self._broken = {} # type: Dict[str, str] - self._redirected = {} # type: Dict[str, Tuple[str, int]] + self.hyperlinks: Dict[str, Hyperlink] = {} + self._good: Set[str] = set() + self._broken: Dict[str, str] = {} + self._redirected: Dict[str, Tuple[str, int]] = {} # set a timeout for non-responding servers socket.setdefaulttimeout(5.0) # create queues and worker threads - self._wqueue = queue.PriorityQueue() # type: queue.PriorityQueue[CheckRequestType] - self._rqueue = queue.Queue() # type: queue.Queue + self._wqueue: PriorityQueue[CheckRequestType] = PriorityQueue() + self._rqueue: Queue = Queue() @property def anchors_ignore(self) -> List[Pattern]: @@ -204,7 +204,7 @@ class CheckExternalLinksBuilder(DummyBuilder): None, None, {}) return worker.limit_rate(response) - def rqueue(self, response: Response) -> queue.Queue: + def rqueue(self, response: Response) -> Queue: warnings.warn( "%s.%s is deprecated." % (self.__class__.__name__, "rqueue"), RemovedInSphinx50Warning, @@ -220,7 +220,7 @@ class CheckExternalLinksBuilder(DummyBuilder): ) return [] - def wqueue(self, response: Response) -> queue.Queue: + def wqueue(self, response: Response) -> Queue: warnings.warn( "%s.%s is deprecated." % (self.__class__.__name__, "wqueue"), RemovedInSphinx50Warning, @@ -313,8 +313,8 @@ class HyperlinkAvailabilityChecker: self.builder = builder self.config = config self.env = env - self.rate_limits = {} # type: Dict[str, RateLimit] - self.workers = [] # type: List[Thread] + self.rate_limits: Dict[str, RateLimit] = {} + self.workers: List[Thread] = [] self.to_ignore = [re.compile(x) for x in self.config.linkcheck_ignore] @@ -322,8 +322,8 @@ class HyperlinkAvailabilityChecker: self.rqueue = builder._rqueue self.wqueue = builder._wqueue else: - self.rqueue = queue.Queue() - self.wqueue = queue.PriorityQueue() + self.rqueue = Queue() + self.wqueue = PriorityQueue() def invoke_threads(self) -> None: for i in range(self.config.linkcheck_workers): @@ -364,8 +364,8 @@ class HyperlinkAvailabilityChecker: class HyperlinkAvailabilityCheckWorker(Thread): """A worker class for checking the availability of hyperlinks.""" - def __init__(self, env: BuildEnvironment, config: Config, rqueue: queue.Queue, - wqueue: queue.Queue, rate_limits: Dict[str, RateLimit], + def __init__(self, env: BuildEnvironment, config: Config, rqueue: Queue, + wqueue: Queue, rate_limits: Dict[str, RateLimit], builder: CheckExternalLinksBuilder = None) -> None: # Warning: builder argument will be removed in the sphinx-5.0. # Don't use it from extensions. diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index 292f495de..b993a2df4 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -38,7 +38,7 @@ class ManualPageBuilder(Builder): epilog = __('The manual pages are in %(outdir)s.') default_translator_class = ManualPageTranslator - supported_image_types = [] # type: List[str] + supported_image_types: List[str] = [] def init(self) -> None: if not self.config.man_pages: @@ -56,10 +56,10 @@ class ManualPageBuilder(Builder): @progress_message(__('writing')) def write(self, *ignored: Any) -> None: docwriter = ManualPageWriter(self) - docsettings = OptionParser( + docsettings: Any = OptionParser( defaults=self.env.settings, components=(docwriter,), - read_config_files=True).get_default_values() # type: Any + read_config_files=True).get_default_values() for info in self.config.man_pages: docname, name, description, authors, section = info @@ -90,7 +90,7 @@ class ManualPageBuilder(Builder): encoding='utf-8') tree = self.env.get_doctree(docname) - docnames = set() # type: Set[str] + docnames: Set[str] = set() largetree = inline_all_toctrees(self, docnames, docname, tree, darkgreen, [docname]) largetree.settings = docsettings @@ -109,7 +109,7 @@ class ManualPageBuilder(Builder): def default_man_pages(config: Config) -> List[Tuple[str, str, str, List[str], int]]: """ Better default man_pages settings. """ filename = make_filename_from_project(config.project) - return [(config.master_doc, filename, '%s %s' % (config.project, config.release), + return [(config.root_doc, filename, '%s %s' % (config.project, config.release), [config.author], 1)] @@ -118,7 +118,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('man_pages', default_man_pages, None) app.add_config_value('man_show_urls', False, None) - app.add_config_value('man_make_section_directory', False, None) + app.add_config_value('man_make_section_directory', True, None) return { 'version': 'builtin', diff --git a/sphinx/builders/qthelp.py b/sphinx/builders/qthelp.py deleted file mode 100644 index c5219dd75..000000000 --- a/sphinx/builders/qthelp.py +++ /dev/null @@ -1,42 +0,0 @@ -""" - sphinx.builders.qthelp - ~~~~~~~~~~~~~~~~~~~~~~ - - Build input files for the Qt collection generator. - - :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import warnings -from typing import Any, Dict - -from sphinxcontrib.qthelp import QtHelpBuilder, render_file - -import sphinx -from sphinx.application import Sphinx -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias - -deprecated_alias('sphinx.builders.qthelp', - { - 'render_file': render_file, - 'QtHelpBuilder': QtHelpBuilder, - }, - RemovedInSphinx40Warning, - { - 'render_file': 'sphinxcontrib.qthelp.render_file', - 'QtHelpBuilder': 'sphinxcontrib.qthelp.QtHelpBuilder', - }) - - -def setup(app: Sphinx) -> Dict[str, Any]: - warnings.warn('sphinx.builders.qthelp has been moved to sphinxcontrib-qthelp.', - RemovedInSphinx40Warning) - - app.setup_extension('sphinxcontrib.qthelp') - - return { - 'version': sphinx.__display_version__, - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/sphinx/builders/singlehtml.py b/sphinx/builders/singlehtml.py index 2e72887e3..03d25965b 100644 --- a/sphinx/builders/singlehtml.py +++ b/sphinx/builders/singlehtml.py @@ -42,7 +42,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): def get_target_uri(self, docname: str, typ: str = None) -> str: if docname in self.env.all_docs: # all references are on the same page... - return self.config.master_doc + self.out_suffix + \ + return self.config.root_doc + self.out_suffix + \ '#document-' + docname else: # chances are this is a html_additional_page @@ -54,7 +54,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): def fix_refuris(self, tree: Node) -> None: # fix refuris with double anchor - fname = self.config.master_doc + self.out_suffix + fname = self.config.root_doc + self.out_suffix for refnode in tree.traverse(nodes.reference): if 'refuri' not in refnode: continue @@ -75,7 +75,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): return self.render_partial(toctree)['fragment'] def assemble_doctree(self) -> nodes.document: - master = self.config.master_doc + master = self.config.root_doc tree = self.env.get_doctree(master) tree = inline_all_toctrees(self, set(), master, tree, darkgreen, [master]) tree['docname'] = master @@ -93,13 +93,13 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): # # There are related codes in inline_all_toctres() and # HTMLTranslter#add_secnumber(). - new_secnumbers = {} # type: Dict[str, Tuple[int, ...]] + new_secnumbers: Dict[str, Tuple[int, ...]] = {} for docname, secnums in self.env.toc_secnumbers.items(): for id, secnum in secnums.items(): alias = "%s/%s" % (docname, id) new_secnumbers[alias] = secnum - return {self.config.master_doc: new_secnumbers} + return {self.config.root_doc: new_secnumbers} def assemble_toc_fignumbers(self) -> Dict[str, Dict[str, Dict[str, Tuple[int, ...]]]]: # Assemble toc_fignumbers to resolve figure numbers on SingleHTML. @@ -111,7 +111,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): # # There are related codes in inline_all_toctres() and # HTMLTranslter#add_fignumber(). - new_fignumbers = {} # type: Dict[str, Dict[str, Tuple[int, ...]]] + new_fignumbers: Dict[str, Dict[str, Tuple[int, ...]]] = {} # {'foo': {'figure': {'id2': (2,), 'id1': (1,)}}, 'bar': {'figure': {'id1': (3,)}}} for docname, fignumlist in self.env.toc_fignumbers.items(): for figtype, fignums in fignumlist.items(): @@ -120,11 +120,11 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): for id, fignum in fignums.items(): new_fignumbers[alias][id] = fignum - return {self.config.master_doc: new_fignumbers} + return {self.config.root_doc: new_fignumbers} def get_doc_context(self, docname: str, body: str, metatags: str) -> Dict: # no relation links... - toctree = TocTree(self.env).get_toctree_for(self.config.master_doc, self, False) + toctree = TocTree(self.env).get_toctree_for(self.config.root_doc, self, False) # if there is no toctree, toc is None if toctree: self.fix_refuris(toctree) @@ -160,8 +160,8 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): self.env.toc_fignumbers = self.assemble_toc_fignumbers() with progress_message(__('writing')): - self.write_doc_serialized(self.config.master_doc, doctree) - self.write_doc(self.config.master_doc, doctree) + self.write_doc_serialized(self.config.root_doc, doctree) + self.write_doc(self.config.root_doc, doctree) def finish(self) -> None: self.write_additional_files() diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py index 1a56be0f9..ee10d58c3 100644 --- a/sphinx/builders/texinfo.py +++ b/sphinx/builders/texinfo.py @@ -15,6 +15,7 @@ from typing import Any, Dict, Iterable, List, Tuple, Union from docutils import nodes from docutils.frontend import OptionParser from docutils.io import FileOutput +from docutils.nodes import Node from sphinx import addnodes, package_dir from sphinx.application import Sphinx @@ -52,8 +53,8 @@ class TexinfoBuilder(Builder): default_translator_class = TexinfoTranslator def init(self) -> None: - self.docnames = [] # type: Iterable[str] - self.document_data = [] # type: List[Tuple[str, str, str, str, str, str, str, bool]] + self.docnames: Iterable[str] = [] + self.document_data: List[Tuple[str, str, str, str, str, str, str, bool]] = [] def get_outdated_docs(self) -> Union[str, List[str]]: return 'all documents' # for now @@ -75,7 +76,7 @@ class TexinfoBuilder(Builder): 'will be written')) return # assign subdirs to titles - self.titles = [] # type: List[Tuple[str, str]] + self.titles: List[Tuple[str, str]] = [] for entry in preliminary_document_data: docname = entry[0] if docname not in self.env.all_docs: @@ -108,10 +109,10 @@ class TexinfoBuilder(Builder): with progress_message(__("writing")): self.post_process_images(doctree) docwriter = TexinfoWriter(self) - settings = OptionParser( + settings: Any = OptionParser( defaults=self.env.settings, components=(docwriter,), - read_config_files=True).get_default_values() # type: Any + read_config_files=True).get_default_values() settings.author = author settings.title = title settings.texinfo_filename = targetname[:-5] + '.info' @@ -154,7 +155,7 @@ class TexinfoBuilder(Builder): for pendingnode in largetree.traverse(addnodes.pending_xref): docname = pendingnode['refdocname'] sectname = pendingnode['refsectname'] - newnodes = [nodes.emphasis(sectname, sectname)] # type: List[nodes.Node] + newnodes: List[Node] = [nodes.emphasis(sectname, sectname)] for subdir, title in self.titles: if docname.startswith(subdir): newnodes.append(nodes.Text(_(' (in '), _(' (in '))) @@ -197,7 +198,7 @@ class TexinfoBuilder(Builder): def default_texinfo_documents(config: Config) -> List[Tuple[str, str, str, str, str, str, str]]: # NOQA """ Better default texinfo_documents settings. """ filename = make_filename_from_project(config.project) - return [(config.master_doc, filename, config.project, config.author, filename, + return [(config.root_doc, filename, config.project, config.author, filename, 'One line description of project', 'Miscellaneous')] diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py index ae770818c..2ac1b2878 100644 --- a/sphinx/builders/text.py +++ b/sphinx/builders/text.py @@ -33,11 +33,11 @@ class TextBuilder(Builder): allow_parallel = True default_translator_class = TextTranslator - current_docname = None # type: str + current_docname: str = None def init(self) -> None: # section numbers for headings in the currently visited document - self.secnumbers = {} # type: Dict[str, Tuple[int, ...]] + self.secnumbers: Dict[str, Tuple[int, ...]] = {} def get_outdated_docs(self) -> Iterator[str]: for docname in self.env.found_docs: diff --git a/sphinx/builders/xml.py b/sphinx/builders/xml.py index 1b051119b..865820c36 100644 --- a/sphinx/builders/xml.py +++ b/sphinx/builders/xml.py @@ -9,7 +9,7 @@ """ from os import path -from typing import Any, Dict, Iterator, Set, Union +from typing import Any, Dict, Iterator, Set, Type, Union from docutils import nodes from docutils.io import StringOutput @@ -23,11 +23,6 @@ from sphinx.util import logging from sphinx.util.osutil import ensuredir, os_path from sphinx.writers.xml import PseudoXMLWriter, XMLWriter -if False: - # For type annotation - from typing import Type # for python3.5.1 - - logger = logging.getLogger(__name__) @@ -42,7 +37,7 @@ class XMLBuilder(Builder): out_suffix = '.xml' allow_parallel = True - _writer_class = XMLWriter # type: Union[Type[XMLWriter], Type[PseudoXMLWriter]] + _writer_class: Union[Type[XMLWriter], Type[PseudoXMLWriter]] = XMLWriter default_translator_class = XMLTranslator def init(self) -> None: diff --git a/sphinx/cmd/make_mode.py b/sphinx/cmd/make_mode.py index aaa40fba0..242329ae0 100644 --- a/sphinx/cmd/make_mode.py +++ b/sphinx/cmd/make_mode.py @@ -50,6 +50,7 @@ BUILDERS = [ ("", "doctest", "to run all doctests embedded in the documentation " "(if enabled)"), ("", "coverage", "to run coverage check of the documentation (if enabled)"), + ("", "clean", "to remove everything in the build directory"), ] diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index 4234c039c..a40a21073 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -11,13 +11,11 @@ import argparse import locale import os -import re import sys import time -import warnings from collections import OrderedDict from os import path -from typing import Any, Callable, Dict, List, Pattern, Union +from typing import Any, Callable, Dict, List, Union # try to import readline, unix specific enhancement try: @@ -36,15 +34,11 @@ from docutils.utils import column_width import sphinx.locale from sphinx import __display_version__, package_dir -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import __ -from sphinx.util.console import (bold, color_terminal, colorize, nocolor, red, # type: ignore - turquoise) +from sphinx.util.console import bold, color_terminal, colorize, nocolor, red # type: ignore from sphinx.util.osutil import ensuredir from sphinx.util.template import SphinxRenderer -TERM_ENCODING = getattr(sys.stdin, 'encoding', None) # RemovedInSphinx40Warning - EXTENSIONS = OrderedDict([ ('autodoc', __('automatically insert docstrings from modules')), ('doctest', __('automatically test code snippets in doctest blocks')), @@ -135,30 +129,6 @@ def ok(x: str) -> str: return x -def term_decode(text: Union[bytes, str]) -> str: - warnings.warn('term_decode() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - if isinstance(text, str): - return text - - # Use the known encoding, if possible - if TERM_ENCODING: - return text.decode(TERM_ENCODING) - - # If ascii is safe, use it with no warning - if text.decode('ascii', 'replace').encode('ascii', 'replace') == text: - return text.decode('ascii') - - print(turquoise(__('* Note: non-ASCII characters entered ' - 'and terminal encoding unknown -- assuming ' - 'UTF-8 or Latin-1.'))) - try: - return text.decode() - except UnicodeDecodeError: - return text.decode('latin1') - - def do_prompt(text: str, default: str = None, validator: Callable[[str], Any] = nonempty) -> Union[str, bool]: # NOQA while True: if default is not None: @@ -187,22 +157,27 @@ def do_prompt(text: str, default: str = None, validator: Callable[[str], Any] = return x -def convert_python_source(source: str, rex: Pattern = re.compile(r"[uU]('.*?')")) -> str: - # remove Unicode literal prefixes - warnings.warn('convert_python_source() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - return rex.sub('\\1', source) - - class QuickstartRenderer(SphinxRenderer): def __init__(self, templatedir: str) -> None: self.templatedir = templatedir or '' super().__init__() + def _has_custom_template(self, template_name: str) -> bool: + """Check if custom template file exists. + + Note: Please don't use this function from extensions. + It will be removed in the future without deprecation period. + """ + template = path.join(self.templatedir, path.basename(template_name)) + if self.templatedir and path.exists(template): + return True + else: + return False + def render(self, template_name: str, context: Dict) -> str: - user_template = path.join(self.templatedir, path.basename(template_name)) - if self.templatedir and path.exists(user_template): - return self.render_from_file(user_template, context) + if self._has_custom_template(template_name): + custom_template = path.join(self.templatedir, path.basename(template_name)) + return self.render_from_file(custom_template, context) else: return super().render(template_name, context) @@ -355,6 +330,7 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir: if 'mastertocmaxdepth' not in d: d['mastertocmaxdepth'] = 2 + d['root_doc'] = d['master'] d['now'] = time.asctime() d['project_underline'] = column_width(d['project']) * '=' d.setdefault('extensions', []) @@ -399,7 +375,13 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir: write_file(path.join(srcdir, 'conf.py'), template.render_string(conf_text, d)) masterfile = path.join(srcdir, d['master'] + d['suffix']) - write_file(masterfile, template.render('quickstart/master_doc.rst_t', d)) + if template._has_custom_template('quickstart/master_doc.rst_t'): + msg = ('A custom template `master_doc.rst_t` found. It has been renamed to ' + '`root_doc.rst_t`. Please rename it on your project too.') + print(colorize('red', msg)) # RemovedInSphinx60Warning + write_file(masterfile, template.render('quickstart/master_doc.rst_t', d)) + else: + write_file(masterfile, template.render('quickstart/root_doc.rst_t', d)) if d.get('make_mode') is True: makefile_template = 'quickstart/Makefile.new_t' diff --git a/sphinx/config.py b/sphinx/config.py index 4c038b061..a9fdddc8a 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -11,24 +11,20 @@ import re import traceback import types -import warnings from collections import OrderedDict from os import getenv, path -from typing import (Any, Callable, Dict, Generator, Iterator, List, NamedTuple, Set, Tuple, - Union) +from typing import (TYPE_CHECKING, Any, Callable, Dict, Generator, Iterator, List, NamedTuple, + Optional, Set, Tuple, Union) -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.errors import ConfigError, ExtensionError from sphinx.locale import _, __ from sphinx.util import logging from sphinx.util.i18n import format_date -from sphinx.util.osutil import cd -from sphinx.util.pycompat import execfile_ +from sphinx.util.osutil import cd, fs_encoding from sphinx.util.tags import Tags from sphinx.util.typing import NoneType -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.environment import BuildEnvironment @@ -38,9 +34,11 @@ CONFIG_FILENAME = 'conf.py' UNSERIALIZABLE_TYPES = (type, types.ModuleType, types.FunctionType) copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])') -ConfigValue = NamedTuple('ConfigValue', [('name', str), - ('value', Any), - ('rebuild', Union[bool, str])]) + +class ConfigValue(NamedTuple): + name: str + value: Any + rebuild: Union[bool, str] def is_serializable(obj: Any) -> bool: @@ -73,10 +71,6 @@ class ENUM: return value in self.candidates -# RemovedInSphinx40Warning -string_classes = [str] # type: List - - class Config: """Configuration file abstraction. @@ -94,7 +88,7 @@ class Config: # If you add a value here, don't forget to include it in the # quickstart.py file template as well as in the docs! - config_values = { + config_values: Dict[str, Tuple] = { # general options 'project': ('Python', 'env', []), 'author': ('unknown', 'env', []), @@ -111,9 +105,9 @@ class Config: 'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]), 'master_doc': ('index', 'env', []), + 'root_doc': (lambda config: config.master_doc, 'env', []), 'source_suffix': ({'.rst': 'restructuredtext'}, 'env', Any), 'source_encoding': ('utf-8-sig', 'env', []), - 'source_parsers': ({}, 'env', []), 'exclude_patterns': ([], 'env', []), 'default_role': (None, 'env', [str]), 'add_function_parentheses': (True, 'env', []), @@ -152,20 +146,20 @@ class Config: 'smartquotes_excludes': ({'languages': ['ja'], 'builders': ['man', 'text']}, 'env', []), - } # type: Dict[str, Tuple] + } def __init__(self, config: Dict[str, Any] = {}, overrides: Dict[str, Any] = {}) -> None: self.overrides = dict(overrides) self.values = Config.config_values.copy() self._raw_config = config - self.setup = config.get('setup', None) # type: Callable + self.setup: Optional[Callable] = config.get('setup', None) if 'extensions' in self.overrides: if isinstance(self.overrides['extensions'], str): config['extensions'] = self.overrides.pop('extensions').split(',') else: config['extensions'] = self.overrides.pop('extensions') - self.extensions = config.get('extensions', []) # type: List[str] + self.extensions: List[str] = config.get('extensions', []) @classmethod def read(cls, confdir: str, overrides: Dict = None, tags: Tags = None) -> "Config": @@ -317,14 +311,16 @@ class Config: def eval_config_file(filename: str, tags: Tags) -> Dict[str, Any]: """Evaluate a config file.""" - namespace = {} # type: Dict[str, Any] + namespace: Dict[str, Any] = {} namespace['__file__'] = filename namespace['tags'] = tags with cd(path.dirname(filename)): # during executing config file, current dir is changed to ``confdir``. try: - execfile_(filename, namespace) + with open(filename, 'rb') as f: + code = compile(f.read(), filename.encode(fs_encoding), 'exec') + exec(code, namespace) except SyntaxError as err: msg = __("There is a syntax error in your configuration file: %s\n") raise ConfigError(msg % err) from err @@ -459,22 +455,6 @@ def check_confval_types(app: "Sphinx", config: Config) -> None: default=type(default))) -def check_unicode(config: Config) -> None: - """check all string values for non-ASCII characters in bytestrings, - since that can result in UnicodeErrors all over the place - """ - warnings.warn('sphinx.config.check_unicode() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - nonascii_re = re.compile(br'[\x80-\xff]') - - for name, value in config._raw_config.items(): - if isinstance(value, bytes) and nonascii_re.search(value): - logger.warning(__('the config value %r is set to a string with non-ASCII ' - 'characters; this can lead to Unicode errors occurring. ' - 'Please use Unicode strings, e.g. %r.'), name, 'Content') - - def check_primary_domain(app: "Sphinx", config: Config) -> None: primary_domain = config.primary_domain if primary_domain and not app.registry.has_domain(primary_domain): @@ -482,17 +462,17 @@ def check_primary_domain(app: "Sphinx", config: Config) -> None: config.primary_domain = None # type: ignore -def check_master_doc(app: "Sphinx", env: "BuildEnvironment", added: Set[str], - changed: Set[str], removed: Set[str]) -> Set[str]: - """Adjust master_doc to 'contents' to support an old project which does not have - no master_doc setting. +def check_root_doc(app: "Sphinx", env: "BuildEnvironment", added: Set[str], + changed: Set[str], removed: Set[str]) -> Set[str]: + """Adjust root_doc to 'contents' to support an old project which does not have + no root_doc setting. """ - if (app.config.master_doc == 'index' and + if (app.config.root_doc == 'index' and 'index' not in app.project.docnames and 'contents' in app.project.docnames): - logger.warning(__('Since v2.0, Sphinx uses "index" as master_doc by default. ' - 'Please add "master_doc = \'contents\'" to your conf.py.')) - app.config.master_doc = "contents" # type: ignore + logger.warning(__('Since v2.0, Sphinx uses "index" as root_doc by default. ' + 'Please add "root_doc = \'contents\'" to your conf.py.')) + app.config.root_doc = "contents" # type: ignore return changed @@ -504,7 +484,7 @@ def setup(app: "Sphinx") -> Dict[str, Any]: app.connect('config-inited', correct_copyright_year, priority=800) app.connect('config-inited', check_confval_types, priority=800) app.connect('config-inited', check_primary_domain, priority=800) - app.connect('env-get-outdated', check_master_doc) + app.connect('env-get-outdated', check_root_doc) return { 'version': 'builtin', diff --git a/sphinx/deprecation.py b/sphinx/deprecation.py index 161d7bcac..6b0bdd158 100644 --- a/sphinx/deprecation.py +++ b/sphinx/deprecation.py @@ -11,18 +11,14 @@ import sys import warnings from importlib import import_module -from typing import Any, Dict - -if False: - # For type annotation - from typing import Type # for python3.5.1 +from typing import Any, Dict, Type class RemovedInSphinx40Warning(DeprecationWarning): pass -class RemovedInSphinx50Warning(PendingDeprecationWarning): +class RemovedInSphinx50Warning(DeprecationWarning): pass @@ -30,11 +26,11 @@ class RemovedInSphinx60Warning(PendingDeprecationWarning): pass -RemovedInNextVersionWarning = RemovedInSphinx40Warning +RemovedInNextVersionWarning = RemovedInSphinx50Warning def deprecated_alias(modname: str, objects: Dict[str, object], - warning: "Type[Warning]", names: Dict[str, str] = None) -> None: + warning: Type[Warning], names: Dict[str, str] = {}) -> None: module = import_module(modname) sys.modules[modname] = _ModuleWrapper( # type: ignore module, modname, objects, warning, names) @@ -43,7 +39,7 @@ def deprecated_alias(modname: str, objects: Dict[str, object], class _ModuleWrapper: def __init__(self, module: Any, modname: str, objects: Dict[str, object], - warning: "Type[Warning]", + warning: Type[Warning], names: Dict[str, str]) -> None: self._module = module self._modname = modname @@ -71,7 +67,7 @@ class _ModuleWrapper: class DeprecatedDict(dict): """A deprecated dict which warns on each access.""" - def __init__(self, data: Dict, message: str, warning: "Type[Warning]") -> None: + def __init__(self, data: Dict, message: str, warning: Type[Warning]) -> None: self.message = message self.warning = warning super().__init__(data) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index e386b3eaa..e1ccc8be7 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -9,7 +9,7 @@ """ import re -from typing import Any, Dict, Generic, List, Tuple, TypeVar, cast +from typing import TYPE_CHECKING, Any, Dict, Generic, List, Tuple, TypeVar, cast from docutils import nodes from docutils.nodes import Node @@ -17,15 +17,13 @@ from docutils.parsers.rst import directives, roles from sphinx import addnodes from sphinx.addnodes import desc_signature -from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warning, - deprecated_alias) +from sphinx.deprecation import RemovedInSphinx50Warning, deprecated_alias from sphinx.util import docutils from sphinx.util.docfields import DocFieldTransformer, Field, TypedField from sphinx.util.docutils import SphinxDirective -from sphinx.util.typing import DirectiveOption +from sphinx.util.typing import OptionSpec -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -60,18 +58,18 @@ class ObjectDescription(SphinxDirective, Generic[T]): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = { + option_spec: OptionSpec = { 'noindex': directives.flag, - } # type: Dict[str, DirectiveOption] + } # types of doc fields that this directive handles, see sphinx.util.docfields - doc_field_types = [] # type: List[Field] - domain = None # type: str - objtype = None # type: str - indexnode = None # type: addnodes.index + doc_field_types: List[Field] = [] + domain: str = None + objtype: str = None + indexnode: addnodes.index = None # Warning: this might be removed in future version. Don't touch this from extensions. - _doc_field_type_map = {} # type: Dict[str, Tuple[Field, bool]] + _doc_field_type_map: Dict[str, Tuple[Field, bool]] = {} def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]: if self._doc_field_type_map == {}: @@ -175,7 +173,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): if self.domain: node['classes'].append(self.domain) - self.names = [] # type: List[T] + self.names: List[T] = [] signatures = self.get_signatures() for i, sig in enumerate(signatures): # add a signature node for each signature in the current unit @@ -253,7 +251,7 @@ class DefaultDomain(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: domain_name = self.arguments[0].lower() @@ -266,52 +264,6 @@ class DefaultDomain(SphinxDirective): self.env.temp_data['default_domain'] = self.env.domains.get(domain_name) return [] -from sphinx.directives.code import CodeBlock, Highlight, LiteralInclude # noqa -from sphinx.directives.other import (Acks, Author, Centered, Class, HList, Include, # noqa - Only, SeeAlso, TabularColumns, TocTree, VersionChange) -from sphinx.directives.patches import Figure, Meta # noqa -from sphinx.domains.index import IndexDirective # noqa - -deprecated_alias('sphinx.directives', - { - 'Highlight': Highlight, - 'CodeBlock': CodeBlock, - 'LiteralInclude': LiteralInclude, - 'TocTree': TocTree, - 'Author': Author, - 'Index': IndexDirective, - 'VersionChange': VersionChange, - 'SeeAlso': SeeAlso, - 'TabularColumns': TabularColumns, - 'Centered': Centered, - 'Acks': Acks, - 'HList': HList, - 'Only': Only, - 'Include': Include, - 'Class': Class, - 'Figure': Figure, - 'Meta': Meta, - }, - RemovedInSphinx40Warning, - { - 'Highlight': 'sphinx.directives.code.Highlight', - 'CodeBlock': 'sphinx.directives.code.CodeBlock', - 'LiteralInclude': 'sphinx.directives.code.LiteralInclude', - 'TocTree': 'sphinx.directives.other.TocTree', - 'Author': 'sphinx.directives.other.Author', - 'Index': 'sphinx.directives.other.IndexDirective', - 'VersionChange': 'sphinx.directives.other.VersionChange', - 'SeeAlso': 'sphinx.directives.other.SeeAlso', - 'TabularColumns': 'sphinx.directives.other.TabularColumns', - 'Centered': 'sphinx.directives.other.Centered', - 'Acks': 'sphinx.directives.other.Acks', - 'HList': 'sphinx.directives.other.HList', - 'Only': 'sphinx.directives.other.Only', - 'Include': 'sphinx.directives.other.Include', - 'Class': 'sphinx.directives.other.Class', - 'Figure': 'sphinx.directives.patches.Figure', - 'Meta': 'sphinx.directives.patches.Meta', - }) deprecated_alias('sphinx.directives', { diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index e01b8f9ec..12ab51c58 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -8,9 +8,8 @@ import sys import textwrap -import warnings from difflib import unified_diff -from typing import Any, Dict, List, Tuple +from typing import TYPE_CHECKING, Any, Dict, List, Tuple from docutils import nodes from docutils.nodes import Element, Node @@ -19,14 +18,13 @@ 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 +from sphinx.util.typing import OptionSpec -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx logger = logging.getLogger(__name__) @@ -42,7 +40,7 @@ class Highlight(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = { + option_spec: OptionSpec = { 'force': directives.flag, 'linenothreshold': directives.positive_int, } @@ -58,16 +56,6 @@ class Highlight(SphinxDirective): linenothreshold=linenothreshold)] -class HighlightLang(Highlight): - """highlightlang directive (deprecated)""" - - def run(self) -> List[Node]: - warnings.warn('highlightlang directive is deprecated. ' - 'Please use highlight directive instead.', - RemovedInSphinx40Warning, stacklevel=2) - return super().run() - - def dedent_lines(lines: List[str], dedent: int, location: Tuple[str, int] = None) -> List[str]: if not dedent: return textwrap.dedent(''.join(lines)).splitlines(True) @@ -116,7 +104,7 @@ class CodeBlock(SphinxDirective): required_arguments = 0 optional_arguments = 1 final_argument_whitespace = False - option_spec = { + option_spec: OptionSpec = { 'force': directives.flag, 'linenos': directives.flag, 'dedent': optional_int, @@ -154,7 +142,7 @@ class CodeBlock(SphinxDirective): lines = dedent_lines(lines, self.options['dedent'], location=location) code = '\n'.join(lines) - literal = nodes.literal_block(code, code) # type: Element + literal: Element = nodes.literal_block(code, code) if 'linenos' in self.options or 'lineno-start' in self.options: literal['linenos'] = True literal['classes'] += self.options.get('class', []) @@ -392,7 +380,7 @@ class LiteralInclude(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = { + option_spec: OptionSpec = { 'dedent': optional_int, 'linenos': directives.flag, 'lineno-start': int, @@ -434,7 +422,7 @@ class LiteralInclude(SphinxDirective): reader = LiteralIncludeReader(filename, self.options, self.config) text, lines = reader.read(location=location) - retnode = nodes.literal_block(text, text, source=filename) # type: Element + retnode: Element = nodes.literal_block(text, text, source=filename) retnode['force'] = 'force' in self.options self.set_source_info(retnode) if self.options.get('diff'): # if diff is set, set udiff @@ -470,7 +458,6 @@ class LiteralInclude(SphinxDirective): def setup(app: "Sphinx") -> Dict[str, Any]: directives.register_directive('highlight', Highlight) - directives.register_directive('highlightlang', HighlightLang) directives.register_directive('code-block', CodeBlock) directives.register_directive('sourcecode', CodeBlock) directives.register_directive('literalinclude', LiteralInclude) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 2ace3738f..e131fe820 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -7,7 +7,7 @@ """ import re -from typing import Any, Dict, List, cast +from typing import TYPE_CHECKING, Any, Dict, List, cast from docutils import nodes from docutils.nodes import Element, Node @@ -17,16 +17,15 @@ from docutils.parsers.rst.directives.misc import Class from docutils.parsers.rst.directives.misc import Include as BaseInclude from sphinx import addnodes -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.domains.changeset import VersionChange # NOQA # for compatibility from sphinx.locale import _ from sphinx.util import docname_join, url_re from sphinx.util.docutils import SphinxDirective from sphinx.util.matching import Matcher, patfilter from sphinx.util.nodes import explicit_title_re +from sphinx.util.typing import OptionSpec -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -91,7 +90,7 @@ class TocTree(SphinxDirective): all_docnames = self.env.found_docs.copy() all_docnames.remove(self.env.docname) # remove current document - ret = [] # type: List[Node] + ret: List[Node] = [] excluded = Matcher(self.config.exclude_patterns) for entry in self.content: if not entry: @@ -137,7 +136,13 @@ class TocTree(SphinxDirective): line=self.lineno)) self.env.note_reread() else: - all_docnames.discard(docname) + if docname in all_docnames: + all_docnames.remove(docname) + else: + message = 'duplicated entry found in toctree: %s' + ret.append(self.state.document.reporter.warning(message % docname, + line=self.lineno)) + toctree['entries'].append((title, docname)) toctree['includefiles'].append(docname) @@ -158,12 +163,12 @@ class Author(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: if not self.config.show_authors: return [] - para = nodes.paragraph(translatable=False) # type: Element + para: Element = nodes.paragraph(translatable=False) emph = nodes.emphasis() para += emph if self.name == 'sectionauthor': @@ -178,7 +183,7 @@ class Author(SphinxDirective): inodes, messages = self.state.inline_text(self.arguments[0], self.lineno) emph.extend(inodes) - ret = [para] # type: List[Node] + ret: List[Node] = [para] ret += messages return ret @@ -198,7 +203,7 @@ class TabularColumns(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: node = addnodes.tabular_col_spec() @@ -215,16 +220,16 @@ class Centered(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: if not self.arguments: return [] - subnode = addnodes.centered() # type: Element + subnode: Element = addnodes.centered() inodes, messages = self.state.inline_text(self.arguments[0], self.lineno) subnode.extend(inodes) - ret = [subnode] # type: List[Node] + ret: List[Node] = [subnode] ret += messages return ret @@ -237,7 +242,7 @@ class Acks(SphinxDirective): required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: node = addnodes.acks() @@ -258,7 +263,7 @@ class HList(SphinxDirective): required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False - option_spec = { + option_spec: OptionSpec = { 'columns': int, } @@ -294,7 +299,7 @@ class Only(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: node = addnodes.only() @@ -304,7 +309,7 @@ class Only(SphinxDirective): # Same as util.nested_parse_with_titles but try to handle nested # sections which should be raised higher up the doctree. - memo = self.state.memo # type: Any + memo: Any = self.state.memo surrounding_title_styles = memo.title_styles surrounding_section_level = memo.section_level memo.title_styles = [] @@ -361,19 +366,6 @@ class Include(BaseInclude, SphinxDirective): return super().run() -# Import old modules here for compatibility -from sphinx.domains.index import IndexDirective # NOQA - -deprecated_alias('sphinx.directives.other', - { - 'Index': IndexDirective, - }, - RemovedInSphinx40Warning, - { - 'Index': 'sphinx.domains.index.IndexDirective', - }) - - def setup(app: "Sphinx") -> Dict[str, Any]: directives.register_directive('toctree', TocTree) directives.register_directive('sectionauthor', Author) diff --git a/sphinx/directives/patches.py b/sphinx/directives/patches.py index 1eae6d0c8..9a3034dae 100644 --- a/sphinx/directives/patches.py +++ b/sphinx/directives/patches.py @@ -6,7 +6,10 @@ :license: BSD, see LICENSE for details. """ -from typing import Any, Dict, List, Tuple, cast +import os +import warnings +from os import path +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, cast from docutils import nodes from docutils.nodes import Node, make_id, system_message @@ -14,16 +17,23 @@ from docutils.parsers.rst import directives from docutils.parsers.rst.directives import html, images, tables from sphinx import addnodes +from sphinx.deprecation import RemovedInSphinx60Warning from sphinx.directives import optional_int from sphinx.domains.math import MathDomain +from sphinx.locale import __ +from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import set_source_info +from sphinx.util.osutil import SEP, os_path, relpath +from sphinx.util.typing import OptionSpec -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx +logger = logging.getLogger(__name__) + + class Figure(images.Figure): """The figure directive which applies `:name:` option to the figure node instead of the image node. @@ -72,6 +82,11 @@ class RSTTable(tables.RSTTable): Only for docutils-0.13 or older version.""" + def run(self) -> List[Node]: + warnings.warn('RSTTable is deprecated.', + RemovedInSphinx60Warning) + return super().run() + def make_title(self) -> Tuple[nodes.title, List[system_message]]: title, message = super().make_title() if title: @@ -81,16 +96,25 @@ class RSTTable(tables.RSTTable): class CSVTable(tables.CSVTable): - """The csv-table directive which sets source and line information to its caption. - - Only for docutils-0.13 or older version.""" - - def make_title(self) -> Tuple[nodes.title, List[system_message]]: - title, message = super().make_title() - if title: - set_source_info(self, title) + """The csv-table directive which searches a CSV file from Sphinx project's source + directory when an absolute path is given via :file: option. + """ - return title, message + def run(self) -> List[Node]: + if 'file' in self.options and self.options['file'].startswith((SEP, os.sep)): + env = self.state.document.settings.env + filename = self.options['file'] + if path.exists(filename): + logger.warning(__('":file:" option for csv-table directive now recognizes ' + 'an absolute path as a relative path from source directory. ' + 'Please update your document.'), + location=(env.docname, self.lineno)) + else: + abspath = path.join(env.srcdir, os_path(self.options['file'][1:])) + docdir = path.dirname(env.doc2path(env.docname)) + self.options['file'] = relpath(abspath, docdir) + + return super().run() class ListTable(tables.ListTable): @@ -98,6 +122,11 @@ class ListTable(tables.ListTable): Only for docutils-0.13 or older version.""" + def run(self) -> List[Node]: + warnings.warn('ListTable is deprecated.', + RemovedInSphinx60Warning) + return super().run() + def make_title(self) -> Tuple[nodes.title, List[system_message]]: title, message = super().make_title() if title: @@ -112,7 +141,7 @@ class Code(SphinxDirective): This is compatible with docutils' :rst:dir:`code` directive. """ optional_arguments = 1 - option_spec = { + option_spec: OptionSpec = { 'class': directives.class_option, 'force': directives.flag, 'name': directives.unchanged, @@ -156,7 +185,7 @@ class MathDirective(SphinxDirective): required_arguments = 0 optional_arguments = 1 final_argument_whitespace = True - option_spec = { + option_spec: OptionSpec = { 'label': directives.unchanged, 'name': directives.unchanged, 'class': directives.class_option, @@ -177,7 +206,7 @@ class MathDirective(SphinxDirective): self.add_name(node) self.set_source_info(node) - ret = [node] # type: List[Node] + ret: List[Node] = [node] self.add_target(ret) return ret @@ -208,9 +237,7 @@ class MathDirective(SphinxDirective): def setup(app: "Sphinx") -> Dict[str, Any]: directives.register_directive('figure', Figure) directives.register_directive('meta', Meta) - directives.register_directive('table', RSTTable) directives.register_directive('csv-table', CSVTable) - directives.register_directive('list-table', ListTable) directives.register_directive('code', Code) directives.register_directive('math', MathDirective) diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 60142f584..8f3e6a955 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -10,7 +10,9 @@ """ import copy -from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, Union, cast +from abc import ABC, abstractmethod +from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, + Type, Union, cast) from docutils import nodes from docutils.nodes import Element, Node, system_message @@ -22,10 +24,7 @@ from sphinx.locale import _ from sphinx.roles import XRefRole from sphinx.util.typing import RoleFunction -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.builders import Builder from sphinx.environment import BuildEnvironment @@ -51,21 +50,22 @@ class ObjType: def __init__(self, lname: str, *roles: Any, **attrs: Any) -> None: self.lname = lname - self.roles = roles # type: Tuple - self.attrs = self.known_attrs.copy() # type: Dict + self.roles: Tuple = roles + self.attrs: Dict = self.known_attrs.copy() self.attrs.update(attrs) -IndexEntry = NamedTuple('IndexEntry', [('name', str), - ('subtype', int), - ('docname', str), - ('anchor', str), - ('extra', str), - ('qualifier', str), - ('descr', str)]) +class IndexEntry(NamedTuple): + name: str + subtype: int + docname: str + anchor: str + extra: str + qualifier: str + descr: str -class Index: +class Index(ABC): """ An Index is the description for a domain-specific index. To add an index to a domain, subclass Index, overriding the three name attributes: @@ -88,9 +88,9 @@ class Index: :rst:role:`ref` role. """ - name = None # type: str - localname = None # type: str - shortname = None # type: str + name: str = None + localname: str = None + shortname: str = None def __init__(self, domain: "Domain") -> None: if self.name is None or self.localname is None: @@ -98,6 +98,7 @@ class Index: % self.__class__.__name__) self.domain = domain + @abstractmethod def generate(self, docnames: Iterable[str] = None ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: """Get entries for the index. @@ -180,31 +181,31 @@ class Domain: #: domain label: longer, more descriptive (used in messages) label = '' #: type (usually directive) name -> ObjType instance - object_types = {} # type: Dict[str, ObjType] + object_types: Dict[str, ObjType] = {} #: directive name -> directive class - directives = {} # type: Dict[str, Any] + directives: Dict[str, Any] = {} #: role name -> role callable - roles = {} # type: Dict[str, Union[RoleFunction, XRefRole]] + roles: Dict[str, Union[RoleFunction, XRefRole]] = {} #: a list of Index subclasses - indices = [] # type: List[Type[Index]] + indices: List[Type[Index]] = [] #: role name -> a warning message if reference is missing - dangling_warnings = {} # type: Dict[str, str] + dangling_warnings: Dict[str, str] = {} #: node_class -> (enum_node_type, title_getter) - enumerable_nodes = {} # type: Dict[Type[Node], Tuple[str, Callable]] + enumerable_nodes: Dict[Type[Node], Tuple[str, Callable]] = {} #: data value for a fresh environment - initial_data = {} # type: Dict + initial_data: Dict = {} #: data value - data = None # type: Dict + data: Dict = None #: data version, bump this when the format of `self.data` changes data_version = 0 def __init__(self, env: "BuildEnvironment") -> None: - self.env = env # type: BuildEnvironment - self._role_cache = {} # type: Dict[str, Callable] - self._directive_cache = {} # type: Dict[str, Callable] - self._role2type = {} # type: Dict[str, List[str]] - self._type2role = {} # type: Dict[str, str] + self.env: BuildEnvironment = env + self._role_cache: Dict[str, Callable] = {} + self._directive_cache: Dict[str, Callable] = {} + self._role2type: Dict[str, List[str]] = {} + self._type2role: Dict[str, str] = {} # convert class variables to instance one (to enhance through API) self.object_types = dict(self.object_types) @@ -225,8 +226,8 @@ class Domain: for rolename in obj.roles: self._role2type.setdefault(rolename, []).append(name) self._type2role[name] = obj.roles[0] if obj.roles else '' - self.objtypes_for_role = self._role2type.get # type: Callable[[str], List[str]] - self.role_for_objtype = self._type2role.get # type: Callable[[str], str] + self.objtypes_for_role: Callable[[str], List[str]] = self._role2type.get + self.role_for_objtype: Callable[[str], str] = self._type2role.get def setup(self) -> None: """Set up domain object.""" diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 061010d66..72b9746ac 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -39,6 +39,7 @@ from sphinx.util.cfamily import (ASTAttribute, ASTBaseBase, ASTBaseParenExprList from sphinx.util.docfields import Field, TypedField from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_refnode +from sphinx.util.typing import OptionSpec logger = logging.getLogger(__name__) T = TypeVar('T') @@ -182,10 +183,19 @@ class ASTNestedName(ASTBase): verify_description_mode(mode) # just print the name part, with template args, not template params if mode == 'noneIsName': - signode += nodes.Text(str(self)) + if self.rooted: + signode += nodes.Text('.') + for i in range(len(self.names)): + if i != 0: + signode += nodes.Text('.') + n = self.names[i] + n.describe_signature(signode, mode, env, '', symbol) elif mode == 'param': - name = str(self) - signode += nodes.emphasis(name, name) + assert not self.rooted, str(self) + assert len(self.names) == 1 + node = nodes.emphasis() + self.names[0].describe_signature(node, 'noneIsName', env, '', symbol) + signode += node elif mode == 'markType' or mode == 'lastIsName' or mode == 'markName': # Each element should be a pending xref targeting the complete # prefix. @@ -387,19 +397,6 @@ class ASTPostfixDec(ASTPostfixOp): signode.append(nodes.Text('--')) -class ASTPostfixMember(ASTPostfixOp): - def __init__(self, name): - self.name = name - - def _stringify(self, transform: StringifyTransform) -> str: - return '.' + transform(self.name) - - def describe_signature(self, signode: TextElement, mode: str, - env: "BuildEnvironment", symbol: "Symbol") -> None: - signode.append(nodes.Text('.')) - self.name.describe_signature(signode, 'noneIsName', env, symbol) - - class ASTPostfixMemberOfPointer(ASTPostfixOp): def __init__(self, name): self.name = name @@ -682,15 +679,24 @@ class ASTParameters(ASTBase): def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: verify_description_mode(mode) - paramlist = addnodes.desc_parameterlist() - for arg in self.args: - param = addnodes.desc_parameter('', '', noemph=True) - if mode == 'lastIsName': # i.e., outer-function params + # only use the desc_parameterlist for the outer list, not for inner lists + if mode == 'lastIsName': + paramlist = addnodes.desc_parameterlist() + for arg in self.args: + param = addnodes.desc_parameter('', '', noemph=True) arg.describe_signature(param, 'param', env, symbol=symbol) - else: - arg.describe_signature(param, 'markType', env, symbol=symbol) - paramlist += param - signode += paramlist + paramlist += param + signode += paramlist + else: + signode += nodes.Text('(', '(') + first = True + for arg in self.args: + if not first: + signode += nodes.Text(', ', ', ') + first = False + arg.describe_signature(signode, 'markType', env, symbol=symbol) + signode += nodes.Text(')', ')') + for attr in self.attrs: signode += nodes.Text(' ') attr.describe_signature(signode) @@ -719,7 +725,7 @@ class ASTDeclSpecsSimple(ASTBaseBase): self.attrs + other.attrs) def _stringify(self, transform: StringifyTransform) -> str: - res = [] # type: List[str] + res: List[str] = [] res.extend(transform(attr) for attr in self.attrs) if self.storage: res.append(self.storage) @@ -773,7 +779,7 @@ class ASTDeclSpecs(ASTBase): self.trailingTypeSpec = trailing def _stringify(self, transform: StringifyTransform) -> str: - res = [] # type: List[str] + res: List[str] = [] l = transform(self.leftSpecs) if len(l) > 0: res.append(l) @@ -791,7 +797,7 @@ class ASTDeclSpecs(ASTBase): def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: verify_description_mode(mode) - modifiers = [] # type: List[Node] + modifiers: List[Node] = [] def _add(modifiers: List[Node], text: str) -> None: if len(modifiers) > 0: @@ -873,7 +879,7 @@ class ASTArray(ASTBase): elif self.size: if addSpace: signode += nodes.Text(' ') - self.size.describe_signature(signode, mode, env, symbol) + self.size.describe_signature(signode, 'markType', env, symbol) signode.append(nodes.Text("]")) @@ -1363,9 +1369,9 @@ class ASTDeclaration(ASTBaseBase): self.declaration = declaration self.semicolon = semicolon - self.symbol = None # type: Symbol + self.symbol: Symbol = None # set by CObject._add_enumerator_to_parent - self.enumeratorScopedSymbol = None # type: Symbol + self.enumeratorScopedSymbol: Symbol = None def clone(self) -> "ASTDeclaration": return ASTDeclaration(self.objectType, self.directiveType, @@ -1497,8 +1503,8 @@ class Symbol: declaration: ASTDeclaration, docname: str, line: int) -> None: self.parent = parent # declarations in a single directive are linked together - self.siblingAbove = None # type: Symbol - self.siblingBelow = None # type: Symbol + self.siblingAbove: Symbol = None + self.siblingBelow: Symbol = None self.ident = ident self.declaration = declaration self.docname = docname @@ -1507,8 +1513,8 @@ class Symbol: self._assert_invariants() # Remember to modify Symbol.remove if modifications to the parent change. - self._children = [] # type: List[Symbol] - self._anonChildren = [] # type: List[Symbol] + self._children: List[Symbol] = [] + self._anonChildren: List[Symbol] = [] # note: _children includes _anonChildren if self.parent: self.parent._children.append(self) @@ -2189,7 +2195,7 @@ class DefinitionParser(BaseParser): # "(" expression ")" # id-expression -> we parse this with _parse_nested_name self.skip_ws() - res = self._parse_literal() # type: ASTExpression + res: ASTExpression = self._parse_literal() if res is not None: return res res = self._parse_paren_expression() @@ -2256,7 +2262,7 @@ class DefinitionParser(BaseParser): # | postfix "[" expression "]" # | postfix "[" braced-init-list [opt] "]" # | postfix "(" expression-list [opt] ")" - # | postfix "." id-expression + # | postfix "." id-expression // taken care of in primary by nested name # | postfix "->" id-expression # | postfix "++" # | postfix "--" @@ -2264,7 +2270,7 @@ class DefinitionParser(BaseParser): prefix = self._parse_primary_expression() # and now parse postfixes - postFixes = [] # type: List[ASTPostfixOp] + postFixes: List[ASTPostfixOp] = [] while True: self.skip_ws() if self.skip_string_and_ws('['): @@ -2274,17 +2280,6 @@ class DefinitionParser(BaseParser): self.fail("Expected ']' in end of postfix expression.") postFixes.append(ASTPostfixArray(expr)) continue - if self.skip_string('.'): - if self.skip_string('*'): - # don't steal the dot - self.pos -= 2 - elif self.skip_string('..'): - # don't steal the dot - self.pos -= 3 - else: - name = self._parse_nested_name() - postFixes.append(ASTPostfixMember(name)) - continue if self.skip_string('->'): if self.skip_string('*'): # don't steal the arrow @@ -2493,7 +2488,7 @@ class DefinitionParser(BaseParser): else: # TODO: add handling of more bracket-like things, and quote handling brackets = {'(': ')', '{': '}', '[': ']'} - symbols = [] # type: List[str] + symbols: List[str] = [] while not self.eof: if (len(symbols) == 0 and self.current_char in end): break @@ -2509,7 +2504,7 @@ class DefinitionParser(BaseParser): return ASTFallbackExpr(value.strip()) def _parse_nested_name(self) -> ASTNestedName: - names = [] # type: List[Any] + names: List[Any] = [] self.skip_ws() rooted = False @@ -2693,16 +2688,13 @@ class DefinitionParser(BaseParser): def _parse_declarator_name_suffix( self, named: Union[bool, str], paramMode: str, typed: bool ) -> ASTDeclarator: + assert named in (True, False, 'single') # now we should parse the name, and then suffixes - if named == 'maybe': - pos = self.pos - try: - declId = self._parse_nested_name() - except DefinitionError: - self.pos = pos - declId = None - elif named == 'single': + if named == 'single': if self.match(identifier_re): + if self.matched_text in _keywords: + self.fail("Expected identifier, " + "got keyword: %s" % self.matched_text) identifier = ASTIdentifier(self.matched_text) declId = ASTNestedName([identifier], rooted=False) else: @@ -2865,7 +2857,7 @@ class DefinitionParser(BaseParser): return ASTInitializer(bracedInit) if outer == 'member': - fallbackEnd = [] # type: List[str] + fallbackEnd: List[str] = [] elif outer is None: # function parameter fallbackEnd = [',', ')'] else: @@ -2880,8 +2872,8 @@ class DefinitionParser(BaseParser): def _parse_type(self, named: Union[bool, str], outer: str = None) -> ASTType: """ - named=False|'maybe'|True: 'maybe' is e.g., for function objects which - doesn't need to name the arguments + named=False|'single'|True: 'single' is e.g., for function objects which + doesn't need to name the arguments, but otherwise is a single name """ if outer: # always named if outer not in ('type', 'member', 'function'): @@ -3012,7 +3004,7 @@ class DefinitionParser(BaseParser): def parse_pre_v3_type_definition(self) -> ASTDeclaration: self.skip_ws() - declaration = None # type: DeclarationType + declaration: DeclarationType = None if self.skip_word('struct'): typ = 'struct' declaration = self._parse_struct() @@ -3035,7 +3027,7 @@ class DefinitionParser(BaseParser): 'macro', 'struct', 'union', 'enum', 'enumerator', 'type'): raise Exception('Internal error, unknown directiveType "%s".' % directiveType) - declaration = None # type: DeclarationType + declaration: DeclarationType = None if objectType == 'member': declaration = self._parse_type_with_init(named=True, outer='member') elif objectType == 'function': @@ -3074,7 +3066,7 @@ class DefinitionParser(BaseParser): def parse_expression(self) -> Union[ASTExpression, ASTType]: pos = self.pos - res = None # type: Union[ASTExpression, ASTType] + res: Union[ASTExpression, ASTType] = None try: res = self._parse_expression() self.skip_ws() @@ -3113,7 +3105,7 @@ class CObject(ObjectDescription[ASTDeclaration]): names=('rtype',)), ] - option_spec = { + option_spec: OptionSpec = { 'noindexentry': directives.flag, } @@ -3221,7 +3213,7 @@ class CObject(ObjectDescription[ASTDeclaration]): return super().run() def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration: - parentSymbol = self.env.temp_data['c:parent_symbol'] # type: Symbol + parentSymbol: Symbol = self.env.temp_data['c:parent_symbol'] parser = DefinitionParser(sig, location=signode, config=self.env.config) try: @@ -3285,10 +3277,10 @@ class CObject(ObjectDescription[ASTDeclaration]): return ast def before_content(self) -> None: - lastSymbol = self.env.temp_data['c:last_symbol'] # type: Symbol + lastSymbol: Symbol = self.env.temp_data['c:last_symbol'] assert lastSymbol self.oldParentSymbol = self.env.temp_data['c:parent_symbol'] - self.oldParentKey = self.env.ref_context['c:parent_key'] # type: LookupKey + self.oldParentKey: LookupKey = self.env.ref_context['c:parent_key'] self.env.temp_data['c:parent_symbol'] = lastSymbol self.env.ref_context['c:parent_key'] = lastSymbol.get_lookup_key() @@ -3353,13 +3345,13 @@ class CNamespaceObject(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: rootSymbol = self.env.domaindata['c']['root_symbol'] if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): symbol = rootSymbol - stack = [] # type: List[Symbol] + stack: List[Symbol] = [] else: parser = DefinitionParser(self.arguments[0], location=self.get_source_info(), @@ -3383,7 +3375,7 @@ class CNamespacePushObject(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): @@ -3414,7 +3406,7 @@ class CNamespacePopObject(SphinxDirective): required_arguments = 0 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: stack = self.env.temp_data.get('c:namespace_stack', None) @@ -3470,7 +3462,7 @@ class AliasTransform(SphinxTransform): maxdepth -= 1 recurse = True - nodes = [] # type: List[Node] + nodes: List[Node] = [] if not skipThis: signode = addnodes.desc_signature('', '') nodes.append(signode) @@ -3478,7 +3470,7 @@ class AliasTransform(SphinxTransform): if recurse: if skipThis: - childContainer = nodes # type: Union[List[Node], addnodes.desc] + childContainer: Union[List[Node], addnodes.desc] = nodes else: content = addnodes.desc_content() desc = addnodes.desc() @@ -3524,8 +3516,8 @@ class AliasTransform(SphinxTransform): node.replace_self(signode) continue - rootSymbol = self.env.domains['c'].data['root_symbol'] # type: Symbol - parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol + rootSymbol: Symbol = self.env.domains['c'].data['root_symbol'] + parentSymbol: Symbol = rootSymbol.direct_lookup(parentKey) if not parentSymbol: print("Target: ", sig) print("ParentKey: ", parentKey) @@ -3568,10 +3560,10 @@ class AliasTransform(SphinxTransform): class CAliasObject(ObjectDescription): - option_spec = { + option_spec: OptionSpec = { 'maxdepth': directives.nonnegative_int, 'noroot': directives.flag, - } # type: Dict + } def run(self) -> List[Node]: """ @@ -3591,7 +3583,7 @@ class CAliasObject(ObjectDescription): node['objtype'] = node['desctype'] = self.objtype node['noindex'] = True - self.names = [] # type: List[str] + self.names: List[str] = [] aliasOptions = { 'maxdepth': self.options.get('maxdepth', 1), 'noroot': 'noroot' in self.options, @@ -3669,7 +3661,7 @@ class CExprRole(SphinxRole): if asCode: # render the expression as inline code self.class_type = 'c-expr' - self.node_type = nodes.literal # type: Type[TextElement] + self.node_type: Type[TextElement] = nodes.literal else: # render the expression as inline text self.class_type = 'c-texpr' @@ -3748,10 +3740,10 @@ class CDomain(Domain): 'expr': CExprRole(asCode=True), 'texpr': CExprRole(asCode=False) } - initial_data = { + initial_data: Dict[str, Union[Symbol, Dict[str, Tuple[str, str, str]]]] = { 'root_symbol': Symbol(None, None, None, None, None), 'objects': {}, # fullname -> docname, node_id, objtype - } # type: Dict[str, Union[Symbol, Dict[str, Tuple[str, str, str]]]] + } def clear_doc(self, docname: str) -> None: if Symbol.debug_show_tree: @@ -3809,10 +3801,10 @@ class CDomain(Domain): logger.warning('Unparseable C cross-reference: %r\n%s', target, e, location=node) return None, None - parentKey = node.get("c:parent_key", None) # type: LookupKey + parentKey: LookupKey = node.get("c:parent_key", None) rootSymbol = self.data['root_symbol'] if parentKey: - parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol + parentSymbol: Symbol = rootSymbol.direct_lookup(parentKey) if not parentSymbol: print("Target: ", target) print("ParentKey: ", parentKey) diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py index ba323b729..1b31e0248 100644 --- a/sphinx/domains/changeset.py +++ b/sphinx/domains/changeset.py @@ -8,8 +8,7 @@ :license: BSD, see LICENSE for details. """ -from collections import namedtuple -from typing import Any, Dict, List, cast +from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, cast from docutils import nodes from docutils.nodes import Node @@ -18,9 +17,9 @@ from sphinx import addnodes from sphinx.domains import Domain from sphinx.locale import _ from sphinx.util.docutils import SphinxDirective +from sphinx.util.typing import OptionSpec -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.environment import BuildEnvironment @@ -38,9 +37,13 @@ versionlabel_classes = { } -# TODO: move to typing.NamedTuple after dropping py35 support (see #5958) -ChangeSet = namedtuple('ChangeSet', - ['type', 'docname', 'lineno', 'module', 'descname', 'content']) +class ChangeSet(NamedTuple): + type: str + docname: str + lineno: int + module: str + descname: str + content: str class VersionChange(SphinxDirective): @@ -51,7 +54,7 @@ class VersionChange(SphinxDirective): required_arguments = 1 optional_arguments = 1 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: node = addnodes.versionmodified() @@ -91,7 +94,7 @@ class VersionChange(SphinxDirective): domain = cast(ChangeSetDomain, self.env.get_domain('changeset')) domain.note_changeset(node) - ret = [node] # type: List[Node] + ret: List[Node] = [node] ret += messages return ret @@ -102,9 +105,9 @@ class ChangeSetDomain(Domain): name = 'changeset' label = 'changeset' - initial_data = { + initial_data: Dict = { 'changes': {}, # version -> list of ChangeSet - } # type: Dict + } @property def changesets(self) -> Dict[str, List[ChangeSet]]: diff --git a/sphinx/domains/citation.py b/sphinx/domains/citation.py index 1ceb53037..772d486af 100644 --- a/sphinx/domains/citation.py +++ b/sphinx/domains/citation.py @@ -8,7 +8,7 @@ :license: BSD, see LICENSE for details. """ -from typing import Any, Dict, List, Set, Tuple, cast +from typing import TYPE_CHECKING, Any, Dict, List, Set, Tuple, cast from docutils import nodes from docutils.nodes import Element @@ -20,8 +20,7 @@ from sphinx.transforms import SphinxTransform from sphinx.util import logging from sphinx.util.nodes import copy_source_info, make_refnode -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.environment import BuildEnvironment diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 7ee023d1d..abe961791 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -39,6 +39,7 @@ from sphinx.util.cfamily import (ASTAttribute, ASTBaseBase, ASTBaseParenExprList from sphinx.util.docfields import Field, GroupedField from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_refnode +from sphinx.util.typing import OptionSpec logger = logging.getLogger(__name__) T = TypeVar('T') @@ -179,7 +180,6 @@ T = TypeVar('T') declarator -> ptr-declarator | noptr-declarator parameters-and-qualifiers trailing-return-type - (TODO: for now we don't support trailing-eturn-type) ptr-declarator -> noptr-declarator | ptr-operator ptr-declarator @@ -306,6 +306,7 @@ _operator_re = re.compile(r'''(?x) | \+\+ | -- | ->\*? | \, | (<<|>>)=? | && | \|\| + | <=> | [!<>=/*%+|&^~-]=? | (\b(and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq)\b) ''') @@ -494,6 +495,7 @@ _id_operator_v2 = { '>': 'gt', '<=': 'le', '>=': 'ge', + '<=>': 'ss', '!': 'nt', 'not': 'nt', '&&': 'aa', 'and': 'aa', '||': 'oo', 'or': 'oo', @@ -516,10 +518,10 @@ _id_operator_unary_v2 = { '!': 'nt', 'not': 'nt', '~': 'co', 'compl': 'co' } -_id_char_from_prefix = { +_id_char_from_prefix: Dict[Optional[str], str] = { None: 'c', 'u8': 'c', 'u': 'Ds', 'U': 'Di', 'L': 'w' -} # type: Dict[Any, str] +} # these are ordered by preceedence _expression_bin_ops = [ ['||', 'or'], @@ -528,7 +530,7 @@ _expression_bin_ops = [ ['^', 'xor'], ['&', 'bitand'], ['==', '!=', 'not_eq'], - ['<=', '>=', '<', '>'], + ['<=>', '<=', '>=', '<', '>'], ['<<', '>>'], ['+', '-'], ['*', '/', '%'], @@ -716,8 +718,7 @@ class ASTNestedName(ASTBase): res.append('') for i in range(len(self.names)): n = self.names[i] - t = self.templates[i] - if t: + if self.templates[i]: res.append("template " + transform(n)) else: res.append(transform(n)) @@ -728,16 +729,29 @@ class ASTNestedName(ASTBase): verify_description_mode(mode) # just print the name part, with template args, not template params if mode == 'noneIsName': - signode += nodes.Text(str(self)) + if self.rooted: + signode += nodes.Text('::') + for i in range(len(self.names)): + if i != 0: + signode += nodes.Text('::') + n = self.names[i] + if self.templates[i]: + signode += nodes.Text("template") + signode += nodes.Text(" ") + n.describe_signature(signode, mode, env, '', symbol) elif mode == 'param': - name = str(self) - signode += nodes.emphasis(name, name) + assert not self.rooted, str(self) + assert len(self.names) == 1 + assert not self.templates[0] + node = nodes.emphasis() + self.names[0].describe_signature(node, 'noneIsName', env, '', symbol) + signode += node elif mode == 'markType' or mode == 'lastIsName' or mode == 'markName': # Each element should be a pending xref targeting the complete # prefix. however, only the identifier part should be a link, such # that template args can be a link as well. # For 'lastIsName' we should also prepend template parameter lists. - templateParams = [] # type: List[Any] + templateParams: List[Any] = [] if mode == 'lastIsName': assert symbol is not None if symbol.declaration.templatePrefix is not None: @@ -1248,7 +1262,7 @@ class ASTSizeofParamPack(ASTExpression): def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('sizeof...(')) - self.identifier.describe_signature(signode, mode, env, + self.identifier.describe_signature(signode, 'markType', env, symbol=symbol, prefix="", templateArgs="") signode.append(nodes.Text(')')) @@ -1965,15 +1979,23 @@ class ASTParametersQualifiers(ASTBase): def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: verify_description_mode(mode) - paramlist = addnodes.desc_parameterlist() - for arg in self.args: - param = addnodes.desc_parameter('', '', noemph=True) - if mode == 'lastIsName': # i.e., outer-function params + # only use the desc_parameterlist for the outer list, not for inner lists + if mode == 'lastIsName': + paramlist = addnodes.desc_parameterlist() + for arg in self.args: + param = addnodes.desc_parameter('', '', noemph=True) arg.describe_signature(param, 'param', env, symbol=symbol) - else: - arg.describe_signature(param, 'markType', env, symbol=symbol) - paramlist += param - signode += paramlist + paramlist += param + signode += paramlist + else: + signode += nodes.Text('(', '(') + first = True + for arg in self.args: + if not first: + signode += nodes.Text(', ', ', ') + first = False + arg.describe_signature(signode, 'markType', env, symbol=symbol) + signode += nodes.Text(')', ')') def _add_anno(signode: TextElement, text: str) -> None: signode += nodes.Text(' ') @@ -2035,7 +2057,7 @@ class ASTDeclSpecsSimple(ASTBase): self.attrs + other.attrs) def _stringify(self, transform: StringifyTransform) -> str: - res = [] # type: List[str] + res: List[str] = [] res.extend(transform(attr) for attr in self.attrs) if self.storage: res.append(self.storage) @@ -2122,7 +2144,7 @@ class ASTDeclSpecs(ASTBase): return ''.join(res) def _stringify(self, transform: StringifyTransform) -> str: - res = [] # type: List[str] + res: List[str] = [] l = transform(self.leftSpecs) if len(l) > 0: res.append(l) @@ -2189,7 +2211,7 @@ class ASTArray(ASTBase): verify_description_mode(mode) signode.append(nodes.Text("[")) if self.size: - self.size.describe_signature(signode, mode, env, symbol) + self.size.describe_signature(signode, 'markType', env, symbol) signode.append(nodes.Text("]")) @@ -2657,7 +2679,7 @@ class ASTDeclaratorMemPtr(ASTDeclarator): def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: verify_description_mode(mode) - self.className.describe_signature(signode, mode, env, symbol) + self.className.describe_signature(signode, 'markType', env, symbol) signode += nodes.Text('::*') def _add_anno(signode: TextElement, text: str) -> None: @@ -3613,9 +3635,9 @@ class ASTDeclaration(ASTBase): self.trailingRequiresClause = trailingRequiresClause self.semicolon = semicolon - self.symbol = None # type: Symbol + self.symbol: Symbol = None # set by CPPObject._add_enumerator_to_parent - self.enumeratorScopedSymbol = None # type: Symbol + self.enumeratorScopedSymbol: Symbol = None def clone(self) -> "ASTDeclaration": templatePrefixClone = self.templatePrefix.clone() if self.templatePrefix else None @@ -3835,8 +3857,8 @@ class Symbol: docname: str, line: int) -> None: self.parent = parent # declarations in a single directive are linked together - self.siblingAbove = None # type: Symbol - self.siblingBelow = None # type: Symbol + self.siblingAbove: Symbol = None + self.siblingBelow: Symbol = None self.identOrOp = identOrOp self.templateParams = templateParams # template<templateParams> self.templateArgs = templateArgs # identifier<templateArgs> @@ -3847,8 +3869,8 @@ class Symbol: self._assert_invariants() # Remember to modify Symbol.remove if modifications to the parent change. - self._children = [] # type: List[Symbol] - self._anonChildren = [] # type: List[Symbol] + self._children: List[Symbol] = [] + self._anonChildren: List[Symbol] = [] # note: _children includes _anonChildren if self.parent: self.parent._children.append(self) @@ -3918,7 +3940,7 @@ class Symbol: self.parent = None def clear_doc(self, docname: str) -> None: - newChildren = [] # type: List[Symbol] + newChildren: List[Symbol] = [] for sChild in self._children: sChild.clear_doc(docname) if sChild.declaration and sChild.docname == docname: @@ -4950,7 +4972,7 @@ class DefinitionParser(BaseParser): # fold-expression # id-expression -> we parse this with _parse_nested_name self.skip_ws() - res = self._parse_literal() # type: ASTExpression + res: ASTExpression = self._parse_literal() if res is not None: return res self.skip_ws() @@ -4978,7 +5000,7 @@ class DefinitionParser(BaseParser): if self.skip_string(close): return [], False - exprs = [] # type: List[Union[ASTExpression, ASTBracedInitList]] + exprs: List[Union[ASTExpression, ASTBracedInitList]] = [] trailingComma = False while True: self.skip_ws() @@ -5057,7 +5079,7 @@ class DefinitionParser(BaseParser): # | "typeid" "(" type-id ")" prefixType = None - prefix = None # type: Any + prefix: Any = None self.skip_ws() cast = None @@ -5140,7 +5162,7 @@ class DefinitionParser(BaseParser): raise self._make_multi_error(errors, header) from eInner # and now parse postfixes - postFixes = [] # type: List[ASTPostfixOp] + postFixes: List[ASTPostfixOp] = [] while True: self.skip_ws() if prefixType in ['expr', 'cast', 'typeid']: @@ -5309,7 +5331,7 @@ class DefinitionParser(BaseParser): # exclusive-or = and ^ # and = equality & # equality = relational ==, != - # relational = shift <, >, <=, >= + # relational = shift <, >, <=, >=, <=> # shift = additive <<, >> # additive = multiplicative +, - # multiplicative = pm *, /, % @@ -5370,7 +5392,7 @@ class DefinitionParser(BaseParser): # logical-or-expression # | logical-or-expression "?" expression ":" assignment-expression # | logical-or-expression assignment-operator initializer-clause - exprs = [] # type: List[Union[ASTExpression, ASTBracedInitList]] + exprs: List[Union[ASTExpression, ASTBracedInitList]] = [] ops = [] orExpr = self._parse_logical_or_expression(inTemplate=inTemplate) exprs.append(orExpr) @@ -5443,7 +5465,7 @@ class DefinitionParser(BaseParser): else: # TODO: add handling of more bracket-like things, and quote handling brackets = {'(': ')', '{': '}', '[': ']', '<': '>'} - symbols = [] # type: List[str] + symbols: List[str] = [] while not self.eof: if (len(symbols) == 0 and self.current_char in end): break @@ -5506,7 +5528,7 @@ class DefinitionParser(BaseParser): if self.skip_string('>'): return ASTTemplateArgs([], False) prevErrors = [] - templateArgs = [] # type: List[Union[ASTType, ASTTemplateArgConstant]] + templateArgs: List[Union[ASTType, ASTTemplateArgConstant]] = [] packExpansion = False while 1: pos = self.pos @@ -5558,8 +5580,8 @@ class DefinitionParser(BaseParser): return ASTTemplateArgs(templateArgs, packExpansion) def _parse_nested_name(self, memberPointer: bool = False) -> ASTNestedName: - names = [] # type: List[ASTNestedNameElement] - templates = [] # type: List[bool] + names: List[ASTNestedNameElement] = [] + templates: List[bool] = [] self.skip_ws() rooted = False @@ -5572,7 +5594,7 @@ class DefinitionParser(BaseParser): else: template = False templates.append(template) - identOrOp = None # type: Union[ASTIdentifier, ASTOperator] + identOrOp: Union[ASTIdentifier, ASTOperator] = None if self.skip_word_and_ws('operator'): identOrOp = self._parse_operator() else: @@ -6075,7 +6097,7 @@ class DefinitionParser(BaseParser): return ASTInitializer(bracedInit) if outer == 'member': - fallbackEnd = [] # type: List[str] + fallbackEnd: List[str] = [] elif outer == 'templateParam': fallbackEnd = [',', '>'] elif outer is None: # function parameter @@ -6161,7 +6183,7 @@ class DefinitionParser(BaseParser): typed=typed) else: paramMode = 'type' - if outer == 'member': # i.e., member + if outer == 'member': named = True elif outer == 'operatorCast': paramMode = 'operatorCast' @@ -6354,7 +6376,7 @@ class DefinitionParser(BaseParser): def _parse_template_parameter_list(self) -> ASTTemplateParams: # only: '<' parameter-list '>' # we assume that 'template' has just been parsed - templateParams = [] # type: List[ASTTemplateParam] + templateParams: List[ASTTemplateParam] = [] self.skip_ws() if not self.skip_string("<"): self.fail("Expected '<' after 'template'") @@ -6477,11 +6499,11 @@ class DefinitionParser(BaseParser): def _parse_template_declaration_prefix(self, objectType: str ) -> Optional[ASTTemplateDeclarationPrefix]: - templates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]] + templates: List[Union[ASTTemplateParams, ASTTemplateIntroduction]] = [] while 1: self.skip_ws() # the saved position is only used to provide a better error message - params = None # type: Union[ASTTemplateParams, ASTTemplateIntroduction] + params: Union[ASTTemplateParams, ASTTemplateIntroduction] = None pos = self.pos if self.skip_word("template"): try: @@ -6537,7 +6559,7 @@ class DefinitionParser(BaseParser): msg += str(nestedName) self.warn(msg) - newTemplates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]] + newTemplates: List[Union[ASTTemplateParams, ASTTemplateIntroduction]] = [] for i in range(numExtra): newTemplates.append(ASTTemplateParams([])) if templatePrefix and not isMemberInstantiation: @@ -6557,7 +6579,7 @@ class DefinitionParser(BaseParser): templatePrefix = None requiresClause = None trailingRequiresClause = None - declaration = None # type: Any + declaration: Any = None self.skip_ws() if self.match(_visibility_re): @@ -6697,7 +6719,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]): names=('returns', 'return')), ] - option_spec = { + option_spec: OptionSpec = { 'noindexentry': directives.flag, 'tparam-line-spec': directives.flag, } @@ -6856,7 +6878,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]): return super().run() def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration: - parentSymbol = self.env.temp_data['cpp:parent_symbol'] # type: Symbol + parentSymbol: Symbol = self.env.temp_data['cpp:parent_symbol'] parser = DefinitionParser(sig, location=signode, config=self.env.config) try: @@ -6903,10 +6925,10 @@ class CPPObject(ObjectDescription[ASTDeclaration]): return ast def before_content(self) -> None: - lastSymbol = self.env.temp_data['cpp:last_symbol'] # type: Symbol + lastSymbol: Symbol = self.env.temp_data['cpp:last_symbol'] assert lastSymbol self.oldParentSymbol = self.env.temp_data['cpp:parent_symbol'] - self.oldParentKey = self.env.ref_context['cpp:parent_key'] # type: LookupKey + self.oldParentKey: LookupKey = self.env.ref_context['cpp:parent_key'] self.env.temp_data['cpp:parent_symbol'] = lastSymbol self.env.ref_context['cpp:parent_key'] = lastSymbol.get_lookup_key() @@ -6963,13 +6985,13 @@ class CPPNamespaceObject(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: rootSymbol = self.env.domaindata['cpp']['root_symbol'] if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): symbol = rootSymbol - stack = [] # type: List[Symbol] + stack: List[Symbol] = [] else: parser = DefinitionParser(self.arguments[0], location=self.get_source_info(), @@ -6994,7 +7016,7 @@ class CPPNamespacePushObject(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): @@ -7026,7 +7048,7 @@ class CPPNamespacePopObject(SphinxDirective): required_arguments = 0 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: stack = self.env.temp_data.get('cpp:namespace_stack', None) @@ -7081,7 +7103,7 @@ class AliasTransform(SphinxTransform): maxdepth -= 1 recurse = True - nodes = [] # type: List[Node] + nodes: List[Node] = [] if not skipThis: signode = addnodes.desc_signature('', '') nodes.append(signode) @@ -7089,7 +7111,7 @@ class AliasTransform(SphinxTransform): if recurse: if skipThis: - childContainer = nodes # type: Union[List[Node], addnodes.desc] + childContainer: Union[List[Node], addnodes.desc] = nodes else: content = addnodes.desc_content() desc = addnodes.desc() @@ -7138,15 +7160,15 @@ class AliasTransform(SphinxTransform): node.replace_self(signode) continue - rootSymbol = self.env.domains['cpp'].data['root_symbol'] # type: Symbol - parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol + rootSymbol: Symbol = self.env.domains['cpp'].data['root_symbol'] + parentSymbol: Symbol = rootSymbol.direct_lookup(parentKey) if not parentSymbol: print("Target: ", sig) print("ParentKey: ", parentKey) print(rootSymbol.dump(1)) assert parentSymbol # should be there - symbols = [] # type: List[Symbol] + symbols: List[Symbol] = [] if isShorthand: assert isinstance(ast, ASTNamespace) ns = ast @@ -7203,10 +7225,10 @@ class AliasTransform(SphinxTransform): class CPPAliasObject(ObjectDescription): - option_spec = { + option_spec: OptionSpec = { 'maxdepth': directives.nonnegative_int, 'noroot': directives.flag, - } # type: Dict + } def run(self) -> List[Node]: """ @@ -7225,7 +7247,7 @@ class CPPAliasObject(ObjectDescription): # 'desctype' is a backwards compatible attribute node['objtype'] = node['desctype'] = self.objtype - self.names = [] # type: List[str] + self.names: List[str] = [] aliasOptions = { 'maxdepth': self.options.get('maxdepth', 1), 'noroot': 'noroot' in self.options, @@ -7285,7 +7307,7 @@ class CPPExprRole(SphinxRole): if asCode: # render the expression as inline code self.class_type = 'cpp-expr' - self.node_type = nodes.literal # type: Type[TextElement] + self.node_type: Type[TextElement] = nodes.literal else: # render the expression as inline text self.class_type = 'cpp-texpr' @@ -7466,10 +7488,10 @@ class CPPDomain(Domain): logger.warning('Unparseable C++ cross-reference: %r\n%s', t, ex, location=node) return None, None - parentKey = node.get("cpp:parent_key", None) # type: LookupKey + parentKey: LookupKey = node.get("cpp:parent_key", None) rootSymbol = self.data['root_symbol'] if parentKey: - parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol + parentSymbol: Symbol = rootSymbol.direct_lookup(parentKey) if not parentSymbol: print("Target: ", target) print("ParentKey: ", parentKey.data) @@ -7623,7 +7645,7 @@ class CPPDomain(Domain): target = node.get('reftarget', None) if target is None: return None - parentKey = node.get("cpp:parent_key", None) # type: LookupKey + parentKey: LookupKey = node.get("cpp:parent_key", None) if parentKey is None or len(parentKey.data) <= 0: return None @@ -7644,10 +7666,11 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value("cpp_debug_lookup", False, '') app.add_config_value("cpp_debug_show_tree", False, '') - def setDebugFlags(app): + def initStuff(app): Symbol.debug_lookup = app.config.cpp_debug_lookup Symbol.debug_show_tree = app.config.cpp_debug_show_tree - app.connect("builder-inited", setDebugFlags) + app.config.cpp_index_common_prefix.sort(reverse=True) + app.connect("builder-inited", initStuff) return { 'version': 'builtin', diff --git a/sphinx/domains/index.py b/sphinx/domains/index.py index 4a91d6ad1..9ecfae439 100644 --- a/sphinx/domains/index.py +++ b/sphinx/domains/index.py @@ -8,7 +8,7 @@ :license: BSD, see LICENSE for details. """ -from typing import Any, Dict, Iterable, List, Tuple +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Tuple from docutils import nodes from docutils.nodes import Node, system_message @@ -20,9 +20,9 @@ from sphinx.environment import BuildEnvironment from sphinx.util import logging, split_index_msg from sphinx.util.docutils import ReferenceRole, SphinxDirective from sphinx.util.nodes import process_index_entry +from sphinx.util.typing import OptionSpec -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -68,7 +68,7 @@ class IndexDirective(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = { + option_spec: OptionSpec = { 'name': directives.unchanged, } diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index f612fb914..92d0e1d61 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -28,6 +28,7 @@ from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_id, make_refnode +from sphinx.util.typing import OptionSpec logger = logging.getLogger(__name__) @@ -41,13 +42,13 @@ class JSObject(ObjectDescription[Tuple[str, str]]): has_arguments = False #: what is displayed right before the documentation entry - display_prefix = None # type: str + display_prefix: str = None #: If ``allow_nesting`` is ``True``, the object prefixes will be accumulated #: based on directive nesting allow_nesting = False - option_spec = { + option_spec: OptionSpec = { 'noindex': directives.flag, 'noindexentry': directives.flag, } @@ -253,7 +254,7 @@ class JSModule(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = { + option_spec: OptionSpec = { 'noindex': directives.flag } @@ -261,7 +262,7 @@ class JSModule(SphinxDirective): mod_name = self.arguments[0].strip() self.env.ref_context['js:module'] = mod_name noindex = 'noindex' in self.options - ret = [] # type: List[Node] + ret: List[Node] = [] if not noindex: domain = cast(JavaScriptDomain, self.env.get_domain('js')) @@ -345,10 +346,10 @@ class JavaScriptDomain(Domain): 'attr': JSXRefRole(), 'mod': JSXRefRole(), } - initial_data = { + initial_data: Dict[str, Dict[str, Tuple[str, str]]] = { 'objects': {}, # fullname -> docname, node_id, objtype 'modules': {}, # modname -> docname, node_id - } # type: Dict[str, Dict[str, Tuple[str, str]]] + } @property def objects(self) -> Dict[str, Tuple[str, str, str]]: diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py index 248a7a2a6..88db1ad0e 100644 --- a/sphinx/domains/math.py +++ b/sphinx/domains/math.py @@ -8,14 +8,12 @@ :license: BSD, see LICENSE for details. """ -import warnings -from typing import Any, Dict, Iterable, List, Tuple +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Tuple from docutils import nodes from docutils.nodes import Element, Node, make_id, system_message from sphinx.addnodes import pending_xref -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain from sphinx.environment import BuildEnvironment from sphinx.locale import __ @@ -23,8 +21,7 @@ from sphinx.roles import XRefRole from sphinx.util import logging from sphinx.util.nodes import make_refnode -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.builders import Builder @@ -44,10 +41,10 @@ class MathDomain(Domain): name = 'math' label = 'mathematics' - initial_data = { + initial_data: Dict = { 'objects': {}, # labelid -> (docname, eqno) 'has_equations': {}, # docname -> bool - } # type: Dict + } dangling_warnings = { 'eq': 'equation not found: %(target)s', } @@ -139,24 +136,6 @@ class MathDomain(Domain): def get_objects(self) -> List: return [] - def add_equation(self, env: BuildEnvironment, docname: str, labelid: str) -> int: - warnings.warn('MathDomain.add_equation() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - if labelid in self.equations: - path = env.doc2path(self.equations[labelid][0]) - msg = __('duplicate label of equation %s, other instance in %s') % (labelid, path) - raise UserWarning(msg) - else: - eqno = self.get_next_equation_number(docname) - self.equations[labelid] = (docname, eqno) - return eqno - - def get_next_equation_number(self, docname: str) -> int: - warnings.warn('MathDomain.get_next_equation_number() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - targets = [eq for eq in self.equations.values() if eq[0] == docname] - return len(targets) + 1 - def has_equations(self, docname: str = None) -> bool: if docname: return self.data['has_equations'].get(docname, False) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 552742bb8..d0c5f7118 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -15,17 +15,17 @@ import sys import typing import warnings from inspect import Parameter -from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Tuple, cast +from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Tuple, Type, cast from docutils import nodes from docutils.nodes import Element, Node from docutils.parsers.rst import directives from sphinx import addnodes -from sphinx.addnodes import desc_signature, pending_xref +from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition from sphinx.application import Sphinx from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, Index, IndexEntry, ObjType from sphinx.environment import BuildEnvironment @@ -37,13 +37,8 @@ from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective from sphinx.util.inspect import signature_from_str -from sphinx.util.nodes import make_id, make_refnode -from sphinx.util.typing import TextlikeNode - -if False: - # For type annotation - from typing import Type # for python3.5.1 - +from sphinx.util.nodes import find_pending_xref_condition, make_id, make_refnode +from sphinx.util.typing import OptionSpec, TextlikeNode logger = logging.getLogger(__name__) @@ -68,14 +63,20 @@ pairindextypes = { 'builtin': _('built-in function'), } -ObjectEntry = NamedTuple('ObjectEntry', [('docname', str), - ('node_id', str), - ('objtype', str)]) -ModuleEntry = NamedTuple('ModuleEntry', [('docname', str), - ('node_id', str), - ('synopsis', str), - ('platform', str), - ('deprecated', bool)]) + +class ObjectEntry(NamedTuple): + docname: str + node_id: str + objtype: str + canonical: bool + + +class ModuleEntry(NamedTuple): + docname: str + node_id: str + synopsis: str + platform: str + deprecated: bool def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xref: @@ -91,7 +92,17 @@ def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xr else: kwargs = {} - return pending_xref('', nodes.Text(text), + if env.config.python_use_unqualified_type_names: + # Note: It would be better to use qualname to describe the object to support support + # nested classes. But python domain can't access the real python object because this + # module should work not-dynamically. + shortname = text.split('.')[-1] + contnodes: List[Node] = [pending_xref_condition('', shortname, condition='resolved'), + pending_xref_condition('', text, condition='*')] + else: + contnodes = [nodes.Text(text)] + + return pending_xref('', *contnodes, refdomain='py', reftype=reftype, reftarget=text, **kwargs) @@ -101,12 +112,17 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod if isinstance(node, ast.Attribute): return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))] elif isinstance(node, ast.BinOp): - result = unparse(node.left) # type: List[Node] + result: List[Node] = unparse(node.left) result.extend(unparse(node.op)) result.extend(unparse(node.right)) return result elif isinstance(node, ast.BitOr): return [nodes.Text(' '), addnodes.desc_sig_punctuation('', '|'), nodes.Text(' ')] + elif isinstance(node, ast.Constant): # type: ignore + if node.value is Ellipsis: + return [addnodes.desc_sig_punctuation('', "...")] + else: + return [nodes.Text(node.value)] elif isinstance(node, ast.Expr): return unparse(node.value) elif isinstance(node, ast.Index): @@ -142,13 +158,6 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod return result else: - if sys.version_info >= (3, 6): - if isinstance(node, ast.Constant): - if node.value is Ellipsis: - return [addnodes.desc_sig_punctuation('', "...")] - else: - return [nodes.Text(node.value)] - if sys.version_info < (3, 8): if isinstance(node, ast.Ellipsis): return [addnodes.desc_sig_punctuation('', "...")] @@ -230,7 +239,7 @@ def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None: string literal (e.g. default argument value). """ paramlist = addnodes.desc_parameterlist() - stack = [paramlist] # type: List[Element] + stack: List[Element] = [paramlist] try: for argument in arglist.split(','): argument = argument.strip() @@ -274,7 +283,7 @@ def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None: # when it comes to handling "." and "~" prefixes. class PyXrefMixin: def make_xref(self, rolename: str, domain: str, target: str, - innernode: "Type[TextlikeNode]" = nodes.emphasis, + innernode: Type[TextlikeNode] = nodes.emphasis, contnode: Node = None, env: BuildEnvironment = None) -> Node: result = super().make_xref(rolename, domain, target, # type: ignore innernode, contnode, env) @@ -293,7 +302,7 @@ class PyXrefMixin: return result def make_xrefs(self, rolename: str, domain: str, target: str, - innernode: "Type[TextlikeNode]" = nodes.emphasis, + innernode: Type[TextlikeNode] = nodes.emphasis, contnode: Node = None, env: BuildEnvironment = None) -> List[Node]: delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+)' delims_re = re.compile(delims) @@ -317,7 +326,7 @@ class PyXrefMixin: class PyField(PyXrefMixin, Field): def make_xref(self, rolename: str, domain: str, target: str, - innernode: "Type[TextlikeNode]" = nodes.emphasis, + innernode: Type[TextlikeNode] = nodes.emphasis, contnode: Node = None, env: BuildEnvironment = None) -> Node: if rolename == 'class' and target == 'None': # None is not a type, so use obj role instead. @@ -332,7 +341,7 @@ class PyGroupedField(PyXrefMixin, GroupedField): class PyTypedField(PyXrefMixin, TypedField): def make_xref(self, rolename: str, domain: str, target: str, - innernode: "Type[TextlikeNode]" = nodes.emphasis, + innernode: Type[TextlikeNode] = nodes.emphasis, contnode: Node = None, env: BuildEnvironment = None) -> Node: if rolename == 'class' and target == 'None': # None is not a type, so use obj role instead. @@ -348,10 +357,11 @@ class PyObject(ObjectDescription[Tuple[str, str]]): :cvar allow_nesting: Class is an object that allows for nested namespaces :vartype allow_nesting: bool """ - option_spec = { + option_spec: OptionSpec = { 'noindex': directives.flag, 'noindexentry': directives.flag, 'module': directives.unchanged, + 'canonical': directives.unchanged, 'annotation': directives.unchanged, } @@ -361,7 +371,7 @@ class PyObject(ObjectDescription[Tuple[str, str]]): 'keyword', 'kwarg', 'kwparam'), typerolename='class', typenames=('paramtype', 'type'), can_collapse=True), - PyTypedField('variable', label=_('Variables'), rolename='obj', + PyTypedField('variable', label=_('Variables'), names=('var', 'ivar', 'cvar'), typerolename='class', typenames=('vartype',), can_collapse=True), @@ -493,6 +503,11 @@ class PyObject(ObjectDescription[Tuple[str, str]]): domain = cast(PythonDomain, self.env.get_domain('py')) domain.note_object(fullname, self.objtype, node_id, location=signode) + canonical_name = self.options.get('canonical') + if canonical_name: + domain.note_object(canonical_name, self.objtype, node_id, canonical=True, + location=signode) + if 'noindexentry' not in self.options: indextext = self.get_index_text(modname, name_cls) if indextext: @@ -557,44 +572,10 @@ class PyObject(ObjectDescription[Tuple[str, str]]): self.env.ref_context.pop('py:module') -class PyModulelevel(PyObject): - """ - Description of an object on module level (functions, data). - """ - - def run(self) -> List[Node]: - for cls in self.__class__.__mro__: - if cls.__name__ != 'DirectiveAdapter': - warnings.warn('PyModulelevel is deprecated. ' - 'Please check the implementation of %s' % cls, - RemovedInSphinx40Warning, stacklevel=2) - break - else: - warnings.warn('PyModulelevel is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - - return super().run() - - def needs_arglist(self) -> bool: - return self.objtype == 'function' - - def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: - if self.objtype == 'function': - if not modname: - return _('%s() (built-in function)') % name_cls[0] - return _('%s() (in module %s)') % (name_cls[0], modname) - elif self.objtype == 'data': - if not modname: - return _('%s (built-in variable)') % name_cls[0] - return _('%s (in module %s)') % (name_cls[0], modname) - else: - return '' - - class PyFunction(PyObject): """Description of a function.""" - option_spec = PyObject.option_spec.copy() + option_spec: OptionSpec = PyObject.option_spec.copy() option_spec.update({ 'async': directives.flag, }) @@ -648,7 +629,7 @@ class PyDecoratorFunction(PyFunction): class PyVariable(PyObject): """Description of a variable.""" - option_spec = PyObject.option_spec.copy() + option_spec: OptionSpec = PyObject.option_spec.copy() option_spec.update({ 'type': directives.unchanged, 'value': directives.unchanged, @@ -681,7 +662,7 @@ class PyClasslike(PyObject): Description of a class-like object (classes, interfaces, exceptions). """ - option_spec = PyObject.option_spec.copy() + option_spec: OptionSpec = PyObject.option_spec.copy() option_spec.update({ 'final': directives.flag, }) @@ -705,95 +686,10 @@ class PyClasslike(PyObject): return '' -class PyClassmember(PyObject): - """ - Description of a class member (methods, attributes). - """ - - def run(self) -> List[Node]: - for cls in self.__class__.__mro__: - if cls.__name__ != 'DirectiveAdapter': - warnings.warn('PyClassmember is deprecated. ' - 'Please check the implementation of %s' % cls, - RemovedInSphinx40Warning, stacklevel=2) - break - else: - warnings.warn('PyClassmember is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - - return super().run() - - def needs_arglist(self) -> bool: - return self.objtype.endswith('method') - - def get_signature_prefix(self, sig: str) -> str: - if self.objtype == 'staticmethod': - return 'static ' - elif self.objtype == 'classmethod': - return 'classmethod ' - return '' - - def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: - name, cls = name_cls - add_modules = self.env.config.add_module_names - if self.objtype == 'method': - try: - clsname, methname = name.rsplit('.', 1) - except ValueError: - if modname: - return _('%s() (in module %s)') % (name, modname) - else: - return '%s()' % name - if modname and add_modules: - return _('%s() (%s.%s method)') % (methname, modname, clsname) - else: - return _('%s() (%s method)') % (methname, clsname) - elif self.objtype == 'staticmethod': - try: - clsname, methname = name.rsplit('.', 1) - except ValueError: - if modname: - return _('%s() (in module %s)') % (name, modname) - else: - return '%s()' % name - if modname and add_modules: - return _('%s() (%s.%s static method)') % (methname, modname, - clsname) - else: - return _('%s() (%s static method)') % (methname, clsname) - elif self.objtype == 'classmethod': - try: - clsname, methname = name.rsplit('.', 1) - except ValueError: - if modname: - return _('%s() (in module %s)') % (name, modname) - else: - return '%s()' % name - if modname: - return _('%s() (%s.%s class method)') % (methname, modname, - clsname) - else: - return _('%s() (%s class method)') % (methname, clsname) - elif self.objtype == 'attribute': - try: - clsname, attrname = name.rsplit('.', 1) - except ValueError: - if modname: - return _('%s (in module %s)') % (name, modname) - else: - return name - if modname and add_modules: - return _('%s (%s.%s attribute)') % (attrname, modname, clsname) - else: - return _('%s (%s attribute)') % (attrname, clsname) - else: - return '' - - class PyMethod(PyObject): """Description of a method.""" - option_spec = PyObject.option_spec.copy() + option_spec: OptionSpec = PyObject.option_spec.copy() option_spec.update({ 'abstractmethod': directives.flag, 'async': directives.flag, @@ -854,7 +750,7 @@ class PyMethod(PyObject): class PyClassMethod(PyMethod): """Description of a classmethod.""" - option_spec = PyObject.option_spec.copy() + option_spec: OptionSpec = PyObject.option_spec.copy() def run(self) -> List[Node]: self.name = 'py:method' @@ -866,7 +762,7 @@ class PyClassMethod(PyMethod): class PyStaticMethod(PyMethod): """Description of a staticmethod.""" - option_spec = PyObject.option_spec.copy() + option_spec: OptionSpec = PyObject.option_spec.copy() def run(self) -> List[Node]: self.name = 'py:method' @@ -894,7 +790,7 @@ class PyDecoratorMethod(PyMethod): class PyAttribute(PyObject): """Description of an attribute.""" - option_spec = PyObject.option_spec.copy() + option_spec: OptionSpec = PyObject.option_spec.copy() option_spec.update({ 'type': directives.unchanged, 'value': directives.unchanged, @@ -929,6 +825,46 @@ class PyAttribute(PyObject): return _('%s (%s attribute)') % (attrname, clsname) +class PyProperty(PyObject): + """Description of an attribute.""" + + option_spec = PyObject.option_spec.copy() + option_spec.update({ + 'abstractmethod': directives.flag, + 'type': directives.unchanged, + }) + + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: + fullname, prefix = super().handle_signature(sig, signode) + + typ = self.options.get('type') + if typ: + signode += addnodes.desc_annotation(typ, ': ' + typ) + + return fullname, prefix + + def get_signature_prefix(self, sig: str) -> str: + prefix = ['property'] + if 'abstractmethod' in self.options: + prefix.insert(0, 'abstract') + + return ' '.join(prefix) + ' ' + + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: + name, cls = name_cls + try: + clsname, attrname = name.rsplit('.', 1) + if modname and self.env.config.add_module_names: + clsname = '.'.join([modname, clsname]) + except ValueError: + if modname: + return _('%s (in module %s)') % (name, modname) + else: + return name + + return _('%s (%s property)') % (attrname, clsname) + + class PyDecoratorMixin: """ Mixin for decorator directives. @@ -961,7 +897,7 @@ class PyModule(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = { + option_spec: OptionSpec = { 'platform': lambda x: x, 'synopsis': lambda x: x, 'noindex': directives.flag, @@ -974,7 +910,7 @@ class PyModule(SphinxDirective): modname = self.arguments[0].strip() noindex = 'noindex' in self.options self.env.ref_context['py:module'] = modname - ret = [] # type: List[Node] + ret: List[Node] = [] if not noindex: # note module to the domain node_id = make_id(self.env, self.state.document, 'module', modname) @@ -1025,7 +961,7 @@ class PyCurrentModule(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: modname = self.arguments[0].strip() @@ -1085,10 +1021,9 @@ class PythonModuleIndex(Index): def generate(self, docnames: Iterable[str] = None ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: - content = {} # type: Dict[str, List[IndexEntry]] + content: Dict[str, List[IndexEntry]] = {} # list of prefixes to ignore - ignores = None # type: List[str] - ignores = self.domain.env.config['modindex_common_prefix'] # type: ignore + ignores: List[str] = self.domain.env.config['modindex_common_prefix'] # type: ignore ignores = sorted(ignores, key=len, reverse=True) # list of all modules, sorted by module name modules = sorted(self.domain.data['modules'].items(), @@ -1151,7 +1086,7 @@ class PythonDomain(Domain): """Python language domain.""" name = 'py' label = 'Python' - object_types = { + object_types: Dict[str, ObjType] = { 'function': ObjType(_('function'), 'func', 'obj'), 'data': ObjType(_('data'), 'data', 'obj'), 'class': ObjType(_('class'), 'class', 'exc', 'obj'), @@ -1160,8 +1095,9 @@ class PythonDomain(Domain): 'classmethod': ObjType(_('class method'), 'meth', 'obj'), 'staticmethod': ObjType(_('static method'), 'meth', 'obj'), 'attribute': ObjType(_('attribute'), 'attr', 'obj'), + 'property': ObjType(_('property'), 'attr', '_prop', 'obj'), 'module': ObjType(_('module'), 'mod', 'obj'), - } # type: Dict[str, ObjType] + } directives = { 'function': PyFunction, @@ -1172,6 +1108,7 @@ class PythonDomain(Domain): 'classmethod': PyClassMethod, 'staticmethod': PyStaticMethod, 'attribute': PyAttribute, + 'property': PyProperty, 'module': PyModule, 'currentmodule': PyCurrentModule, 'decorator': PyDecoratorFunction, @@ -1188,10 +1125,10 @@ class PythonDomain(Domain): 'mod': PyXRefRole(), 'obj': PyXRefRole(), } - initial_data = { + initial_data: Dict[str, Dict[str, Tuple[Any]]] = { 'objects': {}, # fullname -> docname, objtype 'modules': {}, # modname -> docname, synopsis, platform, deprecated - } # type: Dict[str, Dict[str, Tuple[Any]]] + } indices = [ PythonModuleIndex, ] @@ -1200,7 +1137,8 @@ class PythonDomain(Domain): def objects(self) -> Dict[str, ObjectEntry]: return self.data.setdefault('objects', {}) # fullname -> ObjectEntry - def note_object(self, name: str, objtype: str, node_id: str, location: Any = None) -> None: + def note_object(self, name: str, objtype: str, node_id: str, + canonical: bool = False, location: Any = None) -> None: """Note a python object for cross reference. .. versionadded:: 2.1 @@ -1210,7 +1148,7 @@ class PythonDomain(Domain): logger.warning(__('duplicate object description of %s, ' 'other instance in %s, use :noindex: for one of them'), name, other.docname, location=location) - self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype) + self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype, canonical) @property def modules(self) -> Dict[str, ModuleEntry]: @@ -1255,7 +1193,7 @@ class PythonDomain(Domain): if not name: return [] - matches = [] # type: List[Tuple[str, ObjectEntry]] + matches: List[Tuple[str, ObjectEntry]] = [] newname = None if searchmode == 1: @@ -1308,8 +1246,17 @@ class PythonDomain(Domain): type, searchmode) if not matches and type == 'attr': - # fallback to meth (for property) + # fallback to meth (for property; Sphinx-2.4.x) + # this ensures that `:attr:` role continues to refer to the old property entry + # that defined by ``method`` directive in old reST files. matches = self.find_obj(env, modname, clsname, target, 'meth', searchmode) + if not matches and type == 'meth': + # fallback to attr (for property) + # this ensures that `:meth:` in the old reST files can refer to the property + # entry that defined by ``property`` directive. + # + # Note: _prop is a secret role only for internal look-up. + matches = self.find_obj(env, modname, clsname, target, '_prop', searchmode) if not matches: return None @@ -1322,14 +1269,22 @@ class PythonDomain(Domain): if obj[2] == 'module': return self._make_module_refnode(builder, fromdocname, name, contnode) else: - return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name) + # determine the content of the reference by conditions + content = find_pending_xref_condition(node, 'resolved') + if content: + children = content.children + else: + # if not found, use contnode + children = [contnode] + + return make_refnode(builder, fromdocname, obj[0], obj[1], children, name) def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element ) -> List[Tuple[str, Element]]: modname = node.get('py:module') clsname = node.get('py:class') - results = [] # type: List[Tuple[str, Element]] + results: List[Tuple[str, Element]] = [] # always search in "refspecific" mode with the :any: role matches = self.find_obj(env, modname, clsname, target, None, 1) @@ -1339,9 +1294,17 @@ class PythonDomain(Domain): self._make_module_refnode(builder, fromdocname, name, contnode))) else: + # determine the content of the reference by conditions + content = find_pending_xref_condition(node, 'resolved') + if content: + children = content.children + else: + # if not found, use contnode + children = [contnode] + results.append(('py:' + self.role_for_objtype(obj[2]), make_refnode(builder, fromdocname, obj[0], obj[1], - contnode, name))) + children, name))) return results def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, @@ -1363,7 +1326,11 @@ class PythonDomain(Domain): yield (modname, modname, 'module', mod.docname, mod.node_id, 0) for refname, obj in self.objects.items(): if obj.objtype != 'module': # modules are already handled - yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1) + if obj.canonical: + # canonical names are not full-text searchable. + yield (refname, refname, obj.objtype, obj.docname, obj.node_id, -1) + else: + yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1) def get_full_qualified_name(self, node: Element) -> str: modname = node.get('py:module') @@ -1384,6 +1351,10 @@ def builtin_resolver(app: Sphinx, env: BuildEnvironment, return s in typing.__all__ # type: ignore + content = find_pending_xref_condition(node, 'resolved') + if content: + contnode = content.children[0] # type: ignore + if node.get('refdomain') != 'py': return None elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None': @@ -1404,12 +1375,13 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.setup_extension('sphinx.directives') app.add_domain(PythonDomain) + app.add_config_value('python_use_unqualified_type_names', False, 'env') app.connect('object-description-transform', filter_meta_fields) app.connect('missing-reference', builtin_resolver, priority=900) return { 'version': 'builtin', - 'env_version': 2, + 'env_version': 3, 'parallel_read_safe': True, 'parallel_write_safe': True, } diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index 07bf46b75..d048c2dfb 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -25,6 +25,7 @@ from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging from sphinx.util.nodes import make_id, make_refnode +from sphinx.util.typing import OptionSpec logger = logging.getLogger(__name__) @@ -117,7 +118,7 @@ class ReSTDirectiveOption(ReSTMarkup): """ Description of an option for reST directive. """ - option_spec = ReSTMarkup.option_spec.copy() + option_spec: OptionSpec = ReSTMarkup.option_spec.copy() option_spec.update({ 'type': directives.unchanged, }) @@ -217,9 +218,9 @@ class ReSTDomain(Domain): 'dir': XRefRole(), 'role': XRefRole(), } - initial_data = { + initial_data: Dict[str, Dict[Tuple[str, str], str]] = { 'objects': {}, # fullname -> docname, objtype - } # type: Dict[str, Dict[Tuple[str, str], str]] + } @property def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]: @@ -258,7 +259,7 @@ class ReSTDomain(Domain): def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element ) -> List[Tuple[str, Element]]: - results = [] # type: List[Tuple[str, Element]] + results: List[Tuple[str, Element]] = [] for objtype in self.object_types: todocname, node_id = self.objects.get((objtype, target), (None, None)) if todocname: diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 352e5c7f3..5e10646f0 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -12,7 +12,8 @@ import re import unicodedata import warnings from copy import copy -from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast +from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional, + Tuple, Type, Union, cast) from docutils import nodes from docutils.nodes import Element, Node, system_message @@ -21,7 +22,7 @@ from docutils.statemachine import StringList from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.locale import _, __ @@ -29,12 +30,9 @@ from sphinx.roles import XRefRole from sphinx.util import docname_join, logging, ws_re from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import clean_astext, make_id, make_refnode -from sphinx.util.typing import RoleFunction - -if False: - # For type annotation - from typing import Type # for python3.5.1 +from sphinx.util.typing import OptionSpec, RoleFunction +if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.environment import BuildEnvironment @@ -52,8 +50,8 @@ class GenericObject(ObjectDescription[str]): """ A generic x-ref directive registered with Sphinx.add_object_type(). """ - indextemplate = '' - parse_node = None # type: Callable[[GenericObject, BuildEnvironment, str, desc_signature], str] # NOQA + indextemplate: str = '' + parse_node: Callable[["GenericObject", "BuildEnvironment", str, desc_signature], str] = None # NOQA def handle_signature(self, sig: str, signode: desc_signature) -> str: if self.parse_node: @@ -134,7 +132,7 @@ class Target(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: # normalize whitespace in fullname like XRefRole does @@ -150,7 +148,7 @@ class Target(SphinxDirective): node['ids'].append(old_node_id) self.state.document.note_explicit_target(node) - ret = [node] # type: List[Node] + ret: List[Node] = [node] if self.indextemplate: indexentry = self.indextemplate % (fullname,) indextype = 'single' @@ -267,7 +265,7 @@ class Program(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: program = ws_re.sub('-', self.arguments[0].strip()) @@ -292,8 +290,8 @@ def split_term_classifiers(line: str) -> List[Optional[str]]: def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index_key: str, - source: str, lineno: int, node_id: str = None, - document: nodes.document = None) -> nodes.term: + source: str, lineno: int, node_id: str, document: nodes.document + ) -> nodes.term: # get a text-only representation of the term and register it # as a cross-reference target term = nodes.term('', '', *textnodes) @@ -304,23 +302,10 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index if node_id: # node_id is given from outside (mainly i18n module), use it forcedly term['ids'].append(node_id) - elif document: + else: node_id = make_id(env, document, 'term', termtext) term['ids'].append(node_id) document.note_explicit_target(term) - else: - warnings.warn('make_glossary_term() expects document is passed as an argument.', - RemovedInSphinx40Warning, stacklevel=2) - gloss_entries = env.temp_data.setdefault('gloss_entries', set()) - node_id = nodes.make_id('term-' + termtext) - if node_id == 'term': - # "term" is not good for node_id. Generate it by sequence number instead. - node_id = 'term-%d' % env.new_serialno('glossary') - - while node_id in gloss_entries: - node_id = 'term-%d' % env.new_serialno('glossary') - gloss_entries.add(node_id) - term['ids'].append(node_id) std = cast(StandardDomain, env.get_domain('std')) std._note_term(termtext, node_id, location=term) @@ -344,7 +329,7 @@ class Glossary(SphinxDirective): required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False - option_spec = { + option_spec: OptionSpec = { 'sorted': directives.flag, } @@ -358,11 +343,11 @@ class Glossary(SphinxDirective): # be* a definition list. # first, collect single entries - entries = [] # type: List[Tuple[List[Tuple[str, str, int]], StringList]] + entries: List[Tuple[List[Tuple[str, str, int]], StringList]] = [] in_definition = True in_comment = False was_empty = True - messages = [] # type: List[Node] + messages: List[Node] = [] for line, (source, lineno) in zip(self.content, self.content.items): # empty line -> add to last definition if not line: @@ -417,9 +402,9 @@ class Glossary(SphinxDirective): # now, parse all the entries into a big definition list items = [] for terms, definition in entries: - termtexts = [] # type: List[str] - termnodes = [] # type: List[Node] - system_messages = [] # type: List[Node] + termtexts: List[str] = [] + termnodes: List[Node] = [] + system_messages: List[Node] = [] for line, source, lineno in terms: parts = split_term_classifiers(line) # parse the term with inline markup @@ -428,7 +413,7 @@ class Glossary(SphinxDirective): # use first classifier as a index key term = make_glossary_term(self.env, textnodes, parts[1], source, lineno, - document=self.state.document) + node_id=None, document=self.state.document) term.rawsource = line system_messages.extend(sysmsg) termtexts.append(term.astext()) @@ -458,7 +443,7 @@ class Glossary(SphinxDirective): def token_xrefs(text: str, productionGroup: str = '') -> List[Node]: if len(productionGroup) != 0: productionGroup += ':' - retnodes = [] # type: List[Node] + retnodes: List[Node] = [] pos = 0 for m in token_re.finditer(text): if m.start() > pos: @@ -497,11 +482,11 @@ class ProductionList(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: domain = cast(StandardDomain, self.env.get_domain('std')) - node = addnodes.productionlist() # type: Element + node: Element = addnodes.productionlist() self.set_source_info(node) # The backslash handling is from ObjectDescription.get_signatures nl_escape_re = re.compile(r'\\\n') @@ -574,7 +559,7 @@ class StandardDomain(Domain): name = 'std' label = 'Default' - object_types = { + object_types: Dict[str, ObjType] = { 'term': ObjType(_('glossary term'), 'term', searchprio=-1), 'token': ObjType(_('grammar token'), 'token', searchprio=-1), 'label': ObjType(_('reference label'), 'ref', 'keyword', @@ -582,17 +567,17 @@ class StandardDomain(Domain): 'envvar': ObjType(_('environment variable'), 'envvar'), 'cmdoption': ObjType(_('program option'), 'option'), 'doc': ObjType(_('document'), 'doc', searchprio=-1) - } # type: Dict[str, ObjType] + } - directives = { + directives: Dict[str, Type[Directive]] = { 'program': Program, 'cmdoption': Cmdoption, # old name for backwards compatibility 'option': Cmdoption, 'envvar': EnvVar, 'glossary': Glossary, 'productionlist': ProductionList, - } # type: Dict[str, Type[Directive]] - roles = { + } + roles: Dict[str, Union[RoleFunction, XRefRole]] = { 'option': OptionXRefRole(warn_dangling=True), 'envvar': EnvVarXRefRole(), # links to tokens in grammar productions @@ -610,7 +595,7 @@ class StandardDomain(Domain): 'keyword': XRefRole(warn_dangling=True), # links to documents 'doc': XRefRole(warn_dangling=True, innernodeclass=nodes.inline), - } # type: Dict[str, Union[RoleFunction, XRefRole]] + } initial_data = { 'progoptions': {}, # (program, name) -> docname, labelid @@ -635,11 +620,12 @@ class StandardDomain(Domain): 'option': 'unknown option: %(target)s', } - enumerable_nodes = { # node_class -> (figtype, title_getter) + # node_class -> (figtype, title_getter) + enumerable_nodes: Dict[Type[Node], Tuple[str, Callable]] = { nodes.figure: ('figure', None), nodes.table: ('table', None), nodes.container: ('code-block', None), - } # type: Dict[Type[Node], Tuple[str, Callable]] + } def __init__(self, env: "BuildEnvironment") -> None: super().__init__(env) @@ -721,7 +707,7 @@ class StandardDomain(Domain): return self.data.setdefault('anonlabels', {}) # labelname -> docname, labelid def clear_doc(self, docname: str) -> None: - key = None # type: Any + key: Any = None for key, (fn, _l) in list(self.progoptions.items()): if fn == docname: del self.progoptions[key] @@ -837,11 +823,6 @@ class StandardDomain(Domain): resolver = self._resolve_doc_xref elif typ == 'option': resolver = self._resolve_option_xref - elif typ == 'citation': - warnings.warn('pending_xref(domain=std, type=citation) is deprecated: %r' % node, - RemovedInSphinx40Warning, stacklevel=2) - domain = env.get_domain('citation') - return domain.resolve_xref(env, fromdocname, builder, typ, target, node, contnode) elif typ == 'term': resolver = self._resolve_term_xref else: @@ -1012,7 +993,7 @@ class StandardDomain(Domain): def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", target: str, node: pending_xref, contnode: Element) -> List[Tuple[str, Element]]: - results = [] # type: List[Tuple[str, Element]] + results: List[Tuple[str, Element]] = [] ltarget = target.lower() # :ref: lowercases its target automatically for role in ('ref', 'option'): # do not try "keyword" res = self.resolve_xref(env, fromdocname, builder, role, @@ -1076,7 +1057,7 @@ class StandardDomain(Domain): def get_enumerable_node_type(self, node: Node) -> str: """Get type of enumerable nodes.""" - def has_child(node: Element, cls: "Type") -> bool: + def has_child(node: Element, cls: Type) -> bool: return any(isinstance(child, cls) for child in node) if isinstance(node, nodes.section): @@ -1127,18 +1108,6 @@ class StandardDomain(Domain): else: return None - def note_citations(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA - warnings.warn('StandardDomain.note_citations() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - def note_citation_refs(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA - warnings.warn('StandardDomain.note_citation_refs() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - def note_labels(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA - warnings.warn('StandardDomain.note_labels() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - def warn_missing_reference(app: "Sphinx", domain: Domain, node: pending_xref) -> bool: if (domain and domain.name != 'std') or node['reftype'] != 'ref': diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 6b2acab9f..0483dcdf9 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -10,18 +10,18 @@ import os import pickle -import warnings from collections import defaultdict from copy import copy +from datetime import datetime from os import path -from typing import Any, Callable, Dict, Generator, Iterator, List, Set, Tuple, Union, cast +from typing import (TYPE_CHECKING, Any, Callable, Dict, Generator, Iterator, List, Set, Tuple, + Union) from docutils import nodes from docutils.nodes import Node from sphinx import addnodes from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain from sphinx.environment.adapters.toctree import TocTree from sphinx.errors import BuildEnvironmentError, DocumentError, ExtensionError, SphinxError @@ -35,15 +35,14 @@ from sphinx.util.i18n import CatalogRepository, docname_to_domain from sphinx.util.nodes import is_translatable from sphinx.util.osutil import canon_path, os_path -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.builders import Builder logger = logging.getLogger(__name__) -default_settings = { +default_settings: Dict[str, Any] = { 'embed_stylesheet': False, 'cloak_email_addresses': True, 'pep_base_url': 'https://www.python.org/dev/peps/', @@ -56,7 +55,7 @@ default_settings = { 'halt_level': 5, 'file_insertion_enabled': True, 'smartquotes_locales': [], -} # type: Dict[str, Any] +} # This is increased every time an environment attribute is added # or changed to properly invalidate pickle files. @@ -75,10 +74,10 @@ CONFIG_CHANGED_REASON = { } -versioning_conditions = { +versioning_conditions: Dict[str, Union[bool, Callable]] = { 'none': False, 'text': is_translatable, -} # type: Dict[str, Union[bool, Callable]] +} class BuildEnvironment: @@ -88,24 +87,24 @@ class BuildEnvironment: transformations to resolve links to them. """ - domains = None # type: Dict[str, Domain] + domains: Dict[str, Domain] = None # --------- ENVIRONMENT INITIALIZATION ------------------------------------- def __init__(self, app: "Sphinx" = None): - self.app = None # type: Sphinx - self.doctreedir = None # type: str - self.srcdir = None # type: str - self.config = None # type: Config - self.config_status = None # type: int - self.config_status_extra = None # type: str - self.events = None # type: EventManager - self.project = None # type: Project - self.version = None # type: Dict[str, str] + self.app: Sphinx = None + self.doctreedir: str = None + self.srcdir: str = None + self.config: Config = None + self.config_status: int = None + self.config_status_extra: str = None + self.events: EventManager = None + self.project: Project = None + self.version: Dict[str, str] = None # the method of doctree versioning; see set_versioning_method - self.versioning_condition = None # type: Union[bool, Callable] - self.versioning_compare = None # type: bool + self.versioning_condition: Union[bool, Callable] = None + self.versioning_compare: bool = None # all the registered domains, set by the application self.domains = {} @@ -117,70 +116,67 @@ class BuildEnvironment: # All "docnames" here are /-separated and relative and exclude # the source suffix. - self.all_docs = {} # type: Dict[str, float] - # docname -> mtime at the time of reading - # contains all read docnames - self.dependencies = defaultdict(set) # type: Dict[str, Set[str]] - # docname -> set of dependent file - # names, relative to documentation root - self.included = defaultdict(set) # type: Dict[str, Set[str]] - # docname -> set of included file - # docnames included from other documents - self.reread_always = set() # type: Set[str] - # docnames to re-read unconditionally on - # next build + # docname -> mtime at the time of reading + # contains all read docnames + self.all_docs: Dict[str, float] = {} + # docname -> set of dependent file + # names, relative to documentation root + self.dependencies: Dict[str, Set[str]] = defaultdict(set) + # docname -> set of included file + # docnames included from other documents + self.included: Dict[str, Set[str]] = defaultdict(set) + # docnames to re-read unconditionally on next build + self.reread_always: Set[str] = set() # File metadata - self.metadata = defaultdict(dict) # type: Dict[str, Dict[str, Any]] - # docname -> dict of metadata items + # docname -> dict of metadata items + self.metadata: Dict[str, Dict[str, Any]] = defaultdict(dict) # TOC inventory - self.titles = {} # type: Dict[str, nodes.title] - # docname -> title node - self.longtitles = {} # type: Dict[str, nodes.title] - # docname -> title node; only different if - # set differently with title directive - self.tocs = {} # type: Dict[str, nodes.bullet_list] - # docname -> table of contents nodetree - self.toc_num_entries = {} # type: Dict[str, int] - # docname -> number of real entries + # docname -> title node + self.titles: Dict[str, nodes.title] = {} + # docname -> title node; only different if + # set differently with title directive + self.longtitles: Dict[str, nodes.title] = {} + # docname -> table of contents nodetree + self.tocs: Dict[str, nodes.bullet_list] = {} + # docname -> number of real entries + self.toc_num_entries: Dict[str, int] = {} # used to determine when to show the TOC # in a sidebar (don't show if it's only one item) - self.toc_secnumbers = {} # type: Dict[str, Dict[str, Tuple[int, ...]]] - # docname -> dict of sectionid -> number - self.toc_fignumbers = {} # type: Dict[str, Dict[str, Dict[str, Tuple[int, ...]]]] - # docname -> dict of figtype -> - # dict of figureid -> number - - self.toctree_includes = {} # type: Dict[str, List[str]] - # docname -> list of toctree includefiles - self.files_to_rebuild = {} # type: Dict[str, Set[str]] - # docname -> set of files - # (containing its TOCs) to rebuild too - self.glob_toctrees = set() # type: Set[str] - # docnames that have :glob: toctrees - self.numbered_toctrees = set() # type: Set[str] - # docnames that have :numbered: toctrees + # docname -> dict of sectionid -> number + self.toc_secnumbers: Dict[str, Dict[str, Tuple[int, ...]]] = {} + # docname -> dict of figtype -> dict of figureid -> number + self.toc_fignumbers: Dict[str, Dict[str, Dict[str, Tuple[int, ...]]]] = {} + + # docname -> list of toctree includefiles + self.toctree_includes: Dict[str, List[str]] = {} + # docname -> set of files (containing its TOCs) to rebuild too + self.files_to_rebuild: Dict[str, Set[str]] = {} + # docnames that have :glob: toctrees + self.glob_toctrees: Set[str] = set() + # docnames that have :numbered: toctrees + self.numbered_toctrees: Set[str] = set() # domain-specific inventories, here to be pickled - self.domaindata = {} # type: Dict[str, Dict] - # domainname -> domain-specific dict + # domainname -> domain-specific dict + self.domaindata: Dict[str, Dict] = {} # these map absolute path -> (docnames, unique filename) - self.images = FilenameUniqDict() # type: FilenameUniqDict - self.dlfiles = DownloadFiles() # type: DownloadFiles - # filename -> (set of docnames, destination) + self.images: FilenameUniqDict = FilenameUniqDict() + # filename -> (set of docnames, destination) + self.dlfiles: DownloadFiles = DownloadFiles() # the original URI for images - self.original_image_uri = {} # type: Dict[str, str] + self.original_image_uri: Dict[str, str] = {} # temporary data storage while reading a document - self.temp_data = {} # type: Dict[str, Any] + self.temp_data: Dict[str, Any] = {} # context for cross-references (e.g. current module or class) # this is similar to temp_data, but will for example be copied to # attributes of "any" cross references - self.ref_context = {} # type: Dict[str, Any] + self.ref_context: Dict[str, Any] = {} # set up environment if app: @@ -270,7 +266,7 @@ class BuildEnvironment: raise an exception if the user tries to use an environment with an incompatible versioning method. """ - condition = None # type: Union[bool, Callable] + condition: Union[bool, Callable] = None if callable(method): condition = method else: @@ -320,28 +316,13 @@ class BuildEnvironment: """ return self.project.path2doc(filename) - def doc2path(self, docname: str, base: Union[bool, str] = True, suffix: str = None) -> str: + def doc2path(self, docname: str, base: bool = True) -> str: """Return the filename for the document name. If *base* is True, return absolute path under self.srcdir. - If *base* is None, return relative path to self.srcdir. - If *base* is a path string, return absolute path under that. - If *suffix* is not None, add it instead of config.source_suffix. + If *base* is False, return relative path to self.srcdir. """ - if suffix: - warnings.warn('The suffix argument for doc2path() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - if base not in (True, False, None): - warnings.warn('The string style base argument for doc2path() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - pathname = self.project.doc2path(docname, base is True) - if suffix: - filename, _ = path.splitext(pathname) - pathname = filename + suffix - if base and base is not True: - pathname = path.join(base, pathname) # type: ignore - return pathname + return self.project.doc2path(docname, base) def relfn2path(self, filename: str, docname: str = None) -> Tuple[str, str]: """Return paths to a file referenced from a document, relative to @@ -401,8 +382,8 @@ class BuildEnvironment: # clear all files no longer present removed = set(self.all_docs) - self.found_docs - added = set() # type: Set[str] - changed = set() # type: Set[str] + added: Set[str] = set() + changed: Set[str] = set() if config_changed: # config values affect e.g. substitutions @@ -410,21 +391,28 @@ class BuildEnvironment: else: for docname in self.found_docs: if docname not in self.all_docs: + logger.debug('[build target] added %r', docname) added.add(docname) continue # if the doctree file is not there, rebuild filename = path.join(self.doctreedir, docname + '.doctree') if not path.isfile(filename): + logger.debug('[build target] changed %r', docname) changed.add(docname) continue # check the "reread always" list if docname in self.reread_always: + logger.debug('[build target] changed %r', docname) changed.add(docname) continue # check the mtime of the document mtime = self.all_docs[docname] newmtime = path.getmtime(self.doc2path(docname)) if newmtime > mtime: + logger.debug('[build target] outdated %r: %s -> %s', + docname, + datetime.utcfromtimestamp(mtime), + datetime.utcfromtimestamp(newmtime)) changed.add(docname) continue # finally, check the mtime of dependencies @@ -447,7 +435,7 @@ class BuildEnvironment: return added, changed, removed def check_dependents(self, app: "Sphinx", already: Set[str]) -> Generator[str, None, None]: - to_rewrite = [] # type: List[str] + to_rewrite: List[str] = [] for docnames in self.events.emit('env-get-updated', self): to_rewrite.extend(docnames) for docname in set(to_rewrite): @@ -610,7 +598,7 @@ class BuildEnvironment: traversed.add(subdocname) relations = {} - docnames = traverse_toctree(None, self.config.master_doc) + docnames = traverse_toctree(None, self.config.root_doc) prevdoc = None parent, docname = next(docnames) for nextparent, nextdoc in docnames: @@ -628,7 +616,7 @@ class BuildEnvironment: included = set().union(*self.included.values()) # type: ignore for docname in sorted(self.all_docs): if docname not in self.files_to_rebuild: - if docname == self.config.master_doc: + if docname == self.config.root_doc: # the master file is not included anywhere ;) continue if docname in included: @@ -643,19 +631,3 @@ class BuildEnvironment: for domain in self.domains.values(): domain.check_consistency() self.events.emit('env-check-consistency', self) - - @property - def indexentries(self) -> Dict[str, List[Tuple[str, str, str, str, str]]]: - warnings.warn('env.indexentries() is deprecated. Please use IndexDomain instead.', - RemovedInSphinx40Warning, stacklevel=2) - from sphinx.domains.index import IndexDomain - domain = cast(IndexDomain, self.get_domain('index')) - return domain.entries - - @indexentries.setter - def indexentries(self, entries: Dict[str, List[Tuple[str, str, str, str, str]]]) -> None: - warnings.warn('env.indexentries() is deprecated. Please use IndexDomain instead.', - RemovedInSphinx40Warning, stacklevel=2) - from sphinx.domains.index import IndexDomain - domain = cast(IndexDomain, self.get_domain('index')) - domain.data['entries'] = entries diff --git a/sphinx/environment/adapters/indexentries.py b/sphinx/environment/adapters/indexentries.py index e662bfe4a..5b7510143 100644 --- a/sphinx/environment/adapters/indexentries.py +++ b/sphinx/environment/adapters/indexentries.py @@ -31,7 +31,7 @@ class IndexEntries: _fixre: Pattern = re.compile(r'(.*) ([(][^()]*[)])') ) -> List[Tuple[str, List[Tuple[str, Any]]]]: """Create the real index from the collected index entries.""" - new = {} # type: Dict[str, List] + new: Dict[str, List] = {} def add_entry(word: str, subword: str, main: str, link: bool = True, dic: Dict = new, key: str = None) -> None: @@ -126,7 +126,7 @@ class IndexEntries: # (in module foo) # (in module bar) oldkey = '' - oldsubitems = None # type: Dict[str, List] + oldsubitems: Dict[str, List] = None i = 0 while i < len(newlist): key, (targets, subitems, _key) = newlist[i] diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py index 93555d172..3ee2f9ff0 100644 --- a/sphinx/environment/adapters/toctree.py +++ b/sphinx/environment/adapters/toctree.py @@ -8,7 +8,7 @@ :license: BSD, see LICENSE for details. """ -from typing import Any, Iterable, List, cast +from typing import TYPE_CHECKING, Any, Iterable, List, cast from docutils import nodes from docutils.nodes import Element, Node @@ -19,8 +19,7 @@ from sphinx.util import logging, url_re from sphinx.util.matching import Matcher from sphinx.util.nodes import clean_astext, process_only_nodes -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.builders import Builder from sphinx.environment import BuildEnvironment @@ -103,7 +102,7 @@ class TocTree: if not subnode['anchorname']: # give the whole branch a 'current' class # (useful for styling it differently) - branchnode = subnode # type: Element + branchnode: Element = subnode while branchnode: branchnode['classes'].append('current') branchnode = branchnode.parent @@ -120,7 +119,7 @@ class TocTree: ) -> List[Element]: """Return TOC entries for a toctree node.""" refs = [(e[0], e[1]) for e in toctreenode['entries']] - entries = [] # type: List[Element] + entries: List[Element] = [] for (title, ref) in refs: try: refdoc = None @@ -237,7 +236,7 @@ class TocTree: newnode = addnodes.compact_paragraph('', '') caption = toctree.attributes.get('caption') if caption: - caption_node = nodes.caption(caption, '', *[nodes.Text(caption)]) + caption_node = nodes.title(caption, '', *[nodes.Text(caption)]) caption_node.line = toctree.line caption_node.source = toctree.source caption_node.rawsource = toctree['rawcaption'] @@ -269,7 +268,7 @@ class TocTree: for p, children in self.env.toctree_includes.items(): for child in children: parent[child] = p - ancestors = [] # type: List[str] + ancestors: List[str] = [] d = docname while d in parent and d not in ancestors: ancestors.append(d) @@ -316,8 +315,8 @@ class TocTree: def get_toctree_for(self, docname: str, builder: "Builder", collapse: bool, **kwargs: Any) -> Element: """Return the global TOC nodetree.""" - doctree = self.env.get_doctree(self.env.config.master_doc) - toctrees = [] # type: List[Element] + doctree = self.env.get_doctree(self.env.config.root_doc) + toctrees: List[Element] = [] if 'includehidden' not in kwargs: kwargs['includehidden'] = True if 'maxdepth' not in kwargs or not kwargs['maxdepth']: diff --git a/sphinx/environment/collectors/__init__.py b/sphinx/environment/collectors/__init__.py index 4b7ac3472..e27091018 100644 --- a/sphinx/environment/collectors/__init__.py +++ b/sphinx/environment/collectors/__init__.py @@ -8,14 +8,13 @@ :license: BSD, see LICENSE for details. """ -from typing import Dict, List, Set +from typing import TYPE_CHECKING, Dict, List, Set from docutils import nodes from sphinx.environment import BuildEnvironment -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -28,7 +27,7 @@ class EnvironmentCollector: entries and toctrees, etc. """ - listener_ids = None # type: Dict[str, int] + listener_ids: Dict[str, int] = None def enable(self, app: "Sphinx") -> None: assert self.listener_ids is None diff --git a/sphinx/environment/collectors/asset.py b/sphinx/environment/collectors/asset.py index b8d7ae4c0..0a696aa8d 100644 --- a/sphinx/environment/collectors/asset.py +++ b/sphinx/environment/collectors/asset.py @@ -48,7 +48,7 @@ class ImageCollector(EnvironmentCollector): # choose the best image from these candidates. The special key * is # set if there is only single candidate to be used by a writer. # The special key ? is set for nonlocal URIs. - candidates = {} # type: Dict[str, str] + candidates: Dict[str, str] = {} node['candidates'] = candidates imguri = node['uri'] if imguri.startswith('data:'): @@ -94,7 +94,7 @@ class ImageCollector(EnvironmentCollector): def collect_candidates(self, env: BuildEnvironment, imgpath: str, candidates: Dict[str, str], node: Node) -> None: - globbed = {} # type: Dict[str, List[str]] + globbed: Dict[str, List[str]] = {} for filename in glob(imgpath): new_imgpath = relative_path(path.join(env.srcdir, 'dummy'), filename) diff --git a/sphinx/environment/collectors/indexentries.py b/sphinx/environment/collectors/indexentries.py deleted file mode 100644 index 351cdc2de..000000000 --- a/sphinx/environment/collectors/indexentries.py +++ /dev/null @@ -1,63 +0,0 @@ -""" - sphinx.environment.collectors.indexentries - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Index entries collector for sphinx.environment. - - :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import warnings -from typing import Any, Dict, Set - -from docutils import nodes - -from sphinx import addnodes -from sphinx.application import Sphinx -from sphinx.deprecation import RemovedInSphinx40Warning -from sphinx.environment import BuildEnvironment -from sphinx.environment.collectors import EnvironmentCollector -from sphinx.util import logging, split_index_msg - -logger = logging.getLogger(__name__) - - -class IndexEntriesCollector(EnvironmentCollector): - name = 'indices' - - def __init__(self) -> None: - warnings.warn('IndexEntriesCollector is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None: - env.indexentries.pop(docname, None) - - def merge_other(self, app: Sphinx, env: BuildEnvironment, - docnames: Set[str], other: BuildEnvironment) -> None: - for docname in docnames: - env.indexentries[docname] = other.indexentries[docname] - - def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: - docname = app.env.docname - entries = app.env.indexentries[docname] = [] - for node in doctree.traverse(addnodes.index): - try: - for entry in node['entries']: - split_index_msg(entry[0], entry[1]) - except ValueError as exc: - logger.warning(str(exc), location=node) - node.parent.remove(node) - else: - for entry in node['entries']: - entries.append(entry) - - -def setup(app: Sphinx) -> Dict[str, Any]: - app.add_env_collector(IndexEntriesCollector) - - return { - 'version': 'builtin', - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/sphinx/environment/collectors/title.py b/sphinx/environment/collectors/title.py index 5225155e8..28e967427 100644 --- a/sphinx/environment/collectors/title.py +++ b/sphinx/environment/collectors/title.py @@ -50,7 +50,7 @@ class TitleCollector(EnvironmentCollector): break else: # document has no title - titlenode += nodes.Text('<no title>') + titlenode += nodes.Text(doctree.get('title', '<no title>')) app.env.titles[app.env.docname] = titlenode app.env.longtitles[app.env.docname] = longtitlenode diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py index 772451c56..921fc83de 100644 --- a/sphinx/environment/collectors/toctree.py +++ b/sphinx/environment/collectors/toctree.py @@ -8,7 +8,7 @@ :license: BSD, see LICENSE for details. """ -from typing import Any, Dict, List, Set, Tuple, TypeVar, cast +from typing import Any, Dict, List, Set, Tuple, Type, TypeVar, cast from docutils import nodes from docutils.nodes import Element, Node @@ -22,11 +22,6 @@ from sphinx.locale import __ from sphinx.transforms import SphinxContentsFilter from sphinx.util import logging, url_re -if False: - # For type annotation - from typing import Type # for python3.5.1 - - N = TypeVar('N') logger = logging.getLogger(__name__) @@ -67,9 +62,9 @@ class TocTreeCollector(EnvironmentCollector): docname = app.env.docname numentries = [0] # nonlocal again... - def traverse_in_section(node: Element, cls: "Type[N]") -> List[N]: + def traverse_in_section(node: Element, cls: Type[N]) -> List[N]: """Like traverse(), but stay within the same section.""" - result = [] # type: List[N] + result: List[N] = [] if isinstance(node, cls): result.append(node) for child in node.children: @@ -80,7 +75,7 @@ class TocTreeCollector(EnvironmentCollector): return result def build_toc(node: Element, depth: int = 1) -> nodes.bullet_list: - entries = [] # type: List[Element] + entries: List[Element] = [] for sectionnode in node: # find all toctree nodes in this section and add them # to the toc (just copying the toctree node which is then @@ -105,7 +100,7 @@ class TocTreeCollector(EnvironmentCollector): '', '', internal=True, refuri=docname, anchorname=anchorname, *nodetext) para = addnodes.compact_paragraph('', '', reference) - item = nodes.list_item('', para) # type: Element + item: Element = nodes.list_item('', para) sub_item = build_toc(sectionnode, depth + 1) if sub_item: item += sub_item @@ -141,7 +136,7 @@ class TocTreeCollector(EnvironmentCollector): # a list of all docnames whose section numbers changed rewrite_needed = [] - assigned = set() # type: Set[str] + assigned: Set[str] = set() old_secnumbers = env.toc_secnumbers env.toc_secnumbers = {} @@ -191,7 +186,7 @@ class TocTreeCollector(EnvironmentCollector): '(nested numbered toctree?)'), ref, location=toctreenode, type='toc', subtype='secnum') elif ref in env.tocs: - secnums = {} # type: Dict[str, Tuple[int, ...]] + secnums: Dict[str, Tuple[int, ...]] = {} env.toc_secnumbers[ref] = secnums assigned.add(ref) _walk_toc(env.tocs[ref], secnums, depth, env.titles.get(ref)) @@ -215,10 +210,10 @@ class TocTreeCollector(EnvironmentCollector): rewrite_needed = [] - assigned = set() # type: Set[str] + assigned: Set[str] = set() old_fignumbers = env.toc_fignumbers env.toc_fignumbers = {} - fignum_counter = {} # type: Dict[str, Dict[Tuple[int, ...], int]] + fignum_counter: Dict[str, Dict[Tuple[int, ...], int]] = {} def get_figtype(node: Node) -> str: for domain in env.domains.values(): @@ -286,7 +281,7 @@ class TocTreeCollector(EnvironmentCollector): _walk_doctree(docname, doctree, secnum) if env.config.numfig: - _walk_doc(env.config.master_doc, tuple()) + _walk_doc(env.config.root_doc, tuple()) for docname, fignums in env.toc_fignumbers.items(): if fignums != old_fignumbers.get(docname): rewrite_needed.append(docname) diff --git a/sphinx/events.py b/sphinx/events.py index e3a3b964f..634fdc6ca 100644 --- a/sphinx/events.py +++ b/sphinx/events.py @@ -10,29 +10,26 @@ :license: BSD, see LICENSE for details. """ -import warnings from collections import defaultdict from operator import attrgetter -from typing import Any, Callable, Dict, List, NamedTuple, Tuple +from typing import TYPE_CHECKING, Any, Callable, Dict, List, NamedTuple, Tuple, Type -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.errors import ExtensionError, SphinxError from sphinx.locale import __ from sphinx.util import logging from sphinx.util.inspect import safe_getattr -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.application import Sphinx logger = logging.getLogger(__name__) -EventListener = NamedTuple('EventListener', [('id', int), - ('handler', Callable), - ('priority', int)]) + +class EventListener(NamedTuple): + id: int + handler: Callable + priority: int # List of all known core events. Maps name to arguments description. @@ -58,13 +55,10 @@ core_events = { class EventManager: """Event manager for Sphinx.""" - def __init__(self, app: "Sphinx" = None) -> None: - if app is None: - warnings.warn('app argument is required for EventManager.', - RemovedInSphinx40Warning) + def __init__(self, app: "Sphinx") -> None: self.app = app self.events = core_events.copy() - self.listeners = defaultdict(list) # type: Dict[str, List[EventListener]] + self.listeners: Dict[str, List[EventListener]] = defaultdict(list) self.next_listener_id = 0 def add(self, name: str) -> None: @@ -91,7 +85,7 @@ class EventManager: listeners.remove(listener) def emit(self, name: str, *args: Any, - allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> List: + allowed_exceptions: Tuple[Type[Exception], ...] = ()) -> List: """Emit a Sphinx event.""" try: logger.debug('[app] emitting event: %r%s', name, repr(args)[:100]) @@ -104,11 +98,7 @@ class EventManager: listeners = sorted(self.listeners[name], key=attrgetter("priority")) for listener in listeners: try: - if self.app is None: - # for compatibility; RemovedInSphinx40Warning - results.append(listener.handler(*args)) - else: - results.append(listener.handler(self.app, *args)) + results.append(listener.handler(self.app, *args)) except allowed_exceptions: # pass through the errors specified as *allowed_exceptions* raise @@ -121,7 +111,7 @@ class EventManager: return results def emit_firstresult(self, name: str, *args: Any, - allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> Any: + allowed_exceptions: Tuple[Type[Exception], ...] = ()) -> Any: """Emit a Sphinx event and returns first result. This returns the result of the first handler that doesn't return ``None``. diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index ebbdadbe8..d9a9648c6 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -19,7 +19,6 @@ import glob import locale import os import sys -import warnings from copy import copy from fnmatch import fnmatch from importlib.machinery import EXTENSION_SUFFIXES @@ -29,9 +28,7 @@ from typing import Any, Generator, List, Tuple import sphinx.locale from sphinx import __display_version__, package_dir from sphinx.cmd.quickstart import EXTENSIONS -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.locale import __ -from sphinx.util import rst from sphinx.util.osutil import FileAvoidWrite, ensuredir from sphinx.util.template import ReSTRenderer @@ -51,20 +48,6 @@ PY_SUFFIXES = ('.py', '.pyx') + tuple(EXTENSION_SUFFIXES) template_dir = path.join(package_dir, 'templates', 'apidoc') -def makename(package: str, module: str) -> str: - """Join package and module with a dot.""" - warnings.warn('makename() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - # Both package and module can be None/empty. - if package: - name = package - if module: - name += '.' + module - else: - name = module - return name - - def is_initpy(filename: str) -> bool: """Check *filename* is __init__ file or not.""" basename = path.basename(filename) @@ -109,26 +92,6 @@ def write_file(name: str, text: str, opts: Any) -> None: f.write(text) -def format_heading(level: int, text: str, escape: bool = True) -> str: - """Create a heading of <level> [1, 2 or 3 supported].""" - warnings.warn('format_warning() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - if escape: - text = rst.escape(text) - underlining = ['=', '-', '~', ][level - 1] * len(text) - return '%s\n%s\n\n' % (text, underlining) - - -def format_directive(module: str, package: str = None) -> str: - """Create the automodule directive and add the options.""" - warnings.warn('format_directive() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - directive = '.. automodule:: %s\n' % module_join(package, module) - for option in OPTIONS: - directive += ' :%s:\n' % option - return directive - - def create_module_file(package: str, basename: str, opts: Any, user_template_dir: str = None) -> None: """Build the text of the file and write the file.""" @@ -206,33 +169,6 @@ def create_modules_toc_file(modules: List[str], opts: Any, name: str = 'modules' write_file(name, text, opts) -def shall_skip(module: str, opts: Any, excludes: List[str] = []) -> bool: - """Check if we want to skip this module.""" - warnings.warn('shall_skip() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - # skip if the file doesn't exist and not using implicit namespaces - if not opts.implicit_namespaces and not path.exists(module): - return True - - # Are we a package (here defined as __init__.py, not the folder in itself) - if is_initpy(module): - # Yes, check if we have any non-excluded modules at all here - all_skipped = True - basemodule = path.dirname(module) - for submodule in glob.glob(path.join(basemodule, '*.py')): - if not is_excluded(path.join(basemodule, submodule), excludes): - # There's a non-excluded module here, we won't skip - all_skipped = False - if all_skipped: - return True - - # skip if it has a "private" name and this is selected - filename = path.basename(module) - if is_initpy(filename) and filename.startswith('_') and not opts.includeprivate: - return True - return False - - def is_skipped_package(dirname: str, opts: Any, excludes: List[str] = []) -> bool: """Check if we want to skip this module.""" if not path.isdir(dirname): @@ -279,7 +215,7 @@ def walk(rootpath: str, excludes: List[str], opts: Any # remove hidden ('.') and private ('_') directories, as well as # excluded dirs if includeprivate: - exclude_prefixes = ('.',) # type: Tuple[str, ...] + exclude_prefixes: Tuple[str, ...] = ('.',) else: exclude_prefixes = ('.', '_') @@ -536,13 +472,6 @@ def main(argv: List[str] = sys.argv[1:]) -> int: return 0 -deprecated_alias('sphinx.ext.apidoc', - { - 'INITPY': '__init__.py', - }, - RemovedInSphinx40Warning) - - # So program can be started with "python -m sphinx.apidoc ..." if __name__ == "__main__": main() diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 3ae6dff75..5a0a8ca10 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -14,16 +14,15 @@ import re import warnings from inspect import Parameter, Signature from types import ModuleType -from typing import (Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, - TypeVar, Union) +from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Sequence, + Set, Tuple, Type, TypeVar, Union) from docutils.statemachine import StringList import sphinx from sphinx.application import Sphinx from sphinx.config import ENUM, Config -from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warning, - RemovedInSphinx60Warning) +from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning from sphinx.environment import BuildEnvironment from sphinx.ext.autodoc.importer import (get_class_members, get_object_members, import_module, import_object) @@ -34,13 +33,10 @@ from sphinx.util import inspect, logging from sphinx.util.docstrings import extract_metadata, prepare_docstring from sphinx.util.inspect import (evaluate_signature, getdoc, object_description, safe_getattr, stringify_signature) -from sphinx.util.typing import get_type_hints, restify +from sphinx.util.typing import OptionSpec, get_type_hints, restify from sphinx.util.typing import stringify as stringify_typehint -if False: - # For type annotation - from typing import Type # NOQA # for python3.5.1 - +if TYPE_CHECKING: from sphinx.ext.autodoc.directive import DocumenterBridge @@ -313,7 +309,9 @@ class Documenter: #: true if the generated content may contain titles titles_allowed = False - option_spec = {'noindex': bool_option} # type: Dict[str, Callable] + option_spec: OptionSpec = { + 'noindex': bool_option + } def get_attr(self, obj: Any, name: str, *defargs: Any) -> Any: """getattr() override for types such as Zope interfaces.""" @@ -327,31 +325,31 @@ class Documenter: def __init__(self, directive: "DocumenterBridge", name: str, indent: str = '') -> None: self.directive = directive - self.config = directive.env.config - self.env = directive.env # type: BuildEnvironment + self.config: Config = directive.env.config + self.env: BuildEnvironment = directive.env self.options = directive.genopt self.name = name self.indent = indent # the module and object path within the module, and the fully # qualified name (all set after resolve_name succeeds) - self.modname = None # type: str - self.module = None # type: ModuleType - self.objpath = None # type: List[str] - self.fullname = None # type: str + self.modname: str = None + self.module: ModuleType = None + self.objpath: List[str] = None + self.fullname: str = None # extra signature items (arguments and return annotation, # also set after resolve_name succeeds) - self.args = None # type: str - self.retann = None # type: str + self.args: str = None + self.retann: str = None # the object to document (set after import_object succeeds) - self.object = None # type: Any - self.object_name = None # type: str + self.object: Any = None + self.object_name: str = None # the parent/owner of the object to document - self.parent = None # type: Any + self.parent: Any = None # the module analyzer to get at attribute docs, or None - self.analyzer = None # type: ModuleAnalyzer + self.analyzer: ModuleAnalyzer = None @property - def documenters(self) -> Dict[str, "Type[Documenter]"]: + def documenters(self) -> Dict[str, Type["Documenter"]]: """Returns registered Documenter classes""" return self.env.app.registry.documenters @@ -540,16 +538,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) -> Optional[List[List[str]]]: + def get_doc(self, 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__, - RemovedInSphinx40Warning, stacklevel=2) if ignore is not None: warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." % self.__class__.__name__, @@ -828,7 +822,7 @@ class Documenter: members_check_module, members = self.get_object_members(want_all) # document non-skipped members - memberdocumenters = [] # type: List[Tuple[Documenter, bool]] + memberdocumenters: List[Tuple[Documenter, bool]] = [] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] @@ -910,7 +904,7 @@ class Documenter: # This is used for situations where you have a module that collects the # functions and classes of internal submodules. guess_modname = self.get_real_modname() - self.real_modname = real_modname or guess_modname + self.real_modname: str = real_modname or guess_modname # try to also get a source code analyzer for attribute docs try: @@ -924,15 +918,15 @@ class Documenter: self.analyzer = None # at least add the module.__file__ as a dependency if hasattr(self.module, '__file__') and self.module.__file__: - self.directive.filename_set.add(self.module.__file__) + self.directive.record_dependencies.add(self.module.__file__) else: - self.directive.filename_set.add(self.analyzer.srcname) + self.directive.record_dependencies.add(self.analyzer.srcname) if self.real_modname != guess_modname: # Add module to dependency list if target object is defined in other module. try: analyzer = ModuleAnalyzer.for_module(guess_modname) - self.directive.filename_set.add(analyzer.srcname) + self.directive.record_dependencies.add(analyzer.srcname) except PycodeError: pass @@ -978,7 +972,7 @@ class ModuleDocumenter(Documenter): content_indent = '' titles_allowed = True - option_spec = { + option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'synopsis': identity, @@ -986,12 +980,12 @@ class ModuleDocumenter(Documenter): 'member-order': member_order_option, 'exclude-members': exclude_members_option, 'private-members': members_option, 'special-members': members_option, 'imported-members': bool_option, 'ignore-module-all': bool_option - } # type: Dict[str, Callable] + } def __init__(self, *args: Any) -> None: super().__init__(*args) merge_members_option(self.options) - self.__all__ = None # type: Optional[Sequence[str]] + self.__all__: Optional[Sequence[str]] = None @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any @@ -1048,7 +1042,7 @@ class ModuleDocumenter(Documenter): else: attr_docs = {} - members = {} # type: Dict[str, ObjectMember] + members: Dict[str, ObjectMember] = {} for name in dir(self.object): try: value = safe_getattr(self.object, name, None) @@ -1103,7 +1097,7 @@ class ModuleDocumenter(Documenter): # Sort by __all__ def keyfunc(entry: Tuple[Documenter, bool]) -> int: name = entry[0].name.split('::')[1] - if name in self.__all__: + if self.__all__ and name in self.__all__: return self.__all__.index(name) else: return len(self.__all__) @@ -1173,15 +1167,10 @@ class DocstringSignatureMixin: Mixin for FunctionDocumenter and MethodDocumenter to provide the feature of reading the signature from the docstring. """ - _new_docstrings = None # type: List[List[str]] - _signatures = None # type: List[str] - - def _find_signature(self, encoding: str = None) -> Tuple[str, str]: - if encoding is not None: - warnings.warn("The 'encoding' argument to autodoc.%s._find_signature() is " - "deprecated." % self.__class__.__name__, - RemovedInSphinx40Warning, stacklevel=2) + _new_docstrings: List[List[str]] = None + _signatures: List[str] = None + def _find_signature(self) -> Tuple[str, str]: # candidates of the object name valid_names = [self.objpath[-1]] # type: ignore if isinstance(self, ClassDocumenter): @@ -1202,20 +1191,17 @@ class DocstringSignatureMixin: break if line.endswith('\\'): - multiline = True line = line.rstrip('\\').rstrip() - else: - multiline = False # match first line of docstring against signature RE match = py_ext_sig_re.match(line) if not match: - continue + break exmod, path, base, args, retann = match.groups() # the base name must match ours if base not in valid_names: - continue + break # re-prepare docstring to ignore more leading indentation tab_width = self.directive.state.document.settings.tab_width # type: ignore @@ -1229,27 +1215,16 @@ class DocstringSignatureMixin: # subsequent signatures self._signatures.append("(%s) -> %s" % (args, retann)) - if multiline: - # the signature have multiple signatures on docstring - continue - else: - # don't look any further - break - if result: # finish the loop when signature found break return result - 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) + def get_doc(self, ignore: int = None) -> List[List[str]]: if self._new_docstrings is not None: return self._new_docstrings - return super().get_doc(None, ignore) # type: ignore + return super().get_doc(ignore) # type: ignore def format_signature(self, **kwargs: Any) -> str: if self.args is None and self.config.autodoc_docstring_signature: # type: ignore @@ -1436,16 +1411,16 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: """ objtype = 'class' member_order = 20 - option_spec = { + option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'member-order': member_order_option, 'exclude-members': exclude_members_option, 'private-members': members_option, 'special-members': members_option, - } # type: Dict[str, Callable] + } - _signature_class = None # type: Any - _signature_method_name = None # type: str + _signature_class: Any = None + _signature_method_name: str = None def __init__(self, *args: Any) -> None: super().__init__(*args) @@ -1605,6 +1580,20 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: return [] + def get_canonical_fullname(self) -> Optional[str]: + __modname__ = safe_getattr(self.object, '__module__', self.modname) + __qualname__ = safe_getattr(self.object, '__qualname__', None) + if __qualname__ is None: + __qualname__ = safe_getattr(self.object, '__name__', None) + if __qualname__ and '<locals>' in __qualname__: + # No valid qualname found if the object is defined as locals + __qualname__ = None + + if __modname__ and __qualname__: + return '.'.join([__modname__, __qualname__]) + else: + return None + def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() @@ -1615,6 +1604,10 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: self.add_line(' :final:', sourcename) + canonical_fullname = self.get_canonical_fullname() + if not self.doc_as_attr and canonical_fullname and self.fullname != canonical_fullname: + self.add_line(' :canonical: %s' % canonical_fullname, sourcename) + # add inheritance info, if wanted if not self.doc_as_attr and self.options.show_inheritance: sourcename = self.get_sourcename() @@ -1649,11 +1642,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: 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) + def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: if self.doc_as_attr: # Don't show the docstring of the class when it is an alias. return None @@ -1746,12 +1735,12 @@ class ExceptionDocumenter(ClassDocumenter): 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] + config: Config = None + env: BuildEnvironment = None + modname: str = None + parent: Any = None + object: Any = None + objpath: List[str] = None def should_suppress_directive_header(self) -> bool: """Check directive header should be suppressed.""" @@ -1814,7 +1803,7 @@ class TypeVarMixin(DataDocumenterMixinBase): 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]]]: + def get_doc(self, 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__, @@ -1833,6 +1822,8 @@ class TypeVarMixin(DataDocumenterMixinBase): attrs = [repr(self.object.__name__)] for constraint in self.object.__constraints__: attrs.append(stringify_typehint(constraint)) + if self.object.__bound__: + attrs.append(r"bound=\ " + restify(self.object.__bound__)) if self.object.__covariant__: attrs.append("covariant=True") if self.object.__contravariant__: @@ -1878,11 +1869,11 @@ class UninitializedGlobalVariableMixin(DataDocumenterMixinBase): 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]]]: + def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: if self.object is UNINITIALIZED_ATTR: return [] else: - return super().get_doc(encoding, ignore) # type: ignore + return super().get_doc(ignore) # type: ignore class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, @@ -1893,7 +1884,7 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, objtype = 'data' member_order = 40 priority = -10 - option_spec = dict(ModuleLevelDocumenter.option_spec) + option_spec: OptionSpec = dict(ModuleLevelDocumenter.option_spec) option_spec["annotation"] = annotation_option option_spec["no-value"] = bool_option @@ -1977,13 +1968,13 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, return None - def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: + def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: # Check the variable has a docstring-comment comment = self.get_module_comment(self.objpath[-1]) if comment: return [comment] else: - return super().get_doc(encoding, ignore) + return super().get_doc(ignore) def add_content(self, more_content: Optional[StringList], no_docstring: bool = False ) -> None: @@ -2204,13 +2195,13 @@ class NonDataDescriptorMixin(DataDocumenterMixinBase): 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]]]: + def get_doc(self, 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 + return super().get_doc(ignore) # type: ignore class SlotsMixin(DataDocumenterMixinBase): @@ -2243,7 +2234,7 @@ class SlotsMixin(DataDocumenterMixinBase): else: return super().should_suppress_directive_header() - def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: if self.object is SLOTSATTR: try: __slots__ = inspect.getslots(self.parent) @@ -2257,7 +2248,7 @@ class SlotsMixin(DataDocumenterMixinBase): (self.parent.__qualname__, exc), type='autodoc') return [] else: - return super().get_doc(encoding, ignore) # type: ignore + return super().get_doc(ignore) # type: ignore class RuntimeInstanceAttributeMixin(DataDocumenterMixinBase): @@ -2361,11 +2352,11 @@ class UninitializedInstanceAttributeMixin(DataDocumenterMixinBase): 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]]]: + def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: if self.object is UNINITIALIZED_ATTR: return None else: - return super().get_doc(encoding, ignore) # type: ignore + return super().get_doc(ignore) # type: ignore class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: ignore @@ -2377,7 +2368,7 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: """ objtype = 'attribute' member_order = 60 - option_spec = dict(ModuleLevelDocumenter.option_spec) + option_spec: OptionSpec = dict(ModuleLevelDocumenter.option_spec) option_spec["annotation"] = annotation_option option_spec["no-value"] = bool_option @@ -2512,7 +2503,7 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: return None - def get_doc(self, encoding: str = None, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self, 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: @@ -2524,7 +2515,7 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: # ref: https://github.com/sphinx-doc/sphinx/issues/7805 orig = self.config.autodoc_inherit_docstrings self.config.autodoc_inherit_docstrings = False # type: ignore - return super().get_doc(encoding, ignore) + return super().get_doc(ignore) finally: self.config.autodoc_inherit_docstrings = orig # type: ignore @@ -2545,7 +2536,6 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # Specialized Documenter subclass for properties. """ objtype = 'property' - directivetype = 'method' member_order = 60 # before AttributeDocumenter @@ -2568,7 +2558,20 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # sourcename = self.get_sourcename() if inspect.isabstractmethod(self.object): self.add_line(' :abstractmethod:', sourcename) - self.add_line(' :property:', sourcename) + + if safe_getattr(self.object, 'fget', None): + try: + signature = inspect.signature(self.object.fget, + type_aliases=self.config.autodoc_type_aliases) + if signature.return_annotation is not Parameter.empty: + objrepr = stringify_typehint(signature.return_annotation) + self.add_line(' :type: ' + objrepr, sourcename) + except TypeError as exc: + logger.warning(__("Failed to get a function signature for %s: %s"), + self.fullname, exc) + return None + except ValueError: + raise class NewTypeAttributeDocumenter(AttributeDocumenter): @@ -2589,7 +2592,7 @@ class NewTypeAttributeDocumenter(AttributeDocumenter): return not isinstance(parent, ModuleDocumenter) and inspect.isNewType(member) -def get_documenters(app: Sphinx) -> Dict[str, "Type[Documenter]"]: +def get_documenters(app: Sphinx) -> Dict[str, Type[Documenter]]: """Returns registered Documenter classes""" warnings.warn("get_documenters() is deprecated.", RemovedInSphinx50Warning, stacklevel=2) return app.registry.documenters @@ -2643,6 +2646,8 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('autodoc_mock_imports', [], True) app.add_config_value('autodoc_typehints', "signature", True, ENUM("signature", "description", "none")) + app.add_config_value('autodoc_typehints_description_target', 'all', True, + ENUM('all', 'documented')) app.add_config_value('autodoc_type_aliases', {}, True) app.add_config_value('autodoc_warningiserror', True, True) app.add_config_value('autodoc_inherit_docstrings', True, True) @@ -2653,6 +2658,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('config-inited', migrate_autodoc_member_order, priority=800) + app.setup_extension('sphinx.ext.autodoc.preserve_defaults') app.setup_extension('sphinx.ext.autodoc.type_comment') app.setup_extension('sphinx.ext.autodoc.typehints') diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py index c932c6f9f..c58d0c411 100644 --- a/sphinx/ext/autodoc/directive.py +++ b/sphinx/ext/autodoc/directive.py @@ -7,27 +7,22 @@ """ import warnings -from typing import Any, Callable, Dict, List, Set +from typing import Any, Callable, Dict, List, Set, Type from docutils import nodes from docutils.nodes import Element, Node -from docutils.parsers.rst.states import RSTState, Struct +from docutils.parsers.rst.states import RSTState from docutils.statemachine import StringList from docutils.utils import Reporter, assemble_option_dict from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning from sphinx.environment import BuildEnvironment from sphinx.ext.autodoc import Documenter, Options from sphinx.util import logging from sphinx.util.docutils import SphinxDirective, switch_source_input from sphinx.util.nodes import nested_parse_with_titles -if False: - # For type annotation - from typing import Type # for python3.5.1 - - logger = logging.getLogger(__name__) @@ -56,35 +51,35 @@ class DocumenterBridge: """A parameters container for Documenters.""" def __init__(self, env: BuildEnvironment, reporter: Reporter, options: Options, - lineno: int, state: Any = None) -> None: + lineno: int, state: Any) -> None: self.env = env self._reporter = reporter self.genopt = options self.lineno = lineno - self.filename_set = set() # type: Set[str] + self.record_dependencies: Set[str] = set() self.result = StringList() - - if state: - self.state = state - else: - # create fake object for self.state.document.settings.tab_width - warnings.warn('DocumenterBridge requires a state object on instantiation.', - RemovedInSphinx40Warning, stacklevel=2) - settings = Struct(tab_width=8) - document = Struct(settings=settings) - self.state = Struct(document=document) + self.state = state def warn(self, msg: str) -> None: + warnings.warn('DocumenterBridge.warn is deprecated. Plase use sphinx.util.logging ' + 'module instead.', + RemovedInSphinx60Warning, stacklevel=2) logger.warning(msg, location=(self.env.docname, self.lineno)) @property + def filename_set(self) -> Set: + warnings.warn('DocumenterBridge.filename_set is deprecated.', + RemovedInSphinx60Warning, stacklevel=2) + return self.record_dependencies + + @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 +def process_documenter_options(documenter: Type[Documenter], config: Config, options: Dict ) -> Options: """Recognize options of Documenter from user input.""" for name in AUTODOC_DEFAULT_OPTIONS: @@ -115,7 +110,7 @@ def parse_generated_content(state: RSTState, content: StringList, documenter: Do """Parse a generated content by Documenter.""" with switch_source_input(state, content): if documenter.titles_allowed: - node = nodes.section() # type: Element + node: Element = nodes.section() # necessary so that the child nodes get the right source/line set node.document = state.document nested_parse_with_titles(state, content, node) @@ -172,7 +167,7 @@ class AutodocDirective(SphinxDirective): # record all filenames as dependencies -- this will at least # partially make automatic invalidation possible - for fn in params.filename_set: + for fn in params.record_dependencies: self.state.document.settings.record_dependencies.add(fn) result = parse_generated_content(self.state, params.result, documenter) diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index b792b1a5f..ebb60b38b 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -11,10 +11,9 @@ import importlib import traceback import warnings -from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tuple +from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple -from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warning, - deprecated_alias) +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.ext.autodoc.mock import ismock, undecorate from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import logging @@ -148,7 +147,7 @@ def get_module_members(module: Any) -> List[Tuple[str, Any]]: warnings.warn('sphinx.ext.autodoc.importer.get_module_members() is deprecated.', RemovedInSphinx50Warning) - members = {} # type: Dict[str, Tuple[str, Any]] + members: Dict[str, Tuple[str, Any]] = {} for name in dir(module): try: value = safe_getattr(module, name, None) @@ -164,21 +163,10 @@ def get_module_members(module: Any) -> List[Tuple[str, Any]]: return sorted(list(members.values())) -Attribute = NamedTuple('Attribute', [('name', str), - ('directly_defined', bool), - ('value', Any)]) - - -def _getmro(obj: Any) -> Tuple["Type", ...]: - warnings.warn('sphinx.ext.autodoc.importer._getmro() is deprecated.', - RemovedInSphinx40Warning) - return getmro(obj) - - -def _getannotations(obj: Any) -> Mapping[str, Any]: - warnings.warn('sphinx.ext.autodoc.importer._getannotations() is deprecated.', - RemovedInSphinx40Warning) - return getannotations(obj) +class Attribute(NamedTuple): + name: str + directly_defined: bool + value: Any def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, @@ -189,7 +177,7 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, # the members directly defined in the class obj_dict = attrgetter(subject, '__dict__', {}) - members = {} # type: Dict[str, Attribute] + members: Dict[str, Attribute] = {} # enum members if isenumclass(subject): @@ -250,7 +238,7 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable # the members directly defined in the class obj_dict = attrgetter(subject, '__dict__', {}) - members = {} # type: Dict[str, ObjectMember] + members: Dict[str, ObjectMember] = {} # enum members if isenumclass(subject): @@ -327,24 +315,3 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable pass return members - - -from sphinx.ext.autodoc.mock import (MockFinder, MockLoader, _MockModule, _MockObject, # NOQA - mock) - -deprecated_alias('sphinx.ext.autodoc.importer', - { - '_MockModule': _MockModule, - '_MockObject': _MockObject, - 'MockFinder': MockFinder, - 'MockLoader': MockLoader, - 'mock': mock, - }, - RemovedInSphinx40Warning, - { - '_MockModule': 'sphinx.ext.autodoc.mock._MockModule', - '_MockObject': 'sphinx.ext.autodoc.mock._MockObject', - 'MockFinder': 'sphinx.ext.autodoc.mock.MockFinder', - 'MockLoader': 'sphinx.ext.autodoc.mock.MockLoader', - 'mock': 'sphinx.ext.autodoc.mock.mock', - }) diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index d3f4291c2..b562f47fd 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -14,7 +14,7 @@ import sys from importlib.abc import Loader, MetaPathFinder from importlib.machinery import ModuleSpec from types import ModuleType -from typing import Any, Generator, Iterator, List, Sequence, Tuple, Union +from typing import Any, Generator, Iterator, List, Optional, Sequence, Tuple, Union from sphinx.util import logging from sphinx.util.inspect import safe_getattr @@ -27,7 +27,7 @@ class _MockObject: __display_name__ = '_MockObject' __sphinx_mock__ = True - __sphinx_decorator_args__ = () # type: Tuple[Any, ...] + __sphinx_decorator_args__: Tuple[Any, ...] = () def __new__(cls, *args: Any, **kwargs: Any) -> Any: if len(args) == 3 and isinstance(args[1], tuple): @@ -86,8 +86,8 @@ class _MockModule(ModuleType): def __init__(self, name: str) -> None: super().__init__(name) - self.__all__ = [] # type: List[str] - self.__path__ = [] # type: List[str] + self.__all__: List[str] = [] + self.__path__: List[str] = [] def __getattr__(self, name: str) -> _MockObject: return _make_subclass(name, self.__name__)() @@ -118,10 +118,10 @@ class MockFinder(MetaPathFinder): super().__init__() self.modnames = modnames self.loader = MockLoader(self) - self.mocked_modules = [] # type: List[str] + self.mocked_modules: List[str] = [] - def find_spec(self, fullname: str, path: Sequence[Union[bytes, str]], - target: ModuleType = None) -> ModuleSpec: + def find_spec(self, fullname: str, path: Optional[Sequence[Union[bytes, str]]], + target: ModuleType = None) -> Optional[ModuleSpec]: for modname in self.modnames: # check if fullname is (or is a descendant of) one of our targets if modname == fullname or fullname.startswith(modname + '.'): diff --git a/sphinx/ext/autodoc/preserve_defaults.py b/sphinx/ext/autodoc/preserve_defaults.py new file mode 100644 index 000000000..3d859fe8e --- /dev/null +++ b/sphinx/ext/autodoc/preserve_defaults.py @@ -0,0 +1,88 @@ +""" + sphinx.ext.autodoc.preserve_defaults + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Preserve the default argument values of function signatures in source code + and keep them not evaluated for readability. + + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import ast +import inspect +from typing import Any, Dict + +from sphinx.application import Sphinx +from sphinx.locale import __ +from sphinx.pycode.ast import parse as ast_parse +from sphinx.pycode.ast import unparse as ast_unparse +from sphinx.util import logging + +logger = logging.getLogger(__name__) + + +class DefaultValue: + def __init__(self, name: str) -> None: + self.name = name + + def __repr__(self) -> str: + return self.name + + +def get_function_def(obj: Any) -> ast.FunctionDef: + """Get FunctionDef object from living object. + This tries to parse original code for living object and returns + AST node for given *obj*. + """ + try: + source = inspect.getsource(obj) + if source.startswith((' ', r'\t')): + # subject is placed inside class or block. To read its docstring, + # this adds if-block before the declaration. + module = ast_parse('if True:\n' + source) + return module.body[0].body[0] # type: ignore + else: + module = ast_parse(source) + return module.body[0] # type: ignore + except (OSError, TypeError): # failed to load source code + return None + + +def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None: + """Update defvalue info of *obj* using type_comments.""" + if not app.config.autodoc_preserve_defaults: + return + + try: + function = get_function_def(obj) + if function.args.defaults or function.args.kw_defaults: + sig = inspect.signature(obj) + defaults = list(function.args.defaults) + kw_defaults = list(function.args.kw_defaults) + parameters = list(sig.parameters.values()) + for i, param in enumerate(parameters): + if param.default is not param.empty: + if param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD): + value = DefaultValue(ast_unparse(defaults.pop(0))) # type: ignore + parameters[i] = param.replace(default=value) + else: + value = DefaultValue(ast_unparse(kw_defaults.pop(0))) # type: ignore + parameters[i] = param.replace(default=value) + sig = sig.replace(parameters=parameters) + obj.__signature__ = sig + except (AttributeError, TypeError): + # failed to update signature (ex. built-in or extension types) + pass + except NotImplementedError as exc: # failed to ast.unparse() + logger.warning(__("Failed to parse a default argument value for %r: %s"), obj, exc) + + +def setup(app: Sphinx) -> Dict[str, Any]: + app.add_config_value('autodoc_preserve_defaults', False, True) + app.connect('autodoc-before-process-signature', update_defvalue) + + return { + 'version': '1.0', + 'parallel_read_safe': True + } diff --git a/sphinx/ext/autodoc/typehints.py b/sphinx/ext/autodoc/typehints.py index 533b71e42..1cc2abd09 100644 --- a/sphinx/ext/autodoc/typehints.py +++ b/sphinx/ext/autodoc/typehints.py @@ -10,7 +10,7 @@ import re from collections import OrderedDict -from typing import Any, Dict, Iterable, cast +from typing import Any, Dict, Iterable, Set, cast from docutils import nodes from docutils.nodes import Element @@ -42,8 +42,6 @@ def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element return if app.config.autodoc_typehints != 'description': return - if objtype == 'class' and app.config.autoclass_content not in ('init', 'both'): - return try: signature = cast(addnodes.desc_signature, contentnode.parent[0]) @@ -63,7 +61,10 @@ def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element field_lists.append(field_list) for field_list in field_lists: - modify_field_list(field_list, annotations[fullname]) + if app.config.autodoc_typehints_description_target == "all": + modify_field_list(field_list, annotations[fullname]) + else: + augment_descriptions_with_types(field_list, annotations[fullname]) def insert_field_list(node: Element) -> nodes.field_list: @@ -80,7 +81,7 @@ def insert_field_list(node: Element) -> nodes.field_list: def modify_field_list(node: nodes.field_list, annotations: Dict[str, str]) -> None: - arguments = {} # type: Dict[str, Dict[str, bool]] + arguments: Dict[str, Dict[str, bool]] = {} fields = cast(Iterable[nodes.field], node) for field in fields: field_name = field[0].astext() @@ -126,6 +127,52 @@ def modify_field_list(node: nodes.field_list, annotations: Dict[str, str]) -> No node += field +def augment_descriptions_with_types( + node: nodes.field_list, + annotations: Dict[str, str], +) -> None: + fields = cast(Iterable[nodes.field], node) + has_description = set() # type: Set[str] + has_type = set() # type: Set[str] + for field in fields: + field_name = field[0].astext() + parts = re.split(' +', field_name) + if parts[0] == 'param': + if len(parts) == 2: + # :param xxx: + has_description.add(parts[1]) + elif len(parts) > 2: + # :param xxx yyy: + name = ' '.join(parts[2:]) + has_description.add(name) + has_type.add(name) + elif parts[0] == 'type': + name = ' '.join(parts[1:]) + has_type.add(name) + elif parts[0] == 'return': + has_description.add('return') + elif parts[0] == 'rtype': + has_type.add('return') + + # Add 'type' for parameters with a description but no declared type. + for name in annotations: + if name == 'return': + continue + if name in has_description and name not in has_type: + field = nodes.field() + field += nodes.field_name('', 'type ' + name) + field += nodes.field_body('', nodes.paragraph('', annotations[name])) + node += field + + # Add 'rtype' if 'return' is present and 'rtype' isn't. + if 'return' in annotations: + if 'return' in has_description and 'return' not in has_type: + field = nodes.field() + field += nodes.field_name('', 'rtype') + field += nodes.field_body('', nodes.paragraph('', annotations['return'])) + node += field + + def setup(app: Sphinx) -> Dict[str, Any]: app.connect('autodoc-process-signature', record_typehints) app.connect('object-description-transform', merge_typehints) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index b268127d0..c29714ad4 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -60,19 +60,19 @@ import sys import warnings from os import path from types import ModuleType -from typing import Any, Dict, List, Tuple, cast +from typing import Any, Dict, List, Optional, Tuple, Type, cast from docutils import nodes from docutils.nodes import Element, Node, system_message from docutils.parsers.rst import directives -from docutils.parsers.rst.states import Inliner, RSTStateMachine, Struct, state_classes +from docutils.parsers.rst.states import RSTStateMachine, Struct, state_classes from docutils.statemachine import StringList import sphinx from sphinx import addnodes from sphinx.application import Sphinx from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.environment import BuildEnvironment from sphinx.environment.adapters.toctree import TocTree from sphinx.ext.autodoc import INSTANCEATTR, Documenter @@ -85,13 +85,9 @@ from sphinx.util import logging, rst from sphinx.util.docutils import (NullReporter, SphinxDirective, SphinxRole, new_document, switch_source_input) from sphinx.util.matching import Matcher +from sphinx.util.typing import OptionSpec from sphinx.writers.html import HTMLTranslator -if False: - # For type annotation - from typing import Type # for python3.5.1 - - logger = logging.getLogger(__name__) @@ -169,7 +165,7 @@ def autosummary_table_visit_html(self: HTMLTranslator, node: autosummary_table) # -- autodoc integration ------------------------------------------------------- # current application object (used in `get_documenter()`). -_app = None # type: Sphinx +_app: Sphinx = None class FakeDirective(DocumenterBridge): @@ -182,7 +178,7 @@ class FakeDirective(DocumenterBridge): super().__init__(env, None, Options(), 0, state) -def get_documenter(app: Sphinx, obj: Any, parent: Any) -> "Type[Documenter]": +def get_documenter(app: Sphinx, obj: Any, parent: Any) -> Type[Documenter]: """Get an autodoc.Documenter class suitable for documenting the given object. @@ -230,7 +226,7 @@ class Autosummary(SphinxDirective): optional_arguments = 0 final_argument_whitespace = False has_content = True - option_spec = { + option_spec: OptionSpec = { 'caption': directives.unchanged_required, 'toctree': directives.unchanged, 'nosignatures': directives.flag, @@ -315,7 +311,7 @@ class Autosummary(SphinxDirective): """ prefixes = get_import_prefixes_from_env(self.env) - items = [] # type: List[Tuple[str, str, str, str]] + items: List[Tuple[str, str, str, str]] = [] max_item_chars = 50 @@ -435,29 +431,6 @@ class Autosummary(SphinxDirective): return [table_spec, table] - def warn(self, msg: str) -> None: - warnings.warn('Autosummary.warn() is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - logger.warning(msg) - - @property - def genopt(self) -> Options: - warnings.warn('Autosummary.genopt is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - return self.bridge.genopt - - @property - def warnings(self) -> List[Node]: - warnings.warn('Autosummary.warnings is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - return [] - - @property - def result(self) -> StringList: - warnings.warn('Autosummary.result is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - return self.bridge.result - def strip_arg_typehint(s: str) -> str: """Strip a type hint from argument definition.""" @@ -488,8 +461,8 @@ def mangle_signature(sig: str, max_chars: int = 30) -> str: s = re.sub(r'{[^}]*}', '', s) # Parse the signature to arguments + options - args = [] # type: List[str] - opts = [] # type: List[str] + args: List[str] = [] + opts: List[str] = [] opt_re = re.compile(r"^(.*, |)([a-zA-Z0-9_*]+)\s*=\s*") while s: @@ -606,7 +579,7 @@ def get_import_prefixes_from_env(env: BuildEnvironment) -> List[str]: Obtain current Python import prefixes (for `import_by_name`) from ``document.env`` """ - prefixes = [None] # type: List[str] + prefixes: List[Optional[str]] = [None] currmodule = env.ref_context.get('py:module') if currmodule: @@ -700,33 +673,6 @@ def import_ivar_by_name(name: str, prefixes: List[str] = [None]) -> Tuple[str, A # -- :autolink: (smart default role) ------------------------------------------- -def autolink_role(typ: str, rawtext: str, etext: str, lineno: int, inliner: Inliner, - options: Dict = {}, content: List[str] = [] - ) -> Tuple[List[Node], List[system_message]]: - """Smart linking role. - - Expands to ':obj:`text`' if `text` is an object that can be imported; - otherwise expands to '*text*'. - """ - warnings.warn('autolink_role() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) - env = inliner.document.settings.env - pyobj_role = env.get_domain('py').role('obj') - objects, msg = pyobj_role('obj', rawtext, etext, lineno, inliner, options, content) - if msg != []: - return objects, msg - - assert len(objects) == 1 - pending_xref = cast(addnodes.pending_xref, objects[0]) - prefixes = get_import_prefixes_from_env(env) - try: - name, obj, parent, modname = import_by_name(pending_xref['reftarget'], prefixes) - except ImportError: - literal = cast(nodes.literal, pending_xref[0]) - objects[0] = nodes.emphasis(rawtext, literal.astext(), classes=literal['classes']) - - return objects, msg - - class AutoLink(SphinxRole): """Smart linking role. @@ -761,7 +707,7 @@ def get_rst_suffix(app: Sphinx) -> str: return ('restructuredtext',) return parser_class.supported - suffix = None # type: str + suffix: str = None for suffix in app.config.source_suffix: if 'restructuredtext' in get_supported_format(suffix): return suffix @@ -827,7 +773,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('builder-inited', process_generate_options) app.add_config_value('autosummary_context', {}, True) app.add_config_value('autosummary_filename_map', {}, 'html') - app.add_config_value('autosummary_generate', [], True, [bool]) + app.add_config_value('autosummary_generate', True, True, [bool]) app.add_config_value('autosummary_generate_overwrite', True, False) app.add_config_value('autosummary_mock_imports', lambda config: config.autodoc_mock_imports, 'env') diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 0240d2c7c..d1130d096 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -28,7 +28,7 @@ import sys import warnings from gettext import NullTranslations from os import path -from typing import Any, Callable, Dict, List, NamedTuple, Set, Tuple, Union +from typing import Any, Dict, List, NamedTuple, Set, Tuple, Type, Union from jinja2 import TemplateNotFound from jinja2.sandbox import SandboxedEnvironment @@ -38,7 +38,7 @@ from sphinx import __display_version__, package_dir from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import 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 @@ -50,11 +50,6 @@ from sphinx.util.inspect import safe_getattr from sphinx.util.osutil import ensuredir from sphinx.util.template import SphinxTemplateLoader -if False: - # For type annotation - from typing import Type # for python3.5.1 - - logger = logging.getLogger(__name__) @@ -64,7 +59,7 @@ class DummyApplication: def __init__(self, translator: NullTranslations) -> None: self.config = Config() self.registry = SphinxComponentRegistry() - self.messagelog = [] # type: List[str] + self.messagelog: List[str] = [] self.srcdir = "/" self.translator = translator self.verbosity = 0 @@ -79,10 +74,11 @@ class DummyApplication: pass -AutosummaryEntry = NamedTuple('AutosummaryEntry', [('name', str), - ('path', str), - ('template', str), - ('recursive', bool)]) +class AutosummaryEntry(NamedTuple): + name: str + path: str + template: str + recursive: bool def setup_documenters(app: Any) -> None: @@ -91,11 +87,11 @@ def setup_documenters(app: Any) -> None: FunctionDocumenter, MethodDocumenter, ModuleDocumenter, NewTypeAttributeDocumenter, NewTypeDataDocumenter, PropertyDocumenter) - documenters = [ + documenters: List[Type[Documenter]] = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter, NewTypeDataDocumenter, AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, - ] # type: List[Type[Documenter]] + ] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) @@ -245,8 +241,8 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, def get_members(obj: Any, types: Set[str], include_public: List[str] = [], imported: bool = True) -> Tuple[List[str], List[str]]: - items = [] # type: List[str] - public = [] # type: List[str] + items: List[str] = [] + public: List[str] = [] for name in dir(obj): try: value = safe_getattr(obj, name) @@ -286,7 +282,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, return public, attrs def get_modules(obj: Any) -> Tuple[List[str], List[str]]: - items = [] # type: List[str] + items: List[str] = [] for _, modname, ispkg in pkgutil.iter_modules(obj.__path__): fullname = name + '.' + modname try: @@ -300,7 +296,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, public = [x for x in items if not x.split('.')[-1].startswith('_')] return public, items - ns = {} # type: Dict[str, Any] + ns: Dict[str, Any] = {} ns.update(context) if doc.objtype == 'module': @@ -352,25 +348,10 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, def generate_autosummary_docs(sources: List[str], output_dir: str = None, - suffix: str = '.rst', warn: Callable = None, - info: Callable = None, base_path: str = None, + suffix: str = '.rst', base_path: str = None, builder: Builder = None, template_dir: str = None, imported_members: bool = False, app: Any = None, overwrite: bool = True, encoding: str = 'utf-8') -> None: - if info: - warnings.warn('info argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - _info = info - else: - _info = logger.info - - if warn: - warnings.warn('warn argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - _warn = warn - else: - _warn = logger.warning - if builder: warnings.warn('builder argument for generate_autosummary_docs() is deprecated.', RemovedInSphinx50Warning, stacklevel=2) @@ -382,11 +363,11 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, showed_sources = list(sorted(sources)) if len(showed_sources) > 20: showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:] - _info(__('[autosummary] generating autosummary for: %s') % - ', '.join(showed_sources)) + logger.info(__('[autosummary] generating autosummary for: %s') % + ', '.join(showed_sources)) if output_dir: - _info(__('[autosummary] writing to %s') % output_dir) + logger.info(__('[autosummary] writing to %s') % output_dir) if base_path is not None: sources = [os.path.join(base_path, filename) for filename in sources] @@ -423,10 +404,10 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, name, obj, parent, modname = import_ivar_by_name(entry.name) qualname = name.replace(modname + ".", "") except ImportError: - _warn(__('[autosummary] failed to import %r: %s') % (entry.name, e)) + logger.warning(__('[autosummary] failed to import %r: %s') % (entry.name, e)) continue - context = {} + context: Dict[str, Any] = {} if app: context.update(app.config.autosummary_context) @@ -453,8 +434,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, # descend recursively to new files if new_files: generate_autosummary_docs(new_files, output_dir=output_dir, - suffix=suffix, warn=warn, info=info, - base_path=base_path, + suffix=suffix, base_path=base_path, + builder=builder, template_dir=template_dir, imported_members=imported_members, app=app, overwrite=overwrite) @@ -466,7 +447,7 @@ def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: See `find_autosummary_in_lines`. """ - documented = [] # type: List[AutosummaryEntry] + documented: List[AutosummaryEntry] = [] for filename in filenames: with open(filename, encoding='utf-8', errors='ignore') as f: lines = f.read().splitlines() @@ -520,10 +501,10 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$') - documented = [] # type: List[AutosummaryEntry] + documented: List[AutosummaryEntry] = [] recursive = False - toctree = None # type: str + toctree: str = None template = None current_module = module in_autosummary = False diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index f052b8810..75460348e 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -53,19 +53,19 @@ class CoverageBuilder(Builder): 'results in %(outdir)s' + path.sep + 'python.txt.') def init(self) -> None: - self.c_sourcefiles = [] # type: List[str] + self.c_sourcefiles: List[str] = [] for pattern in self.config.coverage_c_path: pattern = path.join(self.srcdir, pattern) self.c_sourcefiles.extend(glob.glob(pattern)) - self.c_regexes = [] # type: List[Tuple[str, Pattern]] + self.c_regexes: List[Tuple[str, Pattern]] = [] for (name, exp) in self.config.coverage_c_regexes.items(): try: self.c_regexes.append((name, re.compile(exp))) except Exception: logger.warning(__('invalid regex %r in coverage_c_regexes'), exp) - self.c_ignorexps = {} # type: Dict[str, List[Pattern]] + self.c_ignorexps: Dict[str, List[Pattern]] = {} for (name, exps) in self.config.coverage_ignore_c_items.items(): self.c_ignorexps[name] = compile_regex_list('coverage_ignore_c_items', exps) @@ -82,11 +82,11 @@ class CoverageBuilder(Builder): return 'coverage overview' def write(self, *ignored: Any) -> None: - self.py_undoc = {} # type: Dict[str, Dict[str, Any]] + self.py_undoc: Dict[str, Dict[str, Any]] = {} self.build_py_coverage() self.write_py_coverage() - self.c_undoc = {} # type: Dict[str, Set[Tuple[str, str]]] + self.c_undoc: Dict[str, Set[Tuple[str, str]]] = {} self.build_c_coverage() self.write_c_coverage() @@ -94,7 +94,7 @@ class CoverageBuilder(Builder): # Fetch all the info from the header files c_objects = self.env.domaindata['c']['objects'] for filename in self.c_sourcefiles: - undoc = set() # type: Set[Tuple[str, str]] + undoc: Set[Tuple[str, str]] = set() with open(filename) as f: for line in f: for key, regex in self.c_regexes: @@ -161,7 +161,7 @@ class CoverageBuilder(Builder): continue funcs = [] - classes = {} # type: Dict[str, List[str]] + classes: Dict[str, List[str]] = {} for name, obj in inspect.getmembers(mod): # diverse module attributes are ignored: @@ -200,7 +200,7 @@ class CoverageBuilder(Builder): classes[name] = [] continue - attrs = [] # type: List[str] + attrs: List[str] = [] for attr_name in dir(obj): if attr_name not in obj.__dict__: diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index 20afa2ec6..242356b61 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -13,10 +13,10 @@ import doctest import re import sys import time -import warnings from io import StringIO from os import path -from typing import Any, Callable, Dict, Iterable, List, Sequence, Set, Tuple +from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Sequence, Set, Tuple, + Type) from docutils import nodes from docutils.nodes import Element, Node, TextElement @@ -26,17 +26,14 @@ from packaging.version import Version import sphinx from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import __ from sphinx.util import logging from sphinx.util.console import bold # type: ignore from sphinx.util.docutils import SphinxDirective from sphinx.util.osutil import relpath +from sphinx.util.typing import OptionSpec -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -46,12 +43,6 @@ blankline_re = re.compile(r'^\s*<BLANKLINE>', re.MULTILINE) doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE) -def doctest_encode(text: str, encoding: str) -> str: - warnings.warn('doctest_encode() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - return text - - def is_allowed_version(spec: str, version: str) -> bool: """Check `spec` satisfies `version` or not. @@ -96,7 +87,7 @@ class TestDirective(SphinxDirective): if not test: test = code code = doctestopt_re.sub('', code) - nodetype = nodes.literal_block # type: Type[TextElement] + nodetype: Type[TextElement] = nodes.literal_block if self.name in ('testsetup', 'testcleanup') or 'hide' in self.options: nodetype = nodes.comment if self.arguments: @@ -160,15 +151,19 @@ class TestDirective(SphinxDirective): class TestsetupDirective(TestDirective): - option_spec = {'skipif': directives.unchanged_required} # type: Dict + option_spec: OptionSpec = { + 'skipif': directives.unchanged_required + } class TestcleanupDirective(TestDirective): - option_spec = {'skipif': directives.unchanged_required} # type: Dict + option_spec: OptionSpec = { + 'skipif': directives.unchanged_required + } class DoctestDirective(TestDirective): - option_spec = { + option_spec: OptionSpec = { 'hide': directives.flag, 'no-trim-doctest-flags': directives.flag, 'options': directives.unchanged, @@ -179,7 +174,7 @@ class DoctestDirective(TestDirective): class TestcodeDirective(TestDirective): - option_spec = { + option_spec: OptionSpec = { 'hide': directives.flag, 'no-trim-doctest-flags': directives.flag, 'pyversion': directives.unchanged_required, @@ -189,7 +184,7 @@ class TestcodeDirective(TestDirective): class TestoutputDirective(TestDirective): - option_spec = { + option_spec: OptionSpec = { 'hide': directives.flag, 'no-trim-doctest-flags': directives.flag, 'options': directives.unchanged, @@ -207,9 +202,9 @@ parser = doctest.DocTestParser() class TestGroup: def __init__(self, name: str) -> None: self.name = name - self.setup = [] # type: List[TestCode] - self.tests = [] # type: List[List[TestCode]] - self.cleanup = [] # type: List[TestCode] + self.setup: List[TestCode] = [] + self.tests: List[List[TestCode]] = [] + self.cleanup: List[TestCode] = [] def add_code(self, code: "TestCode", prepend: bool = False) -> None: if code.type == 'testsetup': @@ -397,7 +392,7 @@ Doctest summary return False else: condition = node['skipif'] - context = {} # type: Dict[str, Any] + context: Dict[str, Any] = {} if self.config.doctest_global_setup: exec(self.config.doctest_global_setup, context) should_skip = eval(condition, context) @@ -406,7 +401,7 @@ Doctest summary return should_skip def test_doc(self, docname: str, doctree: Node) -> None: - groups = {} # type: Dict[str, TestGroup] + groups: Dict[str, TestGroup] = {} add_to_all_groups = [] self.setup_runner = SphinxDocTestRunner(verbose=False, optionflags=self.opt) @@ -487,7 +482,7 @@ Doctest summary return compile(code, name, self.type, flags, dont_inherit) def test_group(self, group: TestGroup) -> None: - ns = {} # type: Dict + ns: Dict = {} def run_setup_cleanup(runner: Any, testcodes: List[TestCode], what: Any) -> bool: examples = [] diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index f10285086..b1c5ca481 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -30,6 +30,7 @@ from sphinx.util.fileutil import copy_asset from sphinx.util.i18n import search_image_for_language from sphinx.util.nodes import set_source_info from sphinx.util.osutil import ensuredir +from sphinx.util.typing import OptionSpec from sphinx.writers.html import HTMLTranslator from sphinx.writers.latex import LaTeXTranslator from sphinx.writers.manpage import ManualPageTranslator @@ -49,10 +50,10 @@ class ClickableMapDefinition: href_re = re.compile('href=".*?"') def __init__(self, filename: str, content: str, dot: str = '') -> None: - self.id = None # type: str + self.id: str = None self.filename = filename self.content = content.splitlines() - self.clickable = [] # type: List[str] + self.clickable: List[str] = [] self.parse(dot=dot) @@ -113,7 +114,7 @@ class Graphviz(SphinxDirective): required_arguments = 0 optional_arguments = 1 final_argument_whitespace = False - option_spec = { + option_spec: OptionSpec = { 'alt': directives.unchanged, 'align': align_spec, 'caption': directives.unchanged, @@ -181,7 +182,7 @@ class GraphvizSimple(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = { + option_spec: OptionSpec = { 'alt': directives.unchanged, 'align': align_spec, 'caption': directives.unchanged, diff --git a/sphinx/ext/ifconfig.py b/sphinx/ext/ifconfig.py index df8545028..0e42984da 100644 --- a/sphinx/ext/ifconfig.py +++ b/sphinx/ext/ifconfig.py @@ -28,6 +28,7 @@ import sphinx from sphinx.application import Sphinx from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import nested_parse_with_titles +from sphinx.util.typing import OptionSpec class ifconfig(nodes.Element): @@ -40,7 +41,7 @@ class IfConfig(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: node = ifconfig() diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py index b0d40b551..84fe6549c 100644 --- a/sphinx/ext/imgconverter.py +++ b/sphinx/ext/imgconverter.py @@ -37,10 +37,10 @@ class ImagemagickConverter(ImageConverter): logger.debug('Invoking %r ...', args) subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True) return True - except OSError: + except OSError as exc: logger.warning(__('convert command %r cannot be run, ' - 'check the image_converter setting'), - self.config.image_converter) + 'check the image_converter setting: %s'), + self.config.image_converter, exc) return False except CalledProcessError as exc: logger.warning(__('convert exited with error:\n' diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index cef50d548..a0f4ddcc3 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -26,7 +26,6 @@ from sphinx import package_dir from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.errors import SphinxError from sphinx.locale import _, __ from sphinx.util import logging, sha1 @@ -58,35 +57,8 @@ class InvokeError(SphinxError): SUPPORT_FORMAT = ('png', 'svg') -DOC_HEAD = r''' -\documentclass[12pt]{article} -\usepackage[utf8x]{inputenc} -\usepackage{amsmath} -\usepackage{amsthm} -\usepackage{amssymb} -\usepackage{amsfonts} -\usepackage{anyfontsize} -\usepackage{bm} -\pagestyle{empty} -''' - -DOC_BODY = r''' -\begin{document} -\fontsize{%d}{%d}\selectfont %s -\end{document} -''' - -DOC_BODY_PREVIEW = r''' -\usepackage[active]{preview} -\begin{document} -\begin{preview} -\fontsize{%s}{%s}\selectfont %s -\end{preview} -\end{document} -''' - -depth_re = re.compile(br'\[\d+ depth=(-?\d+)\]') -depthsvg_re = re.compile(br'.*, depth=(.*)pt') +depth_re = re.compile(r'\[\d+ depth=(-?\d+)\]') +depthsvg_re = re.compile(r'.*, depth=(.*)pt') depthsvgcomment_re = re.compile(r'<!-- DEPTH=(-?\d+) -->') @@ -174,10 +146,10 @@ def compile_math(latex: str, builder: Builder) -> str: raise MathExtError('latex exited with error', exc.stderr, exc.stdout) from exc -def convert_dvi_to_image(command: List[str], name: str) -> Tuple[bytes, bytes]: +def convert_dvi_to_image(command: List[str], name: str) -> Tuple[str, str]: """Convert DVI file to specific image format.""" try: - ret = subprocess.run(command, stdout=PIPE, stderr=PIPE, check=True) + ret = subprocess.run(command, stdout=PIPE, stderr=PIPE, check=True, encoding='ascii') return ret.stdout, ret.stderr except OSError as exc: logger.warning(__('%s command %r cannot be run (needed for math ' @@ -370,15 +342,6 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None raise nodes.SkipNode -deprecated_alias('sphinx.ext.imgmath', - { - 'DOC_BODY': DOC_BODY, - 'DOC_BODY_PREVIEW': DOC_BODY_PREVIEW, - 'DOC_HEAD': DOC_HEAD, - }, - RemovedInSphinx40Warning) - - def setup(app: Sphinx) -> Dict[str, Any]: app.add_html_math_renderer('imgmath', (html_visit_math, None), diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index 63a171087..377f85d3a 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -53,6 +53,7 @@ from sphinx.ext.graphviz import (figure_wrapper, graphviz, render_dot_html, rend render_dot_texinfo) from sphinx.util import md5 from sphinx.util.docutils import SphinxDirective +from sphinx.util.typing import OptionSpec from sphinx.writers.html import HTMLTranslator from sphinx.writers.latex import LaTeXTranslator from sphinx.writers.texinfo import TexinfoTranslator @@ -154,7 +155,7 @@ class InheritanceGraph: def _import_classes(self, class_names: List[str], currmodule: str) -> List[Any]: """Import a list of classes.""" - classes = [] # type: List[Any] + classes: List[Any] = [] for name in class_names: classes.extend(import_classes(name, currmodule)) return classes @@ -198,7 +199,7 @@ class InheritanceGraph: except Exception: # might raise AttributeError for strange classes pass - baselist = [] # type: List[str] + baselist: List[str] = [] all_classes[cls] = (nodename, fullname, baselist, tooltip) if fullname in top_classes: @@ -292,7 +293,7 @@ class InheritanceGraph: n_attrs.update(env.config.inheritance_node_attrs) e_attrs.update(env.config.inheritance_edge_attrs) - res = [] # type: List[str] + res: List[str] = [] res.append('digraph %s {\n' % name) res.append(self._format_graph_attrs(g_attrs)) @@ -331,7 +332,7 @@ class InheritanceDiagram(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = { + option_spec: OptionSpec = { 'parts': int, 'private-bases': directives.flag, 'caption': directives.unchanged, diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 5569ad9de..5c6af2dfb 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -33,10 +33,11 @@ from typing import IO, Any, Dict, List, Tuple from urllib.parse import urlsplit, urlunsplit from docutils import nodes -from docutils.nodes import Element, TextElement +from docutils.nodes import TextElement from docutils.utils import relative_path import sphinx +from sphinx.addnodes import pending_xref from sphinx.application import Sphinx from sphinx.builders.html import INVENTORY_FILENAME from sphinx.config import Config @@ -44,6 +45,7 @@ from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.util import logging, requests from sphinx.util.inventory import InventoryFile +from sphinx.util.nodes import find_pending_xref_condition from sphinx.util.typing import Inventory logger = logging.getLogger(__name__) @@ -257,12 +259,12 @@ def load_mappings(app: Sphinx) -> None: inventories.main_inventory.setdefault(type, {}).update(objects) -def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: TextElement - ) -> nodes.reference: +def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref, + contnode: TextElement) -> nodes.reference: """Attempt to resolve a missing reference via intersphinx references.""" target = node['reftarget'] inventories = InventoryAdapter(env) - objtypes = None # type: List[str] + objtypes: List[str] = None if node['reftype'] == 'any': # we search anything! objtypes = ['%s:%s' % (domain.name, objtype) @@ -284,6 +286,17 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod if 'py:attribute' in objtypes: # Since Sphinx-2.1, properties are stored as py:method objtypes.append('py:method') + + # determine the contnode by pending_xref_condition + content = find_pending_xref_condition(node, 'resolved') + if content: + # resolved condition found. + contnodes = content.children + contnode = content.children[0] # type: ignore + else: + # not resolved. Use the given contnode + contnodes = [contnode] + to_try = [(inventories.main_inventory, target)] if domain: full_qualified_name = env.get_domain(domain).get_full_qualified_name(node) @@ -316,7 +329,7 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle) if node.get('refexplicit'): # use whatever title was given - newnode.append(contnode) + newnode.extend(contnodes) elif dispname == '-' or \ (domain == 'std' and node['reftype'] == 'keyword'): # use whatever title was given, but strip prefix @@ -325,7 +338,7 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod newnode.append(contnode.__class__(title[len(in_set) + 1:], title[len(in_set) + 1:])) else: - newnode.append(contnode) + newnode.extend(contnodes) else: # else use the given display name (used for :ref:) newnode.append(contnode.__class__(dispname, dispname)) @@ -385,7 +398,7 @@ def inspect_main(argv: List[str]) -> None: sys.exit(1) class MockConfig: - intersphinx_timeout = None # type: int + intersphinx_timeout: int = None tls_verify = False user_agent = None diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py deleted file mode 100644 index cec40e224..000000000 --- a/sphinx/ext/jsmath.py +++ /dev/null @@ -1,33 +0,0 @@ -""" - sphinx.ext.jsmath - ~~~~~~~~~~~~~~~~~ - - Set up everything for use of JSMath to display math in HTML - via JavaScript. - - :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import warnings -from typing import Any, Dict - -from sphinxcontrib.jsmath import (html_visit_displaymath, html_visit_math, # NOQA - install_jsmath) - -import sphinx -from sphinx.application import Sphinx -from sphinx.deprecation import RemovedInSphinx40Warning - - -def setup(app: Sphinx) -> Dict[str, Any]: - warnings.warn('sphinx.ext.jsmath has been moved to sphinxcontrib-jsmath.', - RemovedInSphinx40Warning) - - app.setup_extension('sphinxcontrib.jsmath') - - return { - 'version': sphinx.__display_version__, - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/sphinx/ext/linkcode.py b/sphinx/ext/linkcode.py index 5c118a9fb..6aaea0e9e 100644 --- a/sphinx/ext/linkcode.py +++ b/sphinx/ext/linkcode.py @@ -41,7 +41,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: for objnode in doctree.traverse(addnodes.desc): domain = objnode.get('domain') - uris = set() # type: Set[str] + uris: Set[str] = set() for signode in objnode: if not isinstance(signode, addnodes.desc_signature): continue diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py index ff8ef3718..1cd2bd1cc 100644 --- a/sphinx/ext/mathjax.py +++ b/sphinx/ext/mathjax.py @@ -25,8 +25,7 @@ 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') +MATHJAX_URL = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js' def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None: diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py index 4a8c2135a..ba8f62a18 100644 --- a/sphinx/ext/napoleon/__init__.py +++ b/sphinx/ext/napoleon/__init__.py @@ -388,7 +388,7 @@ def _process_docstring(app: Sphinx, what: str, name: str, obj: Any, """ result_lines = lines - docstring = None # type: GoogleDocstring + docstring: GoogleDocstring = None if app.config.napoleon_numpy_docstring: docstring = NumpyDocstring(result_lines, app.config, app, what, name, obj, options) diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index bd1c44509..d8cb75a5f 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -14,7 +14,7 @@ import collections import inspect import re from functools import partial -from typing import Any, Callable, Dict, List, Tuple, Union +from typing import Any, Callable, Dict, List, Tuple, Type, Union from sphinx.application import Sphinx from sphinx.config import Config as SphinxConfig @@ -24,11 +24,6 @@ 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 - from typing import Type # for python3.5.1 - - logger = logging.getLogger(__name__) _directive_regex = re.compile(r'\.\. \S+::') @@ -167,13 +162,13 @@ class GoogleDocstring: else: lines = docstring self._line_iter = modify_iter(lines, modifier=lambda s: s.rstrip()) - self._parsed_lines = [] # type: List[str] + self._parsed_lines: List[str] = [] self._is_in_section = False self._section_indent = 0 if not hasattr(self, '_directive_sections'): - self._directive_sections = [] # type: List[str] + self._directive_sections: List[str] = [] if not hasattr(self, '_sections'): - self._sections = { + self._sections: Dict[str, Callable] = { 'args': self._parse_parameters_section, 'arguments': self._parse_parameters_section, 'attention': partial(self._parse_admonition, 'attention'), @@ -208,7 +203,7 @@ class GoogleDocstring: 'warns': self._parse_warns_section, 'yield': self._parse_yields_section, 'yields': self._parse_yields_section, - } # type: Dict[str, Callable] + } self._load_custom_sections() @@ -466,7 +461,7 @@ class GoogleDocstring: field_type = ':%s:' % field_type.strip() padding = ' ' * len(field_type) multi = len(fields) > 1 - lines = [] # type: List[str] + lines: List[str] = [] for _name, _type, _desc in fields: field = self._format_field(_name, _type, _desc) if multi: @@ -590,7 +585,7 @@ class GoogleDocstring: if self._name and self._what in ('attribute', 'data', 'property'): # Implicit stop using StopIteration no longer allowed in # Python 3.7; see PEP 479 - res = [] # type: List[str] + res: List[str] = [] try: res = self._parse_attribute_docstring() except StopIteration: @@ -708,7 +703,7 @@ class GoogleDocstring: return self._format_fields(_('Keyword Arguments'), fields) def _parse_methods_section(self, section: str) -> List[str]: - lines = [] # type: List[str] + lines: List[str] = [] for _name, _type, _desc in self._consume_fields(parse_type=False): lines.append('.. method:: %s' % _name) if self._opt and 'noindex' in self._opt: @@ -723,7 +718,13 @@ class GoogleDocstring: return self._parse_generic_section(_('Notes'), use_admonition) def _parse_other_parameters_section(self, section: str) -> List[str]: - return self._format_fields(_('Other Parameters'), self._consume_fields()) + 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(_('Other Parameters'), fields) def _parse_parameters_section(self, section: str) -> List[str]: if self._config.napoleon_use_param: @@ -736,7 +737,7 @@ class GoogleDocstring: def _parse_raises_section(self, section: str) -> List[str]: fields = self._consume_fields(parse_type=False, prefer_type=True) - lines = [] # type: List[str] + lines: List[str] = [] for _name, _type, _desc in fields: m = self._name_rgx.match(_type) if m and m.group('name'): @@ -773,7 +774,7 @@ class GoogleDocstring: else: use_rtype = self._config.napoleon_use_rtype - lines = [] # type: List[str] + lines: List[str] = [] for _name, _type, _desc in fields: if use_rtype: field = self._format_field(_name, '', _desc) @@ -825,7 +826,7 @@ class GoogleDocstring: colon, "".join(after_colon).strip()) - def _qualify_name(self, attr_name: str, klass: "Type") -> str: + def _qualify_name(self, attr_name: str, klass: Type) -> str: if klass and '.' not in attr_name: if attr_name.startswith('~'): attr_name = attr_name[1:] @@ -1280,7 +1281,7 @@ class NumpyDocstring(GoogleDocstring): return new_func, description, role current_func = None - rest = [] # type: List[str] + rest: List[str] = [] for line in content: if not line.strip(): @@ -1315,7 +1316,7 @@ class NumpyDocstring(GoogleDocstring): for func, description, role in items ] - lines = [] # type: List[str] + lines: List[str] = [] last_had_desc = True for name, desc, role in items: if role: diff --git a/sphinx/ext/napoleon/iterators.py b/sphinx/ext/napoleon/iterators.py index e0f5cca9c..0e865ad81 100644 --- a/sphinx/ext/napoleon/iterators.py +++ b/sphinx/ext/napoleon/iterators.py @@ -49,8 +49,8 @@ class peek_iter: """ def __init__(self, *args: Any) -> None: """__init__(o, sentinel=None)""" - self._iterable = iter(*args) # type: Iterable - self._cache = collections.deque() # type: collections.deque + self._iterable: Iterable = iter(*args) + self._cache: collections.deque = collections.deque() if len(args) == 2: self.sentinel = args[1] else: diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index a73dea84d..6b7c1b73b 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -11,8 +11,7 @@ :license: BSD, see LICENSE for details. """ -import warnings -from typing import Any, Dict, Iterable, List, Tuple, cast +from typing import Any, Dict, List, Tuple, cast from docutils import nodes from docutils.nodes import Element, Node @@ -22,14 +21,13 @@ from docutils.parsers.rst.directives.admonitions import BaseAdmonition import sphinx from sphinx import addnodes from sphinx.application import Sphinx -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain from sphinx.environment import BuildEnvironment from sphinx.errors import NoUri from sphinx.locale import _, __ from sphinx.util import logging, texescape from sphinx.util.docutils import SphinxDirective, new_document -from sphinx.util.nodes import make_refnode +from sphinx.util.typing import OptionSpec from sphinx.writers.html import HTMLTranslator from sphinx.writers.latex import LaTeXTranslator @@ -54,7 +52,7 @@ class Todo(BaseAdmonition, SphinxDirective): required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False - option_spec = { + option_spec: OptionSpec = { 'class': directives.class_option, 'name': directives.unchanged, } @@ -104,33 +102,6 @@ class TodoDomain(Domain): location=todo) -def process_todos(app: Sphinx, doctree: nodes.document) -> None: - warnings.warn('process_todos() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) - # collect all todos in the environment - # this is not done in the directive itself because it some transformations - # must have already been run, e.g. substitutions - env = app.builder.env - if not hasattr(env, 'todo_all_todos'): - env.todo_all_todos = [] # type: ignore - for node in doctree.traverse(todo_node): - app.events.emit('todo-defined', node) - - newnode = node.deepcopy() - newnode['ids'] = [] - env.todo_all_todos.append({ # type: ignore - 'docname': env.docname, - 'source': node.source or env.doc2path(env.docname), - 'lineno': node.line, - 'todo': newnode, - 'target': node['ids'][0], - }) - - if env.config.todo_emit_warnings: - label = cast(nodes.Element, node[1]) - logger.warning(__("TODO entry found: %s"), label.astext(), - location=node) - - class TodoList(SphinxDirective): """ A list of all todo entries. @@ -140,7 +111,7 @@ class TodoList(SphinxDirective): required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} # type: Dict + option_spec: OptionSpec = {} def run(self) -> List[Node]: # Simply insert an empty todolist node which will be replaced later @@ -159,14 +130,14 @@ class TodoListProcessor: self.process(doctree, docname) def process(self, doctree: nodes.document, docname: str) -> None: - todos = sum(self.domain.todos.values(), []) # type: List[todo_node] + todos: List[todo_node] = sum(self.domain.todos.values(), []) for node in doctree.traverse(todolist): if not self.config.todo_include_todos: node.parent.remove(node) continue if node.get('ids'): - content = [nodes.target()] # type: List[Element] + content: List[Element] = [nodes.target()] else: content = [] @@ -223,80 +194,6 @@ class TodoListProcessor: self.document.remove(todo) -def process_todo_nodes(app: Sphinx, doctree: nodes.document, fromdocname: str) -> None: - """Replace all todolist nodes with a list of the collected todos. - Augment each todo with a backlink to the original location. - """ - warnings.warn('process_todo_nodes() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - domain = cast(TodoDomain, app.env.get_domain('todo')) - todos = sum(domain.todos.values(), []) # type: List[todo_node] - - for node in doctree.traverse(todolist): - if node.get('ids'): - content = [nodes.target()] # type: List[Element] - else: - content = [] - - if not app.config['todo_include_todos']: - node.replace_self(content) - continue - - for todo_info in todos: - para = nodes.paragraph(classes=['todo-source']) - if app.config['todo_link_only']: - description = _('<<original entry>>') - else: - description = ( - _('(The <<original entry>> is located in %s, line %d.)') % - (todo_info.source, todo_info.line) - ) - desc1 = description[:description.find('<<')] - desc2 = description[description.find('>>') + 2:] - para += nodes.Text(desc1, desc1) - - # Create a reference - innernode = nodes.emphasis(_('original entry'), _('original entry')) - try: - para += make_refnode(app.builder, fromdocname, todo_info['docname'], - todo_info['ids'][0], innernode) - except NoUri: - # ignore if no URI can be determined, e.g. for LaTeX output - pass - para += nodes.Text(desc2, desc2) - - todo_entry = todo_info.deepcopy() - todo_entry['ids'].clear() - - # (Recursively) resolve references in the todo content - app.env.resolve_references(todo_entry, todo_info['docname'], app.builder) # type: ignore # NOQA - - # Insert into the todolist - content.append(todo_entry) - content.append(para) - - node.replace_self(content) - - -def purge_todos(app: Sphinx, env: BuildEnvironment, docname: str) -> None: - warnings.warn('purge_todos() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) - if not hasattr(env, 'todo_all_todos'): - return - env.todo_all_todos = [todo for todo in env.todo_all_todos # type: ignore - if todo['docname'] != docname] - - -def merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str], - other: BuildEnvironment) -> None: - warnings.warn('merge_info() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) - if not hasattr(other, 'todo_all_todos'): - return - if not hasattr(env, 'todo_all_todos'): - env.todo_all_todos = [] # type: ignore - env.todo_all_todos.extend(other.todo_all_todos) # type: ignore - - def visit_todo_node(self: HTMLTranslator, node: todo_node) -> None: if self.config.todo_include_todos: self.visit_admonition(node) diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 6bedf2e1c..f2a42f33f 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -111,7 +111,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: for objnode in doctree.traverse(addnodes.desc): if objnode.get('domain') != 'py': continue - names = set() # type: Set[str] + names: Set[str] = set() for signode in objnode: if not isinstance(signode, addnodes.desc_signature): continue @@ -146,7 +146,14 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str], if not hasattr(env, '_viewcode_modules'): env._viewcode_modules = {} # type: ignore # now merge in the information from the subprocess - env._viewcode_modules.update(other._viewcode_modules) # type: ignore + for modname, entry in other._viewcode_modules.items(): # type: ignore + if modname not in env._viewcode_modules: # type: ignore + env._viewcode_modules[modname] = entry # type: ignore + else: + used = env._viewcode_modules[modname][2] # type: ignore + for fullname, docname in entry[2].items(): + if fullname not in used: + used[fullname] = docname def env_purge_doc(app: Sphinx, env: BuildEnvironment, docname: str) -> None: diff --git a/sphinx/extension.py b/sphinx/extension.py index 8129d89af..7ec6c8518 100644 --- a/sphinx/extension.py +++ b/sphinx/extension.py @@ -8,15 +8,14 @@ :license: BSD, see LICENSE for details. """ -from typing import Any, Dict +from typing import TYPE_CHECKING, Any, Dict from sphinx.config import Config from sphinx.errors import VersionRequirementError from sphinx.locale import __ from sphinx.util import logging -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx logger = logging.getLogger(__name__) diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 8425009f7..329561df0 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -8,10 +8,12 @@ :license: BSD, see LICENSE for details. """ +from distutils.version import LooseVersion from functools import partial from importlib import import_module from typing import Any, Dict +from pygments import __version__ as pygmentsversion from pygments import highlight from pygments.filters import ErrorToken from pygments.formatter import Formatter @@ -29,8 +31,8 @@ from sphinx.util import logging, texescape logger = logging.getLogger(__name__) -lexers = {} # type: Dict[str, Lexer] -lexer_classes = { +lexers: Dict[str, Lexer] = {} +lexer_classes: Dict[str, Lexer] = { 'none': partial(TextLexer, stripnl=False), 'python': partial(PythonLexer, stripnl=False), 'python3': partial(Python3Lexer, stripnl=False), @@ -38,7 +40,7 @@ lexer_classes = { 'pycon3': partial(PythonConsoleLexer, python3=True, stripnl=False), 'rest': partial(RstLexer, stripnl=False), 'c': partial(CLexer, stripnl=False), -} # type: Dict[str, Lexer] +} escape_hl_chars = {ord('\\'): '\\PYGZbs{}', @@ -50,6 +52,20 @@ escape_hl_chars = {ord('\\'): '\\PYGZbs{}', _LATEX_ADD_STYLES = r''' \renewcommand\PYGZsq{\textquotesingle} ''' +# fix extra space between lines when Pygments highlighting uses \fcolorbox +# add a {..} to limit \fboxsep scope, and force \fcolorbox use correct value +# cf pygments #1708 which makes this unneeded for Pygments > 2.7.4 +_LATEX_ADD_STYLES_FIXPYG = r''' +\makeatletter +% fix for Pygments <= 2.7.4 +\let\spx@original@fcolorbox\fcolorbox +\def\spx@fixpyg@fcolorbox{\fboxsep-\fboxrule\spx@original@fcolorbox} +\def\PYG#1#2{\PYG@reset\PYG@toks#1+\relax+% + {\let\fcolorbox\spx@fixpyg@fcolorbox\PYG@do{#2}}} +\makeatother +''' +if tuple(LooseVersion(pygmentsversion).version) <= (2, 7, 4): + _LATEX_ADD_STYLES += _LATEX_ADD_STYLES_FIXPYG class PygmentsBridge: @@ -64,7 +80,7 @@ class PygmentsBridge: self.latex_engine = latex_engine style = self.get_style(stylename) - self.formatter_args = {'style': style} # type: Dict[str, Any] + self.formatter_args: Dict[str, Any] = {'style': style} if dest == 'html': self.formatter = self.html_formatter else: diff --git a/sphinx/io.py b/sphinx/io.py index 3508dd58d..382d31c89 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -8,8 +8,7 @@ :license: BSD, see LICENSE for details. """ import codecs -import warnings -from typing import Any, List +from typing import TYPE_CHECKING, Any, List, Type from docutils import nodes from docutils.core import Publisher @@ -23,9 +22,7 @@ from docutils.transforms.references import DanglingReferences from docutils.writers import UnfilteredWriter from sphinx import addnodes -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.environment import BuildEnvironment -from sphinx.errors import FiletypeNotFoundError from sphinx.transforms import (AutoIndexUpgrader, DoctreeReadEvent, FigureAligner, SphinxTransformer) from sphinx.transforms.i18n import (Locale, PreserveTranslatableMessages, @@ -35,10 +32,7 @@ from sphinx.util import UnicodeDecodeErrorHandler, get_filetype, logging from sphinx.util.docutils import LoggingReporter from sphinx.versioning import UIDTransform -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -52,7 +46,7 @@ class SphinxBaseReader(standalone.Reader): This replaces reporter by Sphinx's on generating document. """ - transforms = [] # type: List[Type[Transform]] + transforms: List[Type[Transform]] = [] def __init__(self, *args: Any, **kwargs: Any) -> None: from sphinx.application import Sphinx @@ -63,23 +57,11 @@ class SphinxBaseReader(standalone.Reader): super().__init__(*args, **kwargs) - @property - def app(self) -> "Sphinx": - warnings.warn('SphinxBaseReader.app is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - return self._app - - @property - def env(self) -> BuildEnvironment: - warnings.warn('SphinxBaseReader.env is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - return self._env - def setup(self, app: "Sphinx") -> None: self._app = app # hold application object only for compatibility self._env = app.env - def get_transforms(self) -> List["Type[Transform]"]: + def get_transforms(self) -> List[Type[Transform]]: transforms = super().get_transforms() + self.transforms # remove transforms which is not needed for Sphinx @@ -196,39 +178,12 @@ def read_doc(app: "Sphinx", env: BuildEnvironment, filename: str) -> nodes.docum # CommonMarkParser. parser.settings_spec = RSTParser.settings_spec - input_class = app.registry.get_source_input(filetype) - if input_class: - # Sphinx-1.8 style - source = input_class(app, env, source=None, source_path=filename, # type: ignore - encoding=env.config.source_encoding) - pub = Publisher(reader=reader, - parser=parser, - writer=SphinxDummyWriter(), - source_class=SphinxDummySourceClass, # type: ignore - destination=NullOutput()) - pub.process_programmatic_settings(None, env.settings, None) - pub.set_source(source, filename) - else: - # Sphinx-2.0 style - pub = Publisher(reader=reader, - parser=parser, - writer=SphinxDummyWriter(), - source_class=SphinxFileInput, - destination=NullOutput()) - pub.process_programmatic_settings(None, env.settings, None) - pub.set_source(source_path=filename) - + pub = Publisher(reader=reader, + parser=parser, + writer=SphinxDummyWriter(), + source_class=SphinxFileInput, + destination=NullOutput()) + pub.process_programmatic_settings(None, env.settings, None) + pub.set_source(source_path=filename) pub.publish() return pub.document - - -deprecated_alias('sphinx.io', - { - 'FiletypeNotFoundError': FiletypeNotFoundError, - 'get_filetype': get_filetype, - }, - RemovedInSphinx40Warning, - { - 'FiletypeNotFoundError': 'sphinx.errors.FiletypeNotFoundError', - 'get_filetype': 'sphinx.util.get_filetype', - }) diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index c890455f3..c239f5a4a 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -10,7 +10,7 @@ from os import path from pprint import pformat -from typing import Any, Callable, Dict, Iterator, List, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Tuple, Union from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound, contextfunction from jinja2.environment import Environment @@ -22,8 +22,7 @@ from sphinx.theming import Theme from sphinx.util import logging from sphinx.util.osutil import mtimes_of_files -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.builders import Builder diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index bedd9e1cb..1ce65e1e3 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -103,10 +103,10 @@ class _TranslationProxy(UserString): return '<%s broken>' % self.__class__.__name__ -translators = defaultdict(NullTranslations) # type: Dict[Tuple[str, str], NullTranslations] +translators: Dict[Tuple[str, str], NullTranslations] = defaultdict(NullTranslations) -def init(locale_dirs: List[Optional[str]], language: str, +def init(locale_dirs: List[Optional[str]], language: Optional[str], catalog: str = 'sphinx', namespace: str = 'general') -> Tuple[NullTranslations, bool]: """Look for message catalogs in `locale_dirs` and *ensure* that there is at least a NullTranslations catalog set in `translators`. If called multiple @@ -123,9 +123,11 @@ def init(locale_dirs: List[Optional[str]], language: str, if language and '_' in language: # for language having country code (like "de_AT") - languages = [language, language.split('_')[0]] - else: + languages: Optional[List[str]] = [language, language.split('_')[0]] + elif language: languages = [language] + else: + languages = None # loading for dir_ in locale_dirs: @@ -260,7 +262,7 @@ admonitionlabels = { } # Moved to sphinx.directives.other (will be overriden later) -versionlabels = {} # type: Dict[str, str] +versionlabels: Dict[str, str] = {} # Moved to sphinx.domains.python (will be overriden later) -pairindextypes = {} # type: Dict[str, str] +pairindextypes: Dict[str, str] = {} diff --git a/sphinx/parsers.py b/sphinx/parsers.py index 17c291af7..7e80e4f22 100644 --- a/sphinx/parsers.py +++ b/sphinx/parsers.py @@ -9,24 +9,20 @@ """ import warnings -from typing import Any, Dict, List, Union +from typing import TYPE_CHECKING, Any, Dict, List, Type, Union import docutils.parsers import docutils.parsers.rst from docutils import nodes from docutils.parsers.rst import states from docutils.statemachine import StringList +from docutils.transforms import Transform from docutils.transforms.universal import SmartQuotes from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.util.rst import append_epilog, prepend_prolog -if False: - # For type annotation - from typing import Type # NOQA # for python3.5.1 - - from docutils.transforms import Transform # NOQA - +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -73,7 +69,7 @@ class Parser(docutils.parsers.Parser): class RSTParser(docutils.parsers.rst.Parser, Parser): """A reST parser for Sphinx.""" - def get_transforms(self) -> List["Type[Transform]"]: + def get_transforms(self) -> List[Type[Transform]]: """Sphinx's reST parser replaces a transform class for smart-quotes by own's refs: sphinx.io.SphinxStandaloneReader diff --git a/sphinx/project.py b/sphinx/project.py index e5df4013f..bb2314c63 100644 --- a/sphinx/project.py +++ b/sphinx/project.py @@ -10,17 +10,13 @@ import os from glob import glob +from typing import Dict, List, Set from sphinx.locale import __ from sphinx.util import get_matching_files, logging, path_stabilize from sphinx.util.matching import compile_matchers from sphinx.util.osutil import SEP, relpath -if False: - # For type annotation - from typing import Dict, List, Set # NOQA - - logger = logging.getLogger(__name__) EXCLUDE_PATHS = ['**/_sources', '.#*', '**/.#*', '*.lproj/**'] @@ -28,8 +24,7 @@ EXCLUDE_PATHS = ['**/_sources', '.#*', '**/.#*', '*.lproj/**'] class Project: """A project is source code set of Sphinx document.""" - def __init__(self, srcdir, source_suffix): - # type: (str, Dict[str, str]) -> None + def __init__(self, srcdir: str, source_suffix: Dict[str, str]) -> None: #: Source directory. self.srcdir = srcdir @@ -37,15 +32,13 @@ class Project: self.source_suffix = source_suffix #: The name of documents belongs to this project. - self.docnames = set() # type: Set[str] + self.docnames: Set[str] = set() - def restore(self, other): - # type: (Project) -> None + def restore(self, other: "Project") -> None: """Take over a result of last build.""" self.docnames = other.docnames - def discover(self, exclude_paths=[]): - # type: (List[str]) -> Set[str] + def discover(self, exclude_paths: List[str] = []) -> Set[str]: """Find all document files in the source directory and put them in :attr:`docnames`. """ @@ -67,8 +60,7 @@ class Project: return self.docnames - def path2doc(self, filename): - # type: (str) -> str + def path2doc(self, filename: str) -> str: """Return the docname for the filename if the file is document. *filename* should be absolute or relative to the source directory. @@ -83,8 +75,7 @@ class Project: # the file does not have docname return None - def doc2path(self, docname, basedir=True): - # type: (str, bool) -> str + def doc2path(self, docname: str, basedir: bool = True) -> str: """Return the filename for the document name. If *basedir* is True, return as an absolute path. diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index ab0dfdbf8..c55a4fe4a 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -19,14 +19,14 @@ from os import path from typing import IO, Any, Dict, List, Optional, Tuple from zipfile import ZipFile -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.errors import PycodeError from sphinx.pycode.parser import Parser class ModuleAnalyzer: # cache for analyzer objects -- caches both by module and file name - cache = {} # type: Dict[Tuple[str, str], Any] + cache: Dict[Tuple[str, str], Any] = {} @staticmethod def get_module_source(modname: str) -> Tuple[Optional[str], Optional[str]]: @@ -79,7 +79,7 @@ class ModuleAnalyzer: @classmethod def for_string(cls, string: str, modname: str, srcname: str = '<string>' ) -> "ModuleAnalyzer": - return cls(StringIO(string), modname, srcname, decoded=True) + return cls(StringIO(string), modname, srcname) @classmethod def for_file(cls, filename: str, modname: str) -> "ModuleAnalyzer": @@ -87,7 +87,7 @@ class ModuleAnalyzer: return cls.cache['file', filename] try: with tokenize.open(filename) as f: - obj = cls(f, modname, filename, decoded=True) + obj = cls(f, modname, filename) cls.cache['file', filename] = obj except Exception as err: if '.egg' + path.sep in filename: @@ -127,29 +127,20 @@ class ModuleAnalyzer: cls.cache['module', modname] = obj return obj - def __init__(self, source: IO, modname: str, srcname: str, decoded: bool = False) -> None: + def __init__(self, source: IO, modname: str, srcname: str) -> None: self.modname = modname # name of the module self.srcname = srcname # name of the source file # cache the source code as well - pos = source.tell() - if not decoded: - warnings.warn('decode option for ModuleAnalyzer is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - self._encoding, _ = tokenize.detect_encoding(source.readline) - source.seek(pos) - self.code = source.read().decode(self._encoding) - else: - self._encoding = None - self.code = source.read() + self.code = source.read() # will be filled by analyze() - self.annotations = None # type: Dict[Tuple[str, str], str] - self.attr_docs = None # type: Dict[Tuple[str, str], List[str]] - self.finals = None # type: List[str] - self.overloads = None # type: Dict[str, List[Signature]] - self.tagorder = None # type: Dict[str, int] - self.tags = None # type: Dict[str, Tuple[str, int, int]] + self.annotations: Dict[Tuple[str, str], str] = None + self.attr_docs: Dict[Tuple[str, str], List[str]] = None + self.finals: List[str] = None + self.overloads: Dict[str, List[Signature]] = None + self.tagorder: Dict[str, int] = None + self.tags: Dict[str, Tuple[str, int, int]] = None self._analyzed = False def parse(self) -> None: @@ -164,7 +155,7 @@ class ModuleAnalyzer: return None try: - parser = Parser(self.code, self._encoding) + parser = Parser(self.code) parser.parse() self.attr_docs = OrderedDict() @@ -192,9 +183,3 @@ class ModuleAnalyzer: """Find class, function and method definitions and their location.""" self.analyze() return self.tags - - @property - def encoding(self) -> str: - warnings.warn('ModuleAnalyzer.encoding is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - return self._encoding diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py index 65534f958..f541ec0a9 100644 --- a/sphinx/pycode/ast.py +++ b/sphinx/pycode/ast.py @@ -9,7 +9,7 @@ """ import sys -from typing import Dict, List, Optional, Type +from typing import Dict, List, Optional, Type, overload if sys.version_info > (3, 8): import ast @@ -21,7 +21,7 @@ else: import ast # type: ignore -OPERATORS = { +OPERATORS: Dict[Type[ast.AST], str] = { ast.Add: "+", ast.And: "and", ast.BitAnd: "&", @@ -41,7 +41,7 @@ OPERATORS = { ast.Sub: "-", ast.UAdd: "+", ast.USub: "-", -} # type: Dict[Type[ast.AST], str] +} def parse(code: str, mode: str = 'exec') -> "ast.AST": @@ -62,6 +62,16 @@ def parse(code: str, mode: str = 'exec') -> "ast.AST": return ast.parse(code, mode=mode) +@overload +def unparse(node: None, code: str = '') -> None: + ... + + +@overload +def unparse(node: ast.AST, code: str = '') -> str: + ... + + def unparse(node: Optional[ast.AST], code: str = '') -> Optional[str]: """Unparse an AST to string.""" if node is None: @@ -98,7 +108,7 @@ class _UnparseVisitor(ast.NodeVisitor): return name def visit_arguments(self, node: ast.arguments) -> str: - defaults = list(node.defaults) + defaults: List[Optional[ast.expr]] = list(node.defaults) positionals = len(node.args) posonlyargs = 0 if hasattr(node, "posonlyargs"): # for py38+ @@ -107,11 +117,11 @@ class _UnparseVisitor(ast.NodeVisitor): for _ in range(len(defaults), positionals): defaults.insert(0, None) - kw_defaults = list(node.kw_defaults) + kw_defaults: List[Optional[ast.expr]] = list(node.kw_defaults) for _ in range(len(kw_defaults), len(node.kwonlyargs)): kw_defaults.insert(0, None) - args = [] # type: List[str] + args: List[str] = [] if hasattr(node, "posonlyargs"): # for py38+ for i, arg in enumerate(node.posonlyargs): # type: ignore args.append(self._visit_arg_with_default(arg, defaults[i])) @@ -150,6 +160,17 @@ class _UnparseVisitor(ast.NodeVisitor): ["%s=%s" % (k.arg, self.visit(k.value)) for k in node.keywords]) return "%s(%s)" % (self.visit(node.func), ", ".join(args)) + def visit_Constant(self, node: ast.Constant) -> str: # type: ignore + if node.value is Ellipsis: + return "..." + elif isinstance(node.value, (int, float, complex)): + if self.code and sys.version_info > (3, 8): + return ast.get_source_segment(self.code, node) # type: ignore + else: + return repr(node.value) + else: + return repr(node.value) + def visit_Dict(self, node: ast.Dict) -> str: keys = (self.visit(k) for k in node.keys) values = (self.visit(v) for v in node.values) @@ -197,18 +218,6 @@ class _UnparseVisitor(ast.NodeVisitor): else: return "()" - if sys.version_info >= (3, 6): - def visit_Constant(self, node: ast.Constant) -> str: - if node.value is Ellipsis: - return "..." - elif isinstance(node.value, (int, float, complex)): - if self.code and sys.version_info > (3, 8): - return ast.get_source_segment(self.code, node) - else: - return repr(node.value) - else: - return repr(node.value) - if sys.version_info < (3, 8): # these ast nodes were deprecated in python 3.8 def visit_Bytes(self, node: ast.Bytes) -> str: diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index dca59acd4..fa249d8c5 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -10,7 +10,6 @@ import inspect import itertools import re -import sys import tokenize from collections import OrderedDict from inspect import Signature @@ -26,12 +25,6 @@ indent_re = re.compile('^\\s*$') emptyline_re = re.compile('^\\s*(#.*)?$') -if sys.version_info >= (3, 6): - ASSIGN_NODES = (ast.Assign, ast.AnnAssign) -else: - ASSIGN_NODES = (ast.Assign) - - def filter_whitespace(code: str) -> str: return code.replace('\f', ' ') # replace FF (form feed) with whitespace @@ -94,7 +87,10 @@ def dedent_docstring(s: str) -> str: dummy.__doc__ = s docstring = inspect.getdoc(dummy) - return docstring.lstrip("\r\n").rstrip("\r\n") + if docstring: + return docstring.lstrip("\r\n").rstrip("\r\n") + else: + return "" class Token: @@ -133,8 +129,8 @@ class TokenProcessor: lines = iter(buffers) self.buffers = buffers self.tokens = tokenize.generate_tokens(lambda: next(lines)) - self.current = None # type: Token - self.previous = None # type: Token + self.current: Token = None + self.previous: Token = None def get_line(self, lineno: int) -> str: """Returns specified line.""" @@ -182,7 +178,7 @@ class AfterCommentParser(TokenProcessor): def __init__(self, lines: List[str]) -> None: super().__init__(lines) - self.comment = None # type: str + self.comment: str = None def fetch_rvalue(self) -> List[Token]: """Fetch right-hand value of assignment.""" @@ -225,18 +221,18 @@ class VariableCommentPicker(ast.NodeVisitor): self.counter = itertools.count() self.buffers = buffers self.encoding = encoding - self.context = [] # type: List[str] - self.current_classes = [] # type: List[str] - self.current_function = None # type: ast.FunctionDef - self.comments = OrderedDict() # type: Dict[Tuple[str, str], str] - self.annotations = {} # type: Dict[Tuple[str, str], str] - self.previous = None # type: ast.AST - self.deforders = {} # type: Dict[str, int] - self.finals = [] # type: List[str] - self.overloads = {} # type: Dict[str, List[Signature]] - self.typing = None # type: str - self.typing_final = None # type: str - self.typing_overload = None # type: str + self.context: List[str] = [] + self.current_classes: List[str] = [] + self.current_function: ast.FunctionDef = None + self.comments: Dict[Tuple[str, str], str] = OrderedDict() + self.annotations: Dict[Tuple[str, str], str] = {} + self.previous: ast.AST = None + self.deforders: Dict[str, int] = {} + self.finals: List[str] = [] + self.overloads: Dict[str, List[Signature]] = {} + self.typing: str = None + self.typing_final: str = None + self.typing_overload: str = None super().__init__() def get_qualname_for(self, name: str) -> Optional[List[str]]: @@ -354,7 +350,7 @@ class VariableCommentPicker(ast.NodeVisitor): """Handles Assign node and pick up a variable comment.""" try: targets = get_assign_targets(node) - varnames = sum([get_lvar_names(t, self=self.get_self()) for t in targets], []) # type: List[str] # NOQA + varnames: List[str] = sum([get_lvar_names(t, self=self.get_self()) for t in targets], []) # NOQA current_line = self.get_line(node.lineno) except TypeError: return # this assignment is not new definition! @@ -398,13 +394,14 @@ class VariableCommentPicker(ast.NodeVisitor): for varname in varnames: self.add_entry(varname) - def visit_AnnAssign(self, node: ast.AST) -> None: # Note: ast.AnnAssign not found in py35 + def visit_AnnAssign(self, node: ast.AnnAssign) -> None: """Handles AnnAssign node and pick up a variable comment.""" self.visit_Assign(node) # type: ignore def visit_Expr(self, node: ast.Expr) -> None: """Handles Expr node and pick up a comment if string.""" - if (isinstance(self.previous, ASSIGN_NODES) and isinstance(node.value, ast.Str)): + if (isinstance(self.previous, (ast.Assign, ast.AnnAssign)) and + isinstance(node.value, ast.Str)): try: targets = get_assign_targets(self.previous) varnames = get_lvar_names(targets[0], self.get_self()) @@ -469,10 +466,10 @@ class DefinitionFinder(TokenProcessor): def __init__(self, lines: List[str]) -> None: super().__init__(lines) - self.decorator = None # type: Token - self.context = [] # type: List[str] - self.indents = [] # type: List - self.definitions = {} # type: Dict[str, Tuple[str, int, int]] + self.decorator: Token = None + self.context: List[str] = [] + self.indents: List = [] + self.definitions: Dict[str, Tuple[str, int, int]] = {} def add_definition(self, name: str, entry: Tuple[str, int, int]) -> None: """Add a location of definition.""" @@ -546,12 +543,12 @@ class Parser: def __init__(self, code: str, encoding: str = 'utf-8') -> None: self.code = filter_whitespace(code) self.encoding = encoding - self.annotations = {} # type: Dict[Tuple[str, str], str] - self.comments = {} # type: Dict[Tuple[str, str], str] - self.deforders = {} # type: Dict[str, int] - self.definitions = {} # type: Dict[str, Tuple[str, int, int]] - self.finals = [] # type: List[str] - self.overloads = {} # type: Dict[str, List[Signature]] + self.annotations: Dict[Tuple[str, str], str] = {} + self.comments: Dict[Tuple[str, str], str] = {} + self.deforders: Dict[str, int] = {} + self.definitions: Dict[str, Tuple[str, int, int]] = {} + self.finals: List[str] = [] + self.overloads: Dict[str, List[Signature]] = {} def parse(self) -> None: """Parse the source code.""" diff --git a/sphinx/registry.py b/sphinx/registry.py index c6a249e74..cdf77cb67 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -9,9 +9,11 @@ """ import traceback +import warnings from importlib import import_module from types import MethodType -from typing import Any, Callable, Dict, Iterator, List, Tuple, Union +from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Tuple, Type, + Union) from docutils import nodes from docutils.io import Input @@ -23,6 +25,7 @@ from pkg_resources import iter_entry_points from sphinx.builders import Builder from sphinx.config import Config +from sphinx.deprecation import RemovedInSphinx60Warning from sphinx.domains import Domain, Index, ObjType from sphinx.domains.std import GenericObject, Target from sphinx.environment import BuildEnvironment @@ -35,10 +38,7 @@ from sphinx.util import logging from sphinx.util.logging import prefixed_warnings from sphinx.util.typing import RoleFunction, TitleGetter -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.ext.autodoc import Documenter @@ -54,76 +54,76 @@ EXTENSION_BLACKLIST = { class SphinxComponentRegistry: def __init__(self) -> None: #: special attrgetter for autodoc; class object -> attrgetter - self.autodoc_attrgettrs = {} # type: Dict[Type, Callable[[Any, str, Any], Any]] + self.autodoc_attrgettrs: Dict[Type, Callable[[Any, str, Any], Any]] = {} #: builders; a dict of builder name -> bulider class - self.builders = {} # type: Dict[str, Type[Builder]] + self.builders: Dict[str, Type[Builder]] = {} #: autodoc documenters; a dict of documenter name -> documenter class - self.documenters = {} # type: Dict[str, Type[Documenter]] + self.documenters: Dict[str, Type[Documenter]] = {} #: css_files; a list of tuple of filename and attributes - self.css_files = [] # type: List[Tuple[str, Dict[str, Any]]] + self.css_files: List[Tuple[str, Dict[str, Any]]] = [] #: domains; a dict of domain name -> domain class - self.domains = {} # type: Dict[str, Type[Domain]] + self.domains: Dict[str, Type[Domain]] = {} #: additional directives for domains #: a dict of domain name -> dict of directive name -> directive - self.domain_directives = {} # type: Dict[str, Dict[str, Any]] + self.domain_directives: Dict[str, Dict[str, Any]] = {} #: additional indices for domains #: a dict of domain name -> list of index class - self.domain_indices = {} # type: Dict[str, List[Type[Index]]] + self.domain_indices: Dict[str, List[Type[Index]]] = {} #: additional object types for domains #: a dict of domain name -> dict of objtype name -> objtype - self.domain_object_types = {} # type: Dict[str, Dict[str, ObjType]] + self.domain_object_types: Dict[str, Dict[str, ObjType]] = {} #: additional roles for domains #: a dict of domain name -> dict of role name -> role impl. - self.domain_roles = {} # type: Dict[str, Dict[str, Union[RoleFunction, XRefRole]]] # NOQA + self.domain_roles: Dict[str, Dict[str, Union[RoleFunction, XRefRole]]] = {} #: additional enumerable nodes #: a dict of node class -> tuple of figtype and title_getter function - self.enumerable_nodes = {} # type: Dict[Type[Node], Tuple[str, TitleGetter]] + self.enumerable_nodes: Dict[Type[Node], Tuple[str, TitleGetter]] = {} #: HTML inline and block math renderers #: a dict of name -> tuple of visit function and depart function - self.html_inline_math_renderers = {} # type: Dict[str, Tuple[Callable, Callable]] - self.html_block_math_renderers = {} # type: Dict[str, Tuple[Callable, Callable]] + self.html_inline_math_renderers: Dict[str, Tuple[Callable, Callable]] = {} + self.html_block_math_renderers: Dict[str, Tuple[Callable, Callable]] = {} #: js_files; list of JS paths or URLs - self.js_files = [] # type: List[Tuple[str, Dict[str, Any]]] + self.js_files: List[Tuple[str, Dict[str, Any]]] = [] #: LaTeX packages; list of package names and its options - self.latex_packages = [] # type: List[Tuple[str, str]] + self.latex_packages: List[Tuple[str, str]] = [] - self.latex_packages_after_hyperref = [] # type: List[Tuple[str, str]] + self.latex_packages_after_hyperref: List[Tuple[str, str]] = [] #: post transforms; list of transforms - self.post_transforms = [] # type: List[Type[Transform]] + self.post_transforms: List[Type[Transform]] = [] #: source paresrs; file type -> parser class - self.source_parsers = {} # type: Dict[str, Type[Parser]] + self.source_parsers: Dict[str, Type[Parser]] = {} #: source inputs; file type -> input class - self.source_inputs = {} # type: Dict[str, Type[Input]] + self.source_inputs: Dict[str, Type[Input]] = {} #: source suffix: suffix -> file type - self.source_suffix = {} # type: Dict[str, str] + self.source_suffix: Dict[str, str] = {} #: custom translators; builder name -> translator class - self.translators = {} # type: Dict[str, Type[nodes.NodeVisitor]] + self.translators: Dict[str, Type[nodes.NodeVisitor]] = {} #: custom handlers for translators #: a dict of builder name -> dict of node name -> visitor and departure functions - self.translation_handlers = {} # type: Dict[str, Dict[str, Tuple[Callable, Callable]]] + self.translation_handlers: Dict[str, Dict[str, Tuple[Callable, Callable]]] = {} #: additional transforms; list of transforms - self.transforms = [] # type: List[Type[Transform]] + self.transforms: List[Type[Transform]] = [] - def add_builder(self, builder: "Type[Builder]", override: bool = False) -> None: + def add_builder(self, builder: Type[Builder], override: bool = False) -> None: logger.debug('[app] adding builder: %r', builder) if not hasattr(builder, 'name'): raise ExtensionError(__('Builder class %s has no "name" attribute') % builder) @@ -152,7 +152,7 @@ class SphinxComponentRegistry: return self.builders[name](app) - def add_domain(self, domain: "Type[Domain]", override: bool = False) -> None: + def add_domain(self, domain: Type[Domain], override: bool = False) -> None: logger.debug('[app] adding domain: %r', domain) if domain.name in self.domains and not override: raise ExtensionError(__('domain %s already registered') % domain.name) @@ -175,7 +175,7 @@ class SphinxComponentRegistry: yield domain def add_directive_to_domain(self, domain: str, name: str, - cls: "Type[Directive]", override: bool = False) -> None: + cls: Type[Directive], override: bool = False) -> None: logger.debug('[app] adding directive to domain: %r', (domain, name, cls)) if domain not in self.domains: raise ExtensionError(__('domain %s not yet registered') % domain) @@ -198,7 +198,7 @@ class SphinxComponentRegistry: (name, domain)) roles[name] = role - def add_index_to_domain(self, domain: str, index: "Type[Index]", + def add_index_to_domain(self, domain: str, index: Type[Index], override: bool = False) -> None: logger.debug('[app] adding index to domain: %r', (domain, index)) if domain not in self.domains: @@ -210,7 +210,7 @@ class SphinxComponentRegistry: indices.append(index) def add_object_type(self, directivename: str, rolename: str, indextemplate: str = '', - parse_node: Callable = None, ref_nodeclass: "Type[TextElement]" = None, + parse_node: Callable = None, ref_nodeclass: Type[TextElement] = None, objname: str = '', doc_field_types: List = [], override: bool = False ) -> None: logger.debug('[app] adding object type: %r', @@ -234,7 +234,7 @@ class SphinxComponentRegistry: object_types[directivename] = ObjType(objname or directivename, rolename) def add_crossref_type(self, directivename: str, rolename: str, indextemplate: str = '', - ref_nodeclass: "Type[TextElement]" = None, objname: str = '', + ref_nodeclass: Type[TextElement] = None, objname: str = '', override: bool = False) -> None: logger.debug('[app] adding crossref type: %r', (directivename, rolename, indextemplate, ref_nodeclass, objname)) @@ -260,7 +260,7 @@ class SphinxComponentRegistry: else: self.source_suffix[suffix] = filetype - def add_source_parser(self, parser: "Type[Parser]", override: bool = False) -> None: + def add_source_parser(self, parser: Type[Parser], override: bool = False) -> None: logger.debug('[app] adding search source_parser: %r', parser) # create a map from filetype to parser @@ -271,13 +271,13 @@ class SphinxComponentRegistry: else: self.source_parsers[filetype] = parser - def get_source_parser(self, filetype: str) -> "Type[Parser]": + def get_source_parser(self, filetype: str) -> Type[Parser]: try: return self.source_parsers[filetype] except KeyError as exc: raise SphinxError(__('Source parser for %s not registered') % filetype) from exc - def get_source_parsers(self) -> Dict[str, "Type[Parser]"]: + def get_source_parsers(self) -> Dict[str, Type[Parser]]: return self.source_parsers def create_source_parser(self, app: "Sphinx", filename: str) -> Parser: @@ -287,7 +287,10 @@ class SphinxComponentRegistry: parser.set_application(app) return parser - def get_source_input(self, filetype: str) -> "Type[Input]": + def get_source_input(self, filetype: str) -> Optional[Type[Input]]: + warnings.warn('SphinxComponentRegistry.get_source_input() is deprecated.', + RemovedInSphinx60Warning) + try: return self.source_inputs[filetype] except KeyError: @@ -297,14 +300,14 @@ class SphinxComponentRegistry: except KeyError: return None - def add_translator(self, name: str, translator: "Type[nodes.NodeVisitor]", + def add_translator(self, name: str, translator: Type[nodes.NodeVisitor], override: bool = False) -> None: logger.debug('[app] Change of translator for the %s builder.', name) if name in self.translators and not override: raise ExtensionError(__('Translator for %r already exists') % name) self.translators[name] = translator - def add_translation_handlers(self, node: "Type[Element]", + def add_translation_handlers(self, node: Type[Element], **kwargs: Tuple[Callable, Callable]) -> None: logger.debug('[app] adding translation_handlers: %r, %r', node, kwargs) for builder_name, handlers in kwargs.items(): @@ -318,7 +321,7 @@ class SphinxComponentRegistry: 'function tuple: %r=%r') % (builder_name, handlers) ) from exc - def get_translator_class(self, builder: Builder) -> "Type[nodes.NodeVisitor]": + def get_translator_class(self, builder: Builder) -> Type[nodes.NodeVisitor]: return self.translators.get(builder.name, builder.default_translator_class) @@ -340,24 +343,24 @@ class SphinxComponentRegistry: return translator - def add_transform(self, transform: "Type[Transform]") -> None: + def add_transform(self, transform: Type[Transform]) -> None: logger.debug('[app] adding transform: %r', transform) self.transforms.append(transform) - def get_transforms(self) -> List["Type[Transform]"]: + def get_transforms(self) -> List[Type[Transform]]: return self.transforms - def add_post_transform(self, transform: "Type[Transform]") -> None: + def add_post_transform(self, transform: Type[Transform]) -> None: logger.debug('[app] adding post transform: %r', transform) self.post_transforms.append(transform) - def get_post_transforms(self) -> List["Type[Transform]"]: + def get_post_transforms(self) -> List[Type[Transform]]: return self.post_transforms - def add_documenter(self, objtype: str, documenter: "Type[Documenter]") -> None: + def add_documenter(self, objtype: str, documenter: Type["Documenter"]) -> None: self.documenters[objtype] = documenter - def add_autodoc_attrgetter(self, typ: "Type", + def add_autodoc_attrgetter(self, typ: Type, attrgetter: Callable[[Any, str, Any], Any]) -> None: self.autodoc_attrgettrs[typ] = attrgetter @@ -382,7 +385,7 @@ class SphinxComponentRegistry: else: self.latex_packages.append((name, options)) - def add_enumerable_node(self, node: "Type[Node]", figtype: str, + def add_enumerable_node(self, node: Type[Node], figtype: str, title_getter: TitleGetter = None, override: bool = False) -> None: logger.debug('[app] adding enumerable node: (%r, %r, %r)', node, figtype, title_getter) if node in self.enumerable_nodes and not override: @@ -424,7 +427,7 @@ class SphinxComponentRegistry: if setup is None: logger.warning(__('extension %r has no setup() function; is it really ' 'a Sphinx extension module?'), extname) - metadata = {} # type: Dict[str, Any] + metadata: Dict[str, Any] = {} else: try: metadata = setup(app) diff --git a/sphinx/roles.py b/sphinx/roles.py index 4f9261360..e194db5bc 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -9,25 +9,18 @@ """ import re -import warnings -from typing import Any, Dict, List, Tuple +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type from docutils import nodes, utils from docutils.nodes import Element, Node, TextElement, system_message -from docutils.parsers.rst.states import Inliner from sphinx import addnodes -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import _ from sphinx.util import ws_re from sphinx.util.docutils import ReferenceRole, SphinxRole -from sphinx.util.nodes import process_index_entry, set_role_source_info, split_explicit_title from sphinx.util.typing import RoleFunction -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.environment import BuildEnvironment @@ -72,11 +65,11 @@ class XRefRole(ReferenceRole): * Subclassing and overwriting `process_link()` and/or `result_nodes()`. """ - nodeclass = addnodes.pending_xref # type: Type[Element] - innernodeclass = nodes.literal # type: Type[TextElement] + nodeclass: Type[Element] = addnodes.pending_xref + innernodeclass: Type[TextElement] = nodes.literal def __init__(self, fix_parens: bool = False, lowercase: bool = False, - nodeclass: "Type[Element]" = None, innernodeclass: "Type[TextElement]" = None, + nodeclass: Type[Element] = None, innernodeclass: Type[TextElement] = None, warn_dangling: bool = False) -> None: self.fix_parens = fix_parens self.lowercase = lowercase @@ -88,22 +81,6 @@ class XRefRole(ReferenceRole): super().__init__() - def _fix_parens(self, env: "BuildEnvironment", has_explicit_title: bool, title: str, - target: str) -> Tuple[str, str]: - warnings.warn('XRefRole._fix_parens() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - if not has_explicit_title: - if title.endswith('()'): - # remove parentheses - title = title[:-2] - if env.config.add_function_parentheses: - # add them back to all occurrences if configured - title += '()' - # remove parentheses from the target too - if target.endswith('()'): - target = target[:-2] - return title, target - def update_title_and_target(self, title: str, target: str) -> Tuple[str, str]: if not self.has_explicit_title: if title.endswith('()'): @@ -194,75 +171,6 @@ class AnyXRefRole(XRefRole): return result -def indexmarkup_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner, - options: Dict = {}, content: List[str] = [] - ) -> Tuple[List[Node], List[system_message]]: - """Role for PEP/RFC references that generate an index entry.""" - warnings.warn('indexmarkup_role() is deprecated. Please use PEP or RFC class instead.', - RemovedInSphinx40Warning, stacklevel=2) - env = inliner.document.settings.env - if not typ: - assert env.temp_data['default_role'] - typ = env.temp_data['default_role'].lower() - else: - typ = typ.lower() - - has_explicit_title, title, target = split_explicit_title(text) - title = utils.unescape(title) - target = utils.unescape(target) - targetid = 'index-%s' % env.new_serialno('index') - indexnode = addnodes.index() - targetnode = nodes.target('', '', ids=[targetid]) - inliner.document.note_explicit_target(targetnode) - if typ == 'pep': - indexnode['entries'] = [ - ('single', _('Python Enhancement Proposals; PEP %s') % target, - targetid, '', None)] - anchor = '' - anchorindex = target.find('#') - if anchorindex > 0: - target, anchor = target[:anchorindex], target[anchorindex:] - if not has_explicit_title: - title = "PEP " + utils.unescape(title) - try: - pepnum = int(target) - except ValueError: - msg = inliner.reporter.error('invalid PEP number %s' % target, - line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - ref = inliner.document.settings.pep_base_url + 'pep-%04d' % pepnum - sn = nodes.strong(title, title) - rn = nodes.reference('', '', internal=False, refuri=ref + anchor, - classes=[typ]) - rn += sn - return [indexnode, targetnode, rn], [] - elif typ == 'rfc': - indexnode['entries'] = [ - ('single', 'RFC; RFC %s' % target, targetid, '', None)] - anchor = '' - anchorindex = target.find('#') - if anchorindex > 0: - target, anchor = target[:anchorindex], target[anchorindex:] - if not has_explicit_title: - title = "RFC " + utils.unescape(title) - try: - rfcnum = int(target) - except ValueError: - msg = inliner.reporter.error('invalid RFC number %s' % target, - line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum - sn = nodes.strong(title, title) - rn = nodes.reference('', '', internal=False, refuri=ref + anchor, - classes=[typ]) - rn += sn - return [indexnode, targetnode, rn], [] - else: - raise ValueError('unknown role type: %s' % typ) - - class PEP(ReferenceRole): def run(self) -> Tuple[List[Node], List[system_message]]: target_id = 'index-%s' % self.env.new_serialno('index') @@ -335,44 +243,6 @@ class RFC(ReferenceRole): _amp_re = re.compile(r'(?<!&)&(?![&\s])') -def menusel_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner, - options: Dict = {}, content: List[str] = [] - ) -> Tuple[List[Node], List[system_message]]: - warnings.warn('menusel_role() is deprecated. ' - 'Please use MenuSelection or GUILabel class instead.', - RemovedInSphinx40Warning, stacklevel=2) - env = inliner.document.settings.env - if not typ: - assert env.temp_data['default_role'] - typ = env.temp_data['default_role'].lower() - else: - typ = typ.lower() - - text = utils.unescape(text) - if typ == 'menuselection': - text = text.replace('-->', '\N{TRIANGULAR BULLET}') - spans = _amp_re.split(text) - - node = nodes.inline(rawtext=rawtext) - for i, span in enumerate(spans): - span = span.replace('&&', '&') - if i == 0: - if len(span) > 0: - textnode = nodes.Text(span) - node += textnode - continue - accel_node = nodes.inline() - letter_node = nodes.Text(span[0]) - accel_node += letter_node - accel_node['classes'].append('accelerator') - node += accel_node - textnode = nodes.Text(span[1:]) - node += textnode - - node['classes'].append(typ) - return [node], [] - - class GUILabel(SphinxRole): amp_re = re.compile(r'(?<!&)&(?![&\s])') @@ -403,59 +273,6 @@ _litvar_re = re.compile('{([^}]+)}') parens_re = re.compile(r'(\\*{|\\*})') -def emph_literal_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner, - options: Dict = {}, content: List[str] = [] - ) -> Tuple[List[Node], List[system_message]]: - warnings.warn('emph_literal_role() is deprecated. ' - 'Please use EmphasizedLiteral class instead.', - RemovedInSphinx40Warning, stacklevel=2) - env = inliner.document.settings.env - if not typ: - assert env.temp_data['default_role'] - typ = env.temp_data['default_role'].lower() - else: - typ = typ.lower() - - retnode = nodes.literal(role=typ.lower(), classes=[typ]) - parts = list(parens_re.split(utils.unescape(text))) - stack = [''] - for part in parts: - matched = parens_re.match(part) - if matched: - backslashes = len(part) - 1 - if backslashes % 2 == 1: # escaped - stack[-1] += "\\" * int((backslashes - 1) / 2) + part[-1] - elif part[-1] == '{': # rparen - stack[-1] += "\\" * int(backslashes / 2) - if len(stack) >= 2 and stack[-2] == "{": - # nested - stack[-1] += "{" - else: - # start emphasis - stack.append('{') - stack.append('') - else: # lparen - stack[-1] += "\\" * int(backslashes / 2) - if len(stack) == 3 and stack[1] == "{" and len(stack[2]) > 0: - # emphasized word found - if stack[0]: - retnode += nodes.Text(stack[0], stack[0]) - retnode += nodes.emphasis(stack[2], stack[2]) - stack = [''] - else: - # emphasized word not found; the rparen is not a special symbol - stack.append('}') - stack = [''.join(stack)] - else: - stack[-1] += part - if ''.join(stack): - # remaining is treated as Text - text = ''.join(stack) - retnode += nodes.Text(text, text) - - return [retnode], [] - - class EmphasizedLiteral(SphinxRole): parens_re = re.compile(r'(\\\\|\\{|\\}|{|})') @@ -467,7 +284,7 @@ class EmphasizedLiteral(SphinxRole): return [node], [] def parse(self, text: str) -> List[Node]: - result = [] # type: List[Node] + result: List[Node] = [] stack = [''] for part in self.parens_re.split(text): @@ -509,22 +326,6 @@ class EmphasizedLiteral(SphinxRole): _abbr_re = re.compile(r'\((.*)\)$', re.S) -def abbr_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner, - options: Dict = {}, content: List[str] = [] - ) -> Tuple[List[Node], List[system_message]]: - warnings.warn('abbr_role() is deprecated. Please use Abbrevation class instead.', - RemovedInSphinx40Warning, stacklevel=2) - text = utils.unescape(text) - m = _abbr_re.search(text) - if m is None: - return [nodes.abbreviation(text, text, **options)], [] - abbr = text[:m.start()].strip() - expl = m.group(1) - options = options.copy() - options['explanation'] = expl - return [nodes.abbreviation(abbr, abbr, **options)], [] - - class Abbreviation(SphinxRole): abbr_re = re.compile(r'\((.*)\)$', re.S) @@ -540,63 +341,7 @@ class Abbreviation(SphinxRole): return [nodes.abbreviation(self.rawtext, text, **options)], [] -def index_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner, - options: Dict = {}, content: List[str] = [] - ) -> Tuple[List[Node], List[system_message]]: - warnings.warn('index_role() is deprecated. Please use Index class instead.', - RemovedInSphinx40Warning, stacklevel=2) - # create new reference target - env = inliner.document.settings.env - targetid = 'index-%s' % env.new_serialno('index') - targetnode = nodes.target('', '', ids=[targetid]) - # split text and target in role content - has_explicit_title, title, target = split_explicit_title(text) - title = utils.unescape(title) - target = utils.unescape(target) - # if an explicit target is given, we can process it as a full entry - if has_explicit_title: - entries = process_index_entry(target, targetid) - # otherwise we just create a "single" entry - else: - # but allow giving main entry - main = '' - if target.startswith('!'): - target = target[1:] - title = title[1:] - main = 'main' - entries = [('single', target, targetid, main, None)] - indexnode = addnodes.index() - indexnode['entries'] = entries - set_role_source_info(inliner, lineno, indexnode) - textnode = nodes.Text(title, title) - return [indexnode, targetnode, textnode], [] - - -class Index(ReferenceRole): - def run(self) -> Tuple[List[Node], List[system_message]]: - warnings.warn('Index role is deprecated.', RemovedInSphinx40Warning, stacklevel=2) - target_id = 'index-%s' % self.env.new_serialno('index') - if self.has_explicit_title: - # if an explicit target is given, process it as a full entry - title = self.title - entries = process_index_entry(self.target, target_id) - else: - # otherwise we just create a single entry - if self.target.startswith('!'): - title = self.title[1:] - entries = [('single', self.target[1:], target_id, 'main', None)] - else: - title = self.title - entries = [('single', self.target, target_id, '', None)] - - index = addnodes.index(entries=entries) - target = nodes.target('', '', ids=[target_id]) - text = nodes.Text(title, title) - self.set_source_info(index) - return [index, target, text], [] - - -specific_docroles = { +specific_docroles: Dict[str, RoleFunction] = { # links to download references 'download': XRefRole(nodeclass=addnodes.download_reference), # links to anything @@ -609,7 +354,7 @@ specific_docroles = { 'file': EmphasizedLiteral(), 'samp': EmphasizedLiteral(), 'abbr': Abbreviation(), -} # type: Dict[str, RoleFunction] +} def setup(app: "Sphinx") -> Dict[str, Any]: diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index cd099a6b5..2a3420244 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -10,24 +10,18 @@ import html import pickle import re -import warnings from importlib import import_module from os import path -from typing import IO, Any, Dict, Iterable, List, Set, Tuple +from typing import IO, Any, Dict, Iterable, List, Set, Tuple, Type from docutils import nodes from docutils.nodes import Node from sphinx import addnodes, package_dir -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.environment import BuildEnvironment from sphinx.search.jssplitter import splitter_code from sphinx.util import jsdump -if False: - # For type annotation - from typing import Type # for python3.5.1 - class SearchLanguage: """ @@ -59,11 +53,11 @@ class SearchLanguage: This class is used to preprocess search word which Sphinx HTML readers type, before searching index. Default implementation does nothing. """ - lang = None # type: str - language_name = None # type: str - stopwords = set() # type: Set[str] - js_splitter_code = None # type: str - js_stemmer_rawcode = None # type: str + lang: str = None + language_name: str = None + stopwords: Set[str] = set() + js_splitter_code: str = None + js_stemmer_rawcode: str = None js_stemmer_code = """ /** * Dummy stemmer for languages without stemming rules. @@ -116,7 +110,7 @@ var Stemmer = function() { len(word) == 0 or not ( ((len(word) < 3) and (12353 < ord(word[0]) < 12436)) or (ord(word[0]) < 256 and ( - len(word) < 3 or word in self.stopwords + word in self.stopwords )))) @@ -130,7 +124,7 @@ def parse_stop_word(source: str) -> Set[str]: * http://snowball.tartarus.org/algorithms/finnish/stop.txt """ - result = set() # type: Set[str] + result: Set[str] = set() for line in source.splitlines(): line = line.split('|')[0] # remove comment result.update(line.split()) @@ -138,7 +132,7 @@ def parse_stop_word(source: str) -> Set[str]: # maps language name to module.class or directly a class -languages = { +languages: Dict[str, Any] = { 'da': 'sphinx.search.da.SearchDanish', 'de': 'sphinx.search.de.SearchGerman', 'en': SearchEnglish, @@ -156,7 +150,7 @@ languages = { 'sv': 'sphinx.search.sv.SearchSwedish', 'tr': 'sphinx.search.tr.SearchTurkish', 'zh': 'sphinx.search.zh.SearchChinese', -} # type: Dict[str, Any] +} class _JavaScriptIndex: @@ -195,15 +189,11 @@ class WordCollector(nodes.NodeVisitor): def __init__(self, document: nodes.document, lang: SearchLanguage) -> None: super().__init__(document) - self.found_words = [] # type: List[str] - self.found_title_words = [] # type: List[str] + self.found_words: List[str] = [] + self.found_title_words: List[str] = [] self.lang = lang - def is_meta_keywords(self, node: addnodes.meta, nodetype: Any = None) -> bool: - if nodetype is not None: - warnings.warn('"nodetype" argument for WordCollector.is_meta_keywords() ' - 'is deprecated.', RemovedInSphinx40Warning, stacklevel=2) - + def is_meta_keywords(self, node: addnodes.meta) -> bool: if isinstance(node, addnodes.meta) and node.get('name') == 'keywords': meta_lang = node.get('lang') if meta_lang is None: # lang not specified @@ -248,29 +238,24 @@ class IndexBuilder: def __init__(self, env: BuildEnvironment, lang: str, options: Dict, scoring: str) -> None: self.env = env - self._titles = {} # type: Dict[str, str] - # docname -> title - self._filenames = {} # type: Dict[str, str] - # docname -> filename - self._mapping = {} # type: Dict[str, Set[str]] - # stemmed word -> set(docname) - self._title_mapping = {} # type: Dict[str, Set[str]] - # stemmed words in titles -> set(docname) - self._stem_cache = {} # type: Dict[str, str] - # word -> stemmed word - self._objtypes = {} # type: Dict[Tuple[str, str], int] - # objtype -> index - self._objnames = {} # type: Dict[int, Tuple[str, str, str]] - # objtype index -> (domain, type, objname (localized)) - lang_class = languages.get(lang) # type: Type[SearchLanguage] - # add language-specific SearchLanguage instance + self._titles: Dict[str, str] = {} # docname -> title + self._filenames: Dict[str, str] = {} # docname -> filename + self._mapping: Dict[str, Set[str]] = {} # stemmed word -> set(docname) + # stemmed words in titles -> set(docname) + self._title_mapping: Dict[str, Set[str]] = {} + self._stem_cache: Dict[str, str] = {} # word -> stemmed word + self._objtypes: Dict[Tuple[str, str], int] = {} # objtype -> index + # objtype index -> (domain, type, objname (localized)) + self._objnames: Dict[int, Tuple[str, str, str]] = {} + # add language-specific SearchLanguage instance + lang_class: Type[SearchLanguage] = languages.get(lang) # fallback; try again with language-code if lang_class is None and '_' in lang: lang_class = languages.get(lang.split('_')[0]) if lang_class is None: - self.lang = SearchEnglish(options) # type: SearchLanguage + self.lang: SearchLanguage = SearchEnglish(options) elif isinstance(lang_class, str): module, classname = lang_class.rsplit('.', 1) lang_class = getattr(import_module(module), classname) @@ -320,7 +305,7 @@ class IndexBuilder: def get_objects(self, fn2index: Dict[str, int] ) -> Dict[str, Dict[str, Tuple[int, int, int, str]]]: - rv = {} # type: Dict[str, Dict[str, Tuple[int, int, int, str]]] + rv: Dict[str, Dict[str, Tuple[int, int, int, str]]] = {} otypes = self._objtypes onames = self._objnames for domainname, domain in sorted(self.env.domains.items()): @@ -356,7 +341,7 @@ class IndexBuilder: return rv def get_terms(self, fn2index: Dict) -> Tuple[Dict[str, List[str]], Dict[str, List[str]]]: - rvs = {}, {} # type: Tuple[Dict[str, List[str]], Dict[str, List[str]]] + rvs: Tuple[Dict[str, List[str]], Dict[str, List[str]]] = ({}, {}) for rv, mapping in zip(rvs, (self._mapping, self._title_mapping)): for k, v in mapping.items(): if len(v) == 1: diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py index 6f10a4239..e739f1d16 100644 --- a/sphinx/search/ja.py +++ b/sphinx/search/ja.py @@ -54,8 +54,8 @@ class BaseSplitter: class MecabSplitter(BaseSplitter): def __init__(self, options: Dict) -> None: super().__init__(options) - self.ctypes_libmecab = None # type: Any - self.ctypes_mecab = None # type: Any + self.ctypes_libmecab: Any = None + self.ctypes_mecab: Any = None if not native_module: self.init_ctypes(options) else: diff --git a/sphinx/search/ro.py b/sphinx/search/ro.py index 721f888cc..7b592d1c8 100644 --- a/sphinx/search/ro.py +++ b/sphinx/search/ro.py @@ -19,7 +19,7 @@ class SearchRomanian(SearchLanguage): lang = 'ro' language_name = 'Romanian' js_stemmer_rawcode = 'romanian-stemmer.js' - stopwords = set() # type: Set[str] + stopwords: Set[str] = set() def init(self, options: Dict) -> None: self.stemmer = snowballstemmer.stemmer('romanian') diff --git a/sphinx/search/tr.py b/sphinx/search/tr.py index 644e8a534..8d9d1fade 100644 --- a/sphinx/search/tr.py +++ b/sphinx/search/tr.py @@ -19,7 +19,7 @@ class SearchTurkish(SearchLanguage): lang = 'tr' language_name = 'Turkish' js_stemmer_rawcode = 'turkish-stemmer.js' - stopwords = set() # type: Set[str] + stopwords: Set[str] = set() def init(self, options: Dict) -> None: self.stemmer = snowballstemmer.stemmer('turkish') diff --git a/sphinx/search/zh.py b/sphinx/search/zh.py index 6948a6ad4..747152505 100644 --- a/sphinx/search/zh.py +++ b/sphinx/search/zh.py @@ -230,7 +230,7 @@ class SearchChinese(SearchLanguage): js_stemmer_code = js_porter_stemmer stopwords = english_stopwords latin1_letters = re.compile(r'[a-zA-Z0-9_]+') - latin_terms = [] # type: List[str] + latin_terms: List[str] = [] def init(self, options: Dict) -> None: if JIEBA: @@ -241,7 +241,7 @@ class SearchChinese(SearchLanguage): self.stemmer = get_stemmer() def split(self, input: str) -> List[str]: - chinese = [] # type: List[str] + chinese: List[str] = [] if JIEBA: chinese = list(jieba.cut_for_search(input)) diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py index 0a30ff82f..62c83a911 100644 --- a/sphinx/setup_command.py +++ b/sphinx/setup_command.py @@ -16,6 +16,7 @@ import sys from distutils.cmd import Command from distutils.errors import DistutilsExecError from io import StringIO +from typing import Any, Dict from sphinx.application import Sphinx from sphinx.cmd.build import handle_exception @@ -23,10 +24,6 @@ from sphinx.util.console import color_terminal, nocolor from sphinx.util.docutils import docutils_namespace, patch_docutils from sphinx.util.osutil import abspath -if False: - # For type annotation - from typing import Any, Dict # NOQA - class BuildDoc(Command): """ @@ -91,18 +88,18 @@ class BuildDoc(Command): boolean_options = ['fresh-env', 'all-files', 'warning-is-error', 'link-index', 'nitpicky'] - def initialize_options(self): - # type: () -> None + def initialize_options(self) -> None: self.fresh_env = self.all_files = False self.pdb = False - self.source_dir = self.build_dir = None # type: str + self.source_dir: str = None + self.build_dir: str = None self.builder = 'html' self.warning_is_error = False self.project = '' self.version = '' self.release = '' self.today = '' - self.config_dir = None # type: str + self.config_dir: str = None self.link_index = False self.copyright = '' # Link verbosity to distutils' (which uses 1 by default). @@ -111,8 +108,7 @@ class BuildDoc(Command): self.nitpicky = False self.keep_going = False - def _guess_source_dir(self): - # type: () -> str + def _guess_source_dir(self) -> str: for guess in ('doc', 'docs'): if not os.path.isdir(guess): continue @@ -121,8 +117,7 @@ class BuildDoc(Command): return root return os.curdir - def finalize_options(self): - # type: () -> None + def finalize_options(self) -> None: self.ensure_string_list('builder') if self.source_dir is None: @@ -144,15 +139,14 @@ class BuildDoc(Command): (builder, os.path.join(self.build_dir, builder)) for builder in self.builder] - def run(self): - # type: () -> None + def run(self) -> None: if not color_terminal(): nocolor() if not self.verbose: # type: ignore status_stream = StringIO() else: status_stream = sys.stdout # type: ignore - confoverrides = {} # type: Dict[str, Any] + confoverrides: Dict[str, Any] = {} if self.project: confoverrides['project'] = self.project if self.version: @@ -190,6 +184,6 @@ class BuildDoc(Command): if not self.link_index: continue - src = app.config.master_doc + app.builder.out_suffix # type: ignore + src = app.config.root_doc + app.builder.out_suffix # type: ignore dst = app.builder.get_outfilename('index') # type: ignore os.symlink(src, dst) diff --git a/sphinx/templates/htmlhelp/project.hhc b/sphinx/templates/htmlhelp/project.hhc index c1096e711..705cfeba7 100644 --- a/sphinx/templates/htmlhelp/project.hhc +++ b/sphinx/templates/htmlhelp/project.hhc @@ -18,7 +18,7 @@ </OBJECT> <UL> <LI> - {{ sitemap(short_title, master_doc)|indent(8) }} + {{ sitemap(short_title, root_doc)|indent(8) }} </LI> {%- for indexname, indexcls, content, collapse in domain_indices %} <LI> diff --git a/sphinx/templates/htmlhelp/project.hhp b/sphinx/templates/htmlhelp/project.hhp index b647b3713..17edc1f3d 100644 --- a/sphinx/templates/htmlhelp/project.hhp +++ b/sphinx/templates/htmlhelp/project.hhp @@ -4,7 +4,7 @@ Binary Index=No Compiled file={{ outname }}.chm Contents file={{ outname }}.hhc Default Window={{ outname }} -Default topic={{ master_doc }} +Default topic={{ root_doc }} Display compile progress=No Full text search stop list file={{ outname }}.stp Full-text search=Yes @@ -13,7 +13,7 @@ Language={{ "%#x"|format(lcid) }} Title={{ title }} [WINDOWS] -{{ outname }}="{{ title }}","{{ outname }}.hhc","{{ outname }}.hhk","{{ master_doc }}","{{ master_doc }}",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0 +{{ outname }}="{{ title }}","{{ outname }}.hhc","{{ outname }}.hhk","{{ root_doc }}","{{ root_doc }}",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0 [FILES] {%- for filename in files %} diff --git a/sphinx/templates/latex/latex.tex_t b/sphinx/templates/latex/latex.tex_t index e288dda93..a9fba98d0 100644 --- a/sphinx/templates/latex/latex.tex_t +++ b/sphinx/templates/latex/latex.tex_t @@ -13,6 +13,11 @@ \ifdefined\pdfpxdimen \let\sphinxpxdimen\pdfpxdimen\else\newdimen\sphinxpxdimen \fi \sphinxpxdimen=<%= pxunit %>\relax +\ifdefined\pdfimageresolution + \pdfimageresolution= \numexpr \dimexpr1in\relax/\sphinxpxdimen\relax +\fi +%% let collapsable pdf bookmarks panel have high depth per default +\PassOptionsToPackage{bookmarksdepth=5}{hyperref} <% if use_xindy -%> %% turn off hyperref patch of \index as sphinx.xdy xindy module takes care of %% suitable \hyperpage mark-up, working around hyperref-xindy incompatibility @@ -32,6 +37,7 @@ <%= substitutefont %> <%= textcyrillic %> <%= fontpkg %> +<%= fontsubstitution %> <%= textgreek %> <%= fncychap %> \usepackage<%= sphinxpkgoptions %>{sphinx} @@ -65,7 +71,7 @@ \title{<%= title %>} \date{<%= date %>} -\release{<%= release %>} +\release{<%= release | e %>} \author{<%= author %>} <%- if logofilename %> \newcommand{\sphinxlogo}{\sphinxincludegraphics{<%= logofilename %>}\par} diff --git a/sphinx/templates/quickstart/Makefile_t b/sphinx/templates/quickstart/Makefile_t index af9c56304..14b2dc594 100644 --- a/sphinx/templates/quickstart/Makefile_t +++ b/sphinx/templates/quickstart/Makefile_t @@ -46,6 +46,7 @@ help: @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" + @echo " clean to remove everything in the build directory" .PHONY: clean clean: diff --git a/sphinx/templates/quickstart/conf.py_t b/sphinx/templates/quickstart/conf.py_t index 8a20fc4c8..f1da41c4a 100644 --- a/sphinx/templates/quickstart/conf.py_t +++ b/sphinx/templates/quickstart/conf.py_t @@ -64,9 +64,9 @@ templates_path = ['{{ dot }}templates'] source_suffix = {{ suffix | repr }} {% endif -%} -{% if master != 'index' -%} -# The master toctree document. -master_doc = {{ master | repr }} +{% if root_doc != 'index' -%} +# The root document. +root_doc = {{ root_doc | repr }} {% endif -%} {% if language -%} @@ -105,7 +105,9 @@ html_static_path = ['{{ dot }}static'] # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/3/': None} +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), +} {%- endif %} {%- if 'sphinx.ext.todo' in extensions %} diff --git a/sphinx/templates/quickstart/make.bat_t b/sphinx/templates/quickstart/make.bat_t index 830cf656e..e5d93d1ae 100644 --- a/sphinx/templates/quickstart/make.bat_t +++ b/sphinx/templates/quickstart/make.bat_t @@ -43,6 +43,7 @@ if "%1" == "help" ( echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled echo. dummy to check syntax errors of document sources + echo. clean to remove everything in the build directory goto end ) diff --git a/sphinx/templates/quickstart/master_doc.rst_t b/sphinx/templates/quickstart/root_doc.rst_t index 3aa98af08..3aa98af08 100644 --- a/sphinx/templates/quickstart/master_doc.rst_t +++ b/sphinx/templates/quickstart/root_doc.rst_t diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index 1cf66283f..729d9a2d7 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -8,7 +8,6 @@ :license: BSD, see LICENSE for details. """ -import os import subprocess import sys from collections import namedtuple @@ -42,7 +41,7 @@ def rootdir() -> str: class SharedResult: - cache = {} # type: Dict[str, Dict[str, str]] + cache: Dict[str, Dict[str, str]] = {} def store(self, key: str, app_: SphinxTestApp) -> Any: if key in self.cache: @@ -78,7 +77,7 @@ def app_params(request: Any, test_params: Dict, shared_result: SharedResult, else: markers = request.node.get_marker("sphinx") pargs = {} - kwargs = {} # type: Dict[str, Any] + kwargs: Dict[str, Any] = {} if markers is not None: # to avoid stacking positional args @@ -90,7 +89,6 @@ def app_params(request: Any, test_params: Dict, shared_result: SharedResult, args = [pargs[i] for i in sorted(pargs.keys())] # ##### process pytest.mark.test_params - if test_params['shared_result']: if 'srcdir' in kwargs: raise pytest.Exception('You can not specify shared_result and ' @@ -192,7 +190,7 @@ def make_app(test_params: Dict, monkeypatch: Any) -> Generator[Callable, None, N status, warning = StringIO(), StringIO() kwargs.setdefault('status', status) kwargs.setdefault('warning', warning) - app_ = SphinxTestApp(*args, **kwargs) # type: Any + app_: Any = SphinxTestApp(*args, **kwargs) apps.append(app_) if test_params['shared_result']: app_ = SphinxTestAppWrapperForSkipBuilding(app_) @@ -236,10 +234,7 @@ def sphinx_test_tempdir(tmpdir_factory: Any) -> "util.path": """ temporary directory that wrapped with `path` class. """ - tmpdir = os.environ.get('SPHINX_TEST_TEMPDIR') # RemovedInSphinx40Warning - if tmpdir is None: - tmpdir = tmpdir_factory.getbasetemp() - + tmpdir = tmpdir_factory.getbasetemp() return util.path(tmpdir).abspath() diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 7d0cde143..bfbb1071c 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -21,15 +21,12 @@ from docutils.nodes import Node from docutils.parsers.rst import directives, roles from sphinx import application, locale -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.pycode import ModuleAnalyzer from sphinx.testing.path import path from sphinx.util.osutil import relpath __all__ = [ - 'Struct', - 'SphinxTestApp', 'SphinxTestAppWrapperForSkipBuilding', - 'remove_unicode_literals', + 'Struct', 'SphinxTestApp', 'SphinxTestAppWrapperForSkipBuilding', ] @@ -102,8 +99,8 @@ class SphinxTestApp(application.Sphinx): A subclass of :class:`Sphinx` that runs on the test root, with some better default values for the initialization parameters. """ - _status = None # type: StringIO - _warning = None # type: StringIO + _status: StringIO = None + _warning: StringIO = None def __init__(self, buildername: str = 'html', srcdir: path = None, freshenv: bool = False, confoverrides: Dict = None, status: IO = None, warning: IO = None, @@ -177,12 +174,6 @@ class SphinxTestAppWrapperForSkipBuilding: _unicode_literals_re = re.compile(r'u(".*?")|u(\'.*?\')') -def remove_unicode_literals(s: str) -> str: - warnings.warn('remove_unicode_literals() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - return _unicode_literals_re.sub(lambda x: x.group(1) or x.group(2), s) - - def find_files(root: str, suffix: bool = None) -> Generator[str, None, None]: for dirpath, dirs, files in os.walk(root, followlinks=True): dirpath = path(dirpath) diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index e6c27c123..108c18b64 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -6,7 +6,7 @@ % \NeedsTeXFormat{LaTeX2e}[1995/12/01] -\ProvidesPackage{sphinx}[2021/01/23 v3.5.0 LaTeX package (Sphinx markup)] +\ProvidesPackage{sphinx}[2021/01/27 v4.0.0 LaTeX package (Sphinx markup)] % provides \ltx@ifundefined % (many packages load ltxcmds: graphicx does for pdftex and lualatex but @@ -31,262 +31,20 @@ }} -%% PACKAGES -% -% we delay handling of options to after having loaded packages, because -% of the need to use \definecolor. -\RequirePackage{graphicx} -\@ifclassloaded{memoir}{}{\RequirePackage{fancyhdr}} -% for \text macro and \iffirstchoice@ conditional even if amsmath not loaded -\RequirePackage{amstext} -\RequirePackage{textcomp}% "warn" option issued from template -\RequirePackage[nobottomtitles*]{titlesec} -\@ifpackagelater{titlesec}{2016/03/15}% - {\@ifpackagelater{titlesec}{2016/03/21}% - {}% - {\newif\ifsphinx@ttlpatch@ok - \IfFileExists{etoolbox.sty}{% - \RequirePackage{etoolbox}% - \patchcmd{\ttlh@hang}{\parindent\z@}{\parindent\z@\leavevmode}% - {\sphinx@ttlpatch@oktrue}{}% - \ifsphinx@ttlpatch@ok - \patchcmd{\ttlh@hang}{\noindent}{}{}{\sphinx@ttlpatch@okfalse}% - \fi - }{}% - \ifsphinx@ttlpatch@ok - \typeout{^^J Package Sphinx Info: ^^J - **** titlesec 2.10.1 successfully patched for bugfix ****^^J}% - \else - \AtEndDocument{\PackageWarningNoLine{sphinx}{^^J% -******** titlesec 2.10.1 has a bug, (section numbers disappear) ......|^^J% -******** and Sphinx could not patch it, perhaps because your local ...|^^J% -******** copy is already fixed without a changed release date. .......|^^J% -******** If not, you must update titlesec! ...........................|}}% - \fi - }% - }{} -\RequirePackage{tabulary} -% tabulary has a bug with its re-definition of \multicolumn in its first pass -% which is not \long. But now Sphinx does not use LaTeX's \multicolumn but its -% own macro. Hence we don't even need to patch tabulary. See sphinxmulticell.sty -% X or S (Sphinx) may have meanings if some table package is loaded hence -% \X was chosen to avoid possibility of conflict -\newcolumntype{\X}[2]{p{\dimexpr - (\linewidth-\arrayrulewidth)*#1/#2-\tw@\tabcolsep-\arrayrulewidth\relax}} -\newcolumntype{\Y}[1]{p{\dimexpr - #1\dimexpr\linewidth-\arrayrulewidth\relax-\tw@\tabcolsep-\arrayrulewidth\relax}} -% using here T (for Tabulary) feels less of a problem than the X could be -\newcolumntype{T}{J}% -% For tables allowing pagebreaks -\RequirePackage{longtable} -% User interface to set-up whitespace before and after tables: -\newcommand*\sphinxtablepre {0pt}% -\newcommand*\sphinxtablepost{\medskipamount}% -% Space from caption baseline to top of table or frame of literal-block -\newcommand*\sphinxbelowcaptionspace{.5\sphinxbaselineskip}% -% as one can not use \baselineskip from inside longtable (it is zero there) -% we need \sphinxbaselineskip, which defaults to \baselineskip -\def\sphinxbaselineskip{\baselineskip}% -% The following is to ensure that, whether tabular(y) or longtable: -% - if a caption is on top of table: -% a) the space between its last baseline and the top rule of table is -% exactly \sphinxbelowcaptionspace -% b) the space from last baseline of previous text to first baseline of -% caption is exactly \parskip+\baselineskip+ height of a strut. -% c) the caption text will wrap at width \LTcapwidth (4in) -% - make sure this works also if "caption" package is loaded by user -% (with its width or margin option taking place of \LTcapwidth role) -% TODO: obtain same for caption of literal block: a) & c) DONE, b) TO BE DONE +%% OPTION HANDLING % -% To modify space below such top caption, adjust \sphinxbelowcaptionspace -% To add or remove space above such top caption, adjust \sphinxtablepre: -% notice that \abovecaptionskip, \belowcaptionskip, \LTpre are **ignored** -% A. Table with longtable -\def\sphinxatlongtablestart - {\par - \vskip\parskip - \vskip\dimexpr\sphinxtablepre\relax % adjust vertical position - \vbox{}% get correct baseline from above - \LTpre\z@skip\LTpost\z@skip % set to zero longtable's own skips - \edef\sphinxbaselineskip{\dimexpr\the\dimexpr\baselineskip\relax\relax}% - }% -% Compatibility with caption package -\def\sphinxthelongtablecaptionisattop{% - \spx@ifcaptionpackage{\noalign{\vskip-\belowcaptionskip}}{}% -}% -% Achieves exactly \sphinxbelowcaptionspace below longtable caption -\def\sphinxlongtablecapskipadjust - {\dimexpr-\dp\strutbox - -\spx@ifcaptionpackage{\abovecaptionskip}{\sphinxbaselineskip}% - +\sphinxbelowcaptionspace\relax}% -\def\sphinxatlongtableend{\@nobreakfalse % latex3/latex2e#173 - \prevdepth\z@\vskip\sphinxtablepost\relax}% -% B. Table with tabular or tabulary -\def\sphinxattablestart{\par\vskip\dimexpr\sphinxtablepre\relax}% -\let\sphinxattableend\sphinxatlongtableend -% This is used by tabular and tabulary templates -\newcommand*\sphinxcapstartof[1]{% - \vskip\parskip - \vbox{}% force baselineskip for good positioning by capstart of hyperanchor - % hyperref puts the anchor 6pt above this baseline; in case of caption - % this baseline will be \ht\strutbox above first baseline of caption - \def\@captype{#1}% - \capstart -% move back vertically, as tabular (or its caption) will compensate - \vskip-\baselineskip\vskip-\parskip -}% -\def\sphinxthecaptionisattop{% locate it after \sphinxcapstartof - \spx@ifcaptionpackage - {\caption@setposition{t}% - \vskip\baselineskip\vskip\parskip % undo those from \sphinxcapstartof - \vskip-\belowcaptionskip % anticipate caption package skip - % caption package uses a \vbox, not a \vtop, so "single line" case - % gives different result from "multi-line" without this: - \nointerlineskip - }% - {}% -}% -\def\sphinxthecaptionisatbottom{% (not finalized; for template usage) - \spx@ifcaptionpackage{\caption@setposition{b}}{}% -}% -% The aim of \sphinxcaption is to apply to tabular(y) the maximal width -% of caption as done by longtable -\def\sphinxtablecapwidth{\LTcapwidth}% -\newcommand\sphinxcaption{\@dblarg\spx@caption}% -\long\def\spx@caption[#1]#2{% - \noindent\hb@xt@\linewidth{\hss - \vtop{\@tempdima\dimexpr\sphinxtablecapwidth\relax -% don't exceed linewidth for the caption width - \ifdim\@tempdima>\linewidth\hsize\linewidth\else\hsize\@tempdima\fi -% longtable ignores \abovecaptionskip/\belowcaptionskip, so do the same here - \abovecaptionskip\sphinxabovecaptionskip % \z@skip - \belowcaptionskip\sphinxbelowcaptionskip % \z@skip - \caption[{#1}]% - {\strut\ignorespaces#2\ifhmode\unskip\@finalstrut\strutbox\fi}% - }\hss}% - \par\prevdepth\dp\strutbox -}% -\def\sphinxabovecaptionskip{\z@skip}% Do not use! Flagged for removal -\def\sphinxbelowcaptionskip{\z@skip}% Do not use! Flagged for removal -% This wrapper of \abovecaptionskip is used in sphinxVerbatim for top -% caption, and with another value in sphinxVerbatimintable -% TODO: To unify space above caption of a code-block with the one above -% caption of a table/longtable, \abovecaptionskip must not be used -% This auxiliary will get renamed and receive a different meaning -% in future. -\def\spx@abovecaptionskip{\abovecaptionskip}% -% Achieve \sphinxbelowcaptionspace below a caption located above a tabular -% or a tabulary -\newcommand\sphinxaftertopcaption -{% - \spx@ifcaptionpackage - {\par\prevdepth\dp\strutbox\nobreak\vskip-\abovecaptionskip}{\nobreak}% - \vskip\dimexpr\sphinxbelowcaptionspace\relax - \vskip-\baselineskip\vskip-\parskip -}% -% varwidth is crucial for our handling of general contents in merged cells -\RequirePackage{varwidth} -% but addition of a compatibility patch with hyperref is needed -% (tested with varwidth v 0.92 Mar 2009) -\AtBeginDocument {% - \let\@@vwid@Hy@raisedlink\Hy@raisedlink - \long\def\@vwid@Hy@raisedlink#1{\@vwid@wrap{\@@vwid@Hy@raisedlink{#1}}}% - \edef\@vwid@setup{% - \let\noexpand\Hy@raisedlink\noexpand\@vwid@Hy@raisedlink % HYPERREF ! - \unexpanded\expandafter{\@vwid@setup}}% -}% -% Homemade package to handle merged cells -\RequirePackage{sphinxmulticell} -\RequirePackage{makeidx} -% For framing code-blocks and warning type notices, and shadowing topics -\RequirePackage{framed} + +% We first handle options then load packages, but we need \definecolor from +% xcolor/color. + +% FIXME: we should \RequirePackage{xcolor} always now % The xcolor package draws better fcolorboxes around verbatim code \IfFileExists{xcolor.sty}{ \RequirePackage{xcolor} }{ \RequirePackage{color} } -% For highlighted code. -\RequirePackage{fancyvrb} -\define@key{FV}{hllines}{\def\sphinx@verbatim@checkifhl##1{\in@{, ##1,}{#1}}} -% sphinxVerbatim must be usable by third party without requiring hllines set-up -\def\sphinxresetverbatimhllines{\def\sphinx@verbatim@checkifhl##1{\in@false}} -\sphinxresetverbatimhllines -% For hyperlinked footnotes in tables; also for gathering footnotes from -% topic and warning blocks. Also to allow code-blocks in footnotes. -\RequirePackage{footnotehyper-sphinx} -% For the H specifier. Do not \restylefloat{figure}, it breaks Sphinx code -% for allowing figures in tables. -\RequirePackage{float} -% For floating figures in the text. Better to load after float. -\RequirePackage{wrapfig} -% Separate paragraphs by space by default. -\IfFileExists{parskip-2001-04-09.sty}% since September 2018 TeXLive update -% new parskip.sty, but let it rollback to old one. -% hopefully TeX installation not broken and LaTeX kernel not too old - {\RequirePackage{parskip}[=v1]} -% standard one from 1989. Admittedly \section of article/book gives possibly -% anomalous spacing, but we can't require September 2018 release for some time. - {\RequirePackage{parskip}} -% For parsed-literal blocks. -\RequirePackage{alltt} -% Display "real" single quotes in literal blocks. -\RequirePackage{upquote} -% control caption around literal-block -\RequirePackage{capt-of} -\RequirePackage{needspace} -% LaTeX 2018-04-01 and later provides \@removefromreset -\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 -% for non-Japanese: make sure bookmarks are ok also with lualatex - \PassOptionsToPackage{pdfencoding=unicode}{hyperref} -\else - \RequirePackage{atbegshi} - \ifx\ucs\@undefined - \ifnum 42146=\euc"A4A2 - \AtBeginShipoutFirst{\special{pdf:tounicode EUC-UCS2}} - \else - \AtBeginShipoutFirst{\special{pdf:tounicode 90ms-RKSJ-UCS2}} - \fi - \else - \AtBeginShipoutFirst{\special{pdf:tounicode UTF8-UCS2}} - \fi -\fi - -\ifx\@jsc@uplatextrue\@undefined\else - \PassOptionsToPackage{setpagesize=false}{hyperref} -\fi - -% These options can be overriden inside 'hyperref' key -% or by later use of \hypersetup. -\PassOptionsToPackage{colorlinks,breaklinks,% - linkcolor=InnerLinkColor,filecolor=OuterLinkColor,% - menucolor=OuterLinkColor,urlcolor=OuterLinkColor,% - citecolor=InnerLinkColor}{hyperref} - -% stylesheet for highlighting with pygments -\RequirePackage{sphinxhighlight} -% fix baseline increase from Pygments latex formatter in case of error tokens -% and keep \fboxsep's scope local via added braces -\def\PYG@tok@err{% - \def\PYG@bc##1{{\setlength{\fboxsep}{-\fboxrule}% - \fcolorbox[rgb]{1.00,0.00,0.00}{1,1,1}{\strut ##1}}}% -} -\def\PYG@tok@cs{% - \def\PYG@tc##1{\textcolor[rgb]{0.25,0.50,0.56}{##1}}% - \def\PYG@bc##1{{\setlength{\fboxsep}{0pt}% - \colorbox[rgb]{1.00,0.94,0.94}{\strut ##1}}}% -}% - -%% OPTIONS -% % Handle options via "kvoptions" (later loaded by hyperref anyhow) \RequirePackage{kvoptions} \SetupKeyvalOptions{prefix=spx@opt@} % use \spx@opt@ prefix @@ -307,6 +65,8 @@ \DeclareStringOption[-1]{numfigreset} \DeclareBoolOption[false]{nonumfigreset} \DeclareBoolOption[false]{mathnumfig} +\define@key{sphinx}{bookmarksdepth}{\AtBeginDocument{\hypersetup{bookmarksdepth=#1}}} +\AtBeginDocument{\define@key{sphinx}{bookmarksdepth}{\hypersetup{bookmarksdepth=#1}}} % \DeclareBoolOption[false]{usespart}% not used % dimensions, we declare the \dimen registers here. \newdimen\sphinxverbatimsep @@ -415,6 +175,7 @@ \DisableKeyvalOption{sphinx}{numfigreset} \DisableKeyvalOption{sphinx}{nonumfigreset} \DisableKeyvalOption{sphinx}{mathnumfig} +% FIXME: this is unrelated to an option, move this elsewhere % To allow hyphenation of first word in narrow contexts; no option, % customization to be done via 'preamble' key \newcommand*\sphinxAtStartPar{\leavevmode\nobreak\hskip\z@skip} @@ -424,426 +185,110 @@ \newcommand\sphinxsetup[1]{\setkeys{sphinx}{#1}} -%% ALPHANUMERIC LIST ITEMS -\newcommand\sphinxsetlistlabels[5] -{% #1 = style, #2 = enum, #3 = enumnext, #4 = prefix, #5 = suffix - % #2 and #3 are counters used by enumerate environement e.g. enumi, enumii. - % #1 is a macro such as \arabic or \alph - % prefix and suffix are strings (by default empty and a dot). - \@namedef{the#2}{#1{#2}}% - \@namedef{label#2}{#4\@nameuse{the#2}#5}% - \@namedef{p@#3}{\@nameuse{p@#2}#4\@nameuse{the#2}#5}% -}% - - -%% MAXLISTDEPTH +%% MISCELLANEOUS CONTEXT % -% remove LaTeX's cap on nesting depth if 'maxlistdepth' key used. -% This is a hack, which works with the standard classes: it assumes \@toodeep -% is always used in "true" branches: "\if ... \@toodeep \else .. \fi." - -% will force use the "false" branch (if there is one) -\def\spx@toodeep@hack{\fi\iffalse} - -% do nothing if 'maxlistdepth' key not used or if package enumitem loaded. -\ifnum\spx@opt@maxlistdepth=\z@\expandafter\@gobbletwo\fi +% flag to be set in a framed environment +% (defined here as currently needed by three sphinxlatex....sty files and +% even if not needed if such files are replaced, the definition does no harm) +\newif\ifspx@inframed +% +% \spx@ifcaptionpackage (defined at begin document) +% is needed currently in macros from: +% sphinxlatexliterals.sty (sphinxVerbatim) +% sphinxlatextables.sty (for some macros used in the table templates) +% +% \sphinxcaption is mark-up injected by the tabular and tabulary templates +% it is defined in sphinxlatextables.sty +% +% store the original \caption macro for usage with figures inside longtable +% and tabulary cells. Make sure we get the final \caption in presence of +% caption package, whether the latter was loaded before or after sphinx. \AtBeginDocument{% -\@ifpackageloaded{enumitem}{\remove@to@nnil}{}% - \let\spx@toodeepORI\@toodeep - \def\@toodeep{% - \ifnum\@listdepth<\spx@opt@maxlistdepth\relax - \expandafter\spx@toodeep@hack - \else - \expandafter\spx@toodeepORI - \fi}% -% define all missing \@list... macros - \count@\@ne - \loop - \ltx@ifundefined{@list\romannumeral\the\count@} - {\iffalse}{\iftrue\advance\count@\@ne}% - \repeat - \loop - \ifnum\count@>\spx@opt@maxlistdepth\relax\else - \expandafter\let - \csname @list\romannumeral\the\count@\expandafter\endcsname - \csname @list\romannumeral\the\numexpr\count@-\@ne\endcsname - % workaround 2.6--3.2d babel-french issue (fixed in 3.2e; no change needed) - \ltx@ifundefined{leftmargin\romannumeral\the\count@} - {\expandafter\let - \csname leftmargin\romannumeral\the\count@\expandafter\endcsname - \csname leftmargin\romannumeral\the\numexpr\count@-\@ne\endcsname}{}% - \advance\count@\@ne - \repeat -% define all missing enum... counters and \labelenum... macros and \p@enum.. - \count@\@ne - \loop - \ltx@ifundefined{c@enum\romannumeral\the\count@} - {\iffalse}{\iftrue\advance\count@\@ne}% - \repeat - \loop - \ifnum\count@>\spx@opt@maxlistdepth\relax\else - \newcounter{enum\romannumeral\the\count@}% - \expandafter\def - \csname labelenum\romannumeral\the\count@\expandafter\endcsname - \expandafter - {\csname theenum\romannumeral\the\numexpr\count@\endcsname.}% - \expandafter\def - \csname p@enum\romannumeral\the\count@\expandafter\endcsname - \expandafter - {\csname p@enum\romannumeral\the\numexpr\count@-\@ne\expandafter - \endcsname\csname theenum\romannumeral\the\numexpr\count@-\@ne\endcsname.}% - \advance\count@\@ne - \repeat -% define all missing labelitem... macros - \count@\@ne - \loop - \ltx@ifundefined{labelitem\romannumeral\the\count@} - {\iffalse}{\iftrue\advance\count@\@ne}% - \repeat - \loop - \ifnum\count@>\spx@opt@maxlistdepth\relax\else - \expandafter\let - \csname labelitem\romannumeral\the\count@\expandafter\endcsname - \csname labelitem\romannumeral\the\numexpr\count@-\@ne\endcsname - \advance\count@\@ne - \repeat - \PackageInfo{sphinx}{maximal list depth extended to \spx@opt@maxlistdepth}% -\@gobble\@nnil + \let\spx@originalcaption\caption + \@ifpackageloaded{caption} + {\let\spx@ifcaptionpackage\@firstoftwo + \caption@AtBeginDocument*{\let\spx@originalcaption\caption}% +% in presence of caption package, drop our own \sphinxcaption whose aim was to +% ensure same width of caption to all kinds of tables (tabular(y), longtable), +% because caption package has its own width (or margin) option + \def\sphinxcaption{\caption}% + }% + {\let\spx@ifcaptionpackage\@secondoftwo}% } - -%% INDEX, BIBLIOGRAPHY, APPENDIX, TABLE OF CONTENTS +%% PASS OPTIONS % -% fix the double index and bibliography on the table of contents -% in jsclasses (Japanese standard document classes) -\ifx\@jsc@uplatextrue\@undefined\else - \renewenvironment{sphinxtheindex} - {\cleardoublepage\phantomsection - \begin{theindex}} - {\end{theindex}} +% pass options to hyperref; it must not have been loaded already +\input{sphinxoptionshyperref.sty} +% pass options to geometry; it must not have been loaded already +\input{sphinxoptionsgeometry.sty} - \renewenvironment{sphinxthebibliography}[1] - {\cleardoublepage% \phantomsection % not needed here since TeXLive 2010's hyperref - \begin{thebibliography}{#1}} - {\end{thebibliography}} -\fi - -% disable \@chappos in Appendix in pTeX -\ifx\kanjiskip\@undefined\else - \let\py@OldAppendix=\appendix - \renewcommand{\appendix}{ - \py@OldAppendix - \gdef\@chappos{} - } -\fi - -% make commands known to non-Sphinx document classes -\providecommand*{\sphinxmaketitle}{\maketitle} -\providecommand*{\sphinxtableofcontents}{\tableofcontents} -\ltx@ifundefined{sphinxthebibliography} - {\newenvironment - {sphinxthebibliography}{\begin{thebibliography}}{\end{thebibliography}}% - } - {}% else clause of \ltx@ifundefined -\ltx@ifundefined{sphinxtheindex} - {\newenvironment{sphinxtheindex}{\begin{theindex}}{\end{theindex}}}% - {}% else clause of \ltx@ifundefined - -% for usage with xindy: this string gets internationalized in preamble -\newcommand*{\sphinxnonalphabeticalgroupname}{} -% redefined in preamble, headings for makeindex produced index -\newcommand*{\sphinxsymbolsname}{} -\newcommand*{\sphinxnumbersname}{} %% COLOR (general) % -% FIXME: \normalcolor should probably be used in place of \py@NormalColor -% elsewhere, and \py@NormalColor should never be defined. \normalcolor -% switches to the colour from last \color call in preamble. +% FIXME: these two should be deprecated +% +% FIXME: \normalcolor should be used and \py@NormalColor never defined \def\py@NormalColor{\color{black}} -% FIXME: it is probably better to use \color{TitleColor}, as TitleColor -% can be customized from 'sphinxsetup', and drop usage of \py@TitleColor +% FIXME: \color{TitleColor} should be used directly and \py@TitleColor +% should never get defined. \def\py@TitleColor{\color{TitleColor}} -% FIXME: this line should be dropped, as "9" is default anyhow. -\ifdefined\pdfcompresslevel\pdfcompresslevel = 9 \fi -%% PAGE STYLING +%% PACKAGES % -% Style parameters and macros used by most documents here -\raggedbottom -\sloppy -\hbadness = 5000 % don't print trivial gripes - -% Use \pagestyle{normal} as the primary pagestyle for text. -% Redefine the 'normal' header/footer style when using "fancyhdr" package: -\@ifpackageloaded{fancyhdr}{% - \ltx@ifundefined{c@chapter} - {% no \chapter, "howto" (non-Japanese) docclass - \fancypagestyle{plain}{ - \fancyhf{} - \fancyfoot[C]{{\py@HeaderFamily\thepage}} - \renewcommand{\headrulewidth}{0pt} - \renewcommand{\footrulewidth}{0pt} - } - % Same as 'plain', this way we can use it in template - % FIXME: shouldn't this have a running header with Name and Release like 'manual'? - \fancypagestyle{normal}{ - \fancyhf{} - \fancyfoot[C]{{\py@HeaderFamily\thepage}} - \renewcommand{\headrulewidth}{0pt} - \renewcommand{\footrulewidth}{0pt} - } - }% - {% classes with \chapter command - \fancypagestyle{normal}{ - \fancyhf{} - \fancyfoot[RO]{{\py@HeaderFamily\thepage}} - \fancyfoot[LO]{{\py@HeaderFamily\nouppercase{\rightmark}}} - \fancyhead[RO]{{\py@HeaderFamily \@title\sphinxheadercomma\py@release}} - \if@twoside - \fancyfoot[LE]{{\py@HeaderFamily\thepage}} - \fancyfoot[RE]{{\py@HeaderFamily\nouppercase{\leftmark}}} - \fancyhead[LE]{{\py@HeaderFamily \@title\sphinxheadercomma\py@release}} - \fi - \renewcommand{\headrulewidth}{0.4pt} - \renewcommand{\footrulewidth}{0.4pt} - % define chaptermark with \@chappos when \@chappos is available for Japanese - \ltx@ifundefined{@chappos}{} - {\def\chaptermark##1{\markboth{\@chapapp\space\thechapter\space\@chappos\space ##1}{}}} - } - % Update the plain style so we get the page number & footer line, - % but not a chapter or section title. This is to keep the first - % page of a chapter `clean.' - \fancypagestyle{plain}{ - \fancyhf{} - \fancyfoot[RO]{{\py@HeaderFamily\thepage}} - \if@twoside\fancyfoot[LE]{{\py@HeaderFamily\thepage}}\fi - \renewcommand{\headrulewidth}{0pt} - \renewcommand{\footrulewidth}{0.4pt} - } - } - } - {% no fancyhdr: memoir class - % Provide default for 'normal' style simply as an alias of 'plain' style - % This way we can use \pagestyle{normal} in LaTeX template - \def\ps@normal{\ps@plain} - % Users of memoir class are invited to redefine 'normal' style in preamble - } +% as will be indicated below, secondary style files load some more packages +% +% For \text macro (sphinx.util.texescape) +% also for usage of \firstchoice@true(false) in sphinxlatexgraphics.sty +\RequirePackage{amstext} +% It was passed "warn" option from latex template in case it is already loaded +% via some other package before \usepackage{sphinx} in preamble +\RequirePackage{textcomp} +% For the H specifier. Do not \restylefloat{figure}, it breaks Sphinx code +% for allowing figures in tables. +\RequirePackage{float} +% For floating figures in the text. Better to load after float. +\RequirePackage{wrapfig} +% Provides \captionof, used once by latex writer (\captionof{figure}) +\RequirePackage{capt-of} +% Support hlist directive +\RequirePackage{multicol} -% geometry -\ifx\kanjiskip\@undefined - \PassOptionsToPackage{% - hmargin={\unexpanded{\spx@opt@hmargin}},% - vmargin={\unexpanded{\spx@opt@vmargin}},% - marginpar=\unexpanded{\spx@opt@marginpar}} - {geometry} -\else - % set text width for Japanese documents to be integer multiple of 1zw - % and text height to be integer multiple of \baselineskip - % the execution is delayed to \sphinxsetup then geometry.sty - \normalsize\normalfont - \newcommand*\sphinxtextwidthja[1]{% - \if@twocolumn\tw@\fi - \dimexpr - \numexpr\dimexpr\paperwidth-\tw@\dimexpr#1\relax\relax/ - \dimexpr\if@twocolumn\tw@\else\@ne\fi zw\relax - zw\relax}% - \newcommand*\sphinxmarginparwidthja[1]{% - \dimexpr\numexpr\dimexpr#1\relax/\dimexpr1zw\relax zw\relax}% - \newcommand*\sphinxtextlinesja[1]{% - \numexpr\@ne+\dimexpr\paperheight-\topskip-\tw@\dimexpr#1\relax\relax/ - \baselineskip\relax}% - \ifx\@jsc@uplatextrue\@undefined\else - % the way we found in order for the papersize special written by - % geometry in the dvi file to be correct in case of jsbook class - \ifnum\mag=\@m\else % do nothing special if nomag class option or 10pt - \PassOptionsToPackage{truedimen}{geometry}% - \fi - \fi - \PassOptionsToPackage{% - hmarginratio={1:1},% - textwidth=\unexpanded{\sphinxtextwidthja{\spx@opt@hmargin}},% - vmarginratio={1:1},% - lines=\unexpanded{\sphinxtextlinesja{\spx@opt@vmargin}},% - marginpar=\unexpanded{\sphinxmarginparwidthja{\spx@opt@marginpar}},% - footskip=2\baselineskip,% - }{geometry}% - \AtBeginDocument - {% update a dimension used by the jsclasses - \ifx\@jsc@uplatextrue\@undefined\else\fullwidth\textwidth\fi - % for some reason, jreport normalizes all dimensions with \@settopoint - \@ifclassloaded{jreport} - {\@settopoint\textwidth\@settopoint\textheight\@settopoint\marginparwidth} - {}% <-- "false" clause of \@ifclassloaded - }% -\fi -% fix fncychap's bug which uses prematurely the \textwidth value -\@ifpackagewith{fncychap}{Bjornstrup} - {\AtBeginDocument{\mylen\textwidth\advance\mylen-2\myhi}}% - {}% <-- "false" clause of \@ifpackagewith +%% GRAPHICS +% +% It will always be needed, so let's load it here +\RequirePackage{graphicx} +\input{sphinxlatexgraphics.sty} -%% TITLES +%% FRAMED ENVIRONMENTS % -% Since Sphinx 1.5, users should use HeaderFamily key to 'sphinxsetup' rather -% than defining their own \py@HeaderFamily command (which is still possible). -% Memo: \py@HeaderFamily is also used by \maketitle as defined in -% sphinxmanual.cls/sphinxhowto.cls -\newcommand{\py@HeaderFamily}{\spx@opt@HeaderFamily} +\input{sphinxlatexadmonitions.sty} +\input{sphinxlatexliterals.sty} +\input{sphinxlatexshadowbox.sty} -% This sets up the fancy chapter headings that make the documents look -% at least a little better than the usual LaTeX output. -\@ifpackagewith{fncychap}{Bjarne}{ - \ChNameVar {\raggedleft\normalsize \py@HeaderFamily} - \ChNumVar {\raggedleft\Large \py@HeaderFamily} - \ChTitleVar{\raggedleft\Large \py@HeaderFamily} - % This creates (numbered) chapter heads without the leading \vspace*{}: - \def\@makechapterhead#1{% - {\parindent \z@ \raggedright \normalfont - \ifnum \c@secnumdepth >\m@ne - \if@mainmatter - \DOCH - \fi - \fi - \interlinepenalty\@M - \if@mainmatter - \DOTI{#1}% - \else% - \DOTIS{#1}% - \fi - }} -}{}% <-- "false" clause of \@ifpackagewith -% 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} -\titleformat{\subsection}{\large\py@HeaderFamily}% - {\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor} -\titleformat{\subsubsection}{\py@HeaderFamily}% - {\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} -\titleformat{\subparagraph}{\py@HeaderFamily}% - {\py@TitleColor\thesubparagraph}{0.5em}{\py@TitleColor} +%% PYGMENTS +% stylesheet for highlighting with pygments +\RequirePackage{sphinxhighlight} -%% GRAPHICS -% -% \sphinxincludegraphics resizes images larger than the TeX \linewidth (which -% is adjusted in indented environments), or taller than a certain maximal -% height (usually \textheight and this is reduced in the environments which use -% framed.sty to avoid infinite loop if image too tall). -% -% In case height or width options are present the rescaling is done -% (since 2.0), in a way keeping the width:height ratio either native from -% image or from the width and height options if both were present. +%% TABLES % -\newdimen\spx@image@maxheight -\AtBeginDocument{\spx@image@maxheight\textheight} - -% box scratch register -\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}}% - \in@false % use some handy boolean flag - \ifdim \wd\spx@image@box>\linewidth - \in@true % flag to remember to adjust options and set box dimensions - % compute height which results from rescaling width to \linewidth - % and keep current aspect ratio. multiply-divide in \numexpr uses - % temporarily doubled precision, hence no overflow. (of course we - % assume \ht is not a few sp's below \maxdimen...(about 16384pt). - \edef\spx@image@rescaledheight % with sp units - {\the\numexpr\ht\spx@image@box - *\linewidth/\wd\spx@image@box sp}% - \ifdim\spx@image@rescaledheight>\spx@image@maxheight - % the rescaled height will be too big, so it is height which decides - % the rescaling factor - \def\spx@image@requiredheight{\spx@image@maxheight}% dimen register - \edef\spx@image@requiredwidth % with sp units - {\the\numexpr\wd\spx@image@box - *\spx@image@maxheight/\ht\spx@image@box sp}% - % TODO: decide if this commented-out block could be needed due to - % rounding in numexpr operations going up - % \ifdim\spx@image@requiredwidth>\linewidth - % \def\spx@image@requiredwidth{\linewidth}% dimen register - % \fi - \else - \def\spx@image@requiredwidth{\linewidth}% dimen register - \let\spx@image@requiredheight\spx@image@rescaledheight% sp units - \fi - \else - % width is ok, let's check height - \ifdim\ht\spx@image@box>\spx@image@maxheight - \in@true - \edef\spx@image@requiredwidth % with sp units - {\the\numexpr\wd\spx@image@box - *\spx@image@maxheight/\ht\spx@image@box sp}% - \def\spx@image@requiredheight{\spx@image@maxheight}% dimen register - \fi - \fi % end of check of width and height - \ifin@ - \setbox\spx@image@box - \hbox{\includegraphics - [%#1,% contained only width and/or height and overruled anyhow - width=\spx@image@requiredwidth,height=\spx@image@requiredheight]% - {#2}}% - % \includegraphics does not set box dimensions to the exactly - % requested ones, see https://github.com/latex3/latex2e/issues/112 - \wd\spx@image@box\spx@image@requiredwidth - \ht\spx@image@box\spx@image@requiredheight - \leavevmode\box\spx@image@box - \else - % here we do not modify the options, no need to adjust width and height - % on output, they will be computed exactly as with "draft" option - \setbox\spx@image@box\box\voidb@x % clear memory - \includegraphics[#1]{#2}% - \fi -}% -% Use the "safe" one by default (2.0) -\def\sphinxincludegraphics{\sphinxsafeincludegraphics} +\input{sphinxlatextables.sty} -%% FIGURE IN TABLE +%% NUMBERING OF FIGURES, TABLES, AND LITERAL BLOCKS % -\newenvironment{sphinxfigure-in-table}[1][\linewidth]{% - \def\@captype{figure}% - \sphinxsetvskipsforfigintablecaption - \begin{minipage}{#1}% -}{\end{minipage}} -% store the original \caption macro for usage with figures inside longtable -% and tabulary cells. Make sure we get the final \caption in presence of -% caption package, whether the latter was loaded before or after sphinx. -\AtBeginDocument{% - \let\spx@originalcaption\caption - \@ifpackageloaded{caption} - {\let\spx@ifcaptionpackage\@firstoftwo - \caption@AtBeginDocument*{\let\spx@originalcaption\caption}% -% in presence of caption package, drop our own \sphinxcaption whose aim was to -% ensure same width of caption to all kinds of tables (tabular(y), longtable), -% because caption package has its own width (or margin) option - \def\sphinxcaption{\caption}% - }% - {\let\spx@ifcaptionpackage\@secondoftwo}% -} -% tabulary expands twice contents, we need to prevent double counter stepping -\newcommand*\sphinxfigcaption - {\ifx\equation$%$% this is trick to identify tabulary first pass - \firstchoice@false\else\firstchoice@true\fi - \spx@originalcaption } -\newcommand*\sphinxsetvskipsforfigintablecaption - {\abovecaptionskip\smallskipamount - \belowcaptionskip\smallskipamount} +\input{sphinxlatexnumfig.sty} -%% CITATIONS +%% LISTS % -\protected\def\sphinxcite{\cite} +\input{sphinxlatexlists.sty} + %% FOOTNOTES % @@ -869,1295 +314,33 @@ p. #2, % <- space \fi #1% no space } -% Support large numbered footnotes in minipage -% But now obsolete due to systematic use of \savenotes/\spewnotes -% when minipages are in use in the various macro definitions next. +% support large numbered footnotes in minipage; but this is now obsolete +% from systematic use of savenotes environment around minipages \def\thempfootnote{\arabic{mpfootnote}} +% This package is needed to support hyperlinked footnotes in tables and +% framed contents, and to allow code-blocks in footnotes. +\RequirePackage{sphinxpackagefootnote} -%% 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]% - \def\theliteralblock{\ifnum\c@chapter>\z@\arabic{chapter}.\fi - \arabic{literalblock}}% - }% -\ifspx@opt@nonumfigreset - \ltx@ifundefined{c@chapter}{}{% - \@removefromreset{figure}{chapter}% - \@removefromreset{table}{chapter}% - \@removefromreset{literalblock}{chapter}% - \ifspx@opt@mathnumfig - \@removefromreset{equation}{chapter}% - \fi - }% - \def\thefigure{\arabic{figure}}% - \def\thetable {\arabic{table}}% - \def\theliteralblock{\arabic{literalblock}}% - \ifspx@opt@mathnumfig - \def\theequation{\arabic{equation}}% - \fi -\else -\let\spx@preAthefigure\@empty -\let\spx@preBthefigure\@empty -% \ifspx@opt@usespart % <-- LaTeX writer could pass such a 'usespart' boolean -% % as sphinx.sty package option -% If document uses \part, (triggered in Sphinx by latex_toplevel_sectioning) -% LaTeX core per default does not reset chapter or section -% counters at each part. -% But if we modify this, we need to redefine \thechapter, \thesection to -% include the part number and this will cause problems in table of contents -% because of too wide numbering. Simplest is to do nothing. -% \fi -\ifnum\spx@opt@numfigreset>0 - \ltx@ifundefined{c@chapter} - {} - {\g@addto@macro\spx@preAthefigure{\ifnum\c@chapter>\z@\arabic{chapter}.}% - \g@addto@macro\spx@preBthefigure{\fi}}% -\fi -\ifnum\spx@opt@numfigreset>1 - \@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 - \@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 - \@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 - \@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 - \@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 -\expandafter\g@addto@macro -\expandafter\spx@preAthefigure\expandafter{\spx@preBthefigure}% -\let\thefigure\spx@preAthefigure -\let\thetable\spx@preAthefigure -\let\theliteralblock\spx@preAthefigure -\g@addto@macro\thefigure{\arabic{figure}}% -\g@addto@macro\thetable{\arabic{table}}% -\g@addto@macro\theliteralblock{\arabic{literalblock}}% - \ifspx@opt@mathnumfig - \let\theequation\spx@preAthefigure - \g@addto@macro\theequation{\arabic{equation}}% - \fi -\fi -}% end of big \AtBeginDocument - -%% LITERAL BLOCKS -% -% Based on use of "fancyvrb.sty"'s Verbatim. -% - with framing allowing page breaks ("framed.sty") -% - with breaking of long lines (exploits Pygments mark-up), -% - with possibly of a top caption, non-separable by pagebreak. -% - and usable inside tables or footnotes ("footnotehyper-sphinx"). - -% 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 - -% for captions of literal blocks -% at start of caption title -\newcommand*{\fnum@literalblock}{\literalblockname\nobreakspace\theliteralblock} -% this will be overwritten in document preamble by Babel translation -\newcommand*{\literalblockname}{Listing } -% file extension needed for \caption's good functioning, the file is created -% only if a \listof{literalblock}{foo} command is encountered, which is -% analogous to \listoffigures, but for the code listings (foo = chosen title.) -\newcommand*{\ext@literalblock}{lol} - -\newif\ifspx@inframed % flag set if we are already in a framed environment -% if forced use of minipage encapsulation is needed (e.g. table cells) -\newif\ifsphinxverbatimwithminipage \sphinxverbatimwithminipagefalse - -% Framing macro for use with framed.sty's \FrameCommand -% - it obeys current indentation, -% - frame is \fboxsep separated from the contents, -% - the contents use the full available text width, -% - #1 = color of frame, #2 = color of background, -% - #3 = above frame, #4 = below frame, #5 = within frame, -% - #3 and #4 must be already typeset boxes; they must issue \normalcolor -% or similar, else, they are under scope of color #1 -\long\def\spx@fcolorbox #1#2#3#4#5{% - \hskip\@totalleftmargin - \hskip-\fboxsep\hskip-\fboxrule - % use of \color@b@x here is compatible with both xcolor.sty and color.sty - \color@b@x {\color{#1}\spx@CustomFBox{#3}{#4}}{\color{#2}}{#5}% - \hskip-\fboxsep\hskip-\fboxrule - \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth -}% -% #1 = for material above frame, such as a caption or a "continued" hint -% #2 = for material below frame, such as a caption or "continues on next page" -% #3 = actual contents, which will be typeset with a background color -\long\def\spx@CustomFBox#1#2#3{% - \begingroup - \setbox\@tempboxa\hbox{{#3}}% inner braces to avoid color leaks - \vbox{#1% above frame - % draw frame border _latest_ to avoid pdf viewer issue - \kern\fboxrule - \hbox{\kern\fboxrule - \copy\@tempboxa - \kern-\wd\@tempboxa\kern-\fboxrule - \vrule\@width\fboxrule - \kern\wd\@tempboxa - \vrule\@width\fboxrule}% - \kern-\dimexpr\ht\@tempboxa+\dp\@tempboxa+\fboxrule\relax - \hrule\@height\fboxrule - \kern\dimexpr\ht\@tempboxa+\dp\@tempboxa\relax - \hrule\@height\fboxrule - #2% below frame - }% - \endgroup -}% -\def\spx@fcolorbox@put@c#1{% hide width from framed.sty measuring - \moveright\dimexpr\fboxrule+.5\wd\@tempboxa\hb@xt@\z@{\hss#1\hss}% -}% -\def\spx@fcolorbox@put@r#1{% right align with contents, width hidden - \moveright\dimexpr\fboxrule+\wd\@tempboxa-\fboxsep\hb@xt@\z@{\hss#1}% -}% -\def\spx@fcolorbox@put@l#1{% left align with contents, width hidden - \moveright\dimexpr\fboxrule+\fboxsep\hb@xt@\z@{#1\hss}% -}% -% -\def\sphinxVerbatim@Continued - {\csname spx@fcolorbox@put@\spx@opt@verbatimcontinuedalign\endcsname - {\normalcolor\sphinxstylecodecontinued\literalblockcontinuedname}}% -\def\sphinxVerbatim@Continues - {\csname spx@fcolorbox@put@\spx@opt@verbatimcontinuesalign\endcsname - {\normalcolor\sphinxstylecodecontinues\literalblockcontinuesname}}% -\def\sphinxVerbatim@Title - {\spx@fcolorbox@put@c{\unhcopy\sphinxVerbatim@TitleBox}}% -\let\sphinxVerbatim@Before\@empty -\let\sphinxVerbatim@After\@empty -% Defaults are redefined in document preamble according to language -\newcommand*\literalblockcontinuedname{continued from previous page}% -\newcommand*\literalblockcontinuesname{continues on next page}% -% -\def\spx@verbatimfcolorbox{\spx@fcolorbox{VerbatimBorderColor}{VerbatimColor}}% -\def\sphinxVerbatim@FrameCommand - {\spx@verbatimfcolorbox\sphinxVerbatim@Before\sphinxVerbatim@After}% -\def\sphinxVerbatim@FirstFrameCommand - {\spx@verbatimfcolorbox\sphinxVerbatim@Before\sphinxVerbatim@Continues}% -\def\sphinxVerbatim@MidFrameCommand - {\spx@verbatimfcolorbox\sphinxVerbatim@Continued\sphinxVerbatim@Continues}% -\def\sphinxVerbatim@LastFrameCommand - {\spx@verbatimfcolorbox\sphinxVerbatim@Continued\sphinxVerbatim@After}% - -% For linebreaks inside Verbatim environment from package fancyvrb. -\newbox\sphinxcontinuationbox -\newbox\sphinxvisiblespacebox -\newcommand*\sphinxafterbreak {\copy\sphinxcontinuationbox} - -% Take advantage of the already applied Pygments mark-up to insert -% potential linebreaks for TeX processing. -% {, <, #, %, $, ' and ": go to next line. -% _, }, ^, &, >, -, ~, and \: stay at end of broken line. -% Use of \textquotesingle for straight quote. -% FIXME: convert this to package options ? -\newcommand*\sphinxbreaksbeforelist {% - \do\PYGZob\{\do\PYGZlt\<\do\PYGZsh\#\do\PYGZpc\%% {, <, #, %, - \do\PYGZdl\$\do\PYGZdq\"% $, " - \def\PYGZsq - {\discretionary{}{\sphinxafterbreak\textquotesingle}{\textquotesingle}}% ' -} -\newcommand*\sphinxbreaksafterlist {% - \do\PYGZus\_\do\PYGZcb\}\do\PYGZca\^\do\PYGZam\&% _, }, ^, &, - \do\PYGZgt\>\do\PYGZhy\-\do\PYGZti\~% >, -, ~ - \do\PYGZbs\\% \ -} -\newcommand*\sphinxbreaksatspecials {% - \def\do##1##2% - {\def##1{\discretionary{}{\sphinxafterbreak\char`##2}{\char`##2}}}% - \sphinxbreaksbeforelist - \def\do##1##2% - {\def##1{\discretionary{\char`##2}{\sphinxafterbreak}{\char`##2}}}% - \sphinxbreaksafterlist -} - -\def\sphinx@verbatim@nolig@list {\do \`}% -% Some characters . , ; ? ! / are neither pygmentized nor "tex-escaped". -% This macro makes them "active" and they will insert potential linebreaks. -% Not compatible with math mode (cf \sphinxunactivateextras). -\newcommand*\sphinxbreaksbeforeactivelist {}% none -\newcommand*\sphinxbreaksafteractivelist {\do\.\do\,\do\;\do\?\do\!\do\/} -\newcommand*\sphinxbreaksviaactive {% - \def\do##1{\lccode`\~`##1% - \lowercase{\def~}{\discretionary{}{\sphinxafterbreak\char`##1}{\char`##1}}% - \catcode`##1\active}% - \sphinxbreaksbeforeactivelist - \def\do##1{\lccode`\~`##1% - \lowercase{\def~}{\discretionary{\char`##1}{\sphinxafterbreak}{\char`##1}}% - \catcode`##1\active}% - \sphinxbreaksafteractivelist - \lccode`\~`\~ -} - -% If the linebreak is at a space, the latter will be displayed as visible -% space at end of first line, and a continuation symbol starts next line. -\def\spx@verbatim@space {% - \nobreak\hskip\z@skip - \discretionary{\copy\sphinxvisiblespacebox}{\sphinxafterbreak} - {\kern\fontdimen2\font}% -}% - -% if the available space on page is less than \literalblockneedspace, insert pagebreak -\newcommand{\sphinxliteralblockneedspace}{5\baselineskip} -\newcommand{\sphinxliteralblockwithoutcaptionneedspace}{1.5\baselineskip} -% The title (caption) is specified from outside as macro \sphinxVerbatimTitle. -% \sphinxVerbatimTitle is reset to empty after each use of Verbatim. -\newcommand*\sphinxVerbatimTitle {} -% This box to typeset the caption before framed.sty multiple passes for framing. -\newbox\sphinxVerbatim@TitleBox -% This box to measure contents if nested as inner \MakeFramed requires then -% minipage encapsulation but too long contents then break outer \MakeFramed -\newbox\sphinxVerbatim@ContentsBox -% This is a workaround to a "feature" of French lists, when literal block -% follows immediately; usable generally (does only \par then), a priori... -\newcommand*\sphinxvspacefixafterfrenchlists{% - \ifvmode\ifdim\lastskip<\z@ \vskip\parskip\fi\else\par\fi -} -% Holder macro for labels of literal blocks. Set-up by LaTeX writer. -\newcommand*\sphinxLiteralBlockLabel {} -\newcommand*\sphinxSetupCaptionForVerbatim [1] -{% - \sphinxvspacefixafterfrenchlists - \needspace{\sphinxliteralblockneedspace}% -% insert a \label via \sphinxLiteralBlockLabel -% reset to normal the color for the literal block caption - \def\sphinxVerbatimTitle - {\py@NormalColor\sphinxcaption{\sphinxLiteralBlockLabel #1}}% -} -\newcommand*\sphinxSetupCodeBlockInFootnote {% - \fvset{fontsize=\footnotesize}\let\caption\sphinxfigcaption - \sphinxverbatimwithminipagetrue % reduces vertical spaces - % we counteract (this is in a group) the \@normalsize from \caption - \let\normalsize\footnotesize\let\@parboxrestore\relax - \def\spx@abovecaptionskip{\sphinxverbatimsmallskipamount}% -} -\newcommand*{\sphinxverbatimsmallskipamount}{\smallskipamount} -% serves to implement line highlighting and line wrapping -\newcommand\sphinxFancyVerbFormatLine[1]{% - \expandafter\sphinx@verbatim@checkifhl\expandafter{\the\FV@CodeLineNo}% - \ifin@ - \sphinxVerbatimHighlightLine{#1}% - \else - \sphinxVerbatimFormatLine{#1}% - \fi -}% -\newcommand\sphinxVerbatimHighlightLine[1]{% - \edef\sphinxrestorefboxsep{\fboxsep\the\fboxsep\relax}% - \fboxsep0pt\relax % cf LaTeX bug graphics/4524 - \colorbox{sphinxVerbatimHighlightColor}% - {\sphinxrestorefboxsep\sphinxVerbatimFormatLine{#1}}% - % no need to restore \fboxsep here, as this ends up in a \hbox from fancyvrb -}% -% \sphinxVerbatimFormatLine will be set locally to one of those two: -\newcommand\sphinxVerbatimFormatLineWrap{% - \hsize\linewidth - \ifspx@opt@verbatimforcewraps - \expandafter\spx@verb@FormatLineForceWrap - \else\expandafter\spx@verb@FormatLineWrap - \fi -}% -\newcommand\sphinxVerbatimFormatLineNoWrap[1]{\hb@xt@\linewidth{\strut #1\hss}}% -\long\def\spx@verb@FormatLineWrap#1{% - \vtop{\raggedright\hyphenpenalty\z@\exhyphenpenalty\z@ - \doublehyphendemerits\z@\finalhyphendemerits\z@ - \strut #1\strut}% -}% -% -% The normal line wrapping allows breaks at spaces and ascii non -% letters, non digits. The \raggedright above means there will be -% an overfilled line only if some non-breakable "word" was -% encountered, which is longer than a line (it is moved always to -% be on its own on a new line). -% -% The "forced" line wrapping will parse the tokens to add potential -% breakpoints at each character. As some strings are highlighted, -% we have to apply the highlighting character per character, which -% requires to manipulate the output of the Pygments LaTeXFormatter. -% -% Doing this at latex level is complicated. The contents should -% be as expected: i.e. some active characters from -% \sphinxbreaksviaactive, some Pygments character escapes such as -% \PYGZdl{}, and the highlighting \PYG macro with always 2 -% arguments. No other macros should be there, except perhaps -% zero-parameter macros. In particular: -% - the texcomments Pygments option must be set to False -% -% With pdflatex, Unicode input gives multi-bytes characters -% where the first byte is active. We support the "utf8" macros -% only. "utf8x" is not supported. -% -% The highlighting macro \PYG will be applied character per -% character. Highlighting via a colored background gives thus a -% chain of small colored boxes which may cause some artefact in -% some pdf viewers. Can't do anything here if we do want the line -% break to be possible. -% -% First a measurement step is done of what would the standard line -% wrapping give (i.e line breaks only at spaces and non-letter, -% non-digit ascii characters), cf TeX by Topic for the basic -% dissecting technique: TeX unfortunately when building a vertical -% box does not store in an accessible way what was the maximal -% line-width during paragraph building. -% -% If the max width exceeds the linewidth by more than verbatimmaxoverfull -% character widths, or if the min width plus verbatimmaxunderfull character -% widths is inferior to linewidth, then we apply the "force wrapping" with -% potential line break at each character, else we don't. -\long\def\spx@verb@FormatLineForceWrap#1{% - % \spx@image@box is a scratch box register that we can use here - \global\let\spx@verb@maxwidth\z@ - \global\let\spx@verb@minwidth\linewidth - \setbox\spx@image@box - \vtop{\raggedright\hyphenpenalty\z@\exhyphenpenalty\z@ - \doublehyphendemerits\z@\finalhyphendemerits\z@ - \strut #1\strut\@@par - \spx@verb@getwidths}% - \ifdim\spx@verb@maxwidth> - \dimexpr\linewidth+\spx@opt@verbatimmaxoverfull\fontcharwd\font`X \relax - \spx@verb@FormatLineWrap{\spx@verb@wrapPYG #1\spx@verb@wrapPYG}% - \else - \ifdim\spx@verb@minwidth< - \dimexpr\linewidth-\spx@opt@verbatimmaxunderfull\fontcharwd\font`X \relax - \spx@verb@FormatLineWrap{\spx@verb@wrapPYG #1\spx@verb@wrapPYG}% - \else - \spx@verb@FormatLineWrap{#1}% - \fi\fi -}% -% auxiliary paragraph dissector to get max and min widths -\newbox\spx@scratchbox -\def\spx@verb@getwidths {% - \unskip\unpenalty - \setbox\spx@scratchbox\lastbox - \ifvoid\spx@scratchbox - \else - \setbox\spx@scratchbox\hbox{\unhbox\spx@scratchbox}% - \ifdim\spx@verb@maxwidth<\wd\spx@scratchbox - \xdef\spx@verb@maxwidth{\number\wd\spx@scratchbox sp}% - \fi - \ifdim\spx@verb@minwidth>\wd\spx@scratchbox - \xdef\spx@verb@minwidth{\number\wd\spx@scratchbox sp}% - \fi - \expandafter\spx@verb@getwidths - \fi -}% -% auxiliary macros to implement "cut long line even in middle of word" -\catcode`Z=3 % safe delimiter -\def\spx@verb@wrapPYG{% - \futurelet\spx@nexttoken\spx@verb@wrapPYG@i -}% -\def\spx@verb@wrapPYG@i{% - \ifx\spx@nexttoken\spx@verb@wrapPYG\let\next=\@gobble\else - \ifx\spx@nexttoken\PYG\let\next=\spx@verb@wrapPYG@PYG@onebyone\else - \discretionary{}{\sphinxafterbreak}{}% - \let\next\spx@verb@wrapPYG@ii - \fi\fi - \next -}% -% Let's recognize active characters. We don't support utf8x only utf8. -% And here #1 should not have picked up (non empty) braced contents -\long\def\spx@verb@wrapPYG@ii#1{% - \ifcat\noexpand~\noexpand#1\relax% active character - \expandafter\spx@verb@wrapPYG@active - \else % non-active character, control sequence such as \PYGZdl, or empty - \expandafter\spx@verb@wrapPYG@one - \fi {#1}% -}% -\long\def\spx@verb@wrapPYG@active#1{% -% Let's hope expansion of active character does not really require arguments, -% as we certainly don't want to go into expanding upfront token stream anyway. - \expandafter\spx@verb@wrapPYG@iii#1{}{}{}{}{}{}{}{}{}Z#1% -}% -\long\def\spx@verb@wrapPYG@iii#1#2Z{% - \ifx\UTFviii@four@octets#1\let\next=\spx@verb@wrapPYG@four\else - \ifx\UTFviii@three@octets#1\let\next=\spx@verb@wrapPYG@three\else - \ifx\UTFviii@two@octets#1\let\next=\spx@verb@wrapPYG@two\else - \let\next=\spx@verb@wrapPYG@one - \fi\fi\fi - \next -}% -\long\def\spx@verb@wrapPYG@one #1{#1\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% -\long\def\spx@verb@wrapPYG@two #1#2{#1#2\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% -\long\def\spx@verb@wrapPYG@three #1#2#3{#1#2#3\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% -\long\def\spx@verb@wrapPYG@four #1#2#3#4{#1#2#3#4\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% -% Replace \PYG by itself applied one character at a time! This way breakpoints -% can be inserted. -\def\spx@verb@wrapPYG@PYG@onebyone#1#2#3{% #1 = \PYG, #2 = highlight spec, #3 = tokens - \def\spx@verb@wrapPYG@PYG@spec{{#2}}% - \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i#3Z% -}% -\def\spx@verb@wrapPYG@PYG@i{% - \ifx\spx@nexttokenZ\let\next=\spx@verb@wrapPYG@PYG@done\else - \discretionary{}{\sphinxafterbreak}{}% - \let\next\spx@verb@wrapPYG@PYG@ii - \fi - \next -}% -\def\spx@verb@wrapPYG@PYG@doneZ{\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% -\long\def\spx@verb@wrapPYG@PYG@ii#1{% - \ifcat\noexpand~\noexpand#1\relax% active character - \expandafter\spx@verb@wrapPYG@PYG@active - \else % non-active character, control sequence such as \PYGZdl, or empty - \expandafter\spx@verb@wrapPYG@PYG@one - \fi {#1}% -}% -\long\def\spx@verb@wrapPYG@PYG@active#1{% -% Let's hope expansion of active character does not really require arguments, -% as we certainly don't want to go into expanding upfront token stream anyway. - \expandafter\spx@verb@wrapPYG@PYG@iii#1{}{}{}{}{}{}{}{}{}Z#1% -}% -\long\def\spx@verb@wrapPYG@PYG@iii#1#2Z{% - \ifx\UTFviii@four@octets#1\let\next=\spx@verb@wrapPYG@PYG@four\else - \ifx\UTFviii@three@octets#1\let\next=\spx@verb@wrapPYG@PYG@three\else - \ifx\UTFviii@two@octets#1\let\next=\spx@verb@wrapPYG@PYG@two\else - \let\next=\spx@verb@wrapPYG@PYG@one - \fi\fi\fi - \next -}% -\long\def\spx@verb@wrapPYG@PYG@one#1{% - \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1}% - \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i -}% -\long\def\spx@verb@wrapPYG@PYG@two#1#2{% - \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1#2}% - \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i -}% -\long\def\spx@verb@wrapPYG@PYG@three#1#2#3{% - \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1#2#3}% - \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i -}% -\long\def\spx@verb@wrapPYG@PYG@four#1#2#3#4{% - \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1#2#3#4}% - \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i -}% -\catcode`Z 11 % -% -\g@addto@macro\FV@SetupFont{% - \sbox\sphinxcontinuationbox {\spx@opt@verbatimcontinued}% - \sbox\sphinxvisiblespacebox {\spx@opt@verbatimvisiblespace}% -}% -\newenvironment{sphinxVerbatim}{% - % first, let's check if there is a caption - \ifx\sphinxVerbatimTitle\empty - \sphinxvspacefixafterfrenchlists - \parskip\z@skip - \vskip\sphinxverbatimsmallskipamount - % there was no caption. Check if nevertheless a label was set. - \ifx\sphinxLiteralBlockLabel\empty\else - % we require some space to be sure hyperlink target from \phantomsection - % will not be separated from upcoming verbatim by a page break - \needspace{\sphinxliteralblockwithoutcaptionneedspace}% - \phantomsection\sphinxLiteralBlockLabel - \fi - \else - \parskip\z@skip - \if t\spx@opt@literalblockcappos - \vskip\spx@abovecaptionskip - \def\sphinxVerbatim@Before - {\sphinxVerbatim@Title\nointerlineskip - \kern\dimexpr-\dp\strutbox+\sphinxbelowcaptionspace - % if no frame (code-blocks inside table cells), remove - % the "verbatimsep" whitespace from the top (better visually) - \ifspx@opt@verbatimwithframe\else-\sphinxverbatimsep\fi - % caption package adds \abovecaptionskip vspace, remove it - \spx@ifcaptionpackage{-\abovecaptionskip}{}\relax}% - \else - \vskip\sphinxverbatimsmallskipamount - \def\sphinxVerbatim@After - {\nointerlineskip\kern\dimexpr\dp\strutbox - \ifspx@opt@verbatimwithframe\else-\sphinxverbatimsep\fi - \spx@ifcaptionpackage{-\abovecaptionskip}{}\relax - \sphinxVerbatim@Title}% - \fi - \def\@captype{literalblock}% - \capstart - % \sphinxVerbatimTitle must reset color - \setbox\sphinxVerbatim@TitleBox - \hbox{\begin{minipage}{\linewidth}% - % caption package may detect wrongly if top or bottom, so we help it - \spx@ifcaptionpackage - {\caption@setposition{\spx@opt@literalblockcappos}}{}% - \sphinxVerbatimTitle - \end{minipage}}% - \fi - \global\let\sphinxLiteralBlockLabel\empty - \global\let\sphinxVerbatimTitle\empty - \fboxsep\sphinxverbatimsep \fboxrule\sphinxverbatimborder - \ifspx@opt@verbatimwithframe\else\fboxrule\z@\fi - \let\FrameCommand \sphinxVerbatim@FrameCommand - \let\FirstFrameCommand\sphinxVerbatim@FirstFrameCommand - \let\MidFrameCommand \sphinxVerbatim@MidFrameCommand - \let\LastFrameCommand \sphinxVerbatim@LastFrameCommand - \ifspx@opt@verbatimhintsturnover\else - \let\sphinxVerbatim@Continued\@empty - \let\sphinxVerbatim@Continues\@empty - \fi - \ifspx@opt@verbatimwrapslines - % fancyvrb's Verbatim puts each input line in (unbreakable) horizontal boxes. - % This customization wraps each line from the input in a \vtop, thus - % allowing it to wrap and display on two or more lines in the latex output. - % - The codeline counter will be increased only once. - % - The wrapped material will not break across pages, it is impossible - % to achieve this without extensive rewrite of fancyvrb. - % - The (not used in sphinx) obeytabs option to Verbatim is - % broken by this change (showtabs and tabspace work). - \let\sphinxVerbatimFormatLine\sphinxVerbatimFormatLineWrap - \let\FV@Space\spx@verbatim@space - % Allow breaks at special characters using \PYG... macros. - \sphinxbreaksatspecials - % Breaks at punctuation characters . , ; ? ! and / (needs catcode activation) - \fvset{codes*=\sphinxbreaksviaactive}% - \else % end of conditional code for wrapping long code lines - \let\sphinxVerbatimFormatLine\sphinxVerbatimFormatLineNoWrap - \fi - \let\FancyVerbFormatLine\sphinxFancyVerbFormatLine - \VerbatimEnvironment - % workaround to fancyvrb's check of current list depth - \def\@toodeep {\advance\@listdepth\@ne}% - % The list environment is needed to control perfectly the vertical space. - % Note: \OuterFrameSep used by framed.sty is later set to \topsep hence 0pt. - % - if caption: distance from last text baseline to caption baseline is - % A+(B-F)+\ht\strutbox, A = \abovecaptionskip (default 10pt), B = - % \baselineskip, F is the framed.sty \FrameHeightAdjust macro, default 6pt. - % Formula valid for F < 10pt. - % - distance of baseline of caption to top of frame is like for tables: - % \sphinxbelowcaptionspace (=0.5\baselineskip) - % - if no caption: distance of last text baseline to code frame is S+(B-F), - % with S = \sphinxverbatimtopskip (=\smallskip) - % - and distance from bottom of frame to next text baseline is - % \baselineskip+\parskip. - % The \trivlist is used to avoid possible "too deeply nested" error. - \itemsep \z@skip - \topsep \z@skip - \partopsep \z@skip - % trivlist will set \parsep to \parskip (which itself is set to zero above) - % \leftmargin will be set to zero by trivlist - \rightmargin\z@ - \parindent \z@% becomes \itemindent. Default zero, but perhaps overwritten. - \trivlist\item\relax - \ifspx@inframed\setbox\sphinxVerbatim@ContentsBox\vbox\bgroup - \@setminipage\hsize\linewidth - % use bulk of minipage paragraph shape restores (this is needed - % in indented contexts, at least for some) - \textwidth\hsize \columnwidth\hsize \@totalleftmargin\z@ - \leftskip\z@skip \rightskip\z@skip \@rightskip\z@skip - \else - \ifsphinxverbatimwithminipage\noindent\begin{minipage}{\linewidth}\fi - \MakeFramed {% adapted over from framed.sty's snugshade environment - \advance\hsize-\width\@totalleftmargin\z@\linewidth\hsize\@setminipage - }% - \fi - % For grid placement from \strut's in \FancyVerbFormatLine - \lineskip\z@skip - % active comma should not be overwritten by \@noligs - \ifspx@opt@verbatimwrapslines - \let\verbatim@nolig@list \sphinx@verbatim@nolig@list - \fi - % will fetch its optional arguments if any - \OriginalVerbatim -} -{% - \endOriginalVerbatim - \ifspx@inframed - \egroup % finish \sphinxVerbatim@ContentsBox vbox - \nobreak % update page totals - \ifdim\dimexpr\ht\sphinxVerbatim@ContentsBox+ - \dp\sphinxVerbatim@ContentsBox+ - \ht\sphinxVerbatim@TitleBox+ - \dp\sphinxVerbatim@TitleBox+ - 2\fboxsep+2\fboxrule+ - % try to account for external frame parameters - \FrameSep+\FrameRule+ - % Usage here of 2 baseline distances is empirical. - % In border case where code-block fits barely in remaining space, - % it gets framed and looks good but the outer frame may continue - % on top of next page and give (if no contents after code-block) - % an empty framed line, as testing showed. - 2\baselineskip+ - % now add all to accumulated page totals and compare to \pagegoal - \pagetotal+\pagedepth>\pagegoal - % long contents: do not \MakeFramed. Do make a caption (either before or - % after) if title exists. Continuation hints across pagebreaks dropped. - % FIXME? a bottom caption may end up isolated at top of next page - % (no problem with a top caption, which is default) - \spx@opt@verbatimwithframefalse - \def\sphinxVerbatim@Title{\noindent\box\sphinxVerbatim@TitleBox\par}% - \sphinxVerbatim@Before - \noindent\unvbox\sphinxVerbatim@ContentsBox\par - \sphinxVerbatim@After - \else - % short enough contents: use \MakeFramed. As it is nested, this requires - % minipage encapsulation. - \noindent\begin{minipage}{\linewidth}% - \MakeFramed {% Use it now with the fetched contents - \advance\hsize-\width\@totalleftmargin\z@\linewidth\hsize\@setminipage - }% - \unvbox\sphinxVerbatim@ContentsBox - % some of this may be superfluous: - \par\unskip\@minipagefalse\endMakeFramed - \end{minipage}% - \fi - \else % non-nested \MakeFramed - \par\unskip\@minipagefalse\endMakeFramed % from framed.sty snugshade - \ifsphinxverbatimwithminipage\end{minipage}\fi - \fi - \endtrivlist -} -\newenvironment {sphinxVerbatimNoFrame} - {\spx@opt@verbatimwithframefalse - \VerbatimEnvironment - \begin{sphinxVerbatim}} - {\end{sphinxVerbatim}} -\newenvironment {sphinxVerbatimintable} - {% don't use a frame if in a table cell - \spx@opt@verbatimwithframefalse - \sphinxverbatimwithminipagetrue - % the literal block caption uses \sphinxcaption which is wrapper of \caption, - % but \caption must be modified because longtable redefines it to work only - % for the own table caption, and tabulary has multiple passes - \let\caption\sphinxfigcaption - % reduce above caption skip - \def\spx@abovecaptionskip{\sphinxverbatimsmallskipamount}% - \VerbatimEnvironment - \begin{sphinxVerbatim}} - {\end{sphinxVerbatim}} - - -%% PARSED LITERALS -% allow long lines to wrap like they do in code-blocks - -% this should be kept in sync with definitions in sphinx.util.texescape -\newcommand*\sphinxbreaksattexescapedchars{% - \def\do##1##2% put potential break point before character - {\def##1{\discretionary{}{\sphinxafterbreak\char`##2}{\char`##2}}}% - \do\{\{\do\textless\<\do\#\#\do\%\%\do\$\$% {, <, #, %, $ - \def\do##1##2% put potential break point after character - {\def##1{\discretionary{\char`##2}{\sphinxafterbreak}{\char`##2}}}% - \do\_\_\do\}\}\do\textasciicircum\^\do\&\&% _, }, ^, &, - \do\textgreater\>\do\textasciitilde\~% >, ~ - \do\textbackslash\\% \ -} -\newcommand*\sphinxbreaksviaactiveinparsedliteral{% - \sphinxbreaksviaactive % by default handles . , ; ? ! / - \lccode`\~`\~ % - % update \dospecials as it is used by \url - % but deactivation will already have been done hence this is unneeded: - % \expandafter\def\expandafter\dospecials\expandafter{\dospecials - % \sphinxbreaksbeforeactivelist\sphinxbreaksafteractivelist\do\-}% -} -\newcommand*\sphinxbreaksatspaceinparsedliteral{% - \lccode`~32 \lowercase{\let~}\spx@verbatim@space\lccode`\~`\~ -} -\newcommand*{\sphinxunactivateextras}{\let\do\@makeother - \sphinxbreaksbeforeactivelist\sphinxbreaksafteractivelist}% -% the \catcode13=5\relax (deactivate end of input lines) is left to callers -\newcommand*{\sphinxunactivateextrasandspace}{\catcode32=10\relax - \sphinxunactivateextras}% -% now for the modified alltt environment -\newenvironment{sphinxalltt} -{% at start of next line to workaround Emacs/AUCTeX issue with this file -\begin{alltt}% - \ifspx@opt@parsedliteralwraps - \sbox\sphinxcontinuationbox {\spx@opt@verbatimcontinued}% - \sbox\sphinxvisiblespacebox {\spx@opt@verbatimvisiblespace}% - \sphinxbreaksattexescapedchars - \sphinxbreaksviaactiveinparsedliteral - \sphinxbreaksatspaceinparsedliteral -% alltt takes care of the ' as derivative ("prime") in math mode - \everymath\expandafter{\the\everymath\sphinxunactivateextrasandspace - \catcode`\<=12\catcode`\>=12\catcode`\^=7\catcode`\_=8 }% -% not sure if displayed math (align,...) can end up in parsed-literal, anyway - \everydisplay\expandafter{\the\everydisplay - \catcode13=5 \sphinxunactivateextrasandspace - \catcode`\<=12\catcode`\>=12\catcode`\^=7\catcode`\_=8 }% - \fi } -{\end{alltt}} - -% Protect \href's first argument in contexts such as sphinxalltt (or -% \sphinxcode). Sphinx uses \#, \%, \& ... always inside \sphinxhref. -\protected\def\sphinxhref#1#2{{% - \sphinxunactivateextrasandspace % never do \scantokens with active space! -% for the \endlinechar business, https://github.com/latex3/latex2e/issues/286 - \endlinechar\m@ne\everyeof{{\endlinechar13 #2}}% keep catcode regime for #2 - \scantokens{\href{#1}}% normalise it for #1 during \href expansion -}} -% Same for \url. And also \nolinkurl for coherence. -\protected\def\sphinxurl#1{{% - \sphinxunactivateextrasandspace\everyeof{}% (<- precaution for \scantokens) - \endlinechar\m@ne\scantokens{\url{#1}}% -}} -\protected\def\sphinxnolinkurl#1{{% - \sphinxunactivateextrasandspace\everyeof{}% - \endlinechar\m@ne\scantokens{\nolinkurl{#1}}% -}} - - -%% TOPIC AND CONTENTS BOXES -% -% Again based on use of "framed.sty", this allows breakable framed boxes. -\long\def\spx@ShadowFBox#1{% - \leavevmode\begingroup - % first we frame the box #1 - \setbox\@tempboxa - \hbox{\vrule\@width\sphinxshadowrule - \vbox{\hrule\@height\sphinxshadowrule - \kern\sphinxshadowsep - \hbox{\kern\sphinxshadowsep #1\kern\sphinxshadowsep}% - \kern\sphinxshadowsep - \hrule\@height\sphinxshadowrule}% - \vrule\@width\sphinxshadowrule}% - % Now we add the shadow, like \shadowbox from fancybox.sty would do - \dimen@\dimexpr.5\sphinxshadowrule+\sphinxshadowsize\relax - \hbox{\vbox{\offinterlineskip - \hbox{\copy\@tempboxa\kern-.5\sphinxshadowrule - % add shadow on right side - \lower\sphinxshadowsize - \hbox{\vrule\@height\ht\@tempboxa \@width\dimen@}% - }% - \kern-\dimen@ % shift back vertically to bottom of frame - % and add shadow at bottom - \moveright\sphinxshadowsize - \vbox{\hrule\@width\wd\@tempboxa \@height\dimen@}% - }% - % move left by the size of right shadow so shadow adds no width - \kern-\sphinxshadowsize - }% - \endgroup -} - -% use framed.sty to allow page breaks in frame+shadow -% works well inside Lists and Quote-like environments -% produced by ``topic'' directive (or local contents) -% could nest if LaTeX writer authorized it -\newenvironment{sphinxShadowBox} - {\def\FrameCommand {\spx@ShadowFBox }% - \advance\spx@image@maxheight - -\dimexpr2\sphinxshadowrule - +2\sphinxshadowsep - +\sphinxshadowsize - +\baselineskip\relax - % configure framed.sty not to add extra vertical spacing - \ltx@ifundefined{OuterFrameSep}{}{\OuterFrameSep\z@skip}% - % the \trivlist will add the vertical spacing on top and bottom which is - % typical of center environment as used in Sphinx <= 1.4.1 - % the \noindent has the effet of an extra blank line on top, to - % imitate closely the layout from Sphinx <= 1.4.1; the \FrameHeightAdjust - % will put top part of frame on this baseline. - \def\FrameHeightAdjust {\baselineskip}% - % use package footnote to handle footnotes - \savenotes - \trivlist\item\noindent - % use a minipage if we are already inside a framed environment - \ifspx@inframed\begin{minipage}{\linewidth}\fi - \MakeFramed {\spx@inframedtrue - % framed.sty puts into "\width" the added width (=2shadowsep+2shadowrule) - % adjust \hsize to what the contents must use - \advance\hsize-\width - % adjust LaTeX parameters to behave properly in indented/quoted contexts - \FrameRestore - % typeset the contents as in a minipage (Sphinx <= 1.4.1 used a minipage and - % itemize/enumerate are therein typeset more tightly, we want to keep - % that). We copy-paste from LaTeX source code but don't do a real minipage. - \@pboxswfalse - \let\@listdepth\@mplistdepth \@mplistdepth\z@ - \@minipagerestore - \@setminipage - }% - }% - {% insert the "endminipage" code - \par\unskip - \@minipagefalse - \endMakeFramed - \ifspx@inframed\end{minipage}\fi - \endtrivlist - % output the stored footnotes - \spewnotes - } - - -%% NOTICES AND ADMONITIONS -% -% Some are quite plain -% the spx@notice@bordercolor etc are set in the sphinxadmonition environment -\newenvironment{sphinxlightbox}{% - \par - \noindent{\color{spx@notice@bordercolor}% - \rule{\linewidth}{\spx@notice@border}}\par\nobreak - {\parskip\z@skip\noindent}% - } - {% - % counteract previous possible negative skip (French lists!): - % (we can't cancel that any earlier \vskip introduced a potential pagebreak) - \sphinxvspacefixafterfrenchlists - \nobreak\vbox{\noindent\kern\@totalleftmargin - {\color{spx@notice@bordercolor}% - \rule[\dimexpr.4\baselineskip-\spx@notice@border\relax] - {\linewidth}{\spx@notice@border}}\hss}\allowbreak - }% end of sphinxlightbox environment definition -% may be renewenvironment'd by user for complete customization -\newenvironment{sphinxnote}[1] - {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} -\newenvironment{sphinxhint}[1] - {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} -\newenvironment{sphinximportant}[1] - {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} -\newenvironment{sphinxtip}[1] - {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} -% or just use the package options -% these are needed for common handling by notice environment of lightbox -% and heavybox but they are currently not used by lightbox environment -% and there is consequently no corresponding package option -\definecolor{sphinxnoteBgColor}{rgb}{1,1,1} -\definecolor{sphinxhintBgColor}{rgb}{1,1,1} -\definecolor{sphinximportantBgColor}{rgb}{1,1,1} -\definecolor{sphinxtipBgColor}{rgb}{1,1,1} - -% Others get more distinction -% Code adapted from framed.sty's "snugshade" environment. -% Nesting works (inner frames do not allow page breaks). -\newenvironment{sphinxheavybox}{\par - \setlength{\FrameRule}{\spx@notice@border}% - \setlength{\FrameSep}{\dimexpr.6\baselineskip-\FrameRule\relax} - \advance\spx@image@maxheight - -\dimexpr2\FrameRule - +2\FrameSep - +\baselineskip\relax % will happen again if nested, needed indeed! - % configure framed.sty's parameters to obtain same vertical spacing - % as for "light" boxes. We need for this to manually insert parskip glue and - % revert a skip done by framed before the frame. - \ltx@ifundefined{OuterFrameSep}{}{\OuterFrameSep\z@skip}% - \vspace{\FrameHeightAdjust} - % copied/adapted from framed.sty's snugshade - \def\FrameCommand##1{\hskip\@totalleftmargin - \fboxsep\FrameSep \fboxrule\FrameRule - \fcolorbox{spx@notice@bordercolor}{spx@notice@bgcolor}{##1}% - \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth}% - \savenotes - % use a minipage if we are already inside a framed environment - \ifspx@inframed - \noindent\begin{minipage}{\linewidth} - \else - % handle case where notice is first thing in a list item (or is quoted) - \if@inlabel - \noindent\par\vspace{-\baselineskip} - \else - \vspace{\parskip} - \fi - \fi - \MakeFramed {\spx@inframedtrue - \advance\hsize-\width \@totalleftmargin\z@ \linewidth\hsize - % minipage initialization copied from LaTeX source code. - \@pboxswfalse - \let\@listdepth\@mplistdepth \@mplistdepth\z@ - \@minipagerestore - \@setminipage }% - } - {% - \par\unskip - \@minipagefalse - \endMakeFramed - \ifspx@inframed\end{minipage}\fi - % set footnotes at bottom of page - \spewnotes - % arrange for similar spacing below frame as for "light" boxes. - \vskip .4\baselineskip - }% end of sphinxheavybox environment definition -% may be renewenvironment'd by user for complete customization -\newenvironment{sphinxwarning}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -\newenvironment{sphinxcaution}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -\newenvironment{sphinxattention}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -\newenvironment{sphinxdanger}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -\newenvironment{sphinxerror}[1] - {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} -% or just use package options - -% the \colorlet of xcolor (if at all loaded) is overkill for our use case -\newcommand{\sphinxcolorlet}[2] - {\expandafter\let\csname\@backslashchar color@#1\expandafter\endcsname - \csname\@backslashchar color@#2\endcsname } - -% the main dispatch for all types of notices -\newenvironment{sphinxadmonition}[2]{% #1=type, #2=heading - % can't use #1 directly in definition of end part - \def\spx@noticetype {#1}% - % set parameters of heavybox/lightbox - \sphinxcolorlet{spx@notice@bordercolor}{sphinx#1BorderColor}% - \sphinxcolorlet{spx@notice@bgcolor}{sphinx#1BgColor}% - \spx@notice@border \dimexpr\csname spx@opt@#1border\endcsname\relax - % start specific environment, passing the heading as argument - \begin{sphinx#1}{#2}} - % workaround some LaTeX "feature" of \end command - {\edef\spx@temp{\noexpand\end{sphinx\spx@noticetype}}\spx@temp} - - -%% PYTHON DOCS MACROS AND ENVIRONMENTS -% (some macros here used by \maketitle in sphinxmanual.cls and sphinxhowto.cls) - -% \moduleauthor{name}{email} -\newcommand{\moduleauthor}[2]{} - -% \sectionauthor{name}{email} -\newcommand{\sectionauthor}[2]{} - -% Allow the release number to be specified independently of the -% \date{}. This allows the date to reflect the document's date and -% release to specify the release that is documented. -% -\newcommand{\py@release}{\releasename\space\version} -\newcommand{\version}{}% part of \py@release, used by title page and headers -% \releaseinfo is used on titlepage (sphinxmanual.cls, sphinxhowto.cls) -\newcommand{\releaseinfo}{} -\newcommand{\setreleaseinfo}[1]{\renewcommand{\releaseinfo}{#1}} -% this is inserted via template and #1=release config variable -\newcommand{\release}[1]{\renewcommand{\version}{#1}} -% this is defined by template to 'releasename' latex_elements key -\newcommand{\releasename}{} -% Fix issue in case release and releasename deliberately left blank -\newcommand{\sphinxheadercomma}{, }% used in fancyhdr header definition -\newcommand{\sphinxifemptyorblank}[1]{% -% test after one expansion of macro #1 if contents is empty or spaces - \if&\expandafter\@firstofone\detokenize\expandafter{#1}&% - \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}% -\AtBeginDocument {% - \sphinxifemptyorblank{\releasename} - {\sphinxifemptyorblank{\version}{\let\sphinxheadercomma\empty}{}} - {}% -}% - -% Allow specification of the author's address separately from the -% author's name. This can be used to format them differently, which -% is a good thing. -% -\newcommand{\py@authoraddress}{} -\newcommand{\authoraddress}[1]{\renewcommand{\py@authoraddress}{#1}} - -% {fulllineitems} is the main environment for object descriptions. +%% INDEX, BIBLIOGRAPHY, APPENDIX, TABLE OF CONTENTS % -\newcommand{\py@itemnewline}[1]{% - \kern\labelsep - \@tempdima\linewidth - \advance\@tempdima \labelwidth\makebox[\@tempdima][l]{#1}% - \kern-\labelsep -} +\input{sphinxlatexindbibtoc.sty} -\newenvironment{fulllineitems}{% - \begin{list}{}{\labelwidth \leftmargin - \rightmargin \z@ \topsep -\parskip \partopsep \parskip - \itemsep -\parsep - \let\makelabel=\py@itemnewline}% -}{\end{list}} -% Signatures, possibly multi-line +%% STYLING % -\newlength{\py@argswidth} -\newcommand{\py@sigparams}[2]{% - \parbox[t]{\py@argswidth}{#1\sphinxcode{)}#2}} -\newcommand{\pysigline}[1]{\item[{#1}]} -\newcommand{\pysiglinewithargsret}[3]{% - \settowidth{\py@argswidth}{#1\sphinxcode{(}}% - \addtolength{\py@argswidth}{-2\py@argswidth}% - \addtolength{\py@argswidth}{\linewidth}% - \item[{#1\sphinxcode{(}\py@sigparams{#2}{#3}}]} -\newcommand{\pysigstartmultiline}{% - \def\pysigstartmultiline{\vskip\smallskipamount\parskip\z@skip\itemsep\z@skip}% - \edef\pysigstopmultiline - {\noexpand\leavevmode\parskip\the\parskip\relax\itemsep\the\itemsep\relax}% - \parskip\z@skip\itemsep\z@skip -} +\input{sphinxlatexstylepage.sty} +\input{sphinxlatexstyleheadings.sty} +\input{sphinxlatexstyletext.sty} -% Production lists -% -\newenvironment{productionlist}{% -% \def\sphinxoptional##1{{\Large[}##1{\Large]}} - \def\production##1##2{\\\sphinxcode{\sphinxupquote{##1}}&::=&\sphinxcode{\sphinxupquote{##2}}}% - \def\productioncont##1{\\& &\sphinxcode{\sphinxupquote{##1}}}% - \parindent=2em - \indent - \setlength{\LTpre}{0pt}% - \setlength{\LTpost}{0pt}% - \begin{longtable}[l]{lcl} -}{% - \end{longtable} -} -% Definition lists; requested by AMK for HOWTO documents. Probably useful -% elsewhere as well, so keep in in the general style support. +%% MODULE RELEASE DATA AND OBJECT DESCRIPTIONS % -\newenvironment{definitions}{% - \begin{description}% - \def\term##1{\item[{##1}]\mbox{}\\*[0mm]}% -}{% - \end{description}% -} - -%% FROM DOCTUTILS LATEX WRITER -% -% The following is stuff copied from docutils' latex writer. -% -\newcommand{\optionlistlabel}[1]{\normalfont\bfseries #1 \hfill}% \bf deprecated -\newenvironment{optionlist}[1] -{\begin{list}{} - {\setlength{\labelwidth}{#1} - \setlength{\rightmargin}{1cm} - \setlength{\leftmargin}{\rightmargin} - \addtolength{\leftmargin}{\labelwidth} - \addtolength{\leftmargin}{\labelsep} - \renewcommand{\makelabel}{\optionlistlabel}} -}{\end{list}} +\input{sphinxlatexobjects.sty} -\newlength{\lineblockindentation} -\setlength{\lineblockindentation}{2.5em} -\newenvironment{lineblock}[1] -{\begin{list}{} - {\setlength{\partopsep}{\parskip} - \addtolength{\partopsep}{\baselineskip} - \topsep0pt\itemsep0.15\baselineskip\parsep0pt - \leftmargin#1\relax} - \raggedright} -{\end{list}} - -% From docutils.writers.latex2e -% inline markup (custom roles) -% \DUrole{#1}{#2} tries \DUrole#1{#2} -\providecommand*{\DUrole}[2]{% - \ifcsname DUrole\detokenize{#1}\endcsname - \csname DUrole\detokenize{#1}\endcsname{#2}% - \else% backwards compatibility: try \docutilsrole#1{#2} - \ifcsname docutilsrole\detokenize{#1}\endcsname - \csname docutilsrole\detokenize{#1}\endcsname{#2}% - \else - #2% - \fi - \fi -} -\providecommand*{\DUprovidelength}[2]{% - \ifdefined#1\else\newlength{#1}\setlength{#1}{#2}\fi -} - -\DUprovidelength{\DUlineblockindent}{2.5em} -\ifdefined\DUlineblock\else - \newenvironment{DUlineblock}[1]{% - \list{}{\setlength{\partopsep}{\parskip} - \addtolength{\partopsep}{\baselineskip} - \setlength{\topsep}{0pt} - \setlength{\itemsep}{0.15\baselineskip} - \setlength{\parsep}{0pt} - \setlength{\leftmargin}{#1}} - \raggedright - } - {\endlist} -\fi - -%% TEXT STYLING -% -% to obtain straight quotes we execute \@noligs as patched by upquote, and -% \scantokens is needed in cases where it would be too late for the macro to -% first set catcodes and then fetch its argument. We also make the contents -% breakable at non-escaped . , ; ? ! / using \sphinxbreaksviaactive, -% and also at \ character (which is escaped to \textbackslash{}). -\protected\def\sphinxtextbackslashbreakbefore - {\discretionary{}{\sphinxafterbreak\sphinx@textbackslash}{\sphinx@textbackslash}} -\protected\def\sphinxtextbackslashbreakafter - {\discretionary{\sphinx@textbackslash}{\sphinxafterbreak}{\sphinx@textbackslash}} -\let\sphinxtextbackslash\sphinxtextbackslashbreakafter -% the macro must be protected if it ends up used in moving arguments, -% in 'alltt' \@noligs is done already, and the \scantokens must be avoided. -\protected\def\sphinxupquote#1{{\def\@tempa{alltt}% - \ifx\@tempa\@currenvir\else - \ifspx@opt@inlineliteralwraps - % break at . , ; ? ! / - \sphinxbreaksviaactive - % break also at \ - \let\sphinx@textbackslash\textbackslash - \let\textbackslash\sphinxtextbackslash - % by default, no continuation symbol on next line but may be added - \let\sphinxafterbreak\sphinxafterbreakofinlineliteral - % do not overwrite the comma set-up - \let\verbatim@nolig@list\sphinx@literal@nolig@list - \fi - % fix a space-gobbling issue due to LaTeX's original \do@noligs -% TODO: using \@noligs as patched by upquote.sty is now unneeded because -% either ` and ' are escaped (non-unicode engines) or they don't build -% ligatures (unicode engines). Thus remove this and unify handling of `, <, >, -% ' and - with the characters . , ; ? ! / as handled via -% \sphinxbreaksviaactive. -% Hence \sphinx@do@noligs will be removed, or rather replaced with code -% inserting discretionaries, as they allow a continuation symbol on start of -% next line to achieve common design with code-blocks. - \let\do@noligs\sphinx@do@noligs - \@noligs\endlinechar\m@ne\everyeof{}% (<- in case inside \sphinxhref) - \expandafter\scantokens - \fi {{#1}}}}% extra brace pair to fix end-space gobbling issue... -\def\sphinx@do@noligs #1{\catcode`#1\active\begingroup\lccode`\~`#1\relax - \lowercase{\endgroup\def~{\leavevmode\kern\z@\char`#1 }}} -\def\sphinx@literal@nolig@list {\do\`\do\<\do\>\do\'\do\-}% -\let\sphinxafterbreakofinlineliteral\empty - -% Some custom font markup commands. -\protected\def\sphinxstrong#1{\textbf{#1}} -\protected\def\sphinxcode#1{\texttt{#1}} -\protected\def\sphinxbfcode#1{\textbf{\sphinxcode{#1}}} -\protected\def\sphinxemail#1{\textsf{#1}} -\protected\def\sphinxtablecontinued#1{\textsf{#1}} -\protected\def\sphinxtitleref#1{\emph{#1}} -\protected\def\sphinxmenuselection#1{\emph{#1}} -\protected\def\sphinxguilabel#1{\emph{#1}} -\protected\def\sphinxkeyboard#1{\sphinxcode{#1}} -\protected\def\sphinxaccelerator#1{\underline{#1}} -\protected\def\sphinxcrossref#1{\emph{#1}} -\protected\def\sphinxtermref#1{\emph{#1}} -% \optional is used for ``[, arg]``, i.e. desc_optional nodes. -\long\protected\def\sphinxoptional#1{% - {\textnormal{\Large[}}{#1}\hspace{0.5mm}{\textnormal{\Large]}}} - -% additional customizable styling -\def\sphinxstyleindexentry #1{\texttt{#1}} -\def\sphinxstyleindexextra #1{ (\emph{#1})} -\def\sphinxstyleindexpageref #1{, \pageref{#1}} -\def\sphinxstyleindexpagemain#1{\textbf{#1}} -\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% - {{\Large\sffamily\sphinxnonalphabeticalgroupname}\nopagebreak\vspace{1mm}} -\protected\def\sphinxstyletopictitle #1{\textbf{#1}\par\medskip} -\let\sphinxstylesidebartitle\sphinxstyletopictitle -\protected\def\sphinxstyleothertitle #1{\textbf{#1}} -\protected\def\sphinxstylesidebarsubtitle #1{~\\\textbf{#1} \smallskip} -% \text.. commands do not allow multiple paragraphs -\protected\def\sphinxstyletheadfamily {\sffamily} -\protected\def\sphinxstyleemphasis #1{\emph{#1}} -\protected\def\sphinxstyleliteralemphasis#1{\emph{\sphinxcode{#1}}} -\protected\def\sphinxstylestrong #1{\textbf{#1}} -\protected\def\sphinxstyleliteralstrong#1{\sphinxbfcode{#1}} -\protected\def\sphinxstyleabbreviation #1{\textsc{#1}} -\protected\def\sphinxstyleliteralintitle#1{\sphinxcode{#1}} -\newcommand*\sphinxstylecodecontinued[1]{\footnotesize(#1)}% -\newcommand*\sphinxstylecodecontinues[1]{\footnotesize(#1)}% -% figure legend comes after caption and may contain arbitrary body elements -\newenvironment{sphinxlegend}{\par\small}{\par} -% reduce hyperref "Token not allowed in a PDF string" warnings on PDF builds -\AtBeginDocument{\pdfstringdefDisableCommands{% -% all "protected" macros possibly ending up in section titles should be here -% TODO: examine if \sphinxhref, \sphinxurl, \sphinnolinkurl should be handled - \let\sphinxstyleemphasis \@firstofone - \let\sphinxstyleliteralemphasis \@firstofone - \let\sphinxstylestrong \@firstofone - \let\sphinxstyleliteralstrong \@firstofone - \let\sphinxstyleabbreviation \@firstofone - \let\sphinxstyleliteralintitle \@firstofone - \let\sphinxupquote \@firstofone - \let\sphinxstrong \@firstofone - \let\sphinxcode \@firstofone - \let\sphinxbfcode \@firstofone - \let\sphinxemail \@firstofone - \let\sphinxcrossref \@firstofone - \let\sphinxtermref \@firstofone - \let\sphinxhyphen\sphinxhyphenforbookmarks -}} - -% Special characters -% -% This definition prevents en-dash and em-dash TeX ligatures. -% -% It inserts a potential breakpoint after the hyphen. This is to keep in sync -% with behavior in code-blocks, parsed and inline literals. For a breakpoint -% before the hyphen use \leavevmode\kern\z@- (within \makeatletter/\makeatother) -\protected\def\sphinxhyphen#1{-\kern\z@} -% The {} from texescape mark-up is kept, else -- gives en-dash in PDF bookmark -\def\sphinxhyphenforbookmarks{-} - -% For curly braces inside \index macro -\def\sphinxleftcurlybrace{\{} -\def\sphinxrightcurlybrace{\}} +% FIXME: this line should be dropped, as "9" is default anyhow. +\ifdefined\pdfcompresslevel\pdfcompresslevel = 9 \fi -% Declare Unicode characters used by linux tree command to pdflatex utf8/utf8x -\def\spx@bd#1#2{% - \leavevmode - \begingroup - \ifx\spx@bd@height \@undefined\def\spx@bd@height{\baselineskip}\fi - \ifx\spx@bd@width \@undefined\setbox0\hbox{0}\def\spx@bd@width{\wd0 }\fi - \ifx\spx@bd@thickness\@undefined\def\spx@bd@thickness{.6\p@}\fi - \ifx\spx@bd@lower \@undefined\def\spx@bd@lower{\dp\strutbox}\fi - \lower\spx@bd@lower#1{#2}% - \endgroup -}% -\@namedef{sphinx@u2500}% BOX DRAWINGS LIGHT HORIZONTAL - {\spx@bd{\vbox to\spx@bd@height} - {\vss\hrule\@height\spx@bd@thickness - \@width\spx@bd@width\vss}}% -\@namedef{sphinx@u2502}% BOX DRAWINGS LIGHT VERTICAL - {\spx@bd{\hb@xt@\spx@bd@width} - {\hss\vrule\@height\spx@bd@height - \@width \spx@bd@thickness\hss}}% -\@namedef{sphinx@u2514}% BOX DRAWINGS LIGHT UP AND RIGHT - {\spx@bd{\hb@xt@\spx@bd@width} - {\hss\raise.5\spx@bd@height - \hb@xt@\z@{\hss\vrule\@height.5\spx@bd@height - \@width \spx@bd@thickness\hss}% - \vbox to\spx@bd@height{\vss\hrule\@height\spx@bd@thickness - \@width.5\spx@bd@width\vss}}}% -\@namedef{sphinx@u251C}% BOX DRAWINGS LIGHT VERTICAL AND RIGHT - {\spx@bd{\hb@xt@\spx@bd@width} - {\hss - \hb@xt@\z@{\hss\vrule\@height\spx@bd@height - \@width \spx@bd@thickness\hss}% - \vbox to\spx@bd@height{\vss\hrule\@height\spx@bd@thickness - \@width.5\spx@bd@width\vss}}}% -\protected\def\sphinxunichar#1{\@nameuse{sphinx@u#1}}% -% Tell TeX about pathological hyphenation cases: -\hyphenation{Base-HTTP-Re-quest-Hand-ler} \endinput diff --git a/sphinx/texinputs/sphinxlatexadmonitions.sty b/sphinx/texinputs/sphinxlatexadmonitions.sty new file mode 100644 index 000000000..1e418c8c2 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexadmonitions.sty @@ -0,0 +1,148 @@ +%% NOTICES AND ADMONITIONS +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexadmonitions.sty}[2021/01/27 admonitions] + +% Provides support for this output mark-up from Sphinx latex writer: +% +% - sphinxadmonition (environment) +% This is a dispatch supporting +% +% - note, hint, important, tip (via sphinxlightbox) +% - warning, caution, attention, danger, error (via sphinxheavybox) +% +% Each sphinx<notice name> environment can be redefined by user. +% The defaults are customizable via various colour and dimension +% settings, cf sphinx docs (latex customization). +% +% Requires: +\RequirePackage{framed}% used by sphinxheavybox +% +% Dependencies (they do not need to be defined at time of loading): +% - of course the various colour and dimension options handled via sphinx.sty +% - \sphinxstrong (for sphinxlightbox and sphinxheavybox) +% - dimension register \spx@image@maxheight from sphinxlatexgraphics.sty +% - \savenotes/\spewnotes from sphinxpackagefootnote (for sphinxheavybox) + +% Provides: (also in sphinxlatexliterals.sty) +\providecommand*\sphinxvspacefixafterfrenchlists{% + \ifvmode\ifdim\lastskip<\z@ \vskip\parskip\fi\else\par\fi +} + +% Some are quite plain +% the spx@notice@bordercolor etc are set in the sphinxadmonition environment +\newenvironment{sphinxlightbox}{% + \par + \noindent{\color{spx@notice@bordercolor}% + \rule{\linewidth}{\spx@notice@border}}\par\nobreak + {\parskip\z@skip\noindent}% + } + {% + % counteract previous possible negative skip (French lists!): + % (we can't cancel that any earlier \vskip introduced a potential pagebreak) + \sphinxvspacefixafterfrenchlists + \nobreak\vbox{\noindent\kern\@totalleftmargin + {\color{spx@notice@bordercolor}% + \rule[\dimexpr.4\baselineskip-\spx@notice@border\relax] + {\linewidth}{\spx@notice@border}}\hss}\allowbreak + }% end of sphinxlightbox environment definition +% may be renewenvironment'd by user for complete customization +\newenvironment{sphinxnote}[1] + {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} +\newenvironment{sphinxhint}[1] + {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} +\newenvironment{sphinximportant}[1] + {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} +\newenvironment{sphinxtip}[1] + {\begin{sphinxlightbox}\sphinxstrong{#1} }{\end{sphinxlightbox}} +% or just use the package options +% these are needed for common handling by notice environment of lightbox +% and heavybox but they are currently not used by lightbox environment +% and there is consequently no corresponding package option +\definecolor{sphinxnoteBgColor}{rgb}{1,1,1} +\definecolor{sphinxhintBgColor}{rgb}{1,1,1} +\definecolor{sphinximportantBgColor}{rgb}{1,1,1} +\definecolor{sphinxtipBgColor}{rgb}{1,1,1} + +% Others get more distinction +% Code adapted from framed.sty's "snugshade" environment. +% Nesting works (inner frames do not allow page breaks). +\newenvironment{sphinxheavybox}{\par + \setlength{\FrameRule}{\spx@notice@border}% + \setlength{\FrameSep}{\dimexpr.6\baselineskip-\FrameRule\relax} + \advance\spx@image@maxheight + -\dimexpr2\FrameRule + +2\FrameSep + +\baselineskip\relax % will happen again if nested, needed indeed! + % configure framed.sty's parameters to obtain same vertical spacing + % as for "light" boxes. We need for this to manually insert parskip glue and + % revert a skip done by framed before the frame. + \ltx@ifundefined{OuterFrameSep}{}{\OuterFrameSep\z@skip}% + \vspace{\FrameHeightAdjust} + % copied/adapted from framed.sty's snugshade + \def\FrameCommand##1{\hskip\@totalleftmargin + \fboxsep\FrameSep \fboxrule\FrameRule + \fcolorbox{spx@notice@bordercolor}{spx@notice@bgcolor}{##1}% + \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth}% + \savenotes + % use a minipage if we are already inside a framed environment + \ifspx@inframed + \noindent\begin{minipage}{\linewidth} + \else + % handle case where notice is first thing in a list item (or is quoted) + \if@inlabel + \noindent\par\vspace{-\baselineskip} + \else + \vspace{\parskip} + \fi + \fi + \MakeFramed {\spx@inframedtrue + \advance\hsize-\width \@totalleftmargin\z@ \linewidth\hsize + % minipage initialization copied from LaTeX source code. + \@pboxswfalse + \let\@listdepth\@mplistdepth \@mplistdepth\z@ + \@minipagerestore + \@setminipage }% + } + {% + \par\unskip + \@minipagefalse + \endMakeFramed + \ifspx@inframed\end{minipage}\fi + % set footnotes at bottom of page + \spewnotes + % arrange for similar spacing below frame as for "light" boxes. + \vskip .4\baselineskip + }% end of sphinxheavybox environment definition +% may be renewenvironment'd by user for complete customization +\newenvironment{sphinxwarning}[1] + {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} +\newenvironment{sphinxcaution}[1] + {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} +\newenvironment{sphinxattention}[1] + {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} +\newenvironment{sphinxdanger}[1] + {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} +\newenvironment{sphinxerror}[1] + {\begin{sphinxheavybox}\sphinxstrong{#1} }{\end{sphinxheavybox}} +% or just use package options + +% the \colorlet of xcolor (if at all loaded) is overkill for our use case +\newcommand{\sphinxcolorlet}[2] + {\expandafter\let\csname\@backslashchar color@#1\expandafter\endcsname + \csname\@backslashchar color@#2\endcsname } + +% the main dispatch for all types of notices +\newenvironment{sphinxadmonition}[2]{% #1=type, #2=heading + % can't use #1 directly in definition of end part + \def\spx@noticetype {#1}% + % set parameters of heavybox/lightbox + \sphinxcolorlet{spx@notice@bordercolor}{sphinx#1BorderColor}% + \sphinxcolorlet{spx@notice@bgcolor}{sphinx#1BgColor}% + \spx@notice@border \dimexpr\csname spx@opt@#1border\endcsname\relax + % start specific environment, passing the heading as argument + \begin{sphinx#1}{#2}} + % workaround some LaTeX "feature" of \end command + {\edef\spx@temp{\noexpand\end{sphinx\spx@noticetype}}\spx@temp} + +\endinput diff --git a/sphinx/texinputs/sphinxlatexgraphics.sty b/sphinx/texinputs/sphinxlatexgraphics.sty new file mode 100644 index 000000000..fd0aae638 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexgraphics.sty @@ -0,0 +1,122 @@ +%% GRAPHICS +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexgraphics.sty}[2021/01/27 graphics] + +% Provides support for this output mark-up from Sphinx latex writer: +% +% - macros: +% +% - \sphinxfigcaption +% - \sphinxincludegraphics +% +% - environments: +% +% - sphinxfigure-in-table +% +% May change: +% +% - \sphinxcaption (at begin document) +% +% Also provides: +% +% - \sphinxsafeincludegraphics (default of \sphinxincludegraphics since 2.0) +% - \spx@image@maxheight dimension (used by sphinxlatexadmonitions.sty) +% - \spx@image@box scratch box register (also used by sphinxlatexliterals.sty) +% +% Requires: +% \RequirePackage{graphicx}% done in sphinx.sty +\RequirePackage{amstext}% needed for \firstchoice@true(false) + +% \sphinxincludegraphics resizes images larger than the TeX \linewidth (which +% is adjusted in indented environments), or taller than a certain maximal +% height (usually \textheight and this is reduced in the environments which use +% framed.sty to avoid infinite loop if image too tall). +% +% In case height or width options are present the rescaling is done +% (since 2.0), in a way keeping the width:height ratio either native from +% image or from the width and height options if both were present. +% +\newdimen\spx@image@maxheight +\AtBeginDocument{\spx@image@maxheight\textheight} + +% box scratch register +\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}}% + \in@false % use some handy boolean flag + \ifdim \wd\spx@image@box>\linewidth + \in@true % flag to remember to adjust options and set box dimensions + % compute height which results from rescaling width to \linewidth + % and keep current aspect ratio. multiply-divide in \numexpr uses + % temporarily doubled precision, hence no overflow. (of course we + % assume \ht is not a few sp's below \maxdimen...(about 16384pt). + \edef\spx@image@rescaledheight % with sp units + {\the\numexpr\ht\spx@image@box + *\linewidth/\wd\spx@image@box sp}% + \ifdim\spx@image@rescaledheight>\spx@image@maxheight + % the rescaled height will be too big, so it is height which decides + % the rescaling factor + \def\spx@image@requiredheight{\spx@image@maxheight}% dimen register + \edef\spx@image@requiredwidth % with sp units + {\the\numexpr\wd\spx@image@box + *\spx@image@maxheight/\ht\spx@image@box sp}% + % TODO: decide if this commented-out block could be needed due to + % rounding in numexpr operations going up + % \ifdim\spx@image@requiredwidth>\linewidth + % \def\spx@image@requiredwidth{\linewidth}% dimen register + % \fi + \else + \def\spx@image@requiredwidth{\linewidth}% dimen register + \let\spx@image@requiredheight\spx@image@rescaledheight% sp units + \fi + \else + % width is ok, let's check height + \ifdim\ht\spx@image@box>\spx@image@maxheight + \in@true + \edef\spx@image@requiredwidth % with sp units + {\the\numexpr\wd\spx@image@box + *\spx@image@maxheight/\ht\spx@image@box sp}% + \def\spx@image@requiredheight{\spx@image@maxheight}% dimen register + \fi + \fi % end of check of width and height + \ifin@ + \setbox\spx@image@box + \hbox{\includegraphics + [%#1,% contained only width and/or height and overruled anyhow + width=\spx@image@requiredwidth,height=\spx@image@requiredheight]% + {#2}}% + % \includegraphics does not set box dimensions to the exactly + % requested ones, see https://github.com/latex3/latex2e/issues/112 + \wd\spx@image@box\spx@image@requiredwidth + \ht\spx@image@box\spx@image@requiredheight + \leavevmode\box\spx@image@box + \else + % here we do not modify the options, no need to adjust width and height + % on output, they will be computed exactly as with "draft" option + \setbox\spx@image@box\box\voidb@x % clear memory + \includegraphics[#1]{#2}% + \fi +}% +% Use the "safe" one by default (2.0) +\def\sphinxincludegraphics{\sphinxsafeincludegraphics} + + +%% FIGURE IN TABLE +% +\newenvironment{sphinxfigure-in-table}[1][\linewidth]{% + \def\@captype{figure}% + \sphinxsetvskipsforfigintablecaption + \begin{minipage}{#1}% +}{\end{minipage}} +% tabulary expands twice contents, we need to prevent double counter stepping +\newcommand*\sphinxfigcaption + {\ifx\equation$%$% this is trick to identify tabulary first pass + \firstchoice@false\else\firstchoice@true\fi + \spx@originalcaption } +\newcommand*\sphinxsetvskipsforfigintablecaption + {\abovecaptionskip\smallskipamount + \belowcaptionskip\smallskipamount} + +\endinput diff --git a/sphinx/texinputs/sphinxlatexindbibtoc.sty b/sphinx/texinputs/sphinxlatexindbibtoc.sty new file mode 100644 index 000000000..79e30a1f1 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexindbibtoc.sty @@ -0,0 +1,69 @@ +%% INDEX, BIBLIOGRAPHY, APPENDIX, TABLE OF CONTENTS +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexindbibtoc.sty}[2021/01/27 index, bib., toc] + +% Provides support for this output mark-up from Sphinx latex writer: +% +% - environments: (backup defaults or get redefined) +% +% - sphinxtheindex (direct mark-up or via python.ist or sphinx.xdy) +% - sphinxthebibliography +% +% - macros: (defines defaults) +% +% - \sphinxmaketitle +% - \sphinxtableofcontents +% - \sphinxnonalphabeticalgroupname +% - \sphinxsymbolsname +% - \sphinxnumbersname +% - \sphinxcite +% +% Requires: +\RequirePackage{makeidx} + +% fix the double index and bibliography on the table of contents +% in jsclasses (Japanese standard document classes) +\ifx\@jsc@uplatextrue\@undefined\else + \renewenvironment{sphinxtheindex} + {\cleardoublepage\phantomsection + \begin{theindex}} + {\end{theindex}} + + \renewenvironment{sphinxthebibliography}[1] + {\cleardoublepage% \phantomsection % not needed here since TeXLive 2010's hyperref + \begin{thebibliography}{#1}} + {\end{thebibliography}} +\fi + +% disable \@chappos in Appendix in pTeX +\ifx\kanjiskip\@undefined\else + \let\py@OldAppendix=\appendix + \renewcommand{\appendix}{ + \py@OldAppendix + \gdef\@chappos{} + } +\fi + +% make commands known to non-Sphinx document classes +\providecommand*{\sphinxmaketitle}{\maketitle} +\providecommand*{\sphinxtableofcontents}{\tableofcontents} +\ltx@ifundefined{sphinxthebibliography} + {\newenvironment + {sphinxthebibliography}{\begin{thebibliography}}{\end{thebibliography}}% + } + {}% else clause of \ltx@ifundefined +\ltx@ifundefined{sphinxtheindex} + {\newenvironment{sphinxtheindex}{\begin{theindex}}{\end{theindex}}}% + {}% else clause of \ltx@ifundefined + +% for usage with xindy: this string gets internationalized in preamble +\newcommand*{\sphinxnonalphabeticalgroupname}{} +% redefined in preamble, headings for makeindex produced index +\newcommand*{\sphinxsymbolsname}{} +\newcommand*{\sphinxnumbersname}{} + +\protected\def\sphinxcite{\cite} + + +\endinput diff --git a/sphinx/texinputs/sphinxlatexlists.sty b/sphinx/texinputs/sphinxlatexlists.sty new file mode 100644 index 000000000..3860090c2 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexlists.sty @@ -0,0 +1,97 @@ +%% ALPHANUMERIC LIST ITEMS +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexlists.sty}[2021/01/27 lists] + +% Provides support for this output mark-up from Sphinx latex writer: +% - \sphinxsetlistlabels + +% Dependencies: the \spx@opt@maxlistdepth from sphinx.sty + +\newcommand\sphinxsetlistlabels[5] +{% #1 = style, #2 = enum, #3 = enumnext, #4 = prefix, #5 = suffix + % #2 and #3 are counters used by enumerate environement e.g. enumi, enumii. + % #1 is a macro such as \arabic or \alph + % prefix and suffix are strings (by default empty and a dot). + \@namedef{the#2}{#1{#2}}% + \@namedef{label#2}{#4\@nameuse{the#2}#5}% + \@namedef{p@#3}{\@nameuse{p@#2}#4\@nameuse{the#2}#5}% +}% + + +%% MAXLISTDEPTH +% +% remove LaTeX's cap on nesting depth if 'maxlistdepth' key used. +% This is a hack, which works with the standard classes: it assumes \@toodeep +% is always used in "true" branches: "\if ... \@toodeep \else .. \fi." + +% will force use the "false" branch (if there is one) +\def\spx@toodeep@hack{\fi\iffalse} + +% do nothing if 'maxlistdepth' key not used or if package enumitem loaded. +\ifnum\spx@opt@maxlistdepth=\z@\expandafter\@gobbletwo\fi +\AtBeginDocument{% +\@ifpackageloaded{enumitem}{\remove@to@nnil}{}% + \let\spx@toodeepORI\@toodeep + \def\@toodeep{% + \ifnum\@listdepth<\spx@opt@maxlistdepth\relax + \expandafter\spx@toodeep@hack + \else + \expandafter\spx@toodeepORI + \fi}% +% define all missing \@list... macros + \count@\@ne + \loop + \ltx@ifundefined{@list\romannumeral\the\count@} + {\iffalse}{\iftrue\advance\count@\@ne}% + \repeat + \loop + \ifnum\count@>\spx@opt@maxlistdepth\relax\else + \expandafter\let + \csname @list\romannumeral\the\count@\expandafter\endcsname + \csname @list\romannumeral\the\numexpr\count@-\@ne\endcsname + % workaround 2.6--3.2d babel-french issue (fixed in 3.2e; no change needed) + \ltx@ifundefined{leftmargin\romannumeral\the\count@} + {\expandafter\let + \csname leftmargin\romannumeral\the\count@\expandafter\endcsname + \csname leftmargin\romannumeral\the\numexpr\count@-\@ne\endcsname}{}% + \advance\count@\@ne + \repeat +% define all missing enum... counters and \labelenum... macros and \p@enum.. + \count@\@ne + \loop + \ltx@ifundefined{c@enum\romannumeral\the\count@} + {\iffalse}{\iftrue\advance\count@\@ne}% + \repeat + \loop + \ifnum\count@>\spx@opt@maxlistdepth\relax\else + \newcounter{enum\romannumeral\the\count@}% + \expandafter\def + \csname labelenum\romannumeral\the\count@\expandafter\endcsname + \expandafter + {\csname theenum\romannumeral\the\numexpr\count@\endcsname.}% + \expandafter\def + \csname p@enum\romannumeral\the\count@\expandafter\endcsname + \expandafter + {\csname p@enum\romannumeral\the\numexpr\count@-\@ne\expandafter + \endcsname\csname theenum\romannumeral\the\numexpr\count@-\@ne\endcsname.}% + \advance\count@\@ne + \repeat +% define all missing labelitem... macros + \count@\@ne + \loop + \ltx@ifundefined{labelitem\romannumeral\the\count@} + {\iffalse}{\iftrue\advance\count@\@ne}% + \repeat + \loop + \ifnum\count@>\spx@opt@maxlistdepth\relax\else + \expandafter\let + \csname labelitem\romannumeral\the\count@\expandafter\endcsname + \csname labelitem\romannumeral\the\numexpr\count@-\@ne\endcsname + \advance\count@\@ne + \repeat + \PackageInfo{sphinx}{maximal list depth extended to \spx@opt@maxlistdepth}% +\@gobble\@nnil +} + +\endinput diff --git a/sphinx/texinputs/sphinxlatexliterals.sty b/sphinx/texinputs/sphinxlatexliterals.sty new file mode 100644 index 000000000..4d0312fc6 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexliterals.sty @@ -0,0 +1,794 @@ +%% LITERAL BLOCKS +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexliterals.sty}[2021/01/27 code-blocks and parsed literals] + +% Provides support for this output mark-up from Sphinx latex writer: +% +% - macros: +% - \sphinxLiteralBlockLabel +% - \sphinxSetupCaptionForVerbatim +% - \sphinxSetupCodeBlockInFootnote +% - \sphinxhref +% - \sphinxnolinkurl +% - \sphinxresetverbatimhllines +% - \sphinxunactivateextrasandspace +% - \sphinxupquote +% - \sphinxurl +% +% - environments: +% - sphinxVerbatim +% - sphinxVerbatimintable +% - sphinxalltt +% +% Dependency: +% +% - hyperref (for \phantomsection and \capstart) (loaded later) +% +% Executes \RequirePackage for: +% +% - framed +% - fancyvrb +% - alltt +% - upquote +% - needspace + +% also in sphinxlatexadmonitions.sty: +% This is a workaround to a "feature" of French lists, when literal block +% follows immediately; usable generally (does only \par then), a priori... +\providecommand*\sphinxvspacefixafterfrenchlists{% + \ifvmode\ifdim\lastskip<\z@ \vskip\parskip\fi\else\par\fi +} + +% For framing allowing pagebreaks +\RequirePackage{framed} +% For source code +% MEMO: fancyvrb is used mainly to +% 1- control horizontal and vertical spacing +% 2- optional line numbering +% 3- optional line emphasizing +% 4- while still allowing expansion of Pygments latex mark-up +% Other aspects such as framing, caption handling, codeline wrapping are +% added on top of it. We should stop using fancyvrb and implement +% 1, 2, 3, 4 by own Sphinx fully native Verbatim. This would allow to solve +% limitations with wrapped long code line not allowing page break. +\RequirePackage{fancyvrb} +% For parsed-literal blocks. +\RequirePackage{alltt} +% Display "real" single quotes in literal blocks. +\RequirePackage{upquote} +% Skip to next page if not enough space at bottom +\RequirePackage{needspace} + +% Based on use of "fancyvrb.sty"'s Verbatim. +% - with framing allowing page breaks ("framed.sty") +% - with breaking of long lines (exploits Pygments mark-up), +% - with possibly of a top caption, non-separable by pagebreak. +% - and usable inside tables or footnotes ("sphinxpackagefootnote.sty"). + +% for emphasizing lines +\define@key{FV}{hllines}{\def\sphinx@verbatim@checkifhl##1{\in@{, ##1,}{#1}}} +% sphinxVerbatim must be usable by third party without requiring hllines set-up +\def\sphinxresetverbatimhllines{\def\sphinx@verbatim@checkifhl##1{\in@false}} +\sphinxresetverbatimhllines + +% 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 + +% for captions of literal blocks +% at start of caption title +\newcommand*{\fnum@literalblock}{\literalblockname\nobreakspace\theliteralblock} +% this will be overwritten in document preamble by Babel translation +\newcommand*{\literalblockname}{Listing } +% file extension needed for \caption's good functioning, the file is created +% only if a \listof{literalblock}{foo} command is encountered, which is +% analogous to \listoffigures, but for the code listings (foo = chosen title.) +\newcommand*{\ext@literalblock}{lol} + +% if forced use of minipage encapsulation is needed (e.g. table cells) +\newif\ifsphinxverbatimwithminipage \sphinxverbatimwithminipagefalse + +% Framing macro for use with framed.sty's \FrameCommand +% - it obeys current indentation, +% - frame is \fboxsep separated from the contents, +% - the contents use the full available text width, +% - #1 = color of frame, #2 = color of background, +% - #3 = above frame, #4 = below frame, #5 = within frame, +% - #3 and #4 must be already typeset boxes; they must issue \normalcolor +% or similar, else, they are under scope of color #1 +\long\def\spx@fcolorbox #1#2#3#4#5{% + \hskip\@totalleftmargin + \hskip-\fboxsep\hskip-\fboxrule + % use of \color@b@x here is compatible with both xcolor.sty and color.sty + \color@b@x {\color{#1}\spx@CustomFBox{#3}{#4}}{\color{#2}}{#5}% + \hskip-\fboxsep\hskip-\fboxrule + \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth +}% +% #1 = for material above frame, such as a caption or a "continued" hint +% #2 = for material below frame, such as a caption or "continues on next page" +% #3 = actual contents, which will be typeset with a background color +\long\def\spx@CustomFBox#1#2#3{% + \begingroup + \setbox\@tempboxa\hbox{{#3}}% inner braces to avoid color leaks + \vbox{#1% above frame + % draw frame border _latest_ to avoid pdf viewer issue + \kern\fboxrule + \hbox{\kern\fboxrule + \copy\@tempboxa + \kern-\wd\@tempboxa\kern-\fboxrule + \vrule\@width\fboxrule + \kern\wd\@tempboxa + \vrule\@width\fboxrule}% + \kern-\dimexpr\ht\@tempboxa+\dp\@tempboxa+\fboxrule\relax + \hrule\@height\fboxrule + \kern\dimexpr\ht\@tempboxa+\dp\@tempboxa\relax + \hrule\@height\fboxrule + #2% below frame + }% + \endgroup +}% +\def\spx@fcolorbox@put@c#1{% hide width from framed.sty measuring + \moveright\dimexpr\fboxrule+.5\wd\@tempboxa\hb@xt@\z@{\hss#1\hss}% +}% +\def\spx@fcolorbox@put@r#1{% right align with contents, width hidden + \moveright\dimexpr\fboxrule+\wd\@tempboxa-\fboxsep\hb@xt@\z@{\hss#1}% +}% +\def\spx@fcolorbox@put@l#1{% left align with contents, width hidden + \moveright\dimexpr\fboxrule+\fboxsep\hb@xt@\z@{#1\hss}% +}% +% +\def\sphinxVerbatim@Continued + {\csname spx@fcolorbox@put@\spx@opt@verbatimcontinuedalign\endcsname + {\normalcolor\sphinxstylecodecontinued\literalblockcontinuedname}}% +\def\sphinxVerbatim@Continues + {\csname spx@fcolorbox@put@\spx@opt@verbatimcontinuesalign\endcsname + {\normalcolor\sphinxstylecodecontinues\literalblockcontinuesname}}% +\def\sphinxVerbatim@Title + {\spx@fcolorbox@put@c{\unhcopy\sphinxVerbatim@TitleBox}}% +\let\sphinxVerbatim@Before\@empty +\let\sphinxVerbatim@After\@empty +% Defaults are redefined in document preamble according to language +\newcommand*\literalblockcontinuedname{continued from previous page}% +\newcommand*\literalblockcontinuesname{continues on next page}% +% +\def\spx@verbatimfcolorbox{\spx@fcolorbox{VerbatimBorderColor}{VerbatimColor}}% +\def\sphinxVerbatim@FrameCommand + {\spx@verbatimfcolorbox\sphinxVerbatim@Before\sphinxVerbatim@After}% +\def\sphinxVerbatim@FirstFrameCommand + {\spx@verbatimfcolorbox\sphinxVerbatim@Before\sphinxVerbatim@Continues}% +\def\sphinxVerbatim@MidFrameCommand + {\spx@verbatimfcolorbox\sphinxVerbatim@Continued\sphinxVerbatim@Continues}% +\def\sphinxVerbatim@LastFrameCommand + {\spx@verbatimfcolorbox\sphinxVerbatim@Continued\sphinxVerbatim@After}% + +% For linebreaks inside Verbatim environment from package fancyvrb. +\newbox\sphinxcontinuationbox +\newbox\sphinxvisiblespacebox +\newcommand*\sphinxafterbreak {\copy\sphinxcontinuationbox} + +% Take advantage of the already applied Pygments mark-up to insert +% potential linebreaks for TeX processing. +% {, <, #, %, $, ' and ": go to next line. +% _, }, ^, &, >, -, ~, and \: stay at end of broken line. +% Use of \textquotesingle for straight quote. +% FIXME: convert this to package options ? +\newcommand*\sphinxbreaksbeforelist {% + \do\PYGZob\{\do\PYGZlt\<\do\PYGZsh\#\do\PYGZpc\%% {, <, #, %, + \do\PYGZdl\$\do\PYGZdq\"% $, " + \def\PYGZsq + {\discretionary{}{\sphinxafterbreak\textquotesingle}{\textquotesingle}}% ' +} +\newcommand*\sphinxbreaksafterlist {% + \do\PYGZus\_\do\PYGZcb\}\do\PYGZca\^\do\PYGZam\&% _, }, ^, &, + \do\PYGZgt\>\do\PYGZhy\-\do\PYGZti\~% >, -, ~ + \do\PYGZbs\\% \ +} +\newcommand*\sphinxbreaksatspecials {% + \def\do##1##2% + {\def##1{\discretionary{}{\sphinxafterbreak\char`##2}{\char`##2}}}% + \sphinxbreaksbeforelist + \def\do##1##2% + {\def##1{\discretionary{\char`##2}{\sphinxafterbreak}{\char`##2}}}% + \sphinxbreaksafterlist +} + +\def\sphinx@verbatim@nolig@list {\do \`}% +% Some characters . , ; ? ! / are neither pygmentized nor "tex-escaped". +% This macro makes them "active" and they will insert potential linebreaks. +% Not compatible with math mode (cf \sphinxunactivateextras). +\newcommand*\sphinxbreaksbeforeactivelist {}% none +\newcommand*\sphinxbreaksafteractivelist {\do\.\do\,\do\;\do\?\do\!\do\/} +\newcommand*\sphinxbreaksviaactive {% + \def\do##1{\lccode`\~`##1% + \lowercase{\def~}{\discretionary{}{\sphinxafterbreak\char`##1}{\char`##1}}% + \catcode`##1\active}% + \sphinxbreaksbeforeactivelist + \def\do##1{\lccode`\~`##1% + \lowercase{\def~}{\discretionary{\char`##1}{\sphinxafterbreak}{\char`##1}}% + \catcode`##1\active}% + \sphinxbreaksafteractivelist + \lccode`\~`\~ +} + +% If the linebreak is at a space, the latter will be displayed as visible +% space at end of first line, and a continuation symbol starts next line. +\def\spx@verbatim@space {% + \nobreak\hskip\z@skip + \discretionary{\copy\sphinxvisiblespacebox}{\sphinxafterbreak} + {\kern\fontdimen2\font}% +}% + +% if the available space on page is less than \literalblockneedspace, insert pagebreak +\newcommand{\sphinxliteralblockneedspace}{5\baselineskip} +\newcommand{\sphinxliteralblockwithoutcaptionneedspace}{1.5\baselineskip} +% The title (caption) is specified from outside as macro \sphinxVerbatimTitle. +% \sphinxVerbatimTitle is reset to empty after each use of Verbatim. +\newcommand*\sphinxVerbatimTitle {} +% This box to typeset the caption before framed.sty multiple passes for framing. +\newbox\sphinxVerbatim@TitleBox +% This box to measure contents if nested as inner \MakeFramed requires then +% minipage encapsulation but too long contents then break outer \MakeFramed +\newbox\sphinxVerbatim@ContentsBox +% Holder macro for labels of literal blocks. Set-up by LaTeX writer. +\newcommand*\sphinxLiteralBlockLabel {} +\newcommand*\sphinxSetupCaptionForVerbatim [1] +{% + \sphinxvspacefixafterfrenchlists + \needspace{\sphinxliteralblockneedspace}% +% insert a \label via \sphinxLiteralBlockLabel +% reset to normal the color for the literal block caption + \def\sphinxVerbatimTitle + {\py@NormalColor\sphinxcaption{\sphinxLiteralBlockLabel #1}}% +} +\newcommand*\sphinxSetupCodeBlockInFootnote {% + \fvset{fontsize=\footnotesize}\let\caption\sphinxfigcaption + \sphinxverbatimwithminipagetrue % reduces vertical spaces + % we counteract (this is in a group) the \@normalsize from \caption + \let\normalsize\footnotesize\let\@parboxrestore\relax + \def\spx@abovecaptionskip{\sphinxverbatimsmallskipamount}% +} +\newcommand*{\sphinxverbatimsmallskipamount}{\smallskipamount} +% serves to implement line highlighting and line wrapping +\newcommand\sphinxFancyVerbFormatLine[1]{% + \expandafter\sphinx@verbatim@checkifhl\expandafter{\the\FV@CodeLineNo}% + \ifin@ + \sphinxVerbatimHighlightLine{#1}% + \else + \sphinxVerbatimFormatLine{#1}% + \fi +}% +\newcommand\sphinxVerbatimHighlightLine[1]{% + \edef\sphinxrestorefboxsep{\fboxsep\the\fboxsep\relax}% + \fboxsep0pt\relax % cf LaTeX bug graphics/4524 + \colorbox{sphinxVerbatimHighlightColor}% + {\sphinxrestorefboxsep\sphinxVerbatimFormatLine{#1}}% + % no need to restore \fboxsep here, as this ends up in a \hbox from fancyvrb +}% +% \sphinxVerbatimFormatLine will be set locally to one of those two: +\newcommand\sphinxVerbatimFormatLineWrap{% + \hsize\linewidth + \ifspx@opt@verbatimforcewraps + \expandafter\spx@verb@FormatLineForceWrap + \else\expandafter\spx@verb@FormatLineWrap + \fi +}% +\newcommand\sphinxVerbatimFormatLineNoWrap[1]{\hb@xt@\linewidth{\strut #1\hss}}% +\long\def\spx@verb@FormatLineWrap#1{% + \vtop{\raggedright\hyphenpenalty\z@\exhyphenpenalty\z@ + \doublehyphendemerits\z@\finalhyphendemerits\z@ + \strut #1\strut}% +}% +% +% The normal line wrapping allows breaks at spaces and ascii non +% letters, non digits. The \raggedright above means there will be +% an overfilled line only if some non-breakable "word" was +% encountered, which is longer than a line (it is moved always to +% be on its own on a new line). +% +% The "forced" line wrapping will parse the tokens to add potential +% breakpoints at each character. As some strings are highlighted, +% we have to apply the highlighting character per character, which +% requires to manipulate the output of the Pygments LaTeXFormatter. +% +% Doing this at latex level is complicated. The contents should +% be as expected: i.e. some active characters from +% \sphinxbreaksviaactive, some Pygments character escapes such as +% \PYGZdl{}, and the highlighting \PYG macro with always 2 +% arguments. No other macros should be there, except perhaps +% zero-parameter macros. In particular: +% - the texcomments Pygments option must be set to False +% +% With pdflatex, Unicode input gives multi-bytes characters +% where the first byte is active. We support the "utf8" macros +% only. "utf8x" is not supported. +% +% The highlighting macro \PYG will be applied character per +% character. Highlighting via a colored background gives thus a +% chain of small colored boxes which may cause some artefact in +% some pdf viewers. Can't do anything here if we do want the line +% break to be possible. +% +% First a measurement step is done of what would the standard line +% wrapping give (i.e line breaks only at spaces and non-letter, +% non-digit ascii characters), cf TeX by Topic for the basic +% dissecting technique: TeX unfortunately when building a vertical +% box does not store in an accessible way what was the maximal +% line-width during paragraph building. +% +% Avoid LaTeX 2021 alteration of \@@par which potentially could break our +% measurement step (typically if the para/after hook is configured to use +% \vspace). Of course, breakage could happen only from user or package +% adding things to basic Sphinx latex. And perhaps spring LaTeX 2021 will +% provide a non-hooked \@@par, but this should work anyway and can't be +% beaten for speed. +\ltx@ifundefined{tex_par:D} +% We could use \@ifl@t@r\fmtversion{2020/02/02}{use \tex_par:D}{use \@@par}. + {\let\spx@par\@@par}% \@@par is then expected to be TeX's original \par + {\expandafter\let\expandafter\spx@par\csname tex_par:D\endcsname} +% More hesitation for avoiding the at-start-of-par hooks for our +% measurement : 1. with old LaTeX, we can not avoid hooks from everyhook +% or similar packages, 2. and perhaps the hooks add stuff which we should +% actually measure. Ideally, hooks are for inserting things in margin +% which do not change spacing. Most everything else in fact should not be +% executed in our scratch box for measurement, such as counter stepping. +\ltx@ifundefined{tex_everypar:D} + {\let\spx@everypar\everypar} + {\expandafter\let\expandafter\spx@everypar\csname tex_everypar:D\endcsname} +% +% If the max width exceeds the linewidth by more than verbatimmaxoverfull +% character widths, or if the min width plus verbatimmaxunderfull character +% widths is inferior to linewidth, then we apply the "force wrapping" with +% potential line break at each character, else we don't. +\long\def\spx@verb@FormatLineForceWrap#1{% + % \spx@image@box is a scratch box register that we can use here + \global\let\spx@verb@maxwidth\z@ + \global\let\spx@verb@minwidth\linewidth + \setbox\spx@image@box + \vtop{\raggedright\hyphenpenalty\z@\exhyphenpenalty\z@ + \doublehyphendemerits\z@\finalhyphendemerits\z@ + \spx@everypar{}\noindent\strut #1\strut\spx@par + \spx@verb@getwidths}% + \ifdim\spx@verb@maxwidth> + \dimexpr\linewidth+\spx@opt@verbatimmaxoverfull\fontcharwd\font`X \relax + \spx@verb@FormatLineWrap{\spx@verb@wrapPYG #1\spx@verb@wrapPYG}% + \else + \ifdim\spx@verb@minwidth< + \dimexpr\linewidth-\spx@opt@verbatimmaxunderfull\fontcharwd\font`X \relax + \spx@verb@FormatLineWrap{\spx@verb@wrapPYG #1\spx@verb@wrapPYG}% + \else + \spx@verb@FormatLineWrap{#1}% + \fi\fi +}% +% auxiliary paragraph dissector to get max and min widths +% but minwidth must not take into account the last line +\newbox\spx@scratchbox +\def\spx@verb@getwidths {% + \unskip\unpenalty + \setbox\spx@scratchbox\lastbox + \ifvoid\spx@scratchbox + \else + \setbox\spx@scratchbox\hbox{\unhbox\spx@scratchbox}% + \ifdim\spx@verb@maxwidth<\wd\spx@scratchbox + \xdef\spx@verb@maxwidth{\number\wd\spx@scratchbox sp}% + \fi + \expandafter\spx@verb@getwidths@loop + \fi +}% +\def\spx@verb@getwidths@loop {% + \unskip\unpenalty + \setbox\spx@scratchbox\lastbox + \ifvoid\spx@scratchbox + \else + \setbox\spx@scratchbox\hbox{\unhbox\spx@scratchbox}% + \ifdim\spx@verb@maxwidth<\wd\spx@scratchbox + \xdef\spx@verb@maxwidth{\number\wd\spx@scratchbox sp}% + \fi + \ifdim\spx@verb@minwidth>\wd\spx@scratchbox + \xdef\spx@verb@minwidth{\number\wd\spx@scratchbox sp}% + \fi + \expandafter\spx@verb@getwidths@loop + \fi +}% +% auxiliary macros to implement "cut long line even in middle of word" +\catcode`Z=3 % safe delimiter +\def\spx@verb@wrapPYG{% + \futurelet\spx@nexttoken\spx@verb@wrapPYG@i +}% +\def\spx@verb@wrapPYG@i{% + \ifx\spx@nexttoken\spx@verb@wrapPYG\let\next=\@gobble\else + \ifx\spx@nexttoken\PYG\let\next=\spx@verb@wrapPYG@PYG@onebyone\else + \discretionary{}{\sphinxafterbreak}{}% + \let\next\spx@verb@wrapPYG@ii + \fi\fi + \next +}% +% Let's recognize active characters. We don't support utf8x only utf8. +% And here #1 should not have picked up (non empty) braced contents +\long\def\spx@verb@wrapPYG@ii#1{% + \ifcat\noexpand~\noexpand#1\relax% active character + \expandafter\spx@verb@wrapPYG@active + \else % non-active character, control sequence such as \PYGZdl, or empty + \expandafter\spx@verb@wrapPYG@one + \fi {#1}% +}% +\long\def\spx@verb@wrapPYG@active#1{% +% Let's hope expansion of active character does not really require arguments, +% as we certainly don't want to go into expanding upfront token stream anyway. + \expandafter\spx@verb@wrapPYG@iii#1{}{}{}{}{}{}{}{}{}Z#1% +}% +\long\def\spx@verb@wrapPYG@iii#1#2Z{% + \ifx\UTFviii@four@octets#1\let\next=\spx@verb@wrapPYG@four\else + \ifx\UTFviii@three@octets#1\let\next=\spx@verb@wrapPYG@three\else + \ifx\UTFviii@two@octets#1\let\next=\spx@verb@wrapPYG@two\else + \let\next=\spx@verb@wrapPYG@one + \fi\fi\fi + \next +}% +\long\def\spx@verb@wrapPYG@one #1{#1\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% +\long\def\spx@verb@wrapPYG@two #1#2{#1#2\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% +\long\def\spx@verb@wrapPYG@three #1#2#3{#1#2#3\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% +\long\def\spx@verb@wrapPYG@four #1#2#3#4{#1#2#3#4\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% +% Replace \PYG by itself applied one character at a time! This way breakpoints +% can be inserted. +\def\spx@verb@wrapPYG@PYG@onebyone#1#2#3{% #1 = \PYG, #2 = highlight spec, #3 = tokens + \def\spx@verb@wrapPYG@PYG@spec{{#2}}% + \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i#3Z% +}% +\def\spx@verb@wrapPYG@PYG@i{% + \ifx\spx@nexttokenZ\let\next=\spx@verb@wrapPYG@PYG@done\else + \discretionary{}{\sphinxafterbreak}{}% + \let\next\spx@verb@wrapPYG@PYG@ii + \fi + \next +}% +\def\spx@verb@wrapPYG@PYG@doneZ{\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}% +\long\def\spx@verb@wrapPYG@PYG@ii#1{% + \ifcat\noexpand~\noexpand#1\relax% active character + \expandafter\spx@verb@wrapPYG@PYG@active + \else % non-active character, control sequence such as \PYGZdl, or empty + \expandafter\spx@verb@wrapPYG@PYG@one + \fi {#1}% +}% +\long\def\spx@verb@wrapPYG@PYG@active#1{% +% Let's hope expansion of active character does not really require arguments, +% as we certainly don't want to go into expanding upfront token stream anyway. + \expandafter\spx@verb@wrapPYG@PYG@iii#1{}{}{}{}{}{}{}{}{}Z#1% +}% +\long\def\spx@verb@wrapPYG@PYG@iii#1#2Z{% + \ifx\UTFviii@four@octets#1\let\next=\spx@verb@wrapPYG@PYG@four\else + \ifx\UTFviii@three@octets#1\let\next=\spx@verb@wrapPYG@PYG@three\else + \ifx\UTFviii@two@octets#1\let\next=\spx@verb@wrapPYG@PYG@two\else + \let\next=\spx@verb@wrapPYG@PYG@one + \fi\fi\fi + \next +}% +\long\def\spx@verb@wrapPYG@PYG@one#1{% + \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1}% + \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i +}% +\long\def\spx@verb@wrapPYG@PYG@two#1#2{% + \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1#2}% + \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i +}% +\long\def\spx@verb@wrapPYG@PYG@three#1#2#3{% + \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1#2#3}% + \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i +}% +\long\def\spx@verb@wrapPYG@PYG@four#1#2#3#4{% + \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1#2#3#4}% + \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i +}% +\catcode`Z 11 % +% +\g@addto@macro\FV@SetupFont{% + \sbox\sphinxcontinuationbox {\spx@opt@verbatimcontinued}% + \sbox\sphinxvisiblespacebox {\spx@opt@verbatimvisiblespace}% +}% +\newenvironment{sphinxVerbatim}{% + % first, let's check if there is a caption + \ifx\sphinxVerbatimTitle\empty + \sphinxvspacefixafterfrenchlists + \parskip\z@skip + \vskip\sphinxverbatimsmallskipamount + % there was no caption. Check if nevertheless a label was set. + \ifx\sphinxLiteralBlockLabel\empty\else + % we require some space to be sure hyperlink target from \phantomsection + % will not be separated from upcoming verbatim by a page break + \needspace{\sphinxliteralblockwithoutcaptionneedspace}% + \phantomsection\sphinxLiteralBlockLabel + \fi + \else + \parskip\z@skip + \if t\spx@opt@literalblockcappos + \vskip\spx@abovecaptionskip + \def\sphinxVerbatim@Before + {\sphinxVerbatim@Title\nointerlineskip + \kern\dimexpr-\dp\strutbox+\sphinxbelowcaptionspace + % if no frame (code-blocks inside table cells), remove + % the "verbatimsep" whitespace from the top (better visually) + \ifspx@opt@verbatimwithframe\else-\sphinxverbatimsep\fi + % caption package adds \abovecaptionskip vspace, remove it + \spx@ifcaptionpackage{-\abovecaptionskip}{}\relax}% + \else + \vskip\sphinxverbatimsmallskipamount + \def\sphinxVerbatim@After + {\nointerlineskip\kern\dimexpr\dp\strutbox + \ifspx@opt@verbatimwithframe\else-\sphinxverbatimsep\fi + \spx@ifcaptionpackage{-\abovecaptionskip}{}\relax + \sphinxVerbatim@Title}% + \fi + \def\@captype{literalblock}% + \capstart + % \sphinxVerbatimTitle must reset color + \setbox\sphinxVerbatim@TitleBox + \hbox{\begin{minipage}{\linewidth}% + % caption package may detect wrongly if top or bottom, so we help it + \spx@ifcaptionpackage + {\caption@setposition{\spx@opt@literalblockcappos}}{}% + \sphinxVerbatimTitle + \end{minipage}}% + \fi + \global\let\sphinxLiteralBlockLabel\empty + \global\let\sphinxVerbatimTitle\empty + \fboxsep\sphinxverbatimsep \fboxrule\sphinxverbatimborder + \ifspx@opt@verbatimwithframe\else\fboxrule\z@\fi + \let\FrameCommand \sphinxVerbatim@FrameCommand + \let\FirstFrameCommand\sphinxVerbatim@FirstFrameCommand + \let\MidFrameCommand \sphinxVerbatim@MidFrameCommand + \let\LastFrameCommand \sphinxVerbatim@LastFrameCommand + \ifspx@opt@verbatimhintsturnover\else + \let\sphinxVerbatim@Continued\@empty + \let\sphinxVerbatim@Continues\@empty + \fi + \ifspx@opt@verbatimwrapslines + % fancyvrb's Verbatim puts each input line in (unbreakable) horizontal boxes. + % This customization wraps each line from the input in a \vtop, thus + % allowing it to wrap and display on two or more lines in the latex output. + % - The codeline counter will be increased only once. + % - The wrapped material will not break across pages, it is impossible + % to achieve this without extensive rewrite of fancyvrb. + % - The (not used in sphinx) obeytabs option to Verbatim is + % broken by this change (showtabs and tabspace work). + \let\sphinxVerbatimFormatLine\sphinxVerbatimFormatLineWrap + \let\FV@Space\spx@verbatim@space + % Allow breaks at special characters using \PYG... macros. + \sphinxbreaksatspecials + % Breaks at punctuation characters . , ; ? ! and / (needs catcode activation) + \fvset{codes*=\sphinxbreaksviaactive}% + \else % end of conditional code for wrapping long code lines + \let\sphinxVerbatimFormatLine\sphinxVerbatimFormatLineNoWrap + \fi + \let\FancyVerbFormatLine\sphinxFancyVerbFormatLine + \VerbatimEnvironment + % workaround to fancyvrb's check of current list depth + \def\@toodeep {\advance\@listdepth\@ne}% + % The list environment is needed to control perfectly the vertical space. + % Note: \OuterFrameSep used by framed.sty is later set to \topsep hence 0pt. + % - if caption: distance from last text baseline to caption baseline is + % A+(B-F)+\ht\strutbox, A = \abovecaptionskip (default 10pt), B = + % \baselineskip, F is the framed.sty \FrameHeightAdjust macro, default 6pt. + % Formula valid for F < 10pt. + % - distance of baseline of caption to top of frame is like for tables: + % \sphinxbelowcaptionspace (=0.5\baselineskip) + % - if no caption: distance of last text baseline to code frame is S+(B-F), + % with S = \sphinxverbatimtopskip (=\smallskip) + % - and distance from bottom of frame to next text baseline is + % \baselineskip+\parskip. + % The \trivlist is used to avoid possible "too deeply nested" error. + \itemsep \z@skip + \topsep \z@skip + \partopsep \z@skip + % trivlist will set \parsep to \parskip (which itself is set to zero above) + % \leftmargin will be set to zero by trivlist + \rightmargin\z@ + \parindent \z@% becomes \itemindent. Default zero, but perhaps overwritten. + \trivlist\item\relax + \ifspx@inframed\setbox\sphinxVerbatim@ContentsBox\vbox\bgroup + \@setminipage\hsize\linewidth + % use bulk of minipage paragraph shape restores (this is needed + % in indented contexts, at least for some) + \textwidth\hsize \columnwidth\hsize \@totalleftmargin\z@ + \leftskip\z@skip \rightskip\z@skip \@rightskip\z@skip + \else + \ifsphinxverbatimwithminipage\noindent\begin{minipage}{\linewidth}\fi + \MakeFramed {% adapted over from framed.sty's snugshade environment + \advance\hsize-\width\@totalleftmargin\z@\linewidth\hsize\@setminipage + }% + \fi + % For grid placement from \strut's in \FancyVerbFormatLine + \lineskip\z@skip + % active comma should not be overwritten by \@noligs + \ifspx@opt@verbatimwrapslines + \let\verbatim@nolig@list \sphinx@verbatim@nolig@list + \fi + % will fetch its optional arguments if any + \OriginalVerbatim +} +{% + \endOriginalVerbatim + \ifspx@inframed + \egroup % finish \sphinxVerbatim@ContentsBox vbox + \nobreak % update page totals + \ifdim\dimexpr\ht\sphinxVerbatim@ContentsBox+ + \dp\sphinxVerbatim@ContentsBox+ + \ht\sphinxVerbatim@TitleBox+ + \dp\sphinxVerbatim@TitleBox+ + 2\fboxsep+2\fboxrule+ + % try to account for external frame parameters + \FrameSep+\FrameRule+ + % Usage here of 2 baseline distances is empirical. + % In border case where code-block fits barely in remaining space, + % it gets framed and looks good but the outer frame may continue + % on top of next page and give (if no contents after code-block) + % an empty framed line, as testing showed. + 2\baselineskip+ + % now add all to accumulated page totals and compare to \pagegoal + \pagetotal+\pagedepth>\pagegoal + % long contents: do not \MakeFramed. Do make a caption (either before or + % after) if title exists. Continuation hints across pagebreaks dropped. + % FIXME? a bottom caption may end up isolated at top of next page + % (no problem with a top caption, which is default) + \spx@opt@verbatimwithframefalse + \def\sphinxVerbatim@Title{\noindent\box\sphinxVerbatim@TitleBox\par}% + \sphinxVerbatim@Before + \noindent\unvbox\sphinxVerbatim@ContentsBox\par + \sphinxVerbatim@After + \else + % short enough contents: use \MakeFramed. As it is nested, this requires + % minipage encapsulation. + \noindent\begin{minipage}{\linewidth}% + \MakeFramed {% Use it now with the fetched contents + \advance\hsize-\width\@totalleftmargin\z@\linewidth\hsize\@setminipage + }% + \unvbox\sphinxVerbatim@ContentsBox + % some of this may be superfluous: + \par\unskip\@minipagefalse\endMakeFramed + \end{minipage}% + \fi + \else % non-nested \MakeFramed + \par\unskip\@minipagefalse\endMakeFramed % from framed.sty snugshade + \ifsphinxverbatimwithminipage\end{minipage}\fi + \fi + \endtrivlist +} +\newenvironment {sphinxVerbatimNoFrame} + {\spx@opt@verbatimwithframefalse + \VerbatimEnvironment + \begin{sphinxVerbatim}} + {\end{sphinxVerbatim}} +\newenvironment {sphinxVerbatimintable} + {% don't use a frame if in a table cell + \spx@opt@verbatimwithframefalse + \sphinxverbatimwithminipagetrue + % the literal block caption uses \sphinxcaption which is wrapper of \caption, + % but \caption must be modified because longtable redefines it to work only + % for the own table caption, and tabulary has multiple passes + \let\caption\sphinxfigcaption + % reduce above caption skip + \def\spx@abovecaptionskip{\sphinxverbatimsmallskipamount}% + \VerbatimEnvironment + \begin{sphinxVerbatim}} + {\end{sphinxVerbatim}} + + +%% PARSED LITERALS +% allow long lines to wrap like they do in code-blocks + +% this should be kept in sync with definitions in sphinx.util.texescape +\newcommand*\sphinxbreaksattexescapedchars{% + \def\do##1##2% put potential break point before character + {\def##1{\discretionary{}{\sphinxafterbreak\char`##2}{\char`##2}}}% + \do\{\{\do\textless\<\do\#\#\do\%\%\do\$\$% {, <, #, %, $ + \def\do##1##2% put potential break point after character + {\def##1{\discretionary{\char`##2}{\sphinxafterbreak}{\char`##2}}}% + \do\_\_\do\}\}\do\textasciicircum\^\do\&\&% _, }, ^, &, + \do\textgreater\>\do\textasciitilde\~% >, ~ + \do\textbackslash\\% \ +} +\newcommand*\sphinxbreaksviaactiveinparsedliteral{% + \sphinxbreaksviaactive % by default handles . , ; ? ! / + \lccode`\~`\~ % + % update \dospecials as it is used by \url + % but deactivation will already have been done hence this is unneeded: + % \expandafter\def\expandafter\dospecials\expandafter{\dospecials + % \sphinxbreaksbeforeactivelist\sphinxbreaksafteractivelist\do\-}% +} +\newcommand*\sphinxbreaksatspaceinparsedliteral{% + \lccode`~32 \lowercase{\let~}\spx@verbatim@space\lccode`\~`\~ +} +\newcommand*{\sphinxunactivateextras}{\let\do\@makeother + \sphinxbreaksbeforeactivelist\sphinxbreaksafteractivelist}% +% the \catcode13=5\relax (deactivate end of input lines) is left to callers +\newcommand*{\sphinxunactivateextrasandspace}{\catcode32=10\relax + \sphinxunactivateextras}% +% now for the modified alltt environment +\newenvironment{sphinxalltt} +{% at start of next line to workaround Emacs/AUCTeX issue with this file +\begin{alltt}% + \ifspx@opt@parsedliteralwraps + \sbox\sphinxcontinuationbox {\spx@opt@verbatimcontinued}% + \sbox\sphinxvisiblespacebox {\spx@opt@verbatimvisiblespace}% + \sphinxbreaksattexescapedchars + \sphinxbreaksviaactiveinparsedliteral + \sphinxbreaksatspaceinparsedliteral +% alltt takes care of the ' as derivative ("prime") in math mode + \everymath\expandafter{\the\everymath\sphinxunactivateextrasandspace + \catcode`\<=12\catcode`\>=12\catcode`\^=7\catcode`\_=8 }% +% not sure if displayed math (align,...) can end up in parsed-literal, anyway + \everydisplay\expandafter{\the\everydisplay + \catcode13=5 \sphinxunactivateextrasandspace + \catcode`\<=12\catcode`\>=12\catcode`\^=7\catcode`\_=8 }% + \fi } +{\end{alltt}} + + +%% INLINE MARK-UP +% + +% Protect \href's first argument in contexts such as sphinxalltt (or +% \sphinxcode). Sphinx uses \#, \%, \& ... always inside \sphinxhref. +\protected\def\sphinxhref#1#2{{% + \sphinxunactivateextrasandspace % never do \scantokens with active space! +% for the \endlinechar business, https://github.com/latex3/latex2e/issues/286 + \endlinechar\m@ne\everyeof{{\endlinechar13 #2}}% keep catcode regime for #2 + \scantokens{\href{#1}}% normalise it for #1 during \href expansion +}} +% Same for \url. And also \nolinkurl for coherence. +\protected\def\sphinxurl#1{{% + \sphinxunactivateextrasandspace\everyeof{}% (<- precaution for \scantokens) + \endlinechar\m@ne\scantokens{\url{#1}}% +}} +\protected\def\sphinxnolinkurl#1{{% + \sphinxunactivateextrasandspace\everyeof{}% + \endlinechar\m@ne\scantokens{\nolinkurl{#1}}% +}} + +% \sphinxupquote +% to obtain straight quotes we execute \@noligs as patched by upquote, and +% \scantokens is needed in cases where it would be too late for the macro to +% first set catcodes and then fetch its argument. We also make the contents +% breakable at non-escaped . , ; ? ! / using \sphinxbreaksviaactive, +% and also at \ character (which is escaped to \textbackslash{}). +\protected\def\sphinxtextbackslashbreakbefore + {\discretionary{}{\sphinxafterbreak\sphinx@textbackslash}{\sphinx@textbackslash}} +\protected\def\sphinxtextbackslashbreakafter + {\discretionary{\sphinx@textbackslash}{\sphinxafterbreak}{\sphinx@textbackslash}} +\let\sphinxtextbackslash\sphinxtextbackslashbreakafter +% the macro must be protected if it ends up used in moving arguments, +% in 'alltt' \@noligs is done already, and the \scantokens must be avoided. +\protected\def\sphinxupquote#1{{\def\@tempa{alltt}% + \ifx\@tempa\@currenvir\else + \ifspx@opt@inlineliteralwraps + % break at . , ; ? ! / + \sphinxbreaksviaactive + % break also at \ + \let\sphinx@textbackslash\textbackslash + \let\textbackslash\sphinxtextbackslash + % by default, no continuation symbol on next line but may be added + \let\sphinxafterbreak\sphinxafterbreakofinlineliteral + % do not overwrite the comma set-up + \let\verbatim@nolig@list\sphinx@literal@nolig@list + \fi + % fix a space-gobbling issue due to LaTeX's original \do@noligs +% TODO: using \@noligs as patched by upquote.sty is now unneeded because +% either ` and ' are escaped (non-unicode engines) or they don't build +% ligatures (unicode engines). Thus remove this and unify handling of `, <, >, +% ' and - with the characters . , ; ? ! / as handled via +% \sphinxbreaksviaactive. +% Hence \sphinx@do@noligs will be removed, or rather replaced with code +% inserting discretionaries, as they allow a continuation symbol on start of +% next line to achieve common design with code-blocks. + \let\do@noligs\sphinx@do@noligs + \@noligs\endlinechar\m@ne\everyeof{}% (<- in case inside \sphinxhref) + \expandafter\scantokens + \fi {{#1}}}}% extra brace pair to fix end-space gobbling issue... +\def\sphinx@do@noligs #1{\catcode`#1\active\begingroup\lccode`\~`#1\relax + \lowercase{\endgroup\def~{\leavevmode\kern\z@\char`#1 }}} +\def\sphinx@literal@nolig@list {\do\`\do\<\do\>\do\'\do\-}% +\let\sphinxafterbreakofinlineliteral\empty + + +\endinput diff --git a/sphinx/texinputs/sphinxlatexnumfig.sty b/sphinx/texinputs/sphinxlatexnumfig.sty new file mode 100644 index 000000000..6d7296105 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexnumfig.sty @@ -0,0 +1,122 @@ +%% NUMBERING OF FIGURES, TABLES, AND LITERAL BLOCKS +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexnumfig.sty}[2021/01/27 numbering] + +% Requires: remreset (old LaTeX only) +% relates to numfig and numfig_secnum_depth configuration variables + +% LaTeX 2018-04-01 and later provides \@removefromreset +\ltx@ifundefined{@removefromreset} + {\RequirePackage{remreset}} + {}% avoid warning +% 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]% + \def\theliteralblock{\ifnum\c@chapter>\z@\arabic{chapter}.\fi + \arabic{literalblock}}% + }% +\ifspx@opt@nonumfigreset + \ltx@ifundefined{c@chapter}{}{% + \@removefromreset{figure}{chapter}% + \@removefromreset{table}{chapter}% + \@removefromreset{literalblock}{chapter}% + \ifspx@opt@mathnumfig + \@removefromreset{equation}{chapter}% + \fi + }% + \def\thefigure{\arabic{figure}}% + \def\thetable {\arabic{table}}% + \def\theliteralblock{\arabic{literalblock}}% + \ifspx@opt@mathnumfig + \def\theequation{\arabic{equation}}% + \fi +\else +\let\spx@preAthefigure\@empty +\let\spx@preBthefigure\@empty +% \ifspx@opt@usespart % <-- LaTeX writer could pass such a 'usespart' boolean +% % as sphinx.sty package option +% If document uses \part, (triggered in Sphinx by latex_toplevel_sectioning) +% LaTeX core per default does not reset chapter or section +% counters at each part. +% But if we modify this, we need to redefine \thechapter, \thesection to +% include the part number and this will cause problems in table of contents +% because of too wide numbering. Simplest is to do nothing. +% \fi +\ifnum\spx@opt@numfigreset>0 + \ltx@ifundefined{c@chapter} + {} + {\g@addto@macro\spx@preAthefigure{\ifnum\c@chapter>\z@\arabic{chapter}.}% + \g@addto@macro\spx@preBthefigure{\fi}}% +\fi +\ifnum\spx@opt@numfigreset>1 + \@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 + \@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 + \@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 + \@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 + \@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 +\expandafter\g@addto@macro +\expandafter\spx@preAthefigure\expandafter{\spx@preBthefigure}% +\let\thefigure\spx@preAthefigure +\let\thetable\spx@preAthefigure +\let\theliteralblock\spx@preAthefigure +\g@addto@macro\thefigure{\arabic{figure}}% +\g@addto@macro\thetable{\arabic{table}}% +\g@addto@macro\theliteralblock{\arabic{literalblock}}% + \ifspx@opt@mathnumfig + \let\theequation\spx@preAthefigure + \g@addto@macro\theequation{\arabic{equation}}% + \fi +\fi +}% end of big \AtBeginDocument + +\endinput diff --git a/sphinx/texinputs/sphinxlatexobjects.sty b/sphinx/texinputs/sphinxlatexobjects.sty new file mode 100644 index 000000000..e00881e53 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexobjects.sty @@ -0,0 +1,200 @@ +%% MODULE RELEASE DATA AND OBJECT DESCRIPTIONS +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexobjects.sty}[2021/01/27 documentation environments] + +% Provides support for this output mark-up from Sphinx latex writer: +% +% - environments +% +% - fulllineitems +% - productionlist +% - optionlist +% - DUlineblock (also "lineblock") +% +% - macros +% +% - \DUrole +% - various legacy support macros related to author and release +% data of documented objects and modules. + +% \moduleauthor{name}{email} +\newcommand{\moduleauthor}[2]{} + +% \sectionauthor{name}{email} +\newcommand{\sectionauthor}[2]{} + +% Allow the release number to be specified independently of the +% \date{}. This allows the date to reflect the document's date and +% release to specify the release that is documented. +% +\newcommand{\py@release}{\releasename\space\version} +\newcommand{\version}{}% part of \py@release, used by title page and headers +% \releaseinfo is used on titlepage (sphinxmanual.cls, sphinxhowto.cls) +\newcommand{\releaseinfo}{} +\newcommand{\setreleaseinfo}[1]{\renewcommand{\releaseinfo}{#1}} +% this is inserted via template and #1=release config variable +\newcommand{\release}[1]{\renewcommand{\version}{#1}} +% this is defined by template to 'releasename' latex_elements key +\newcommand{\releasename}{} +% Fix issue in case release and releasename deliberately left blank +\newcommand{\sphinxheadercomma}{, }% used in fancyhdr header definition +\newcommand{\sphinxifemptyorblank}[1]{% +% test after one expansion of macro #1 if contents is empty or spaces + \if&\expandafter\@firstofone\detokenize\expandafter{#1}&% + \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}% +\AtBeginDocument {% + \sphinxifemptyorblank{\releasename} + {\sphinxifemptyorblank{\version}{\let\sphinxheadercomma\empty}{}} + {}% +}% + +% Allow specification of the author's address separately from the +% author's name. This can be used to format them differently, which +% is a good thing. +% +\newcommand{\py@authoraddress}{} +\newcommand{\authoraddress}[1]{\renewcommand{\py@authoraddress}{#1}} + +% {fulllineitems} is the main environment for object descriptions. +% +% With 4.0.0 \pysigline (and \pysiglinewithargsret), used in a fulllineitems +% environment the #1 will already be of the width which is computed here, i.e. +% the available width on line, so the \makebox becomes a bit superfluous +\newcommand{\py@itemnewline}[1]{% macro used as \makelabel in fulllineitems +% Memo: this presupposes \itemindent is 0pt + \kern\labelsep % because \@labels core latex box does \hskip-\labelsep + \makebox[\dimexpr\linewidth+\labelwidth\relax][l]{#1}% + \kern-\labelsep % because at end of \@labels box there is \hskip\labelsep +} + +\newenvironment{fulllineitems}{% + \begin{list}{}{\labelwidth \leftmargin + \rightmargin \z@ \topsep -\parskip \partopsep \parskip + \itemsep -\parsep + \let\makelabel=\py@itemnewline}% +}{\end{list}} + +% Signatures, possibly multi-line +% +\newlength{\py@argswidth} +\newcommand{\py@sigparams}[2]{% + % The \py@argswidth has been computed in \pysiglinewithargsret to make this + % occupy full available width on line. + \parbox[t]{\py@argswidth}{\raggedright #1\sphinxcode{)}#2\strut}% + % final strut is to help get correct vertical separation in case of multi-line + % box with the item contents. +} +\newcommand{\pysigline}[1]{% +% the \py@argswidth is available we use it despite its name (no "args" here) +% the \relax\relax is because \py@argswidth is a "skip" variable and the first +% \relax only ends its "dimen" part + \py@argswidth=\dimexpr\linewidth+\labelwidth\relax\relax + \item[{\parbox[t]{\py@argswidth}{\raggedright #1\strut}}] +% contrarily to \pysiglinewithargsret, we do not do this: +% \leavevmode\par\nobreak\vskip-\parskip\prevdepth\dp\strutbox +% which would give exact vertical spacing if item parbox is multi-line, +% as it affects negatively more common situation of \pysigline +% used twice or more in a row for labels sharing common description, +% due to bad interaction with the \phantomsection in the mark-up +} +\newcommand{\pysiglinewithargsret}[3]{% + \settowidth{\py@argswidth}{#1\sphinxcode{(}}% + \py@argswidth=\dimexpr\linewidth+\labelwidth-\py@argswidth\relax\relax + \item[{#1\sphinxcode{(}\py@sigparams{#2}{#3}}] +% this strange incantation is because at its root LaTeX in fact did not +% imagine a multi-line label, it is always wrapped in a horizontal box at core +% LaTeX level and we have to find tricks to get correct interline distances. + \leavevmode\par\nobreak\vskip-\parskip\prevdepth\dp\strutbox} +\newcommand{\pysigstartmultiline}{% + \def\pysigstartmultiline{\vskip\smallskipamount\parskip\z@skip\itemsep\z@skip}% + \edef\pysigstopmultiline + {\noexpand\leavevmode\parskip\the\parskip\relax\itemsep\the\itemsep\relax}% + \parskip\z@skip\itemsep\z@skip +} + +% Production lists +% +\newenvironment{productionlist}{% +% \def\sphinxoptional##1{{\Large[}##1{\Large]}} + \def\production##1##2{\\\sphinxcode{\sphinxupquote{##1}}&::=&\sphinxcode{\sphinxupquote{##2}}}% + \def\productioncont##1{\\& &\sphinxcode{\sphinxupquote{##1}}}% + \parindent=2em + \indent + \setlength{\LTpre}{0pt}% + \setlength{\LTpost}{0pt}% + \begin{longtable}[l]{lcl} +}{% + \end{longtable} +} + +% Definition lists; requested by AMK for HOWTO documents. Probably useful +% elsewhere as well, so keep in in the general style support. +% +\newenvironment{definitions}{% + \begin{description}% + \def\term##1{\item[{##1}]\mbox{}\\*[0mm]}% +}{% + \end{description}% +} + +%% FROM DOCTUTILS LATEX WRITER +% +% The following is stuff copied from docutils' latex writer. +% +\newcommand{\optionlistlabel}[1]{\normalfont\bfseries #1 \hfill}% \bf deprecated +\newenvironment{optionlist}[1] +{\begin{list}{} + {\setlength{\labelwidth}{#1} + \setlength{\rightmargin}{1cm} + \setlength{\leftmargin}{\rightmargin} + \addtolength{\leftmargin}{\labelwidth} + \addtolength{\leftmargin}{\labelsep} + \renewcommand{\makelabel}{\optionlistlabel}} +}{\end{list}} + +\newlength{\lineblockindentation} +\setlength{\lineblockindentation}{2.5em} +\newenvironment{lineblock}[1] +{\begin{list}{} + {\setlength{\partopsep}{\parskip} + \addtolength{\partopsep}{\baselineskip} + \topsep0pt\itemsep0.15\baselineskip\parsep0pt + \leftmargin#1\relax} + \raggedright} +{\end{list}} + +% From docutils.writers.latex2e +% inline markup (custom roles) +% \DUrole{#1}{#2} tries \DUrole#1{#2} +\providecommand*{\DUrole}[2]{% + \ifcsname DUrole\detokenize{#1}\endcsname + \csname DUrole\detokenize{#1}\endcsname{#2}% + \else% backwards compatibility: try \docutilsrole#1{#2} + \ifcsname docutilsrole\detokenize{#1}\endcsname + \csname docutilsrole\detokenize{#1}\endcsname{#2}% + \else + #2% + \fi + \fi +} + +\providecommand*{\DUprovidelength}[2]{% + \ifdefined#1\else\newlength{#1}\setlength{#1}{#2}\fi +} + +\DUprovidelength{\DUlineblockindent}{2.5em} +\ifdefined\DUlineblock\else + \newenvironment{DUlineblock}[1]{% + \list{}{\setlength{\partopsep}{\parskip} + \addtolength{\partopsep}{\baselineskip} + \setlength{\topsep}{0pt} + \setlength{\itemsep}{0.15\baselineskip} + \setlength{\parsep}{0pt} + \setlength{\leftmargin}{#1}} + \raggedright + } + {\endlist} +\fi + +\endinput diff --git a/sphinx/texinputs/sphinxlatexshadowbox.sty b/sphinx/texinputs/sphinxlatexshadowbox.sty new file mode 100644 index 000000000..8d6c78666 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexshadowbox.sty @@ -0,0 +1,100 @@ +%% TOPIC AND CONTENTS BOXES +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexshadowbox.sty}[2021/01/27 sphinxShadowBox] + +% Provides support for this output mark-up from Sphinx latex writer: +% +% - sphinxShadowBox (environment) +% +% Dependencies (they do not need to be defined at time of loading): +% +% - of course the various colour and dimension options handled via sphinx.sty +% - dimension register \spx@image@maxheight from sphinxlatexgraphics.sty +% - \savenotes/\spewnotes from sphinxpackagefootnote +% - \ifspx@inframed defined in sphinx.sty +% +% Requires: +\RequirePackage{framed} + +% Again based on use of "framed.sty", this allows breakable framed boxes. +\long\def\spx@ShadowFBox#1{% + \leavevmode\begingroup + % first we frame the box #1 + \setbox\@tempboxa + \hbox{\vrule\@width\sphinxshadowrule + \vbox{\hrule\@height\sphinxshadowrule + \kern\sphinxshadowsep + \hbox{\kern\sphinxshadowsep #1\kern\sphinxshadowsep}% + \kern\sphinxshadowsep + \hrule\@height\sphinxshadowrule}% + \vrule\@width\sphinxshadowrule}% + % Now we add the shadow, like \shadowbox from fancybox.sty would do + \dimen@\dimexpr.5\sphinxshadowrule+\sphinxshadowsize\relax + \hbox{\vbox{\offinterlineskip + \hbox{\copy\@tempboxa\kern-.5\sphinxshadowrule + % add shadow on right side + \lower\sphinxshadowsize + \hbox{\vrule\@height\ht\@tempboxa \@width\dimen@}% + }% + \kern-\dimen@ % shift back vertically to bottom of frame + % and add shadow at bottom + \moveright\sphinxshadowsize + \vbox{\hrule\@width\wd\@tempboxa \@height\dimen@}% + }% + % move left by the size of right shadow so shadow adds no width + \kern-\sphinxshadowsize + }% + \endgroup +} + +% use framed.sty to allow page breaks in frame+shadow +% works well inside Lists and Quote-like environments +% produced by ``topic'' directive (or local contents) +% could nest if LaTeX writer authorized it +\newenvironment{sphinxShadowBox} + {\def\FrameCommand {\spx@ShadowFBox }% + \advance\spx@image@maxheight + -\dimexpr2\sphinxshadowrule + +2\sphinxshadowsep + +\sphinxshadowsize + +\baselineskip\relax + % configure framed.sty not to add extra vertical spacing + \ltx@ifundefined{OuterFrameSep}{}{\OuterFrameSep\z@skip}% + % the \trivlist will add the vertical spacing on top and bottom which is + % typical of center environment as used in Sphinx <= 1.4.1 + % the \noindent has the effet of an extra blank line on top, to + % imitate closely the layout from Sphinx <= 1.4.1; the \FrameHeightAdjust + % will put top part of frame on this baseline. + \def\FrameHeightAdjust {\baselineskip}% + % use package footnote to handle footnotes + \savenotes + \trivlist\item\noindent + % use a minipage if we are already inside a framed environment + \ifspx@inframed\begin{minipage}{\linewidth}\fi + \MakeFramed {\spx@inframedtrue + % framed.sty puts into "\width" the added width (=2shadowsep+2shadowrule) + % adjust \hsize to what the contents must use + \advance\hsize-\width + % adjust LaTeX parameters to behave properly in indented/quoted contexts + \FrameRestore + % typeset the contents as in a minipage (Sphinx <= 1.4.1 used a minipage and + % itemize/enumerate are therein typeset more tightly, we want to keep + % that). We copy-paste from LaTeX source code but don't do a real minipage. + \@pboxswfalse + \let\@listdepth\@mplistdepth \@mplistdepth\z@ + \@minipagerestore + \@setminipage + }% + }% + {% insert the "endminipage" code + \par\unskip + \@minipagefalse + \endMakeFramed + \ifspx@inframed\end{minipage}\fi + \endtrivlist + % output the stored footnotes + \spewnotes + } + +\endinput diff --git a/sphinx/texinputs/sphinxlatexstyleheadings.sty b/sphinx/texinputs/sphinxlatexstyleheadings.sty new file mode 100644 index 000000000..fa9be82b4 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexstyleheadings.sty @@ -0,0 +1,83 @@ +%% TITLES +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexstyleheadings.sty}[2021/01/27 headings] + +\RequirePackage[nobottomtitles*]{titlesec} +\@ifpackagelater{titlesec}{2016/03/15}% + {\@ifpackagelater{titlesec}{2016/03/21}% + {}% + {\newif\ifsphinx@ttlpatch@ok + \IfFileExists{etoolbox.sty}{% + \RequirePackage{etoolbox}% + \patchcmd{\ttlh@hang}{\parindent\z@}{\parindent\z@\leavevmode}% + {\sphinx@ttlpatch@oktrue}{}% + \ifsphinx@ttlpatch@ok + \patchcmd{\ttlh@hang}{\noindent}{}{}{\sphinx@ttlpatch@okfalse}% + \fi + }{}% + \ifsphinx@ttlpatch@ok + \typeout{^^J Package Sphinx Info: ^^J + **** titlesec 2.10.1 successfully patched for bugfix ****^^J}% + \else + \AtEndDocument{\PackageWarningNoLine{sphinx}{^^J% +******** titlesec 2.10.1 has a bug, (section numbers disappear) ......|^^J% +******** and Sphinx could not patch it, perhaps because your local ...|^^J% +******** copy is already fixed without a changed release date. .......|^^J% +******** If not, you must update titlesec! ...........................|}}% + \fi + }% + }{} + +% 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} +\titleformat{\subsection}{\large\py@HeaderFamily}% + {\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor} +\titleformat{\subsubsection}{\py@HeaderFamily}% + {\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} +\titleformat{\subparagraph}{\py@HeaderFamily}% + {\py@TitleColor\thesubparagraph}{0.5em}{\py@TitleColor} + + +% Since Sphinx 1.5, users should use HeaderFamily key to 'sphinxsetup' rather +% than defining their own \py@HeaderFamily command (which is still possible). +% Memo: \py@HeaderFamily is also used by \maketitle as defined in +% sphinxmanual.cls/sphinxhowto.cls +\newcommand{\py@HeaderFamily}{\spx@opt@HeaderFamily} + +% This sets up the fancy chapter headings that make the documents look +% at least a little better than the usual LaTeX output. +\@ifpackagewith{fncychap}{Bjarne}{ + \ChNameVar {\raggedleft\normalsize \py@HeaderFamily} + \ChNumVar {\raggedleft\Large \py@HeaderFamily} + \ChTitleVar{\raggedleft\Large \py@HeaderFamily} + % This creates (numbered) chapter heads without the leading \vspace*{}: + \def\@makechapterhead#1{% + {\parindent \z@ \raggedright \normalfont + \ifnum \c@secnumdepth >\m@ne + \if@mainmatter + \DOCH + \fi + \fi + \interlinepenalty\@M + \if@mainmatter + \DOTI{#1}% + \else% + \DOTIS{#1}% + \fi + }} +}{}% <-- "false" clause of \@ifpackagewith + +% fix fncychap's bug which uses prematurely the \textwidth value +\@ifpackagewith{fncychap}{Bjornstrup} + {\AtBeginDocument{\mylen\textwidth\advance\mylen-2\myhi}}% + {}% <-- "false" clause of \@ifpackagewith + + +\endinput diff --git a/sphinx/texinputs/sphinxlatexstylepage.sty b/sphinx/texinputs/sphinxlatexstylepage.sty new file mode 100644 index 000000000..4066129bf --- /dev/null +++ b/sphinx/texinputs/sphinxlatexstylepage.sty @@ -0,0 +1,79 @@ +%% PAGE STYLING +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexstylepage.sty}[2021/01/27 page styling] + +% Separate paragraphs by space by default. +\IfFileExists{parskip-2001-04-09.sty}% since September 2018 TeXLive update +% new parskip.sty, but let it rollback to old one. +% hopefully TeX installation not broken and LaTeX kernel not too old + {\RequirePackage{parskip}[=v1]} +% standard one from 1989. Admittedly \section of article/book gives possibly +% anomalous spacing, but we can't require September 2018 release for some time. + {\RequirePackage{parskip}} + +% Style parameters and macros used by most documents here +\raggedbottom +\sloppy +\hbadness = 5000 % don't print trivial gripes + +% Require package fancyhdr except under memoir class +\@ifclassloaded{memoir}{}{\RequirePackage{fancyhdr}} +% Use \pagestyle{normal} as the primary pagestyle for text. +% Redefine the 'normal' header/footer style when using "fancyhdr" package: +\@ifpackageloaded{fancyhdr}{% + \ltx@ifundefined{c@chapter} + {% no \chapter, "howto" (non-Japanese) docclass + \fancypagestyle{plain}{ + \fancyhf{} + \fancyfoot[C]{{\py@HeaderFamily\thepage}} + \renewcommand{\headrulewidth}{0pt} + \renewcommand{\footrulewidth}{0pt} + } + % Same as 'plain', this way we can use it in template + % FIXME: shouldn't this have a running header with Name and Release like 'manual'? + \fancypagestyle{normal}{ + \fancyhf{} + \fancyfoot[C]{{\py@HeaderFamily\thepage}} + \renewcommand{\headrulewidth}{0pt} + \renewcommand{\footrulewidth}{0pt} + } + }% + {% classes with \chapter command + \fancypagestyle{normal}{ + \fancyhf{} + \fancyfoot[RO]{{\py@HeaderFamily\thepage}} + \fancyfoot[LO]{{\py@HeaderFamily\nouppercase{\rightmark}}} + \fancyhead[RO]{{\py@HeaderFamily \@title\sphinxheadercomma\py@release}} + \if@twoside + \fancyfoot[LE]{{\py@HeaderFamily\thepage}} + \fancyfoot[RE]{{\py@HeaderFamily\nouppercase{\leftmark}}} + \fancyhead[LE]{{\py@HeaderFamily \@title\sphinxheadercomma\py@release}} + \fi + \renewcommand{\headrulewidth}{0.4pt} + \renewcommand{\footrulewidth}{0.4pt} + % define chaptermark with \@chappos when \@chappos is available for Japanese + \ltx@ifundefined{@chappos}{} + {\def\chaptermark##1{\markboth{\@chapapp\space\thechapter\space\@chappos\space ##1}{}}} + } + % Update the plain style so we get the page number & footer line, + % but not a chapter or section title. This is to keep the first + % page of a chapter `clean.' + \fancypagestyle{plain}{ + \fancyhf{} + \fancyfoot[RO]{{\py@HeaderFamily\thepage}} + \if@twoside\fancyfoot[LE]{{\py@HeaderFamily\thepage}}\fi + \renewcommand{\headrulewidth}{0pt} + \renewcommand{\footrulewidth}{0.4pt} + } + } + } + {% no fancyhdr: memoir class + % Provide default for 'normal' style simply as an alias of 'plain' style + % This way we can use \pagestyle{normal} in LaTeX template + \def\ps@normal{\ps@plain} + % Users of memoir class are invited to redefine 'normal' style in preamble + } + + +\endinput diff --git a/sphinx/texinputs/sphinxlatexstyletext.sty b/sphinx/texinputs/sphinxlatexstyletext.sty new file mode 100644 index 000000000..ab50aed56 --- /dev/null +++ b/sphinx/texinputs/sphinxlatexstyletext.sty @@ -0,0 +1,126 @@ +%% TEXT STYLING +% +% change this info string if making any custom modification +\ProvidesFile{sphinxlatexstyletext.sty}[2021/01/27 text styling] + +% Basically everything here consists of macros which are part of the latex +% markup produced by the Sphinx latex writer + +% Some custom font markup commands. +\protected\def\sphinxstrong#1{\textbf{#1}} +\protected\def\sphinxcode#1{\texttt{#1}} +\protected\def\sphinxbfcode#1{\textbf{\sphinxcode{#1}}} +\protected\def\sphinxemail#1{\textsf{#1}} +\protected\def\sphinxtablecontinued#1{\textsf{#1}} +\protected\def\sphinxtitleref#1{\emph{#1}} +\protected\def\sphinxmenuselection#1{\emph{#1}} +\protected\def\sphinxguilabel#1{\emph{#1}} +\protected\def\sphinxkeyboard#1{\sphinxcode{#1}} +\protected\def\sphinxaccelerator#1{\underline{#1}} +\protected\def\sphinxcrossref#1{\emph{#1}} +\protected\def\sphinxtermref#1{\emph{#1}} +% \optional is used for ``[, arg]``, i.e. desc_optional nodes. +\long\protected\def\sphinxoptional#1{% + {\textnormal{\Large[}}{#1}\hspace{0.5mm}{\textnormal{\Large]}}} + +% additional customizable styling +\def\sphinxstyleindexentry #1{\texttt{#1}} +\def\sphinxstyleindexextra #1{ (\emph{#1})} +\def\sphinxstyleindexpageref #1{, \pageref{#1}} +\def\sphinxstyleindexpagemain#1{\textbf{#1}} +\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% + {{\Large\sffamily\sphinxnonalphabeticalgroupname}\nopagebreak\vspace{1mm}} +\protected\def\sphinxstyletopictitle #1{\textbf{#1}\par\medskip} +\let\sphinxstylesidebartitle\sphinxstyletopictitle +\protected\def\sphinxstyleothertitle #1{\textbf{#1}} +\protected\def\sphinxstylesidebarsubtitle #1{~\\\textbf{#1} \smallskip} +% \text.. commands do not allow multiple paragraphs +\protected\def\sphinxstyletheadfamily {\sffamily} +\protected\def\sphinxstyleemphasis #1{\emph{#1}} +\protected\def\sphinxstyleliteralemphasis#1{\emph{\sphinxcode{#1}}} +\protected\def\sphinxstylestrong #1{\textbf{#1}} +\protected\def\sphinxstyleliteralstrong#1{\sphinxbfcode{#1}} +\protected\def\sphinxstyleabbreviation #1{\textsc{#1}} +\protected\def\sphinxstyleliteralintitle#1{\sphinxcode{#1}} +\newcommand*\sphinxstylecodecontinued[1]{\footnotesize(#1)}% +\newcommand*\sphinxstylecodecontinues[1]{\footnotesize(#1)}% +% figure legend comes after caption and may contain arbitrary body elements +\newenvironment{sphinxlegend}{\par\small}{\par} +% reduce hyperref "Token not allowed in a PDF string" warnings on PDF builds +\AtBeginDocument{\pdfstringdefDisableCommands{% +% all "protected" macros possibly ending up in section titles should be here +% TODO: examine if \sphinxhref, \sphinxurl, \sphinnolinkurl should be handled + \let\sphinxstyleemphasis \@firstofone + \let\sphinxstyleliteralemphasis \@firstofone + \let\sphinxstylestrong \@firstofone + \let\sphinxstyleliteralstrong \@firstofone + \let\sphinxstyleabbreviation \@firstofone + \let\sphinxstyleliteralintitle \@firstofone + \let\sphinxupquote \@firstofone + \let\sphinxstrong \@firstofone + \let\sphinxcode \@firstofone + \let\sphinxbfcode \@firstofone + \let\sphinxemail \@firstofone + \let\sphinxcrossref \@firstofone + \let\sphinxtermref \@firstofone + \let\sphinxhyphen\sphinxhyphenforbookmarks +}} + +% Special characters +% +% This definition prevents en-dash and em-dash TeX ligatures. +% +% It inserts a potential breakpoint after the hyphen. This is to keep in sync +% with behavior in code-blocks, parsed and inline literals. For a breakpoint +% before the hyphen use \leavevmode\kern\z@- (within \makeatletter/\makeatother) +\protected\def\sphinxhyphen#1{-\kern\z@} +% The {} from texescape mark-up is kept, else -- gives en-dash in PDF bookmark +\def\sphinxhyphenforbookmarks{-} + +% For curly braces inside \index macro +\def\sphinxleftcurlybrace{\{} +\def\sphinxrightcurlybrace{\}} + +% Declare Unicode characters used by linux tree command to pdflatex utf8/utf8x +\def\spx@bd#1#2{% + \leavevmode + \begingroup + \ifx\spx@bd@height \@undefined\def\spx@bd@height{\baselineskip}\fi + \ifx\spx@bd@width \@undefined\setbox0\hbox{0}\def\spx@bd@width{\wd0 }\fi + \ifx\spx@bd@thickness\@undefined\def\spx@bd@thickness{.6\p@}\fi + \ifx\spx@bd@lower \@undefined\def\spx@bd@lower{\dp\strutbox}\fi + \lower\spx@bd@lower#1{#2}% + \endgroup +}% +\@namedef{sphinx@u2500}% BOX DRAWINGS LIGHT HORIZONTAL + {\spx@bd{\vbox to\spx@bd@height} + {\vss\hrule\@height\spx@bd@thickness + \@width\spx@bd@width\vss}}% +\@namedef{sphinx@u2502}% BOX DRAWINGS LIGHT VERTICAL + {\spx@bd{\hb@xt@\spx@bd@width} + {\hss\vrule\@height\spx@bd@height + \@width \spx@bd@thickness\hss}}% +\@namedef{sphinx@u2514}% BOX DRAWINGS LIGHT UP AND RIGHT + {\spx@bd{\hb@xt@\spx@bd@width} + {\hss\raise.5\spx@bd@height + \hb@xt@\z@{\hss\vrule\@height.5\spx@bd@height + \@width \spx@bd@thickness\hss}% + \vbox to\spx@bd@height{\vss\hrule\@height\spx@bd@thickness + \@width.5\spx@bd@width\vss}}}% +\@namedef{sphinx@u251C}% BOX DRAWINGS LIGHT VERTICAL AND RIGHT + {\spx@bd{\hb@xt@\spx@bd@width} + {\hss + \hb@xt@\z@{\hss\vrule\@height\spx@bd@height + \@width \spx@bd@thickness\hss}% + \vbox to\spx@bd@height{\vss\hrule\@height\spx@bd@thickness + \@width.5\spx@bd@width\vss}}}% +\protected\def\sphinxunichar#1{\@nameuse{sphinx@u#1}}% + +% Tell TeX about pathological hyphenation cases: +\hyphenation{Base-HTTP-Re-quest-Hand-ler} + +\endinput diff --git a/sphinx/texinputs/sphinxmulticell.sty b/sphinx/texinputs/sphinxlatextables.sty index a6454918f..c3c1d6ad1 100644 --- a/sphinx/texinputs/sphinxmulticell.sty +++ b/sphinx/texinputs/sphinxlatextables.sty @@ -1,9 +1,173 @@ -\NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{sphinxmulticell}% - [2017/02/23 v1.6 better span rows and columns of a table (Sphinx team)]% -\DeclareOption*{\PackageWarning{sphinxmulticell}{Option `\CurrentOption' is unknown}}% -\ProcessOptions\relax +%% TABLES (WITH SUPPORT FOR MERGED CELLS OF GENERAL CONTENTS) % +% change this info string if making any custom modification +\ProvidesFile{sphinxlatextables.sty}[2021/01/27 tables]% + +% Provides support for this output mark-up from Sphinx latex writer +% and table templates: +% +% - the tabulary and longtable environments from the eponymous packages +% - the varwidth environment +% - the >{} etc mark-up possible in tabularcolumns is from array package +% which is loaded by longtable and tabulary +% - \X, \Y, T column types; others (L, C, R, J) are from tabulary package +% - \sphinxaftertopcaption +% - \sphinxatlongtableend +% - \sphinxatlongtablestart +% - \sphinxattableend +% - \sphinxattablestart +% - \sphinxcapstartof +% - \sphinxcolwidth +% - \sphinxlongtablecapskipadjust +% - \sphinxmultirow +% - \sphinxstartmulticolumn +% - \sphinxstopmulticolumn +% - \sphinxtablestrut +% - \sphinxthecaptionisattop +% - \sphinxthelongtablecaptionisattop +% +% Executes \RequirePackage for: +% +% - tabulary +% - longtable +% - varwidth +% +% Extends tabulary and longtable via patches and custom macros to support +% merged cells possibly containing code-blocks in complex tables + +\RequirePackage{tabulary} +% tabulary has a bug with its re-definition of \multicolumn in its first pass +% which is not \long. But now Sphinx does not use LaTeX's \multicolumn but its +% own macro. Hence we don't even need to patch tabulary. See +% sphinxpackagemulticell.sty +% X or S (Sphinx) may have meanings if some table package is loaded hence +% \X was chosen to avoid possibility of conflict +\newcolumntype{\X}[2]{p{\dimexpr + (\linewidth-\arrayrulewidth)*#1/#2-\tw@\tabcolsep-\arrayrulewidth\relax}} +\newcolumntype{\Y}[1]{p{\dimexpr + #1\dimexpr\linewidth-\arrayrulewidth\relax-\tw@\tabcolsep-\arrayrulewidth\relax}} +% using here T (for Tabulary) feels less of a problem than the X could be +\newcolumntype{T}{J}% +% For tables allowing pagebreaks +\RequirePackage{longtable} +% User interface to set-up whitespace before and after tables: +\newcommand*\sphinxtablepre {0pt}% +\newcommand*\sphinxtablepost{\medskipamount}% +% Space from caption baseline to top of table or frame of literal-block +\newcommand*\sphinxbelowcaptionspace{.5\sphinxbaselineskip}% +% as one can not use \baselineskip from inside longtable (it is zero there) +% we need \sphinxbaselineskip, which defaults to \baselineskip +\def\sphinxbaselineskip{\baselineskip}% +% The following is to ensure that, whether tabular(y) or longtable: +% - if a caption is on top of table: +% a) the space between its last baseline and the top rule of table is +% exactly \sphinxbelowcaptionspace +% b) the space from last baseline of previous text to first baseline of +% caption is exactly \parskip+\baselineskip+ height of a strut. +% c) the caption text will wrap at width \LTcapwidth (4in) +% - make sure this works also if "caption" package is loaded by user +% (with its width or margin option taking place of \LTcapwidth role) +% TODO: obtain same for caption of literal block: a) & c) DONE, b) TO BE DONE +% +% To modify space below such top caption, adjust \sphinxbelowcaptionspace +% To add or remove space above such top caption, adjust \sphinxtablepre: +% notice that \abovecaptionskip, \belowcaptionskip, \LTpre are **ignored** +% A. Table with longtable +\def\sphinxatlongtablestart + {\par + \vskip\parskip + \vskip\dimexpr\sphinxtablepre\relax % adjust vertical position + \vbox{}% get correct baseline from above + \LTpre\z@skip\LTpost\z@skip % set to zero longtable's own skips + \edef\sphinxbaselineskip{\dimexpr\the\dimexpr\baselineskip\relax\relax}% + }% +% Compatibility with caption package +\def\sphinxthelongtablecaptionisattop{% + \spx@ifcaptionpackage{\noalign{\vskip-\belowcaptionskip}}{}% +}% +% Achieves exactly \sphinxbelowcaptionspace below longtable caption +\def\sphinxlongtablecapskipadjust + {\dimexpr-\dp\strutbox + -\spx@ifcaptionpackage{\abovecaptionskip}{\sphinxbaselineskip}% + +\sphinxbelowcaptionspace\relax}% +\def\sphinxatlongtableend{\@nobreakfalse % latex3/latex2e#173 + \prevdepth\z@\vskip\sphinxtablepost\relax}% +% B. Table with tabular or tabulary +\def\sphinxattablestart{\par\vskip\dimexpr\sphinxtablepre\relax}% +\let\sphinxattableend\sphinxatlongtableend +% This is used by tabular and tabulary templates +\newcommand*\sphinxcapstartof[1]{% + \vskip\parskip + \vbox{}% force baselineskip for good positioning by capstart of hyperanchor + % hyperref puts the anchor 6pt above this baseline; in case of caption + % this baseline will be \ht\strutbox above first baseline of caption + \def\@captype{#1}% + \capstart +% move back vertically, as tabular (or its caption) will compensate + \vskip-\baselineskip\vskip-\parskip +}% +\def\sphinxthecaptionisattop{% locate it after \sphinxcapstartof + \spx@ifcaptionpackage + {\caption@setposition{t}% + \vskip\baselineskip\vskip\parskip % undo those from \sphinxcapstartof + \vskip-\belowcaptionskip % anticipate caption package skip + % caption package uses a \vbox, not a \vtop, so "single line" case + % gives different result from "multi-line" without this: + \nointerlineskip + }% + {}% +}% +\def\sphinxthecaptionisatbottom{% (not finalized; for template usage) + \spx@ifcaptionpackage{\caption@setposition{b}}{}% +}% +% The aim of \sphinxcaption is to apply to tabular(y) the maximal width +% of caption as done by longtable +\def\sphinxtablecapwidth{\LTcapwidth}% +\newcommand\sphinxcaption{\@dblarg\spx@caption}% +\long\def\spx@caption[#1]#2{% + \noindent\hb@xt@\linewidth{\hss + \vtop{\@tempdima\dimexpr\sphinxtablecapwidth\relax +% don't exceed linewidth for the caption width + \ifdim\@tempdima>\linewidth\hsize\linewidth\else\hsize\@tempdima\fi +% longtable ignores \abovecaptionskip/\belowcaptionskip, so do the same here + \abovecaptionskip\sphinxabovecaptionskip % \z@skip + \belowcaptionskip\sphinxbelowcaptionskip % \z@skip + \caption[{#1}]% + {\strut\ignorespaces#2\ifhmode\unskip\@finalstrut\strutbox\fi}% + }\hss}% + \par\prevdepth\dp\strutbox +}% +\def\sphinxabovecaptionskip{\z@skip}% Do not use! Flagged for removal +\def\sphinxbelowcaptionskip{\z@skip}% Do not use! Flagged for removal +% This wrapper of \abovecaptionskip is used in sphinxVerbatim for top +% caption, and with another value in sphinxVerbatimintable +% TODO: To unify space above caption of a code-block with the one above +% caption of a table/longtable, \abovecaptionskip must not be used +% This auxiliary will get renamed and receive a different meaning +% in future. +\def\spx@abovecaptionskip{\abovecaptionskip}% +% Achieve \sphinxbelowcaptionspace below a caption located above a tabular +% or a tabulary +\newcommand\sphinxaftertopcaption +{% + \spx@ifcaptionpackage + {\par\prevdepth\dp\strutbox\nobreak\vskip-\abovecaptionskip}{\nobreak}% + \vskip\dimexpr\sphinxbelowcaptionspace\relax + \vskip-\baselineskip\vskip-\parskip +}% +% varwidth is crucial for our handling of general contents in merged cells +\RequirePackage{varwidth} +% but addition of a compatibility patch with hyperref is needed +% (tested with varwidth v 0.92 Mar 2009) +\AtBeginDocument {% + \let\@@vwid@Hy@raisedlink\Hy@raisedlink + \long\def\@vwid@Hy@raisedlink#1{\@vwid@wrap{\@@vwid@Hy@raisedlink{#1}}}% + \edef\@vwid@setup{% + \let\noexpand\Hy@raisedlink\noexpand\@vwid@Hy@raisedlink % HYPERREF ! + \unexpanded\expandafter{\@vwid@setup}}% +}% + +%%%%%%%%%%%%%%%%%%%%% % --- MULTICOLUMN --- % standard LaTeX's \multicolumn % 1. does not allow verbatim contents, @@ -205,7 +369,8 @@ \def\sphinx@multiwidth #1#2{\dimexpr % #1 to gobble the \@gobble (!) (\ifx\TY@final\@undefined\linewidth\else\sphinx@TY@tablewidth\fi -\arrayrulewidth)*#2-\tw@\tabcolsep-\arrayrulewidth\relax}% -% + +%%%%%%%%%%%%%%%%%% % --- MULTIROW --- % standard \multirow % 1. does not allow verbatim contents, @@ -312,6 +477,5 @@ % we need this to avoid colour panels hiding bottom parts of multirow text \sphinx@hack@CT }% + \endinput -%% -%% End of file `sphinxmulticell.sty'. diff --git a/sphinx/texinputs/sphinxoptionsgeometry.sty b/sphinx/texinputs/sphinxoptionsgeometry.sty new file mode 100644 index 000000000..af5a804d5 --- /dev/null +++ b/sphinx/texinputs/sphinxoptionsgeometry.sty @@ -0,0 +1,54 @@ +%% OPTIONS FOR GEOMETRY +% +% change this info string if making any custom modification +\ProvidesFile{sphinxoptionsgeometry.sty}[2021/01/27 geometry] + +% geometry +\ifx\kanjiskip\@undefined + \PassOptionsToPackage{% + hmargin={\unexpanded{\spx@opt@hmargin}},% + vmargin={\unexpanded{\spx@opt@vmargin}},% + marginpar=\unexpanded{\spx@opt@marginpar}} + {geometry} +\else + % set text width for Japanese documents to be integer multiple of 1zw + % and text height to be integer multiple of \baselineskip + % the execution is delayed to \sphinxsetup then geometry.sty + \normalsize\normalfont + \newcommand*\sphinxtextwidthja[1]{% + \if@twocolumn\tw@\fi + \dimexpr + \numexpr\dimexpr\paperwidth-\tw@\dimexpr#1\relax\relax/ + \dimexpr\if@twocolumn\tw@\else\@ne\fi zw\relax + zw\relax}% + \newcommand*\sphinxmarginparwidthja[1]{% + \dimexpr\numexpr\dimexpr#1\relax/\dimexpr1zw\relax zw\relax}% + \newcommand*\sphinxtextlinesja[1]{% + \numexpr\@ne+\dimexpr\paperheight-\topskip-\tw@\dimexpr#1\relax\relax/ + \baselineskip\relax}% + \ifx\@jsc@uplatextrue\@undefined\else + % the way we found in order for the papersize special written by + % geometry in the dvi file to be correct in case of jsbook class + \ifnum\mag=\@m\else % do nothing special if nomag class option or 10pt + \PassOptionsToPackage{truedimen}{geometry}% + \fi + \fi + \PassOptionsToPackage{% + hmarginratio={1:1},% + textwidth=\unexpanded{\sphinxtextwidthja{\spx@opt@hmargin}},% + vmarginratio={1:1},% + lines=\unexpanded{\sphinxtextlinesja{\spx@opt@vmargin}},% + marginpar=\unexpanded{\sphinxmarginparwidthja{\spx@opt@marginpar}},% + footskip=2\baselineskip,% + }{geometry}% + \AtBeginDocument + {% update a dimension used by the jsclasses + \ifx\@jsc@uplatextrue\@undefined\else\fullwidth\textwidth\fi + % for some reason, jreport normalizes all dimensions with \@settopoint + \@ifclassloaded{jreport} + {\@settopoint\textwidth\@settopoint\textheight\@settopoint\marginparwidth} + {}% <-- "false" clause of \@ifclassloaded + }% +\fi + +\endinput diff --git a/sphinx/texinputs/sphinxoptionshyperref.sty b/sphinx/texinputs/sphinxoptionshyperref.sty new file mode 100644 index 000000000..6017d8970 --- /dev/null +++ b/sphinx/texinputs/sphinxoptionshyperref.sty @@ -0,0 +1,35 @@ +%% Bookmarks and hyperlinks +% +% change this info string if making any custom modification +\ProvidesFile{sphinxoptionshyperref.sty}[2021/01/27 hyperref] + +% to make pdf with correct encoded bookmarks in Japanese +% this should precede the hyperref package +\ifx\kanjiskip\@undefined +% for non-Japanese: make sure bookmarks are ok also with lualatex + \PassOptionsToPackage{pdfencoding=unicode}{hyperref} +\else + \RequirePackage{atbegshi} + \ifx\ucs\@undefined + \ifnum 42146=\euc"A4A2 + \AtBeginShipoutFirst{\special{pdf:tounicode EUC-UCS2}} + \else + \AtBeginShipoutFirst{\special{pdf:tounicode 90ms-RKSJ-UCS2}} + \fi + \else + \AtBeginShipoutFirst{\special{pdf:tounicode UTF8-UCS2}} + \fi +\fi + +\ifx\@jsc@uplatextrue\@undefined\else + \PassOptionsToPackage{setpagesize=false}{hyperref} +\fi + +% These options can be overriden inside 'hyperref' key +% or by later use of \hypersetup. +\PassOptionsToPackage{colorlinks,breaklinks,% + linkcolor=InnerLinkColor,filecolor=OuterLinkColor,% + menucolor=OuterLinkColor,urlcolor=OuterLinkColor,% + citecolor=InnerLinkColor}{hyperref} + +\endinput diff --git a/sphinx/texinputs/sphinxcyrillic.sty b/sphinx/texinputs/sphinxpackagecyrillic.sty index 6747b5ec6..9aa62fc22 100644 --- a/sphinx/texinputs/sphinxcyrillic.sty +++ b/sphinx/texinputs/sphinxpackagecyrillic.sty @@ -1,7 +1,7 @@ %% CYRILLIC IN NON-CYRILLIC DOCUMENTS (pdflatex only) % % refs: https://tex.stackexchange.com/q/460271/ -\ProvidesPackage{sphinxcyrillic}% +\ProvidesPackage{sphinxpackagecyrillic}% [2018/11/21 v2.0 support for Cyrillic in non-Cyrillic documents] \RequirePackage{kvoptions} \SetupKeyvalOptions{prefix=spx@cyropt@} % use \spx@cyropt@ prefix diff --git a/sphinx/texinputs/footnotehyper-sphinx.sty b/sphinx/texinputs/sphinxpackagefootnote.sty index 3bba385a8..a6071cf10 100644 --- a/sphinx/texinputs/footnotehyper-sphinx.sty +++ b/sphinx/texinputs/sphinxpackagefootnote.sty @@ -1,8 +1,13 @@ \NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{footnotehyper-sphinx}% - [2021/02/04 v1.1d hyperref aware footnote.sty for sphinx (JFB)] +\ProvidesPackage{sphinxpackagefootnote}% + [2021/02/04 v1.1d footnotehyper adapted to sphinx (Sphinx team)] +% Provides support for this output mark-up from Sphinx latex writer: +% - footnote environment +% - savenotes environment (table templates) +% - \sphinxfootnotemark +% %% -%% Package: footnotehyper-sphinx +%% Package: sphinxpackagefootnote %% Version: based on footnotehyper.sty 2021/02/04 v1.1d %% as available at https://www.ctan.org/pkg/footnotehyper %% License: the one applying to Sphinx @@ -18,7 +23,7 @@ %% 5. macro definition \sphinxlongtablepatch %% 6. replaced some \undefined by \@undefined \newif\iffootnotehyperparse\footnotehyperparsetrue -\DeclareOption*{\PackageWarning{footnotehyper-sphinx}{Option `\CurrentOption' is unknown}}% +\DeclareOption*{\PackageWarning{sphinxpackagefootnote}{Option `\CurrentOption' is unknown}}% \ProcessOptions\relax \newbox\FNH@notes \newtoks\FNH@toks % 1.1c @@ -292,7 +297,7 @@ }% % slight reformulation for Sphinx \def\FNH@bad@makefntext@alert{% - \PackageWarningNoLine{footnotehyper-sphinx}% + \PackageWarningNoLine{sphinxpackagefootnote}% {Footnotes will be sub-optimal, sorry. This is due to the document class or^^J some package modifying macro \string\@makefntext.^^J You can try to report this incompatibility at^^J @@ -322,6 +327,7 @@ \noexpand\if@endpe\noexpand\@endpetrue\noexpand\fi }% }% +% % some extras for Sphinx : % \sphinxfootnotemark: usable in section titles and silently removed from TOCs. \def\sphinxfootnotemark [#1]% @@ -387,4 +393,4 @@ }% \endinput %% -%% End of file `footnotehyper-sphinx.sty'. +%% End of file `sphinxpackagefootnote.sty'. diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html index 1d9df693b..855ec8ccb 100644 --- a/sphinx/themes/agogo/layout.html +++ b/sphinx/themes/agogo/layout.html @@ -13,14 +13,14 @@ {% block header %} <div class="header-wrapper" role="banner"> <div class="header"> - {%- if logo %} - <p class="logo"><a href="{{ pathto(master_doc)|e }}"> - <img class="logo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/> + {%- if logo_url %} + <p class="logo"><a href="{{ pathto(root_doc)|e }}"> + <img class="logo" src="{{ logo_url|e }}" alt="Logo"/> </a></p> {%- endif %} {%- block headertitle %} <div class="headertitle"><a - href="{{ pathto(master_doc)|e }}">{{ shorttitle|e }}</a></div> + href="{{ pathto(root_doc)|e }}">{{ shorttitle|e }}</a></div> {%- endblock %} <div class="rel" role="navigation" aria-label="related navigation"> {%- for rellink in rellinks|reverse %} diff --git a/sphinx/themes/basic/globaltoc.html b/sphinx/themes/basic/globaltoc.html index a47c32cc0..5ac0abbd4 100644 --- a/sphinx/themes/basic/globaltoc.html +++ b/sphinx/themes/basic/globaltoc.html @@ -7,5 +7,5 @@ :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} -<h3><a href="{{ pathto(master_doc)|e }}">{{ _('Table of Contents') }}</a></h3> +<h3><a href="{{ pathto(root_doc)|e }}">{{ _('Table of Contents') }}</a></h3> {{ toctree(includehidden=theme_globaltoc_includehidden, collapse=theme_globaltoc_collapse, maxdepth=theme_globaltoc_maxdepth) }} diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index bd56681c0..df0707220 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -17,9 +17,7 @@ {%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} {%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and (sidebars != []) %} -{%- set url_root = pathto('', 1) %} {# 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 %} {%- else %} @@ -37,7 +35,7 @@ {%- if not loop.first %}{{ reldelim2 }}{% endif %}</li> {%- endfor %} {%- block rootrellink %} - <li class="nav-item nav-item-0"><a href="{{ pathto(master_doc)|e }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li> + <li class="nav-item nav-item-0"><a href="{{ pathto(root_doc)|e }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li> {%- endblock %} {%- for parent in parents %} <li class="nav-item nav-item-{{ loop.index }}"><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li> @@ -53,9 +51,9 @@ <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> <div class="sphinxsidebarwrapper"> {%- block sidebarlogo %} - {%- if logo %} - <p class="logo"><a href="{{ pathto(master_doc)|e }}"> - <img class="logo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/> + {%- if logo_url %} + <p class="logo"><a href="{{ pathto(root_doc)|e }}"> + <img class="logo" src="{{ logo_url|e }}" alt="Logo"/> </a></p> {%- endif %} {%- endblock %} @@ -88,15 +86,12 @@ {%- endmacro %} {%- macro script() %} - <script id="documentation_options" data-url_root="{{ url_root }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script> {%- for js in script_files %} {{ js_tag(js) }} {%- endfor %} {%- endmacro %} {%- macro css() %} - <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" /> - <link rel="stylesheet" href="{{ pathto('_static/' + style, 1)|e }}" type="text/css" /> {%- for css in css_files %} {%- if css|attr("filename") %} {{ css_tag(css) }} @@ -140,8 +135,8 @@ title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" href="{{ pathto('_static/opensearch.xml', 1) }}"/> {%- endif %} - {%- if favicon %} - <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1)|e }}"/> + {%- if favicon_url %} + <link rel="shortcut icon" href="{{ favicon_url|e }}"/> {%- endif %} {%- endif %} {%- block linktags %} diff --git a/sphinx/themes/basic/localtoc.html b/sphinx/themes/basic/localtoc.html index c15ad2708..d761c2378 100644 --- a/sphinx/themes/basic/localtoc.html +++ b/sphinx/themes/basic/localtoc.html @@ -8,6 +8,6 @@ :license: BSD, see LICENSE for details. #} {%- if display_toc %} - <h3><a href="{{ pathto(master_doc)|e }}">{{ _('Table of Contents') }}</a></h3> + <h3><a href="{{ pathto(root_doc)|e }}">{{ _('Table of Contents') }}</a></h3> {{ toc }} {%- endif %} diff --git a/sphinx/themes/basic/opensearch.xml b/sphinx/themes/basic/opensearch.xml index 0cad2967c..ca7bb5966 100644 --- a/sphinx/themes/basic/opensearch.xml +++ b/sphinx/themes/basic/opensearch.xml @@ -6,8 +6,8 @@ <Url type="text/html" method="get" template="{{ use_opensearch }}/{{ pathto('search') }}?q={searchTerms}"/> <LongName>{{ docstitle|e }}</LongName> -{%- if favicon %} - <Image height="16" width="16" type="image/x-icon">{{ use_opensearch }}/{{ pathto('_static/' + favicon, 1)|e }}</Image> +{%- if favicon_url %} + <Image height="16" width="16" type="image/x-icon">{{ use_opensearch }}/{{ favicon_url|e }}</Image> {%- endif %} {% block extra %} {# Put e.g. an <Image> element here. #} {% endblock %} </OpenSearchDescription> diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 3bb3ea705..c72d20eb6 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -130,7 +130,7 @@ ul.search li a { font-weight: bold; } -ul.search li div.context { +ul.search li p.context { color: #888; margin: 2px 0 0 30px; text-align: left; diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 1a90152eb..2f500ae5d 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -509,7 +509,7 @@ var Search = { var excerpt = ((start > 0) ? '...' : '') + $.trim(text.substr(start, 240)) + ((start + 240 - text.length) ? '...' : ''); - var rv = $('<div class="context"></div>').text(excerpt); + var rv = $('<p class="context"></div>').text(excerpt); $.each(hlwords, function() { rv = rv.highlightText(this, 'highlighted'); }); diff --git a/sphinx/themes/haiku/layout.html b/sphinx/themes/haiku/layout.html index 2c2469722..50ab798a9 100644 --- a/sphinx/themes/haiku/layout.html +++ b/sphinx/themes/haiku/layout.html @@ -21,7 +21,7 @@ «  <a href="{{ prev.link|e }}">{{ prev.title }}</a>   ::   {%- endif %} - <a class="uplink" href="{{ pathto(master_doc)|e }}">{{ _('Contents') }}</a> + <a class="uplink" href="{{ pathto(root_doc)|e }}">{{ _('Contents') }}</a> {%- if next %}   ::   <a href="{{ next.link|e }}">{{ next.title }}</a>  » @@ -36,11 +36,11 @@ {%- block haikuheader %} {%- if theme_full_logo != "false" %} <a href="{{ pathto('index') }}"> - <img class="logo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/> + <img class="logo" src="{{ logo_url|e }}" alt="Logo"/> </a> {%- else %} {%- if logo -%} - <img class="rightlogo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/> + <img class="rightlogo" src="{{ logo_url|e }}" alt="Logo"/> {%- endif -%} <h1 class="heading"><a href="{{ pathto('index') }}"> <span>{{ shorttitle|e }}</span></a></h1> diff --git a/sphinx/themes/pyramid/layout.html b/sphinx/themes/pyramid/layout.html index 02eec1cfb..33d9bb7d1 100644 --- a/sphinx/themes/pyramid/layout.html +++ b/sphinx/themes/pyramid/layout.html @@ -12,8 +12,8 @@ {%- if logo %} <div class="header" role="banner"> <div class="logo"> - <a href="{{ pathto(master_doc)|e }}"> - <img class="logo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/> + <a href="{{ pathto(root_doc)|e }}"> + <img class="logo" src="{{ pathto(logo, 1)|e }}" alt="Logo"/> </a> </div> </div> diff --git a/sphinx/theming.py b/sphinx/theming.py index 30e4dffdb..b7fb652ac 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -13,7 +13,7 @@ import os import shutil import tempfile from os import path -from typing import Any, Dict, List +from typing import TYPE_CHECKING, Any, Dict, List from zipfile import ZipFile import pkg_resources @@ -24,8 +24,7 @@ from sphinx.locale import __ from sphinx.util import logging from sphinx.util.osutil import ensuredir -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -179,8 +178,6 @@ class HTMLThemeFactory: """Try to load a theme having specifed name.""" if name == 'alabaster': self.load_alabaster_theme() - elif name == 'sphinx_rtd_theme': - self.load_sphinx_rtd_theme() else: self.load_external_theme(name) @@ -214,7 +211,7 @@ class HTMLThemeFactory: def find_themes(self, theme_path: str) -> Dict[str, str]: """Search themes from specified directory.""" - themes = {} # type: Dict[str, str] + themes: Dict[str, str] = {} if not path.isdir(theme_path): return themes @@ -238,13 +235,13 @@ class HTMLThemeFactory: if name not in self.themes: self.load_extra_theme(name) + if name not in self.themes and name == 'sphinx_rtd_theme': + # sphinx_rtd_theme (< 0.2.5) # RemovedInSphinx60Warning + logger.warning(__('sphinx_rtd_theme (< 0.3.0) found. ' + 'It will not be available since Sphinx-6.0')) + self.load_sphinx_rtd_theme() + if name not in self.themes: - if name == 'sphinx_rtd_theme': - raise ThemeError(__('sphinx_rtd_theme is no longer a hard dependency ' - 'since version 1.4.0. Please install it manually.' - '(pip install sphinx_rtd_theme)')) - else: - raise ThemeError(__('no theme named %r found ' - '(missing theme.conf?)') % name) + raise ThemeError(__('no theme named %r found (missing theme.conf?)') % name) return Theme(name, self.themes[name], factory=self) diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index 45640308f..213742636 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -9,7 +9,8 @@ """ import re -from typing import Any, Dict, Generator, List, Tuple +import warnings +from typing import TYPE_CHECKING, Any, Dict, Generator, List, Tuple from docutils import nodes from docutils.nodes import Element, Node, Text @@ -21,15 +22,14 @@ from docutils.utils.smartquotes import smartchars from sphinx import addnodes from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias +from sphinx.deprecation import RemovedInSphinx60Warning from sphinx.locale import _, __ from sphinx.util import docutils, logging from sphinx.util.docutils import new_document from sphinx.util.i18n import format_date from sphinx.util.nodes import NodeMatcher, apply_source_workaround, is_smartquotable -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.domain.std import StandardDomain from sphinx.environment import BuildEnvironment @@ -72,8 +72,8 @@ class SphinxTransformer(Transformer): A transformer for Sphinx. """ - document = None # type: nodes.document - env = None # type: BuildEnvironment + document: nodes.document = None + env: "BuildEnvironment" = None def set_environment(self, env: "BuildEnvironment") -> None: self.env = env @@ -170,7 +170,7 @@ class AutoNumbering(SphinxTransform): default_priority = 210 def apply(self, **kwargs: Any) -> None: - domain = self.env.get_domain('std') # type: StandardDomain + domain: StandardDomain = self.env.get_domain('std') for node in self.document.traverse(nodes.Element): if (domain.is_enumerable_node(node) and @@ -286,6 +286,11 @@ class FigureAligner(SphinxTransform): """ default_priority = 700 + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn('FigureAilgner is deprecated.', + RemovedInSphinx60Warning) + super().__init__(*args, **kwargs) + def apply(self, **kwargs: Any) -> None: matcher = NodeMatcher(nodes.table, nodes.figure) for node in self.document.traverse(matcher): # type: Element @@ -400,23 +405,6 @@ class ManpageLink(SphinxTransform): node.attributes.update(info) -from sphinx.domains.citation import CitationDefinitionTransform # NOQA -from sphinx.domains.citation import CitationReferenceTransform # NOQA - -deprecated_alias('sphinx.transforms', - { - 'CitationReferences': CitationReferenceTransform, - 'SmartQuotesSkipper': CitationDefinitionTransform, - }, - RemovedInSphinx40Warning, - { - 'CitationReferences': - 'sphinx.domains.citation.CitationReferenceTransform', - 'SmartQuotesSkipper': - 'sphinx.domains.citation.CitationDefinitionTransform', - }) - - def setup(app: "Sphinx") -> Dict[str, Any]: app.add_transform(ApplySourceWorkaround) app.add_transform(ExtraTranslatableNodes) @@ -425,7 +413,6 @@ def setup(app: "Sphinx") -> Dict[str, Any]: app.add_transform(HandleCodeBlocks) app.add_transform(SortIds) app.add_transform(DoctestTransform) - app.add_transform(FigureAligner) app.add_transform(AutoNumbering) app.add_transform(AutoIndexUpgrader) app.add_transform(FilterSystemMessages) diff --git a/sphinx/transforms/compact_bullet_list.py b/sphinx/transforms/compact_bullet_list.py index 18042358e..e7f040a18 100644 --- a/sphinx/transforms/compact_bullet_list.py +++ b/sphinx/transforms/compact_bullet_list.py @@ -32,7 +32,7 @@ class RefOnlyListChecker(nodes.GenericNodeVisitor): pass def visit_list_item(self, node: nodes.list_item) -> None: - children = [] # type: List[Node] + children: List[Node] = [] for child in node.children: if not isinstance(child, nodes.Invisible): children.append(child) diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index 6bea6c6ee..8f6d37d8d 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -10,7 +10,7 @@ from os import path from textwrap import indent -from typing import Any, Dict, List, Tuple, TypeVar +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, TypeVar from docutils import nodes from docutils.io import StringInput @@ -28,10 +28,7 @@ from sphinx.util.i18n import docname_to_domain from sphinx.util.nodes import (IMAGE_TYPE_NODES, LITERAL_TYPE_NODES, NodeMatcher, extract_messages, is_pending_meta, traverse_translatable_index) -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -245,6 +242,10 @@ class Locale(SphinxTransform): node.details['nodes'][0]['content'] = msgstr continue + if isinstance(node, nodes.image) and node.get('alt') == msg: + node['alt'] = msgstr + continue + # Avoid "Literal block expected; none found." warnings. # If msgstr ends with '::' then it cause warning message at # parser.parse() processing. @@ -274,10 +275,10 @@ class Locale(SphinxTransform): patch = patch.next_node() # ignore unexpected markups in translation message - unexpected = ( + unexpected: Tuple[Type[Element], ...] = ( nodes.paragraph, # expected form of translation nodes.title # generated by above "Subelements phase2" - ) # type: Tuple[Type[Element], ...] + ) # following types are expected if # config.gettext_additional_targets is configured @@ -295,8 +296,8 @@ class Locale(SphinxTransform): lst.append(new) is_autofootnote_ref = NodeMatcher(nodes.footnote_reference, auto=Any) - old_foot_refs = node.traverse(is_autofootnote_ref) # type: List[nodes.footnote_reference] # NOQA - new_foot_refs = patch.traverse(is_autofootnote_ref) # type: List[nodes.footnote_reference] # NOQA + old_foot_refs: List[nodes.footnote_reference] = node.traverse(is_autofootnote_ref) + new_foot_refs: List[nodes.footnote_reference] = patch.traverse(is_autofootnote_ref) if len(old_foot_refs) != len(new_foot_refs): old_foot_ref_rawsources = [ref.rawsource for ref in old_foot_refs] new_foot_ref_rawsources = [ref.rawsource for ref in new_foot_refs] @@ -304,7 +305,7 @@ class Locale(SphinxTransform): ' original: {0}, translated: {1}') .format(old_foot_ref_rawsources, new_foot_ref_rawsources), location=node) - old_foot_namerefs = {} # type: Dict[str, List[nodes.footnote_reference]] + old_foot_namerefs: Dict[str, List[nodes.footnote_reference]] = {} for r in old_foot_refs: old_foot_namerefs.setdefault(r.get('refname'), []).append(r) for newf in new_foot_refs: @@ -338,8 +339,8 @@ class Locale(SphinxTransform): # * use translated refname for section refname. # * inline reference "`Python <...>`_" has no 'refname'. is_refnamed_ref = NodeMatcher(nodes.reference, refname=Any) - old_refs = node.traverse(is_refnamed_ref) # type: List[nodes.reference] - new_refs = patch.traverse(is_refnamed_ref) # type: List[nodes.reference] + old_refs: List[nodes.reference] = node.traverse(is_refnamed_ref) + new_refs: List[nodes.reference] = patch.traverse(is_refnamed_ref) if len(old_refs) != len(new_refs): old_ref_rawsources = [ref.rawsource for ref in old_refs] new_ref_rawsources = [ref.rawsource for ref in new_refs] @@ -367,7 +368,7 @@ class Locale(SphinxTransform): is_refnamed_footnote_ref = NodeMatcher(nodes.footnote_reference, refname=Any) old_foot_refs = node.traverse(is_refnamed_footnote_ref) new_foot_refs = patch.traverse(is_refnamed_footnote_ref) - refname_ids_map = {} # type: Dict[str, List[str]] + refname_ids_map: Dict[str, List[str]] = {} if len(old_foot_refs) != len(new_foot_refs): old_foot_ref_rawsources = [ref.rawsource for ref in old_foot_refs] new_foot_ref_rawsources = [ref.rawsource for ref in new_foot_refs] @@ -384,8 +385,8 @@ class Locale(SphinxTransform): # citation should use original 'ids'. is_citation_ref = NodeMatcher(nodes.citation_reference, refname=Any) - old_cite_refs = node.traverse(is_citation_ref) # type: List[nodes.citation_reference] # NOQA - new_cite_refs = patch.traverse(is_citation_ref) # type: List[nodes.citation_reference] # NOQA + old_cite_refs: List[nodes.citation_reference] = node.traverse(is_citation_ref) + new_cite_refs: List[nodes.citation_reference] = patch.traverse(is_citation_ref) refname_ids_map = {} if len(old_cite_refs) != len(new_cite_refs): old_cite_ref_rawsources = [ref.rawsource for ref in old_cite_refs] @@ -446,15 +447,16 @@ class Locale(SphinxTransform): if isinstance(node, LITERAL_TYPE_NODES): node.rawsource = node.astext() - if isinstance(node, IMAGE_TYPE_NODES): - node.update_all_atts(patch) + if isinstance(node, nodes.image) and node.get('alt') != msg: + node['uri'] = patch['uri'] + continue # do not mark translated node['translated'] = True # to avoid double translation if 'index' in self.config.gettext_additional_targets: # Extract and translate messages for index entries. for node, entries in traverse_translatable_index(self.document): - new_entries = [] # type: List[Tuple[str, str, str, str, str]] + new_entries: List[Tuple[str, str, str, str, str]] = [] for type, msg, tid, main, key_ in entries: msg_parts = split_index_msg(type, msg) msgstr_parts = [] diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py index 1c424050a..653097cd8 100644 --- a/sphinx/transforms/post_transforms/__init__.py +++ b/sphinx/transforms/post_transforms/__init__.py @@ -22,10 +22,14 @@ from sphinx.locale import __ from sphinx.transforms import SphinxTransform from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator -from sphinx.util.nodes import process_only_nodes +from sphinx.util.nodes import find_pending_xref_condition, process_only_nodes logger = logging.getLogger(__name__) +if False: + # For type annotation + from docutils.nodes import Node + class SphinxPostTransform(SphinxTransform): """A base class of post-transforms. @@ -34,8 +38,8 @@ class SphinxPostTransform(SphinxTransform): They do resolving references, convert images, special transformation for each output formats and so on. This class helps to implement these post transforms. """ - builders = () # type: Tuple[str, ...] - formats = () # type: Tuple[str, ...] + builders: Tuple[str, ...] = () + formats: Tuple[str, ...] = () def apply(self, **kwargs: Any) -> None: if self.is_supported(): @@ -97,14 +101,27 @@ class ReferencesResolver(SphinxPostTransform): if newnode is None: self.warn_missing_reference(refdoc, typ, target, node, domain) except NoUri: - newnode = contnode - node.replace_self(newnode or contnode) + newnode = None + + if newnode: + newnodes: List[Node] = [newnode] + else: + newnodes = [contnode] + if newnode is None and isinstance(node[0], addnodes.pending_xref_condition): + matched = find_pending_xref_condition(node, "*") + if matched: + newnodes = matched.children + else: + logger.warning(__('Could not determine the fallback text for the ' + 'cross-reference. Might be a bug.'), location=node) + + node.replace_self(newnodes) def resolve_anyref(self, refdoc: str, node: pending_xref, contnode: Element) -> Element: """Resolve reference generated by the "any" role.""" stddomain = self.env.get_domain('std') target = node['reftarget'] - results = [] # type: List[Tuple[str, Element]] + results: List[Tuple[str, Element]] = [] # first, try resolving as :doc: doc_ref = stddomain.resolve_xref(self.env, refdoc, self.app.builder, 'doc', target, node, contnode) @@ -125,7 +142,7 @@ class ReferencesResolver(SphinxPostTransform): for role in domain.roles: res = domain.resolve_xref(self.env, refdoc, self.app.builder, role, target, node, contnode) - if res and isinstance(res[0], nodes.Element): + if res and len(res) > 0 and isinstance(res[0], nodes.Element): results.append(('%s:%s' % (domain.name, role), res)) # now, see how many matches we got... if not results: @@ -168,14 +185,13 @@ class ReferencesResolver(SphinxPostTransform): if self.app.emit_firstresult('warn-missing-reference', domain, node): return elif domain and typ in domain.dangling_warnings: - msg = domain.dangling_warnings[typ] + msg = domain.dangling_warnings[typ] % {'target': target} elif node.get('refdomain', 'std') not in ('', 'std'): - msg = (__('%s:%s reference target not found: %%(target)s') % - (node['refdomain'], typ)) + msg = (__('%s:%s reference target not found: %s') % + (node['refdomain'], typ, target)) else: - msg = __('%r reference target not found: %%(target)s') % typ - logger.warning(msg % {'target': target}, - location=node, type='ref', subtype=typ) + msg = __('%r reference target not found: %s') % (typ, target) + logger.warning(msg, location=node, type='ref', subtype=typ) class OnlyNodeTransform(SphinxPostTransform): diff --git a/sphinx/transforms/post_transforms/code.py b/sphinx/transforms/post_transforms/code.py index 20df1db3c..52bca8e12 100644 --- a/sphinx/transforms/post_transforms/code.py +++ b/sphinx/transforms/post_transforms/code.py @@ -20,9 +20,11 @@ from sphinx.application import Sphinx from sphinx.ext import doctest from sphinx.transforms import SphinxTransform -HighlightSetting = NamedTuple('HighlightSetting', [('language', str), - ('force', bool), - ('lineno_threshold', int)]) + +class HighlightSetting(NamedTuple): + language: str + force: bool + lineno_threshold: int class HighlightLanguageTransform(SphinxTransform): @@ -47,7 +49,7 @@ class HighlightLanguageTransform(SphinxTransform): class HighlightLanguageVisitor(nodes.NodeVisitor): def __init__(self, document: nodes.document, default_language: str) -> None: self.default_setting = HighlightSetting(default_language, False, sys.maxsize) - self.settings = [] # type: List[HighlightSetting] + self.settings: List[HighlightSetting] = [] super().__init__(document) def unknown_visit(self, node: Node) -> None: diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 2603e0458..f9b78837f 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -75,7 +75,7 @@ class ImageDownloader(BaseImageConverter): headers = {} if os.path.exists(path): - timestamp = ceil(os.stat(path).st_mtime) # type: float + timestamp: float = ceil(os.stat(path).st_mtime) headers['If-Modified-Since'] = epoch_to_rfc1123(timestamp) r = requests.get(node['uri'], headers=headers) @@ -178,7 +178,7 @@ class ImageConverter(BaseImageConverter): #: #: .. todo:: This should be refactored not to store the state without class #: variable. - available = None # type: Optional[bool] + available: Optional[bool] = None #: A conversion rules the image converter supports. #: It is represented as a list of pair of source image format (mimetype) and @@ -189,7 +189,7 @@ class ImageConverter(BaseImageConverter): #: ('image/gif', 'image/png'), #: ('application/pdf', 'image/png'), #: ] - conversion_rules = [] # type: List[Tuple[str, str]] + conversion_rules: List[Tuple[str, str]] = [] def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) diff --git a/sphinx/transforms/references.py b/sphinx/transforms/references.py index cd564d9eb..c1afd0a2f 100644 --- a/sphinx/transforms/references.py +++ b/sphinx/transforms/references.py @@ -8,14 +8,13 @@ :license: BSD, see LICENSE for details. """ -from typing import Any, Dict +from typing import TYPE_CHECKING, Any, Dict from docutils.transforms.references import DanglingReferences from sphinx.transforms import SphinxTransform -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 2fbc182e5..d420f4f77 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -8,7 +8,6 @@ :license: BSD, see LICENSE for details. """ -import fnmatch import functools import hashlib import os @@ -19,20 +18,17 @@ import tempfile import traceback import unicodedata import warnings -from codecs import BOM_UTF8 -from collections import deque from datetime import datetime from importlib import import_module from os import path from time import mktime, strptime -from typing import IO, Any, Callable, Dict, Iterable, Iterator, List, Pattern, Set, Tuple +from typing import (IO, TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Pattern, + Set, Tuple, Type) from urllib.parse import parse_qsl, quote_plus, urlencode, urlsplit, urlunsplit -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning -from sphinx.errors import (ExtensionError, FiletypeNotFoundError, PycodeError, - SphinxParallelError) +from sphinx.deprecation import RemovedInSphinx50Warning +from sphinx.errors import ExtensionError, FiletypeNotFoundError, SphinxParallelError from sphinx.locale import __ -from sphinx.util import smartypants # noqa from sphinx.util import logging from sphinx.util.console import bold, colorize, strip_colors, term_width_line # type: ignore from sphinx.util.matching import patfilter # noqa @@ -41,21 +37,18 @@ from sphinx.util.nodes import (caption_ref_re, explicit_title_re, # noqa # import other utilities; partly for backwards compatibility, so don't # prune unused ones indiscriminately from sphinx.util.osutil import (SEP, copyfile, copytimes, ensuredir, make_filename, # noqa - movefile, mtimes_of_files, os_path, relative_uri, walk) + movefile, mtimes_of_files, os_path, relative_uri) from sphinx.util.typing import PathMatcher -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.application import Sphinx logger = logging.getLogger(__name__) # Generally useful regular expressions. -ws_re = re.compile(r'\s+') # type: Pattern -url_re = re.compile(r'(?P<schema>.+)://.*') # type: Pattern +ws_re: Pattern = re.compile(r'\s+') +url_re: Pattern = re.compile(r'(?P<schema>.+)://.*') # High-level utility functions. @@ -98,23 +91,6 @@ def get_matching_files(dirname: str, yield filename -def get_matching_docs(dirname: str, suffixes: List[str], - exclude_matchers: Tuple[PathMatcher, ...] = ()) -> Iterable[str]: - """Get all file names (without suffixes) matching a suffix in a directory, - recursively. - - Exclude files and dirs matching a pattern in *exclude_patterns*. - """ - warnings.warn('get_matching_docs() is now deprecated. Use get_matching_files() instead.', - RemovedInSphinx40Warning, stacklevel=2) - suffixpatterns = ['*' + s for s in suffixes] - for filename in get_matching_files(dirname, exclude_matchers): - for suffixpattern in suffixpatterns: - if fnmatch.fnmatch(filename, suffixpattern): - yield filename[:-len(suffixpattern) + 1] - break - - def get_filetype(source_suffix: Dict[str, str], filename: str) -> str: for suffix, filetype in source_suffix.items(): if filename.endswith(suffix): @@ -131,7 +107,7 @@ class FilenameUniqDict(dict): appear in. Used for images and downloadable files in the environment. """ def __init__(self) -> None: - self._existing = set() # type: Set[str] + self._existing: Set[str] = set() def add_file(self, docname: str, newfile: str) -> str: if newfile in self: @@ -272,53 +248,6 @@ def save_traceback(app: "Sphinx") -> str: return path -def get_module_source(modname: str) -> Tuple[str, str]: - """Try to find the source code for a module. - - Can return ('file', 'filename') in which case the source is in the given - file, or ('string', 'source') which which case the source is the string. - """ - warnings.warn('get_module_source() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - try: - mod = import_module(modname) - except Exception as err: - raise PycodeError('error importing %r' % modname, err) from err - filename = getattr(mod, '__file__', None) - loader = getattr(mod, '__loader__', None) - if loader and getattr(loader, 'get_filename', None): - try: - filename = loader.get_filename(modname) - except Exception as err: - raise PycodeError('error getting filename for %r' % filename, err) from err - if filename is None and loader: - try: - filename = loader.get_source(modname) - if filename: - return 'string', filename - except Exception as err: - raise PycodeError('error getting source for %r' % modname, err) from err - if filename is None: - raise PycodeError('no source found for module %r' % modname) - filename = path.normpath(path.abspath(filename)) - lfilename = filename.lower() - if lfilename.endswith('.pyo') or lfilename.endswith('.pyc'): - filename = filename[:-1] - if not path.isfile(filename) and path.isfile(filename + 'w'): - filename += 'w' - elif not (lfilename.endswith('.py') or lfilename.endswith('.pyw')): - raise PycodeError('source is not a .py file: %r' % filename) - elif ('.egg' + os.path.sep) in filename: - pat = '(?<=\\.egg)' + re.escape(os.path.sep) - eggpath, _ = re.split(pat, filename, 1) - if path.isfile(eggpath): - return 'file', filename - - if not path.isfile(filename): - raise PycodeError('source file is not present: %r' % filename) - return 'file', filename - - def get_full_modname(modname: str, attribute: str) -> str: if modname is None: # Prevents a TypeError: if the last getattr() call will return None @@ -340,58 +269,6 @@ def get_full_modname(modname: str, attribute: str) -> str: _coding_re = re.compile(r'coding[:=]\s*([-\w.]+)') -def detect_encoding(readline: Callable[[], bytes]) -> str: - """Like tokenize.detect_encoding() from Py3k, but a bit simplified.""" - warnings.warn('sphinx.util.detect_encoding() is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - - def read_or_stop() -> bytes: - try: - return readline() - except StopIteration: - return None - - def get_normal_name(orig_enc: str) -> str: - """Imitates get_normal_name in tokenizer.c.""" - # Only care about the first 12 characters. - enc = orig_enc[:12].lower().replace('_', '-') - if enc == 'utf-8' or enc.startswith('utf-8-'): - return 'utf-8' - if enc in ('latin-1', 'iso-8859-1', 'iso-latin-1') or \ - enc.startswith(('latin-1-', 'iso-8859-1-', 'iso-latin-1-')): - return 'iso-8859-1' - return orig_enc - - def find_cookie(line: bytes) -> str: - try: - line_string = line.decode('ascii') - except UnicodeDecodeError: - return None - - matches = _coding_re.findall(line_string) - if not matches: - return None - return get_normal_name(matches[0]) - - default = sys.getdefaultencoding() - first = read_or_stop() - if first and first.startswith(BOM_UTF8): - first = first[3:] - default = 'utf-8-sig' - if not first: - return default - encoding = find_cookie(first) - if encoding: - return encoding - second = read_or_stop() - if not second: - return default - encoding = find_cookie(second) - if encoding: - return encoding - return default - - class UnicodeDecodeErrorHandler: """Custom error handler for open() that warns and replaces.""" @@ -460,39 +337,6 @@ def parselinenos(spec: str, total: int) -> List[int]: return items -def force_decode(string: str, encoding: str) -> str: - """Forcibly get a unicode string out of a bytestring.""" - warnings.warn('force_decode() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - if isinstance(string, bytes): - try: - if encoding: - string = string.decode(encoding) - else: - # try decoding with utf-8, should only work for real UTF-8 - string = string.decode() - except UnicodeError: - # last resort -- can't fail - string = string.decode('latin1') - return string - - -class attrdict(dict): - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - warnings.warn('The attrdict class is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - def __getattr__(self, key: str) -> str: - return self[key] - - def __setattr__(self, key: str, val: str) -> None: - self[key] = val - - def __delattr__(self, key: str) -> None: - del self[key] - - def rpartition(s: str, t: str) -> Tuple[str, str]: """Similar to str.rpartition from 2.5, but doesn't return the separator.""" warnings.warn('rpartition() is now deprecated.', RemovedInSphinx50Warning, stacklevel=2) @@ -535,48 +379,13 @@ def format_exception_cut_frames(x: int = 1) -> str: """Format an exception with traceback, but only the last x frames.""" typ, val, tb = sys.exc_info() # res = ['Traceback (most recent call last):\n'] - res = [] # type: List[str] + res: List[str] = [] tbres = traceback.format_tb(tb) res += tbres[-x:] res += traceback.format_exception_only(typ, val) return ''.join(res) -class PeekableIterator: - """ - An iterator which wraps any iterable and makes it possible to peek to see - what's the next item. - """ - def __init__(self, iterable: Iterable) -> None: - self.remaining = deque() # type: deque - self._iterator = iter(iterable) - warnings.warn('PeekableIterator is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - def __iter__(self) -> "PeekableIterator": - return self - - def __next__(self) -> Any: - """Return the next item from the iterator.""" - if self.remaining: - return self.remaining.popleft() - return next(self._iterator) - - next = __next__ # Python 2 compatibility - - def push(self, item: Any) -> None: - """Push the `item` on the internal stack, it will be returned on the - next :meth:`next` call. - """ - self.remaining.append(item) - - def peek(self) -> Any: - """Return the next item without changing the state of the iterator.""" - item = next(self) - self.push(item) - return item - - def import_object(objname: str, source: str = None) -> Any: """Import python object by qualname.""" try: @@ -638,6 +447,14 @@ def encode_uri(uri: str) -> str: return urlunsplit(split) +def isurl(url: str) -> bool: + """Check *url* is URL or not.""" + if url and '://' in url: + return True + else: + return False + + def display_chunk(chunk: Any) -> str: if isinstance(chunk, (list, tuple)): if len(chunk) == 1: @@ -693,7 +510,7 @@ class progress_message: def __enter__(self) -> None: logger.info(bold(self.message + '... '), nonl=True) - def __exit__(self, exc_type: "Type[Exception]", exc_value: Exception, traceback: Any) -> bool: # NOQA + def __exit__(self, exc_type: Type[Exception], exc_value: Exception, traceback: Any) -> bool: # NOQA if isinstance(exc_value, SkipProgressMessage): logger.info(__('skipped')) if exc_value.args: diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py index 1549fbf75..2be2e390f 100644 --- a/sphinx/util/cfamily.py +++ b/sphinx/util/cfamily.py @@ -9,7 +9,6 @@ """ import re -import warnings from copy import deepcopy from typing import Any, Callable, List, Match, Optional, Pattern, Tuple, Union @@ -17,7 +16,6 @@ from docutils import nodes from docutils.nodes import TextElement from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.util import logging logger = logging.getLogger(__name__) @@ -85,12 +83,7 @@ def verify_description_mode(mode: str) -> None: class NoOldIdError(Exception): # Used to avoid implementing unneeded id generation for old id schemes. - @property - def description(self) -> str: - warnings.warn('%s.description is deprecated. ' - 'Coerce the instance to a string instead.' % self.__class__.__name__, - RemovedInSphinx40Warning, stacklevel=2) - return str(self) + pass class ASTBaseBase: @@ -105,7 +98,7 @@ class ASTBaseBase: return False return True - __hash__ = None # type: Callable[[], int] + __hash__: Callable[[], int] = None def clone(self) -> Any: return deepcopy(self) @@ -213,21 +206,11 @@ class ASTBaseParenExprList(ASTBaseBase): ################################################################################ class UnsupportedMultiCharacterCharLiteral(Exception): - @property - def decoded(self) -> str: - warnings.warn('%s.decoded is deprecated. ' - 'Coerce the instance to a string instead.' % self.__class__.__name__, - RemovedInSphinx40Warning, stacklevel=2) - return str(self) + pass class DefinitionError(Exception): - @property - def description(self) -> str: - warnings.warn('%s.description is deprecated. ' - 'Coerce the instance to a string instead.' % self.__class__.__name__, - RemovedInSphinx40Warning, stacklevel=2) - return str(self) + pass class BaseParser: @@ -240,9 +223,9 @@ class BaseParser: self.pos = 0 self.end = len(self.definition) - self.last_match = None # type: Match - self._previous_state = (0, None) # type: Tuple[int, Match] - self.otherErrors = [] # type: List[DefinitionError] + self.last_match: Match = None + self._previous_state: Tuple[int, Match] = (0, None) + self.otherErrors: List[DefinitionError] = [] # in our tests the following is set to False to capture bad parsing self.allowFallbackExpressionParsing = True @@ -373,7 +356,7 @@ class BaseParser: # TODO: add handling of string literals and similar brackets = {'(': ')', '[': ']', '{': '}'} startPos = self.pos - symbols = [] # type: List[str] + symbols: List[str] = [] while not self.eof: if len(symbols) == 0 and self.current_char in end: break diff --git a/sphinx/util/compat.py b/sphinx/util/compat.py index dbe902be7..9be80358e 100644 --- a/sphinx/util/compat.py +++ b/sphinx/util/compat.py @@ -9,17 +9,9 @@ """ import sys -import warnings -from typing import Any, Dict +from typing import TYPE_CHECKING, Any, Dict -from docutils.utils import get_source_line - -from sphinx import addnodes -from sphinx.deprecation import RemovedInSphinx40Warning -from sphinx.transforms import SphinxTransform - -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx @@ -36,22 +28,7 @@ def register_application_for_autosummary(app: "Sphinx") -> None: autosummary._app = app -class IndexEntriesMigrator(SphinxTransform): - """Migrating indexentries from old style (4columns) to new style (5columns).""" - default_priority = 700 - - def apply(self, **kwargs: Any) -> None: - for node in self.document.traverse(addnodes.index): - for i, entries in enumerate(node['entries']): - if len(entries) == 4: - source, line = get_source_line(node) - warnings.warn('An old styled index node found: %r at (%s:%s)' % - (node, source, line), RemovedInSphinx40Warning, stacklevel=2) - node['entries'][i] = entries + (None,) - - def setup(app: "Sphinx") -> Dict[str, Any]: - app.add_transform(IndexEntriesMigrator) app.connect('builder-inited', register_application_for_autosummary) return { diff --git a/sphinx/util/console.py b/sphinx/util/console.py index 3ea5b9573..0c8008371 100644 --- a/sphinx/util/console.py +++ b/sphinx/util/console.py @@ -11,7 +11,7 @@ import os import re import sys -from typing import Dict +from typing import Dict, Pattern try: # check if colorama is installed to support color on Windows @@ -20,8 +20,8 @@ except ImportError: colorama = None -_ansi_re = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm') -codes = {} # type: Dict[str, str] +_ansi_re: Pattern = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm') +codes: Dict[str, str] = {} def terminal_safe(s: str) -> str: @@ -44,7 +44,7 @@ def get_terminal_width() -> int: return terminal_width -_tw = get_terminal_width() +_tw: int = get_terminal_width() def term_width_line(text: str) -> str: diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index 3fc72340a..f74f03265 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -9,22 +9,17 @@ :license: BSD, see LICENSE for details. """ -import warnings -from typing import Any, Dict, List, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, Union, cast from docutils import nodes from docutils.nodes import Node from sphinx import addnodes -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.environment import BuildEnvironment from sphinx.util.typing import TextlikeNode -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.directive import ObjectDescription - from sphinx.environment import BuildEnvironment def _is_single_paragraph(node: nodes.field_body) -> bool: @@ -32,7 +27,7 @@ def _is_single_paragraph(node: nodes.field_body) -> bool: if len(node) == 0: return False elif len(node) > 1: - for subnode in node[1:]: # type: nodes.Node + for subnode in node[1:]: # type: Node if not isinstance(subnode, nodes.system_message): return False if isinstance(node[0], nodes.paragraph): @@ -66,8 +61,8 @@ class Field: self.bodyrolename = bodyrolename def make_xref(self, rolename: str, domain: str, target: str, - innernode: "Type[TextlikeNode]" = addnodes.literal_emphasis, - contnode: Node = None, env: "BuildEnvironment" = None) -> Node: + innernode: Type[TextlikeNode] = addnodes.literal_emphasis, + contnode: Node = None, env: BuildEnvironment = None) -> Node: if not rolename: return contnode or innernode(target, target) refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False, @@ -78,15 +73,15 @@ class Field: return refnode def make_xrefs(self, rolename: str, domain: str, target: str, - innernode: "Type[TextlikeNode]" = addnodes.literal_emphasis, - contnode: Node = None, env: "BuildEnvironment" = None) -> List[Node]: + innernode: Type[TextlikeNode] = addnodes.literal_emphasis, + contnode: Node = None, env: BuildEnvironment = None) -> List[Node]: return [self.make_xref(rolename, domain, target, innernode, contnode, env)] def make_entry(self, fieldarg: str, content: List[Node]) -> Tuple[str, List[Node]]: return (fieldarg, content) def make_field(self, types: Dict[str, List[Node]], domain: str, - item: Tuple, env: "BuildEnvironment" = None) -> nodes.field: + item: Tuple, env: BuildEnvironment = None) -> nodes.field: fieldarg, content = item fieldname = nodes.field_name('', self.label) if fieldarg: @@ -126,7 +121,7 @@ class GroupedField(Field): self.can_collapse = can_collapse def make_field(self, types: Dict[str, List[Node]], domain: str, - items: Tuple, env: "BuildEnvironment" = None) -> nodes.field: + items: Tuple, env: BuildEnvironment = None) -> nodes.field: fieldname = nodes.field_name('', self.label) listnode = self.list_type() for fieldarg, content in items: @@ -175,7 +170,7 @@ class TypedField(GroupedField): self.typerolename = typerolename def make_field(self, types: Dict[str, List[Node]], domain: str, - items: Tuple, env: "BuildEnvironment" = None) -> nodes.field: + items: Tuple, env: BuildEnvironment = None) -> nodes.field: def handle_item(fieldarg: str, content: str) -> nodes.paragraph: par = nodes.paragraph() par.extend(self.make_xrefs(self.rolename, domain, fieldarg, @@ -200,7 +195,7 @@ class TypedField(GroupedField): fieldname = nodes.field_name('', self.label) if len(items) == 1 and self.can_collapse: fieldarg, content = items[0] - bodynode = handle_item(fieldarg, content) # type: nodes.Node + bodynode: Node = handle_item(fieldarg, content) else: bodynode = self.list_type() for fieldarg, content in items: @@ -214,31 +209,12 @@ class DocFieldTransformer: Transforms field lists in "doc field" syntax into better-looking equivalents, using the field type definitions given on a domain. """ - typemap = None # type: Dict[str, Tuple[Field, bool]] + typemap: Dict[str, Tuple[Field, bool]] = None def __init__(self, directive: "ObjectDescription") -> None: self.directive = directive - try: - self.typemap = directive.get_field_type_map() - except Exception: - # for 3rd party extensions directly calls this transformer. - warnings.warn('DocFieldTransformer expects given directive object is a subclass ' - 'of ObjectDescription.', RemovedInSphinx40Warning, stacklevel=2) - self.typemap = self.preprocess_fieldtypes(directive.__class__.doc_field_types) - - def preprocess_fieldtypes(self, types: List[Field]) -> Dict[str, Tuple[Field, bool]]: - warnings.warn('DocFieldTransformer.preprocess_fieldtypes() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - typemap = {} - for fieldtype in types: - for name in fieldtype.names: - typemap[name] = fieldtype, False - if fieldtype.is_typed: - typed_field = cast(TypedField, fieldtype) - for name in typed_field.typenames: - typemap[name] = typed_field, True - return typemap + self.typemap = directive.get_field_type_map() def transform_all(self, node: addnodes.desc_content) -> None: """Transform all field list children of a node.""" @@ -251,9 +227,9 @@ class DocFieldTransformer: """Transform a single field list *node*.""" typemap = self.typemap - entries = [] # type: List[Union[nodes.field, Tuple[Field, Any]]] - groupindices = {} # type: Dict[str, int] - types = {} # type: Dict[str, Dict] + entries: List[Union[nodes.field, Tuple[Field, Any]]] = [] + groupindices: Dict[str, int] = {} + types: Dict[str, Dict] = {} # step 1: traverse all fields and collect field types and content for field in cast(List[nodes.field], node): diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py index ac778af87..46bb5b9b8 100644 --- a/sphinx/util/docstrings.py +++ b/sphinx/util/docstrings.py @@ -23,7 +23,7 @@ field_list_item_re = re.compile(Body.patterns['field_marker']) def extract_metadata(s: str) -> Dict[str, str]: """Extract metadata from docstring.""" in_other_element = False - metadata = {} # type: Dict[str, str] + metadata: Dict[str, str] = {} if not s: return metadata diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index ce50c7ab1..44483bdd8 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -15,7 +15,8 @@ from copy import copy from distutils.version import LooseVersion from os import path from types import ModuleType -from typing import IO, Any, Callable, Dict, Generator, List, Optional, Set, Tuple, cast +from typing import (IO, TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional, Set, + Tuple, Type, cast) import docutils from docutils import nodes @@ -27,23 +28,21 @@ from docutils.statemachine import State, StateMachine, StringList from docutils.utils import Reporter, unescape from sphinx.errors import SphinxError +from sphinx.locale import _ from sphinx.util import logging from sphinx.util.typing import RoleFunction logger = logging.getLogger(__name__) report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(\\d+)?\\) ') -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.builders import Builder from sphinx.config import Config from sphinx.environment import BuildEnvironment __version_info__ = tuple(LooseVersion(docutils.__version__).version) -additional_nodes = set() # type: Set[Type[nodes.Element]] +additional_nodes: Set[Type[Element]] = set() @contextmanager @@ -68,7 +67,7 @@ def is_directive_registered(name: str) -> bool: return name in directives._directives # type: ignore -def register_directive(name: str, directive: "Type[Directive]") -> None: +def register_directive(name: str, directive: Type[Directive]) -> None: """Register a directive to docutils. This modifies global state of docutils. So it is better to use this @@ -96,12 +95,12 @@ def unregister_role(name: str) -> None: roles._roles.pop(name, None) # type: ignore -def is_node_registered(node: "Type[Element]") -> bool: +def is_node_registered(node: Type[Element]) -> bool: """Check the *node* is already registered.""" return hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__) -def register_node(node: "Type[Element]") -> None: +def register_node(node: Type[Element]) -> None: """Register a node to docutils. This modifies global state of some visitors. So it is better to use this @@ -112,7 +111,7 @@ def register_node(node: "Type[Element]") -> None: additional_nodes.add(node) -def unregister_node(node: "Type[Element]") -> None: +def unregister_node(node: Type[Element]) -> None: """Unregister a node from docutils. This is inverse of ``nodes._add_nodes_class_names()``. @@ -145,7 +144,7 @@ def patched_get_language() -> Generator[None, None, None]: @contextmanager -def using_user_docutils_conf(confdir: str) -> Generator[None, None, None]: +def using_user_docutils_conf(confdir: Optional[str]) -> Generator[None, None, None]: """Let docutils know the location of ``docutils.conf`` for Sphinx.""" try: docutilsconfig = os.environ.get('DOCUTILSCONFIG', None) @@ -161,7 +160,7 @@ def using_user_docutils_conf(confdir: str) -> Generator[None, None, None]: @contextmanager -def patch_docutils(confdir: str = None) -> Generator[None, None, None]: +def patch_docutils(confdir: Optional[str] = None) -> Generator[None, None, None]: """Patch to docutils temporarily.""" with patched_get_language(), using_user_docutils_conf(confdir): yield @@ -177,13 +176,13 @@ class sphinx_domains: """ def __init__(self, env: "BuildEnvironment") -> None: self.env = env - self.directive_func = None # type: Callable - self.roles_func = None # type: Callable + self.directive_func: Callable = None + self.roles_func: Callable = None def __enter__(self) -> None: self.enable() - def __exit__(self, exc_type: "Type[Exception]", exc_value: Exception, traceback: Any) -> None: # NOQA + def __exit__(self, exc_type: Type[Exception], exc_value: Exception, traceback: Any) -> None: # NOQA self.disable() def enable(self) -> None: @@ -210,6 +209,8 @@ class sphinx_domains: element = getattr(domain, type)(name) if element is not None: return element, [] + else: + logger.warning(_('unknown directive or role name: %s:%s'), domain_name, name) # else look in the default domain else: def_domain = self.env.temp_data.get('default_domain') @@ -225,7 +226,7 @@ class sphinx_domains: raise ElementLookupError - def lookup_directive(self, directive_name: str, language_module: ModuleType, document: nodes.document) -> Tuple[Optional["Type[Directive]"], List[system_message]]: # NOQA + def lookup_directive(self, directive_name: str, language_module: ModuleType, document: nodes.document) -> Tuple[Optional[Type[Directive]], List[system_message]]: # NOQA try: return self.lookup_domain_element('directive', directive_name) except ElementLookupError: @@ -347,15 +348,15 @@ class SphinxRole: .. note:: The subclasses of this class might not work with docutils. This class is strongly coupled with Sphinx. """ - name = None #: The role name actually used in the document. - rawtext = None #: A string containing the entire interpreted text input. - text = None #: The interpreted text content. - lineno = None #: The line number where the interpreted text begins. - inliner = None #: The ``docutils.parsers.rst.states.Inliner`` object. - options = None #: A dictionary of directive options for customization - #: (from the "role" directive). - content = None #: A list of strings, the directive content for customization - #: (from the "role" directive). + name: str #: The role name actually used in the document. + rawtext: str #: A string containing the entire interpreted text input. + text: str #: The interpreted text content. + lineno: int #: The line number where the interpreted text begins. + inliner: Inliner #: The ``docutils.parsers.rst.states.Inliner`` object. + options: Dict #: A dictionary of directive options for customization + #: (from the "role" directive). + content: List[str] #: A list of strings, the directive content for customization + #: (from the "role" directive). def __call__(self, name: str, rawtext: str, text: str, lineno: int, inliner: Inliner, options: Dict = {}, content: List[str] = [] @@ -408,10 +409,10 @@ class ReferenceRole(SphinxRole): the role. The parsed result; link title and target will be stored to ``self.title`` and ``self.target``. """ - has_explicit_title = None #: A boolean indicates the role has explicit title or not. - disabled = False #: A boolean indicates the reference is disabled. - title = None #: The link title for the interpreted text. - target = None #: The link target for the interpreted text. + has_explicit_title: bool #: A boolean indicates the role has explicit title or not. + disabled: bool #: A boolean indicates the reference is disabled. + title: str #: The link title for the interpreted text. + target: str #: The link target for the interpreted text. # \x00 means the "<" was backslash-escaped explicit_title_re = re.compile(r'^(.+?)\s*(?<!\x00)<(.*?)>$', re.DOTALL) @@ -490,7 +491,7 @@ class SphinxTranslator(nodes.NodeVisitor): # cache a vanilla instance of nodes.document # Used in new_document() function -__document_cache__ = None # type: nodes.document +__document_cache__: nodes.document = None def new_document(source_path: str, settings: Any = None) -> nodes.document: diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py index 466c28135..37c038855 100644 --- a/sphinx/util/fileutil.py +++ b/sphinx/util/fileutil.py @@ -10,15 +10,14 @@ import os import posixpath -from typing import Callable, Dict +from typing import TYPE_CHECKING, Callable, Dict from docutils.utils import relative_path from sphinx.util.osutil import copyfile, ensuredir from sphinx.util.typing import PathMatcher -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.util.template import BaseRenderer diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 8341dfffe..1e469d135 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -7,34 +7,33 @@ :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -import gettext + import os import re -import warnings -from collections import namedtuple from datetime import datetime, timezone from os import path -from typing import Callable, Generator, List, Set, Tuple, Union +from typing import TYPE_CHECKING, Callable, Generator, List, NamedTuple, Optional, Tuple, Union import babel.dates from babel.messages.mofile import write_mo from babel.messages.pofile import read_po -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.errors import SphinxError from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.matching import Matcher from sphinx.util.osutil import SEP, canon_path, relpath -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.environment import BuildEnvironment logger = logging.getLogger(__name__) -LocaleFileInfoBase = namedtuple('LocaleFileInfoBase', 'base_dir,domain,charset') + +class LocaleFileInfoBase(NamedTuple): + base_dir: str + domain: str + charset: str class CatalogInfo(LocaleFileInfoBase): @@ -117,90 +116,16 @@ class CatalogRepository: yield CatalogInfo(basedir, domain, self.encoding) -def find_catalog(docname: str, compaction: bool) -> str: - warnings.warn('find_catalog() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - if compaction: - ret = docname.split(SEP, 1)[0] - else: - ret = docname - - return ret - - -def docname_to_domain(docname: str, compation: Union[bool, str]) -> str: +def docname_to_domain(docname: str, compaction: Union[bool, str]) -> str: """Convert docname to domain for catalogs.""" - if isinstance(compation, str): - return compation - if compation: + if isinstance(compaction, str): + return compaction + if compaction: return docname.split(SEP, 1)[0] else: return docname -def find_catalog_files(docname: str, srcdir: str, locale_dirs: List[str], - lang: str, compaction: bool) -> List[str]: - warnings.warn('find_catalog_files() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - if not(lang and locale_dirs): - return [] - - domain = find_catalog(docname, compaction) - files = [gettext.find(domain, path.join(srcdir, dir_), [lang]) - for dir_ in locale_dirs] - files = [relpath(f, srcdir) for f in files if f] - return files - - -def find_catalog_source_files(locale_dirs: List[str], locale: str, domains: List[str] = None, - charset: str = 'utf-8', force_all: bool = False, - excluded: Matcher = Matcher([])) -> Set[CatalogInfo]: - """ - :param list locale_dirs: - list of path as `['locale_dir1', 'locale_dir2', ...]` to find - translation catalogs. Each path contains a structure such as - `<locale>/LC_MESSAGES/domain.po`. - :param str locale: a language as `'en'` - :param list domains: list of domain names to get. If empty list or None - is specified, get all domain names. default is None. - :param boolean force_all: - Set True if you want to get all catalogs rather than updated catalogs. - default is False. - :return: [CatalogInfo(), ...] - """ - warnings.warn('find_catalog_source_files() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - catalogs = set() # type: Set[CatalogInfo] - - if not locale: - return catalogs # locale is not specified - - for locale_dir in locale_dirs: - if not locale_dir: - continue # skip system locale directory - - base_dir = path.join(locale_dir, locale, 'LC_MESSAGES') - - if not path.exists(base_dir): - continue # locale path is not found - - for dirpath, dirnames, filenames in os.walk(base_dir, followlinks=True): - filenames = [f for f in filenames if f.endswith('.po')] - for filename in filenames: - if excluded(path.join(relpath(dirpath, base_dir), filename)): - continue - base = path.splitext(filename)[0] - domain = relpath(path.join(dirpath, base), base_dir).replace(path.sep, SEP) - if domains and domain not in domains: - continue - cat = CatalogInfo(base_dir, domain, charset) - if force_all or cat.is_outdated(): - catalogs.add(cat) - - return catalogs - - # date_format mappings: ustrftime() to bable.dates.format_datetime() date_format_mappings = { '%a': 'EEE', # Weekday as locale’s abbreviated name. @@ -245,7 +170,7 @@ date_format_mappings = { date_format_re = re.compile('(%s)' % '|'.join(date_format_mappings)) -def babel_format_date(date: datetime, format: str, locale: str, +def babel_format_date(date: datetime, format: str, locale: Optional[str], formatter: Callable = babel.dates.format_date) -> str: if locale is None: locale = 'en' @@ -266,7 +191,7 @@ def babel_format_date(date: datetime, format: str, locale: str, return format -def format_date(format: str, date: datetime = None, language: str = None) -> str: +def format_date(format: str, date: datetime = None, language: Optional[str] = None) -> str: if date is None: # If time is not specified, try to use $SOURCE_DATE_EPOCH variable # See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal diff --git a/sphinx/util/images.py b/sphinx/util/images.py index 81a321818..a2a31f55b 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -12,7 +12,7 @@ import base64 import imghdr from collections import OrderedDict from os import path -from typing import IO, NamedTuple, Optional, Tuple +from typing import IO, BinaryIO, NamedTuple, Optional, Tuple import imagesize @@ -31,9 +31,11 @@ mime_suffixes = OrderedDict([ ('.ai', 'application/illustrator'), ]) -DataURI = NamedTuple('DataURI', [('mimetype', str), - ('charset', str), - ('data', bytes)]) + +class DataURI(NamedTuple): + mimetype: str + charset: str + data: bytes def get_image_size(filename: str) -> Optional[Tuple[int, int]]: @@ -101,7 +103,7 @@ def parse_data_uri(uri: str) -> Optional[DataURI]: return DataURI(mimetype, charset, image_data) -def test_svg(h: bytes, f: IO) -> Optional[str]: +def test_svg(h: bytes, f: Optional[BinaryIO]) -> Optional[str]: """An additional imghdr library helper; test the header is SVG's or not.""" try: if '<svg' in h.decode().lower(): diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 5477e64f7..96f04a3d3 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -20,10 +20,10 @@ import warnings from functools import partial, partialmethod from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA from io import StringIO -from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, cast +from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Tuple, Type, cast -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning -from sphinx.pycode.ast import ast # for py35-37 +from sphinx.deprecation import RemovedInSphinx50Warning +from sphinx.pycode.ast import ast # for py36-37 from sphinx.pycode.ast import unparse as ast_unparse from sphinx.util import logging from sphinx.util.typing import ForwardRef @@ -166,7 +166,16 @@ def getannotations(obj: Any) -> Mapping[str, Any]: return {} -def getmro(obj: Any) -> Tuple["Type", ...]: +def getglobals(obj: Any) -> Mapping[str, Any]: + """Get __globals__ from given *obj* safely.""" + __globals__ = safe_getattr(obj, '__globals__', None) + if isinstance(__globals__, Mapping): + return __globals__ + else: + return {} + + +def getmro(obj: Any) -> Tuple[Type, ...]: """Get __mro__ from given *obj* safely.""" __mro__ = safe_getattr(obj, '__mro__', None) if isinstance(__mro__, tuple): @@ -331,7 +340,7 @@ def is_singledispatch_method(obj: Any) -> bool: try: from functools import singledispatchmethod # type: ignore return isinstance(obj, singledispatchmethod) - except ImportError: # py35-37 + except ImportError: # py36-37 return False @@ -409,23 +418,6 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any: raise AttributeError(name) from exc -def safe_getmembers(object: Any, predicate: Callable[[str], bool] = None, - attr_getter: Callable = safe_getattr) -> List[Tuple[str, Any]]: - """A version of inspect.getmembers() that uses safe_getattr().""" - warnings.warn('safe_getmembers() is deprecated', RemovedInSphinx40Warning, stacklevel=2) - - results = [] # type: List[Tuple[str, Any]] - for key in dir(object): - try: - value = attr_getter(object, key, None) - except AttributeError: - continue - if not predicate or predicate(value): - results.append((key, value)) - results.sort() - return results - - def object_description(object: Any) -> str: """A repr() implementation that returns text safe to use in reST context.""" if isinstance(object, dict): @@ -501,9 +493,9 @@ class DefaultValue: def _should_unwrap(subject: Callable) -> bool: """Check the function should be unwrapped on getting signature.""" - if (safe_getattr(subject, '__globals__', None) and - subject.__globals__.get('__name__') == 'contextlib' and # type: ignore - subject.__globals__.get('__file__') == contextlib.__file__): # type: ignore + __globals__ = getglobals(subject) + if (__globals__.get('__name__') == 'contextlib' and + __globals__.get('__file__') == contextlib.__file__): # contextmanger should be unwrapped return True @@ -741,154 +733,6 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> inspect.Signatu return inspect.Signature(params, return_annotation=return_annotation) -class Signature: - """The Signature object represents the call signature of a callable object and - its return annotation. - """ - - empty = inspect.Signature.empty - - def __init__(self, subject: Callable, bound_method: bool = False, - has_retval: bool = True) -> None: - warnings.warn('sphinx.util.inspect.Signature() is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - - # check subject is not a built-in class (ex. int, str) - if (isinstance(subject, type) and - is_builtin_class_method(subject, "__new__") and - is_builtin_class_method(subject, "__init__")): - raise TypeError("can't compute signature for built-in type {}".format(subject)) - - self.subject = subject - self.has_retval = has_retval - self.partialmethod_with_noargs = False - - try: - self.signature = inspect.signature(subject) # type: Optional[inspect.Signature] - except IndexError: - # Until python 3.6.4, cpython has been crashed on inspection for - # partialmethods not having any arguments. - # https://bugs.python.org/issue33009 - if hasattr(subject, '_partialmethod'): - self.signature = None - self.partialmethod_with_noargs = True - else: - raise - - try: - self.annotations = typing.get_type_hints(subject) - except Exception: - # get_type_hints() does not support some kind of objects like partial, - # ForwardRef and so on. For them, it raises an exception. In that case, - # we try to build annotations from argspec. - self.annotations = {} - - if bound_method: - # client gives a hint that the subject is a bound method - - if inspect.ismethod(subject): - # inspect.signature already considers the subject is bound method. - # So it is not need to skip first argument. - self.skip_first_argument = False - else: - self.skip_first_argument = True - else: - # inspect.signature recognizes type of method properly without any hints - self.skip_first_argument = False - - @property - def parameters(self) -> Mapping: - if self.partialmethod_with_noargs: - return {} - else: - return self.signature.parameters - - @property - def return_annotation(self) -> Any: - if self.signature: - if self.has_retval: - return self.signature.return_annotation - else: - return Parameter.empty - else: - return None - - def format_args(self, show_annotation: bool = True) -> str: - def get_annotation(param: Parameter) -> Any: - if isinstance(param.annotation, str) and param.name in self.annotations: - return self.annotations[param.name] - else: - return param.annotation - - args = [] - last_kind = None - for i, param in enumerate(self.parameters.values()): - # skip first argument if subject is bound method - if self.skip_first_argument and i == 0: - continue - - arg = StringIO() - - # insert '*' between POSITIONAL args and KEYWORD_ONLY args:: - # func(a, b, *, c, d): - if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD, - param.POSITIONAL_ONLY, - None): - args.append('*') - - if param.kind in (param.POSITIONAL_ONLY, - param.POSITIONAL_OR_KEYWORD, - param.KEYWORD_ONLY): - arg.write(param.name) - if show_annotation and param.annotation is not param.empty: - arg.write(': ') - arg.write(stringify_annotation(get_annotation(param))) - if param.default is not param.empty: - if param.annotation is param.empty or show_annotation is False: - arg.write('=') - arg.write(object_description(param.default)) - else: - arg.write(' = ') - arg.write(object_description(param.default)) - elif param.kind == param.VAR_POSITIONAL: - arg.write('*') - arg.write(param.name) - if show_annotation and param.annotation is not param.empty: - arg.write(': ') - arg.write(stringify_annotation(get_annotation(param))) - elif param.kind == param.VAR_KEYWORD: - arg.write('**') - arg.write(param.name) - if show_annotation and param.annotation is not param.empty: - arg.write(': ') - arg.write(stringify_annotation(get_annotation(param))) - - args.append(arg.getvalue()) - last_kind = param.kind - - if self.return_annotation is Parameter.empty or show_annotation is False: - return '(%s)' % ', '.join(args) - else: - if 'return' in self.annotations: - annotation = stringify_annotation(self.annotations['return']) - else: - annotation = stringify_annotation(self.return_annotation) - - return '(%s) -> %s' % (', '.join(args), annotation) - - def format_annotation(self, annotation: Any) -> str: - """Return formatted representation of a type annotation.""" - return stringify_annotation(annotation) - - def format_annotation_new(self, annotation: Any) -> str: - """format_annotation() for py37+""" - return stringify_annotation(annotation) - - def format_annotation_old(self, annotation: Any) -> str: - """format_annotation() for py36 or below""" - return stringify_annotation(annotation) - - def getdoc(obj: Any, attrgetter: Callable = safe_getattr, allow_inherited: bool = False, cls: Any = None, name: str = None) -> str: """Get the docstring for the object. @@ -905,7 +749,7 @@ def getdoc(obj: Any, attrgetter: Callable = safe_getattr, elif doc is None and allow_inherited: doc = inspect.getdoc(obj) - if doc is None and cls: + if doc is None and cls and name: # inspect.getdoc() does not support some kind of inherited and decorated methods. # This tries to obtain the docstring from super classes. for basecls in getattr(cls, '__mro__', []): diff --git a/sphinx/util/inventory.py b/sphinx/util/inventory.py index b1af1bfb9..f21c27323 100644 --- a/sphinx/util/inventory.py +++ b/sphinx/util/inventory.py @@ -10,7 +10,7 @@ import os import re import zlib -from typing import IO, Callable, Iterator +from typing import IO, TYPE_CHECKING, Callable, Iterator from sphinx.util import logging from sphinx.util.typing import Inventory @@ -18,8 +18,7 @@ from sphinx.util.typing import Inventory BUFSIZE = 16 * 1024 logger = logging.getLogger(__name__) -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.builders import Builder from sphinx.environment import BuildEnvironment @@ -94,7 +93,7 @@ class InventoryFile: @classmethod def load_v1(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory: - invdata = {} # type: Inventory + invdata: Inventory = {} projname = stream.readline().rstrip()[11:] version = stream.readline().rstrip()[11:] for line in stream.readlines(): @@ -112,7 +111,7 @@ class InventoryFile: @classmethod def load_v2(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory: - invdata = {} # type: Inventory + invdata: Inventory = {} projname = stream.readline().rstrip()[11:] version = stream.readline().rstrip()[11:] line = stream.readline() diff --git a/sphinx/util/jsdump.py b/sphinx/util/jsdump.py index 114fd7075..6d534cb3a 100644 --- a/sphinx/util/jsdump.py +++ b/sphinx/util/jsdump.py @@ -109,8 +109,8 @@ def loads(x: str) -> Any: nothing = object() i = 0 n = len(x) - stack = [] # type: List[Union[List, Dict]] - obj = nothing # type: Any + stack: List[Union[List, Dict]] = [] + obj: Any = nothing key = False keys = [] while i < n: @@ -160,7 +160,7 @@ def loads(x: str) -> Any: raise ValueError("multiple values") key = False else: - y = None # type: Any + y: Any = None m = _str_re.match(x, i) if m: y = decode_string(m.group()[1:-1]) diff --git a/sphinx/util/jsonimpl.py b/sphinx/util/jsonimpl.py deleted file mode 100644 index b038fd4db..000000000 --- a/sphinx/util/jsonimpl.py +++ /dev/null @@ -1,45 +0,0 @@ -""" - sphinx.util.jsonimpl - ~~~~~~~~~~~~~~~~~~~~ - - JSON serializer implementation wrapper. - - :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import json -import warnings -from collections import UserString -from typing import IO, Any - -from sphinx.deprecation import RemovedInSphinx40Warning - -warnings.warn('sphinx.util.jsonimpl is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - - -class SphinxJSONEncoder(json.JSONEncoder): - """JSONEncoder subclass that forces translation proxies.""" - def default(self, obj: Any) -> str: - if isinstance(obj, UserString): - return str(obj) - return super().default(obj) - - -def dump(obj: Any, fp: IO, *args: Any, **kwargs: Any) -> None: - kwargs['cls'] = SphinxJSONEncoder - json.dump(obj, fp, *args, **kwargs) - - -def dumps(obj: Any, *args: Any, **kwargs: Any) -> str: - kwargs['cls'] = SphinxJSONEncoder - return json.dumps(obj, *args, **kwargs) - - -def load(*args: Any, **kwargs: Any) -> Any: - return json.load(*args, **kwargs) - - -def loads(*args: Any, **kwargs: Any) -> Any: - return json.loads(*args, **kwargs) diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index 09780723a..64b7d8fb4 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -12,7 +12,7 @@ import logging import logging.handlers from collections import defaultdict from contextlib import contextmanager -from typing import IO, Any, Dict, Generator, List, Tuple, Union +from typing import IO, TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, Type, Union from docutils import nodes from docutils.nodes import Node @@ -21,17 +21,14 @@ from docutils.utils import get_source_line from sphinx.errors import SphinxWarning from sphinx.util.console import colorize -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.application import Sphinx NAMESPACE = 'sphinx' VERBOSE = 15 -LEVEL_NAMES = defaultdict(lambda: logging.WARNING) # type: Dict[str, int] +LEVEL_NAMES: Dict[str, int] = defaultdict(lambda: logging.WARNING) LEVEL_NAMES.update({ 'CRITICAL': logging.CRITICAL, 'SEVERE': logging.CRITICAL, @@ -42,7 +39,7 @@ LEVEL_NAMES.update({ 'DEBUG': logging.DEBUG, }) -VERBOSITY_MAP = defaultdict(lambda: 0) # type: Dict[int, int] +VERBOSITY_MAP: Dict[int, int] = defaultdict(lambda: 0) VERBOSITY_MAP.update({ 0: logging.INFO, 1: VERBOSE, @@ -94,7 +91,7 @@ def convert_serializable(records: List[logging.LogRecord]) -> None: class SphinxLogRecord(logging.LogRecord): """Log record class supporting location""" prefix = '' - location = None # type: Any + location: Any = None def getMessage(self) -> str: message = super().getMessage() @@ -166,6 +163,8 @@ class NewLineStreamHandler(logging.StreamHandler): class MemoryHandler(logging.handlers.BufferingHandler): """Handler buffering all logs.""" + buffer: List[logging.LogRecord] + def __init__(self) -> None: super().__init__(-1) @@ -177,7 +176,7 @@ class MemoryHandler(logging.handlers.BufferingHandler): try: for record in self.buffer: logger.handle(record) - self.buffer = [] # type: List[logging.LogRecord] + self.buffer = [] finally: self.release() @@ -331,7 +330,7 @@ def prefixed_warnings(prefix: str) -> Generator[None, None, None]: class LogCollector: def __init__(self) -> None: - self.logs = [] # type: List[logging.LogRecord] + self.logs: List[logging.LogRecord] = [] @contextmanager def collect(self) -> Generator[None, None, None]: @@ -356,6 +355,8 @@ def is_suppressed_warning(type: str, subtype: str, suppress_warnings: List[str]) if type is None: return False + subtarget: Optional[str] + for warning_type in suppress_warnings: if '.' in warning_type: target, subtarget = warning_type.split('.', 1) @@ -450,7 +451,7 @@ class OnceFilter(logging.Filter): def __init__(self, name: str = '') -> None: super().__init__(name) - self.messages = {} # type: Dict[str, List] + self.messages: Dict[str, List] = {} def filter(self, record: logging.LogRecord) -> bool: once = getattr(record, 'once', '') @@ -471,7 +472,7 @@ class SphinxLogRecordTranslator(logging.Filter): * Make a instance of SphinxLogRecord * docname to path if location given """ - LogRecordClass = None # type: Type[logging.LogRecord] + LogRecordClass: Type[logging.LogRecord] = None def __init__(self, app: "Sphinx") -> None: self.app = app @@ -509,7 +510,7 @@ class WarningLogRecordTranslator(SphinxLogRecordTranslator): LogRecordClass = SphinxWarningLogRecord -def get_node_location(node: Node) -> str: +def get_node_location(node: Node) -> Optional[str]: (source, line) = get_source_line(node) if source and line: return "%s:%s" % (source, line) diff --git a/sphinx/util/matching.py b/sphinx/util/matching.py index d33ae0333..f32d682f1 100644 --- a/sphinx/util/matching.py +++ b/sphinx/util/matching.py @@ -9,7 +9,7 @@ """ import re -from typing import Callable, Dict, Iterable, List, Match, Pattern +from typing import Callable, Dict, Iterable, List, Match, Optional, Pattern from sphinx.util.osutil import canon_path @@ -21,7 +21,7 @@ def _translate_pattern(pat: str) -> str: match slashes. """ i, n = 0, len(pat) - res = '' # type: str + res = '' while i < n: c = pat[i] i += 1 @@ -60,7 +60,7 @@ def _translate_pattern(pat: str) -> str: return res + '$' -def compile_matchers(patterns: List[str]) -> List[Callable[[str], Match[str]]]: +def compile_matchers(patterns: List[str]) -> List[Callable[[str], Optional[Match[str]]]]: return [re.compile(_translate_pattern(pat)).match for pat in patterns] @@ -86,10 +86,10 @@ class Matcher: DOTFILES = Matcher(['**/.*']) -_pat_cache = {} # type: Dict[str, Pattern] +_pat_cache: Dict[str, Pattern] = {} -def patmatch(name: str, pat: str) -> Match[str]: +def patmatch(name: str, pat: str) -> Optional[Match[str]]: """Return if name matches pat. Adapted from fnmatch module.""" if pat not in _pat_cache: _pat_cache[pat] = re.compile(_translate_pattern(pat)) diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index d5e43e716..44eb5d303 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -10,8 +10,7 @@ import re import unicodedata -import warnings -from typing import Any, Callable, Iterable, List, Set, Tuple, cast +from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Set, Tuple, Type, Union, cast from docutils import nodes from docutils.nodes import Element, Node @@ -20,14 +19,10 @@ from docutils.parsers.rst.states import Inliner from docutils.statemachine import StringList from sphinx import addnodes -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import __ from sphinx.util import logging -if False: - # For type annotation - from typing import Type # for python3.5.1 - +if TYPE_CHECKING: from sphinx.builders import Builder from sphinx.domain import IndexEntry from sphinx.environment import BuildEnvironment @@ -63,7 +58,7 @@ class NodeMatcher: # => [<reference ...>, <reference ...>, ...] """ - def __init__(self, *node_classes: "Type[Node]", **attrs: Any) -> None: + def __init__(self, *node_classes: Type[Node], **attrs: Any) -> None: self.classes = node_classes self.attrs = attrs @@ -201,6 +196,10 @@ def is_translatable(node: Node) -> bool: if isinstance(node, addnodes.translatable): return True + # image node marked as translatable or having alt text + if isinstance(node, nodes.image) and (node.get('translatable') or node.get('alt')): + return True + if isinstance(node, nodes.Inline) and 'translatable' not in node: # type: ignore # inline node must not be translated if 'translatable' is not set return False @@ -228,9 +227,6 @@ def is_translatable(node: Node) -> bool: return False return True - if isinstance(node, nodes.image) and node.get('translatable'): - return True - if isinstance(node, addnodes.meta): return True if is_pending_meta(node): @@ -255,7 +251,7 @@ META_TYPE_NODES = ( def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]: """Extract translatable messages from a document tree.""" - for node in doctree.traverse(is_translatable): # type: nodes.Element + for node in doctree.traverse(is_translatable): # type: Element if isinstance(node, addnodes.translatable): for msg in node.extract_original_messages(): yield node, msg @@ -264,10 +260,13 @@ def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]: msg = node.rawsource if not msg: msg = node.astext() - elif isinstance(node, IMAGE_TYPE_NODES): - msg = '.. image:: %s' % node['uri'] + elif isinstance(node, nodes.image): if node.get('alt'): - msg += '\n :alt: %s' % node['alt'] + yield node, node['alt'] + if node.get('translatable'): + msg = '.. image:: %s' % node['uri'] + else: + msg = None elif isinstance(node, META_TYPE_NODES): msg = node.rawcontent elif isinstance(node, nodes.pending) and is_pending_meta(node): @@ -280,12 +279,6 @@ def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]: yield node, msg -def find_source_node(node: Element) -> str: - warnings.warn('find_source_node() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - return get_node_source(node) - - def get_node_source(node: Element) -> str: for pnode in traverse_parent(node): if pnode.source: @@ -370,7 +363,7 @@ indextypes = [ def process_index_entry(entry: str, targetid: str) -> List[Tuple[str, str, str, str, str]]: from sphinx.domains.python import pairindextypes - indexentries = [] # type: List[Tuple[str, str, str, str, str]] + indexentries: List[Tuple[str, str, str, str, str]] = [] entry = entry.strip() oentry = entry main = '' @@ -538,8 +531,18 @@ def make_id(env: "BuildEnvironment", document: nodes.document, return node_id +def find_pending_xref_condition(node: addnodes.pending_xref, condition: str) -> Element: + """Pick matched pending_xref_condition node up from the pending_xref.""" + for subnode in node: + if (isinstance(subnode, addnodes.pending_xref_condition) and + subnode.get('condition') == condition): + return subnode + else: + return None + + def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: str, - child: Node, title: str = None) -> nodes.reference: + child: Union[Node, List[Node]], title: str = None) -> nodes.reference: """Shortcut to create a reference node.""" node = nodes.reference('', '', internal=True) if fromdocname == todocname and targetid: @@ -552,7 +555,7 @@ def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: node['refuri'] = builder.get_relative_uri(fromdocname, todocname) if title: node['reftitle'] = title - node.append(child) + node += child return node @@ -613,10 +616,12 @@ def process_only_nodes(document: Node, tags: "Tags") -> None: node.replace_self(nodes.comment()) -# monkey-patch Element.copy to copy the rawsource and line -# for docutils-0.14 or older versions. - def _new_copy(self: Element) -> Element: + """monkey-patch Element.copy to copy the rawsource and line + for docutils-0.16 or older versions. + + refs: https://sourceforge.net/p/docutils/patches/165/ + """ newnode = self.__class__(self.rawsource, **self.attributes) if isinstance(self, nodes.Element): newnode.source = self.source diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 53bffd929..8e3d7afda 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -9,7 +9,6 @@ """ import contextlib -import errno import filecmp import os import re @@ -18,9 +17,9 @@ import sys import warnings from io import StringIO from os import path -from typing import Any, Generator, Iterator, List, Optional, Tuple +from typing import Any, Generator, Iterator, List, Optional, Type -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning try: # for ALT Linux (#6712) @@ -28,15 +27,6 @@ try: except ImportError: Path = None # type: ignore -if False: - # For type annotation - from typing import Type # for python3.5.1 - -# Errnos that we need. -EEXIST = getattr(errno, 'EEXIST', 0) # RemovedInSphinx40Warning -ENOENT = getattr(errno, 'ENOENT', 0) # RemovedInSphinx40Warning -EPIPE = getattr(errno, 'EPIPE', 0) # RemovedInSphinx40Warning -EINVAL = getattr(errno, 'EINVAL', 0) # RemovedInSphinx40Warning # SEP separates path elements in the canonical file names # @@ -83,13 +73,6 @@ def ensuredir(path: str) -> None: os.makedirs(path, exist_ok=True) -def walk(top: str, topdown: bool = True, followlinks: bool = False) -> Iterator[Tuple[str, List[str], List[str]]]: # NOQA - warnings.warn('sphinx.util.osutil.walk() is deprecated for removal. ' - 'Please use os.walk() instead.', - RemovedInSphinx40Warning, stacklevel=2) - return os.walk(top, topdown=topdown, followlinks=followlinks) - - def mtimes_of_files(dirnames: List[str], suffix: str) -> Iterator[float]: for dirname in dirnames: for root, dirs, files in os.walk(dirname): @@ -178,13 +161,6 @@ def abspath(pathdir: str) -> str: return pathdir -def getcwd() -> str: - warnings.warn('sphinx.util.osutil.getcwd() is deprecated. ' - 'Please use os.getcwd() instead.', - RemovedInSphinx40Warning, stacklevel=2) - return os.getcwd() - - @contextlib.contextmanager def cd(target_dir: str) -> Generator[None, None, None]: cwd = os.getcwd() @@ -209,7 +185,7 @@ class FileAvoidWrite: """ def __init__(self, path: str) -> None: self._path = path - self._io = None # type: Optional[StringIO] + self._io: Optional[StringIO] = None def write(self, data: str) -> None: if not self._io: @@ -238,7 +214,7 @@ class FileAvoidWrite: def __enter__(self) -> "FileAvoidWrite": return self - def __exit__(self, exc_type: "Type[Exception]", exc_value: Exception, traceback: Any) -> bool: # NOQA + def __exit__(self, exc_type: Type[Exception], exc_value: Exception, traceback: Any) -> bool: # NOQA self.close() return True diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py index ed73ee4d6..7ad7f81e7 100644 --- a/sphinx/util/parallel.py +++ b/sphinx/util/parallel.py @@ -60,15 +60,15 @@ class ParallelTasks: def __init__(self, nproc: int) -> None: self.nproc = nproc # (optional) function performed by each task on the result of main task - self._result_funcs = {} # type: Dict[int, Callable] + self._result_funcs: Dict[int, Callable] = {} # task arguments - self._args = {} # type: Dict[int, List[Any]] + self._args: Dict[int, List[Any]] = {} # list of subprocesses (both started and waiting) - self._procs = {} # type: Dict[int, multiprocessing.Process] + self._procs: Dict[int, multiprocessing.Process] = {} # list of receiving pipe connections of running subprocesses - self._precvs = {} # type: Dict[int, Any] + self._precvs: Dict[int, Any] = {} # list of receiving pipe connections of waiting subprocesses - self._precvsWaiting = {} # type: Dict[int, Any] + self._precvsWaiting: Dict[int, Any] = {} # number of working subprocesses self._pworking = 0 # task number of each subprocess diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 87c38f72e..bb0c0b685 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -8,28 +8,21 @@ :license: BSD, see LICENSE for details. """ -import html -import io -import sys -import textwrap import warnings from typing import Any, Callable -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias -from sphinx.locale import __ -from sphinx.util import logging -from sphinx.util.console import terminal_safe -from sphinx.util.typing import NoneType - -logger = logging.getLogger(__name__) - +from sphinx.deprecation import RemovedInSphinx60Warning # ------------------------------------------------------------------------------ # Python 2/3 compatibility + # convert_with_2to3(): # support for running 2to3 over config files def convert_with_2to3(filepath: str) -> str: + warnings.warn('convert_with_2to3() is deprecated', + RemovedInSphinx60Warning, stacklevel=2) + try: from lib2to3.pgen2.parse import ParseError from lib2to3.refactor import RefactoringTool, get_fixers_from_package @@ -53,57 +46,14 @@ def convert_with_2to3(filepath: str) -> str: return str(tree) -class UnicodeMixin: - """Mixin class to handle defining the proper __str__/__unicode__ - methods in Python 2 or 3. - - .. deprecated:: 2.0 - """ - def __str__(self) -> str: - warnings.warn('UnicodeMixin is deprecated', - RemovedInSphinx40Warning, stacklevel=2) - return self.__unicode__() # type: ignore - - def execfile_(filepath: str, _globals: Any, open: Callable = open) -> None: + warnings.warn('execfile_() is deprecated', + RemovedInSphinx60Warning, stacklevel=2) from sphinx.util.osutil import fs_encoding with open(filepath, 'rb') as f: source = f.read() # compile to a code object, handle syntax errors filepath_enc = filepath.encode(fs_encoding) - try: - code = compile(source, filepath_enc, 'exec') - except SyntaxError: - # maybe the file uses 2.x syntax; try to refactor to - # 3.x syntax using 2to3 - source = convert_with_2to3(filepath) - code = compile(source, filepath_enc, 'exec') - # TODO: When support for evaluating Python 2 syntax is removed, - # deprecate convert_with_2to3(). - logger.warning(__('Support for evaluating Python 2 syntax is deprecated ' - 'and will be removed in Sphinx 4.0. ' - 'Convert %s to Python 3 syntax.'), - filepath) + code = compile(source, filepath_enc, 'exec') exec(code, _globals) - - -deprecated_alias('sphinx.util.pycompat', - { - 'NoneType': NoneType, - 'TextIOWrapper': io.TextIOWrapper, - 'htmlescape': html.escape, - 'indent': textwrap.indent, - 'terminal_safe': terminal_safe, - 'sys_encoding': sys.getdefaultencoding(), - 'u': '', - }, - RemovedInSphinx40Warning, - { - 'NoneType': 'sphinx.util.typing.NoneType', - 'TextIOWrapper': 'io.TextIOWrapper', - 'htmlescape': 'html.escape', - 'indent': 'textwrap.indent', - 'terminal_safe': 'sphinx.util.console.terminal_safe', - 'sys_encoding': 'sys.getdefaultencoding', - }) diff --git a/sphinx/util/rst.py b/sphinx/util/rst.py index 79ede3432..82b3f6bda 100644 --- a/sphinx/util/rst.py +++ b/sphinx/util/rst.py @@ -30,8 +30,7 @@ symbols_re = re.compile(r'([!-\-/:-@\[-`{-~])') # symbols without dot(0x2e) SECTIONING_CHARS = ['=', '-', '~'] # width of characters -WIDECHARS = defaultdict(lambda: "WF") # type: Dict[str, str] - # WF: Wide + Full-width +WIDECHARS: Dict[str, str] = defaultdict(lambda: "WF") # WF: Wide + Full-width WIDECHARS["ja"] = "WFA" # In Japanese, Ambiguous characters also have double width diff --git a/sphinx/util/smartypants.py b/sphinx/util/smartypants.py index ec6b2172c..ad13fcaad 100644 --- a/sphinx/util/smartypants.py +++ b/sphinx/util/smartypants.py @@ -26,12 +26,17 @@ """ import re +import warnings from typing import Generator, Iterable, Tuple from docutils.utils import smartquotes +from sphinx.deprecation import RemovedInSphinx60Warning from sphinx.util.docutils import __version_info__ as docutils_version +warnings.warn('sphinx.util.smartypants is deprecated.', + RemovedInSphinx60Warning) + langquotes = {'af': '“”‘’', 'af-x-altquot': '„”‚’', 'bg': '„“‚‘', # Bulgarian, https://bg.wikipedia.org/wiki/Кавички diff --git a/sphinx/util/tags.py b/sphinx/util/tags.py index c50231220..cf3d53400 100644 --- a/sphinx/util/tags.py +++ b/sphinx/util/tags.py @@ -22,7 +22,7 @@ class BooleanParser(Parser): """ def parse_compare(self) -> Node: - node = None # type: Node + node: Node token = self.stream.current if token.type == 'name': if token.value in ('true', 'false', 'True', 'False'): diff --git a/sphinx/util/texescape.py b/sphinx/util/texescape.py index b67dcfe82..417a963a7 100644 --- a/sphinx/util/texescape.py +++ b/sphinx/util/texescape.py @@ -11,8 +11,6 @@ import re from typing import Dict -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias - tex_replacements = [ # map TeX special chars ('$', r'\$'), @@ -100,20 +98,12 @@ unicode_tex_replacements = [ # %, {, }, \, #, and ~ are the only ones which must be replaced by _ character # It would be simpler to define it entirely here rather than in init(). # Unicode replacements are superfluous, as idescape() uses backslashreplace -tex_replace_map = {} # type: Dict[int, str] - -_tex_escape_map = {} # type: Dict[int, str] -_tex_escape_map_without_unicode = {} # type: Dict[int, str] -_tex_hlescape_map = {} # type: Dict[int, str] -_tex_hlescape_map_without_unicode = {} # type: Dict[int, str] - +tex_replace_map: Dict[int, str] = {} -deprecated_alias('sphinx.util.texescape', - { - 'tex_escape_map': _tex_escape_map, - 'tex_hl_escape_map_new': _tex_hlescape_map, - }, - RemovedInSphinx40Warning) +_tex_escape_map: Dict[int, str] = {} +_tex_escape_map_without_unicode: Dict[int, str] = {} +_tex_hlescape_map: Dict[int, str] = {} +_tex_hlescape_map_without_unicode: Dict[int, str] = {} def escape(s: str, latex_engine: str = None) -> str: diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index db754bb03..fcecb8bb1 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -11,18 +11,21 @@ import sys import typing from struct import Struct -from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, TypeVar, Union +from types import TracebackType +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar, Union from docutils import nodes from docutils.parsers.rst.states import Inliner +from sphinx.deprecation import RemovedInSphinx60Warning, deprecated_alias + if sys.version_info > (3, 7): from typing import ForwardRef else: from typing import _ForwardRef # type: ignore class ForwardRef: - """A pseudo ForwardRef class for py35 and py36.""" + """A pseudo ForwardRef class for py36.""" def __init__(self, arg: Any, is_argument: bool = True) -> None: self.arg = arg @@ -40,8 +43,12 @@ if False: from typing import Type # NOQA # for python3.5.1 -# An entry of Directive.option_spec -DirectiveOption = Callable[[str], Any] +# builtin classes that have incorrect __module__ +INVALID_BUILTIN_CLASSES = { + Struct: 'struct.Struct', # Before Python 3.9 + TracebackType: 'types.TracebackType', +} + # Text like nodes which are initialized with text and rawsource TextlikeNode = Union[nodes.Text, nodes.TextElement] @@ -56,6 +63,9 @@ PathMatcher = Callable[[str], bool] RoleFunction = Callable[[str, str, str, int, Inliner, Dict[str, Any], List[str]], Tuple[List[nodes.Node], List[nodes.system_message]]] +# A option spec for directive +OptionSpec = Dict[str, Callable[[Optional[str]], Any]] + # title getter functions for enumerable nodes (see sphinx.domains.std) TitleGetter = Callable[[nodes.Node], str] @@ -83,9 +93,6 @@ def get_type_hints(obj: Any, globalns: Dict = None, localns: Dict = None) -> Dic except KeyError: # a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084) return {} - except AttributeError: - # AttributeError is raised on 3.5.2 (fixed by 3.5.3) - return {} def is_system_TypeVar(typ: Any) -> bool: @@ -94,7 +101,7 @@ def is_system_TypeVar(typ: Any) -> bool: return modname == 'typing' and isinstance(typ, TypeVar) -def restify(cls: Optional["Type"]) -> str: +def restify(cls: Optional[Type]) -> str: """Convert python class to a reST reference.""" from sphinx.util import inspect # lazy loading @@ -102,9 +109,8 @@ 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 cls in INVALID_BUILTIN_CLASSES: + return ':class:`%s`' % INVALID_BUILTIN_CLASSES[cls] elif inspect.isNewType(cls): return ':class:`%s`' % cls.__name__ elif types_Union and isinstance(cls, types_Union): @@ -122,7 +128,7 @@ def restify(cls: Optional["Type"]) -> str: return _restify_py36(cls) -def _restify_py37(cls: Optional["Type"]) -> str: +def _restify_py37(cls: Optional[Type]) -> str: """Convert python class to a reST reference.""" from sphinx.util import inspect # lazy loading @@ -177,7 +183,7 @@ def _restify_py37(cls: Optional["Type"]) -> str: return ':obj:`%s.%s`' % (cls.__module__, cls.__name__) -def _restify_py36(cls: Optional["Type"]) -> str: +def _restify_py36(cls: Optional[Type]) -> str: module = getattr(cls, '__module__', None) if module == 'typing': if getattr(cls, '_name', None): @@ -196,7 +202,7 @@ def _restify_py36(cls: Optional["Type"]) -> str: qualname = repr(cls) if (isinstance(cls, typing.TupleMeta) and # type: ignore - not hasattr(cls, '__tuple_params__')): # for Python 3.6 + not hasattr(cls, '__tuple_params__')): params = cls.__args__ if params: param_str = ', '.join(restify(p) for p in params) @@ -204,40 +210,22 @@ def _restify_py36(cls: Optional["Type"]) -> str: else: return ':class:`%s`' % qualname elif isinstance(cls, typing.GenericMeta): - params = None - if hasattr(cls, '__args__'): - # for Python 3.5.2+ - if cls.__args__ is None or len(cls.__args__) <= 2: # type: ignore # NOQA - params = cls.__args__ # type: ignore - elif cls.__origin__ == Generator: # type: ignore - params = cls.__args__ # type: ignore - else: # typing.Callable - args = ', '.join(restify(arg) for arg in cls.__args__[:-1]) # type: ignore - result = restify(cls.__args__[-1]) # type: ignore - return ':class:`%s`\\ [[%s], %s]' % (qualname, args, result) - elif hasattr(cls, '__parameters__'): - # for Python 3.5.0 and 3.5.1 - params = cls.__parameters__ # type: ignore + if cls.__args__ is None or len(cls.__args__) <= 2: # type: ignore # NOQA + params = cls.__args__ # type: ignore + elif cls.__origin__ == Generator: # type: ignore + params = cls.__args__ # type: ignore + else: # typing.Callable + args = ', '.join(restify(arg) for arg in cls.__args__[:-1]) # type: ignore + result = restify(cls.__args__[-1]) # type: ignore + return ':class:`%s`\\ [[%s], %s]' % (qualname, args, result) if params: param_str = ', '.join(restify(p) for p in params) return ':class:`%s`\\ [%s]' % (qualname, param_str) else: return ':class:`%s`' % qualname - elif (hasattr(typing, 'UnionMeta') and - isinstance(cls, typing.UnionMeta) and # type: ignore - hasattr(cls, '__union_params__')): # for Python 3.5 - params = cls.__union_params__ - if params is not None: - if len(params) == 2 and params[1] is NoneType: - return ':obj:`Optional`\\ [%s]' % restify(params[0]) - else: - param_str = ', '.join(restify(p) for p in params) - return ':obj:`%s`\\ [%s]' % (qualname, param_str) - else: - return ':obj:`%s`' % qualname elif (hasattr(cls, '__origin__') and - cls.__origin__ is typing.Union): # for Python 3.5.2+ + cls.__origin__ is typing.Union): params = cls.__args__ if params is not None: if len(params) > 1 and params[-1] is NoneType: @@ -251,31 +239,6 @@ def _restify_py36(cls: Optional["Type"]) -> str: return ':obj:`Union`\\ [%s]' % param_str else: return ':obj:`Union`' - elif (isinstance(cls, typing.CallableMeta) and # type: ignore - getattr(cls, '__args__', None) is not None and - hasattr(cls, '__result__')): # for Python 3.5 - # Skipped in the case of plain typing.Callable - args = cls.__args__ - if args is None: - return qualname - elif args is Ellipsis: - args_str = '...' - else: - formatted_args = (restify(a) for a in args) # type: ignore - args_str = '[%s]' % ', '.join(formatted_args) - - return ':class:`%s`\\ [%s, %s]' % (qualname, args_str, stringify(cls.__result__)) - elif (isinstance(cls, typing.TupleMeta) and # type: ignore - hasattr(cls, '__tuple_params__') and - hasattr(cls, '__tuple_use_ellipsis__')): # for Python 3.5 - params = cls.__tuple_params__ - if params is not None: - param_strings = [restify(p) for p in params] - if cls.__tuple_use_ellipsis__: - param_strings.append('...') - return ':class:`%s`\\ [%s]' % (qualname, ', '.join(param_strings)) - else: - return ':class:`%s`' % qualname elif hasattr(cls, '__qualname__'): if cls.__module__ == 'typing': return ':class:`%s`' % cls.__qualname__ @@ -309,7 +272,10 @@ def stringify(annotation: Any) -> str: else: return annotation elif isinstance(annotation, TypeVar): - return annotation.__name__ + if annotation.__module__ == 'typing': + return annotation.__name__ + else: + return '.'.join([annotation.__module__, annotation.__name__]) elif inspect.isNewType(annotation): # Could not get the module where it defiend return annotation.__name__ @@ -317,14 +283,13 @@ def stringify(annotation: Any) -> str: return repr(annotation) elif annotation is NoneType: return 'None' + elif annotation in INVALID_BUILTIN_CLASSES: + return INVALID_BUILTIN_CLASSES[annotation] elif (getattr(annotation, '__module__', None) == 'builtins' and hasattr(annotation, '__qualname__')): 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) @@ -393,7 +358,7 @@ def _stringify_py37(annotation: Any) -> str: def _stringify_py36(annotation: Any) -> str: - """stringify() for py35 and py36.""" + """stringify() for py36.""" module = getattr(annotation, '__module__', None) if module == 'typing': if getattr(annotation, '_name', None): @@ -421,35 +386,20 @@ def _stringify_py36(annotation: Any) -> str: return qualname elif isinstance(annotation, typing.GenericMeta): params = None - if hasattr(annotation, '__args__'): - # for Python 3.5.2+ - if annotation.__args__ is None or len(annotation.__args__) <= 2: # type: ignore # NOQA - params = annotation.__args__ # type: ignore - elif annotation.__origin__ == Generator: # type: ignore - params = annotation.__args__ # type: ignore - else: # typing.Callable - args = ', '.join(stringify(arg) for arg - in annotation.__args__[:-1]) # type: ignore - result = stringify(annotation.__args__[-1]) # type: ignore - return '%s[[%s], %s]' % (qualname, args, result) - elif hasattr(annotation, '__parameters__'): - # for Python 3.5.0 and 3.5.1 - params = annotation.__parameters__ # type: ignore + if annotation.__args__ is None or len(annotation.__args__) <= 2: # type: ignore # NOQA + params = annotation.__args__ # type: ignore + elif annotation.__origin__ == Generator: # type: ignore + params = annotation.__args__ # type: ignore + else: # typing.Callable + args = ', '.join(stringify(arg) for arg + in annotation.__args__[:-1]) # type: ignore + result = stringify(annotation.__args__[-1]) # type: ignore + return '%s[[%s], %s]' % (qualname, args, result) if params is not None: param_str = ', '.join(stringify(p) for p in params) return '%s[%s]' % (qualname, param_str) - elif (hasattr(typing, 'UnionMeta') and - isinstance(annotation, typing.UnionMeta) and # type: ignore - hasattr(annotation, '__union_params__')): # for Python 3.5 - params = annotation.__union_params__ - if params is not None: - if len(params) == 2 and params[1] is NoneType: - return 'Optional[%s]' % stringify(params[0]) - else: - param_str = ', '.join(stringify(p) for p in params) - return '%s[%s]' % (qualname, param_str) elif (hasattr(annotation, '__origin__') and - annotation.__origin__ is typing.Union): # for Python 3.5.2+ + annotation.__origin__ is typing.Union): params = annotation.__args__ if params is not None: if len(params) > 1 and params[-1] is NoneType: @@ -461,30 +411,12 @@ def _stringify_py36(annotation: Any) -> str: else: param_str = ', '.join(stringify(p) for p in params) return 'Union[%s]' % param_str - elif (isinstance(annotation, typing.CallableMeta) and # type: ignore - getattr(annotation, '__args__', None) is not None and - hasattr(annotation, '__result__')): # for Python 3.5 - # Skipped in the case of plain typing.Callable - args = annotation.__args__ - if args is None: - return qualname - elif args is Ellipsis: - args_str = '...' - else: - formatted_args = (stringify(a) for a in args) - args_str = '[%s]' % ', '.join(formatted_args) - return '%s[%s, %s]' % (qualname, - args_str, - stringify(annotation.__result__)) - elif (isinstance(annotation, typing.TupleMeta) and # type: ignore - hasattr(annotation, '__tuple_params__') and - hasattr(annotation, '__tuple_use_ellipsis__')): # for Python 3.5 - params = annotation.__tuple_params__ - if params is not None: - param_strings = [stringify(p) for p in params] - if annotation.__tuple_use_ellipsis__: - param_strings.append('...') - return '%s[%s]' % (qualname, - ', '.join(param_strings)) return qualname + + +deprecated_alias('sphinx.util.typing', + { + 'DirectiveOption': Callable[[str], Any], + }, + RemovedInSphinx60Warning) diff --git a/sphinx/versioning.py b/sphinx/versioning.py index 6e5a9eb26..6824efe17 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -12,15 +12,14 @@ import pickle from itertools import product, zip_longest from operator import itemgetter from os import path -from typing import Any, Dict, Iterator +from typing import TYPE_CHECKING, Any, Dict, Iterator from uuid import uuid4 from docutils.nodes import Node from sphinx.transforms import SphinxTransform -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.application import Sphinx try: diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index d3e7e03a4..a92927cab 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -13,7 +13,7 @@ import os import posixpath import re import warnings -from typing import Any, Iterable, Tuple, cast +from typing import TYPE_CHECKING, Iterable, Tuple, cast from docutils import nodes from docutils.nodes import Element, Node, Text @@ -22,14 +22,13 @@ from docutils.writers.html4css1 import Writer from sphinx import addnodes from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.locale import _, __, admonitionlabels from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator from sphinx.util.images import get_image_size -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.builders.html import StandaloneHTMLBuilder @@ -84,16 +83,9 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): Our custom HTML translator. """ - builder = None # type: StandaloneHTMLBuilder + builder: "StandaloneHTMLBuilder" = None - def __init__(self, *args: Any) -> None: - if isinstance(args[0], nodes.document) and isinstance(args[1], Builder): - document, builder = args - else: - warnings.warn('The order of arguments for HTMLTranslator has been changed. ' - 'Please give "document" as 1st and "builder" as 2nd.', - RemovedInSphinx40Warning, stacklevel=2) - builder, document = args + def __init__(self, document: nodes.document, builder: Builder) -> None: super().__init__(document, builder) self.highlighter = self.builder.highlighter @@ -379,7 +371,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): def depart_classifier(self, node: Element) -> None: self.body.append('</span>') - next_node = node.next_node(descend=False, siblings=True) # type: Node + next_node: Node = node.next_node(descend=False, siblings=True) if not isinstance(next_node, nodes.classifier): # close `<dt>` tag at the tail of classifiers self.body.append('</dt>') @@ -390,7 +382,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): # overwritten def depart_term(self, node: Element) -> None: - next_node = node.next_node(descend=False, siblings=True) # type: Node + next_node: Node = node.next_node(descend=False, siblings=True) if isinstance(next_node, nodes.classifier): # Leave the end tag to `self.depart_classifier()`, in case # there's a classifier. @@ -404,7 +396,12 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): # overwritten def visit_title(self, node: Element) -> None: - super().visit_title(node) + if isinstance(node.parent, addnodes.compact_paragraph) and node.parent.get('toctree'): + self.body.append(self.starttag(node, 'p', '', CLASS='caption')) + self.body.append('<span class="caption-text">') + self.context.append('</span></p>\n') + else: + super().visit_title(node) self.add_secnumber(node) self.add_fignumber(node.parent) if isinstance(node.parent, nodes.table): @@ -582,6 +579,13 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): self.body.append(self.context.pop()) # overwritten + def visit_figure(self, node: Element) -> None: + # set align=default if align not specified to give a default style + node.setdefault('align', 'default') + + return super().visit_figure(node) + + # overwritten def visit_image(self, node: Element) -> None: olduri = node['uri'] # rewrite the URI if the environment knows about it @@ -782,6 +786,10 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): def visit_table(self, node: Element) -> None: self._table_row_index = 0 + + # set align=default if align not specified to give a default style + node.setdefault('align', 'default') + return super().visit_table(node) def visit_row(self, node: Element) -> None: diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index 5666e4d02..fa7360c4e 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -12,7 +12,7 @@ import os import posixpath import re import warnings -from typing import Any, Iterable, Tuple, cast +from typing import TYPE_CHECKING, Iterable, Tuple, cast from docutils import nodes from docutils.nodes import Element, Node, Text @@ -20,14 +20,13 @@ from docutils.writers.html5_polyglot import HTMLTranslator as BaseTranslator from sphinx import addnodes from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning from sphinx.locale import _, __, admonitionlabels from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator from sphinx.util.images import get_image_size -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.builders.html import StandaloneHTMLBuilder @@ -55,16 +54,9 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): Our custom HTML translator. """ - builder = None # type: StandaloneHTMLBuilder + builder: "StandaloneHTMLBuilder" = None - def __init__(self, *args: Any) -> None: - if isinstance(args[0], nodes.document) and isinstance(args[1], Builder): - document, builder = args - else: - warnings.warn('The order of arguments for HTML5Translator has been changed. ' - 'Please give "document" as 1st and "builder" as 2nd.', - RemovedInSphinx40Warning, stacklevel=2) - builder, document = args + def __init__(self, document: nodes.document, builder: Builder) -> None: super().__init__(document, builder) self.highlighter = self.builder.highlighter @@ -330,7 +322,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): def depart_classifier(self, node: Element) -> None: self.body.append('</span>') - next_node = node.next_node(descend=False, siblings=True) # type: Node + next_node: Node = node.next_node(descend=False, siblings=True) if not isinstance(next_node, nodes.classifier): # close `<dt>` tag at the tail of classifiers self.body.append('</dt>') @@ -341,7 +333,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # overwritten def depart_term(self, node: Element) -> None: - next_node = node.next_node(descend=False, siblings=True) # type: Node + next_node: Node = node.next_node(descend=False, siblings=True) if isinstance(next_node, nodes.classifier): # Leave the end tag to `self.depart_classifier()`, in case # there's a classifier. @@ -355,7 +347,12 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # overwritten def visit_title(self, node: Element) -> None: - super().visit_title(node) + if isinstance(node.parent, addnodes.compact_paragraph) and node.parent.get('toctree'): + self.body.append(self.starttag(node, 'p', '', CLASS='caption')) + self.body.append('<span class="caption-text">') + self.context.append('</span></p>\n') + else: + super().visit_title(node) self.add_secnumber(node) self.add_fignumber(node.parent) if isinstance(node.parent, nodes.table): @@ -522,6 +519,13 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): self.body.append(self.context.pop()) # overwritten + def visit_figure(self, node: Element) -> None: + # set align=default if align not specified to give a default style + node.setdefault('align', 'default') + + return super().visit_figure(node) + + # overwritten def visit_image(self, node: Element) -> None: olduri = node['uri'] # rewrite the URI if the environment knows about it @@ -716,29 +720,16 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # overwritten to add even/odd classes - def generate_targets_for_table(self, node: Element) -> None: - """Generate hyperlink targets for tables. - - Original visit_table() generates hyperlink targets inside table tags - (<table>) if multiple IDs are assigned to listings. - That is invalid DOM structure. (This is a bug of docutils <= 0.13.1) - - This exports hyperlink targets before tables to make valid DOM structure. - """ - for id in node['ids'][1:]: - self.body.append('<span id="%s"></span>' % id) - node['ids'].remove(id) - def visit_table(self, node: Element) -> None: - self.generate_targets_for_table(node) - self._table_row_index = 0 atts = {} classes = [cls.strip(' \t\n') for cls in self.settings.table_style.split(',')] classes.insert(0, "docutils") # compat - if 'align' in node: - classes.append('align-%s' % node['align']) + + # set align-default if align not specified to give a default style + classes.append('align-%s' % node.get('align', 'default')) + if 'width' in node: atts['style'] = 'width: %s' % node['width'] tag = self.starttag(node, 'table', CLASS=' '.join(classes), **atts) @@ -794,3 +785,18 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): warnings.warn('HTMLTranslator.permalink_text is deprecated.', RemovedInSphinx50Warning, stacklevel=2) return self.config.html_permalinks_icon + + def generate_targets_for_table(self, node: Element) -> None: + """Generate hyperlink targets for tables. + + Original visit_table() generates hyperlink targets inside table tags + (<table>) if multiple IDs are assigned to listings. + That is invalid DOM structure. (This is a bug of docutils <= 0.13.1) + + This exports hyperlink targets before tables to make valid DOM structure. + """ + warnings.warn('generate_targets_for_table() is deprecated', + RemovedInSphinx60Warning, stacklevel=2) + for id in node['ids'][1:]: + self.body.append('<span id="%s"></span>' % id) + node['ids'].remove(id) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 247a7dfb5..edef5417f 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -15,14 +15,13 @@ import re import warnings from collections import defaultdict from os import path -from typing import Any, Dict, Iterable, Iterator, List, Set, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Set, Tuple, cast from docutils import nodes, writers from docutils.nodes import Element, Node, Text from sphinx import addnodes, highlighting -from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warning, - deprecated_alias) +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.domains import IndexEntry from sphinx.domains.std import StandardDomain from sphinx.errors import SphinxError @@ -39,8 +38,7 @@ except ImportError: # In Debain/Ubuntu, roman package is provided as roman, not as docutils.utils.roman from roman import toRoman # type: ignore -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.builders.latex import LaTeXBuilder from sphinx.builders.latex.theming import Theme @@ -81,14 +79,14 @@ class LaTeXWriter(writers.Writer): ('Document class', ['--docclass'], {'default': 'manual'}), ('Author', ['--author'], {'default': ''}), )) - settings_defaults = {} # type: Dict + settings_defaults: Dict = {} output = None def __init__(self, builder: "LaTeXBuilder") -> None: super().__init__() self.builder = builder - self.theme = None # type: Theme + self.theme: Theme = None def translate(self) -> None: try: @@ -108,28 +106,26 @@ class Table: """A table data""" def __init__(self, node: Element) -> None: - self.header = [] # type: List[str] - self.body = [] # type: List[str] - self.align = node.get('align') + self.header: List[str] = [] + self.body: List[str] = [] + self.align = node.get('align', 'default') + self.classes: List[str] = node.get('classes', []) self.colcount = 0 - self.colspec = None # type: str - self.colwidths = [] # type: List[int] + self.colspec: str = None + self.colwidths: List[int] = [] self.has_problematic = False self.has_oldproblematic = False self.has_verbatim = False - self.caption = None # type: List[str] - self.stubs = [] # type: List[int] + self.caption: List[str] = None + self.stubs: List[int] = [] # current position self.col = 0 self.row = 0 - # for internal use - self.classes = node.get('classes', []) # type: List[str] - self.cells = defaultdict(int) # type: Dict[Tuple[int, int], int] - # it maps table location to cell_id - # (cell = rectangular area) - self.cell_id = 0 # last assigned cell_id + # A mapping a table location to the cell_id (cell = rectangular area) + self.cells: Dict[Tuple[int, int], int] = defaultdict(int) + self.cell_id = 0 # last assigned cell_id def is_longtable(self) -> bool: """True if and only if table uses longtable environment.""" @@ -166,15 +162,15 @@ class Table: return self.colspec elif self.colwidths and 'colwidths-given' in self.classes: total = sum(self.colwidths) - colspecs = ['\\X{%d}{%d}' % (width, total) for width in self.colwidths] + colspecs = [r'\X{%d}{%d}' % (width, total) for width in self.colwidths] return '{|%s|}' % '|'.join(colspecs) + CR elif self.has_problematic: - return '{|*{%d}{\\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR + return r'{|*{%d}{\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR elif self.get_table_type() == 'tabulary': # sphinx.sty sets T to be J by default. return '{|' + ('T|' * self.colcount) + '}' + CR elif self.has_oldproblematic: - return '{|*{%d}{\\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR + return r'{|*{%d}{\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR else: return '{|' + ('l|' * self.colcount) + '}' + CR @@ -255,26 +251,26 @@ def rstdim_to_latexdim(width_str: str, scale: int = 100) -> str: if scale == 100: float(amount) # validate amount is float if unit in ('', "px"): - res = "%s\\sphinxpxdimen" % amount + res = r"%s\sphinxpxdimen" % amount elif unit == 'pt': res = '%sbp' % amount # convert to 'bp' elif unit == "%": - res = "%.3f\\linewidth" % (float(amount) / 100.0) + res = r"%.3f\linewidth" % (float(amount) / 100.0) else: amount_float = float(amount) * scale / 100.0 if unit in ('', "px"): - res = "%.5f\\sphinxpxdimen" % amount_float + res = r"%.5f\sphinxpxdimen" % amount_float elif unit == 'pt': res = '%.5fbp' % amount_float elif unit == "%": - res = "%.5f\\linewidth" % (amount_float / 100.0) + res = r"%.5f\linewidth" % (amount_float / 100.0) else: res = "%.5f%s" % (amount_float, unit) return res class LaTeXTranslator(SphinxTranslator): - builder = None # type: LaTeXBuilder + builder: "LaTeXBuilder" = None secnumdepth = 2 # legacy sphinxhowto.cls uses this, whereas article.cls # default is originally 3. For book/report, 2 is already LaTeX default. @@ -286,7 +282,7 @@ class LaTeXTranslator(SphinxTranslator): def __init__(self, document: nodes.document, builder: "LaTeXBuilder", theme: "Theme" = None) -> None: super().__init__(document, builder) - self.body = [] # type: List[str] + self.body: List[str] = [] self.theme = theme if theme is None: @@ -375,9 +371,9 @@ class LaTeXTranslator(SphinxTranslator): if (self.config.language not in {None, 'en', 'ja'} and 'fncychap' not in self.config.latex_elements): # use Sonny style if any language specified (except English) - self.elements['fncychap'] = ('\\usepackage[Sonny]{fncychap}' + CR + - '\\ChNameVar{\\Large\\normalfont\\sffamily}' + CR + - '\\ChTitleVar{\\Large\\normalfont\\sffamily}') + self.elements['fncychap'] = (r'\usepackage[Sonny]{fncychap}' + CR + + r'\ChNameVar{\Large\normalfont\sffamily}' + CR + + r'\ChTitleVar{\Large\normalfont\sffamily}') self.babel = self.builder.babel if self.config.language and not self.babel.is_supported_language(): @@ -402,19 +398,19 @@ class LaTeXTranslator(SphinxTranslator): logger.warning(__('too large :maxdepth:, ignored.')) tocdepth = len(LATEXSECTIONNAMES) - 2 - self.elements['tocdepth'] = '\\setcounter{tocdepth}{%d}' % tocdepth + self.elements['tocdepth'] = r'\setcounter{tocdepth}{%d}' % tocdepth minsecnumdepth = max(minsecnumdepth, tocdepth) if self.config.numfig and (self.config.numfig_secnum_depth > 0): minsecnumdepth = max(minsecnumdepth, self.numfig_secnum_depth - 1) if minsecnumdepth > self.secnumdepth: - self.elements['secnumdepth'] = '\\setcounter{secnumdepth}{%d}' %\ + self.elements['secnumdepth'] = r'\setcounter{secnumdepth}{%d}' %\ minsecnumdepth contentsname = document.get('contentsname') if contentsname: - self.elements['contentsname'] = self.babel_renewcommand('\\contentsname', + self.elements['contentsname'] = self.babel_renewcommand(r'\contentsname', contentsname) if self.elements['maxlistdepth']: @@ -422,23 +418,22 @@ class LaTeXTranslator(SphinxTranslator): if sphinxpkgoptions: self.elements['sphinxpkgoptions'] = '[,%s]' % ','.join(sphinxpkgoptions) if self.elements['sphinxsetup']: - self.elements['sphinxsetup'] = ('\\sphinxsetup{%s}' % - self.elements['sphinxsetup']) + self.elements['sphinxsetup'] = (r'\sphinxsetup{%s}' % self.elements['sphinxsetup']) if self.elements['extraclassoptions']: self.elements['classoptions'] += ',' + \ self.elements['extraclassoptions'] self.highlighter = highlighting.PygmentsBridge('latex', self.config.pygments_style, latex_engine=self.config.latex_engine) - self.context = [] # type: List[Any] - self.descstack = [] # type: List[str] - self.tables = [] # type: List[Table] - self.next_table_colspec = None # type: str - self.bodystack = [] # type: List[List[str]] - self.footnote_restricted = None # type: nodes.Element - self.pending_footnotes = [] # type: List[nodes.footnote_reference] - self.curfilestack = [] # type: List[str] - self.handled_abbrs = set() # type: Set[str] + self.context: List[Any] = [] + self.descstack: List[str] = [] + self.tables: List[Table] = [] + self.next_table_colspec: str = None + self.bodystack: List[List[str]] = [] + self.footnote_restricted: Element = None + self.pending_footnotes: List[nodes.footnote_reference] = [] + self.curfilestack: List[str] = [] + self.handled_abbrs: Set[str] = set() def pushbody(self, newbody: List[str]) -> None: self.bodystack.append(self.body) @@ -468,8 +463,7 @@ class LaTeXTranslator(SphinxTranslator): def hypertarget(self, id: str, withdoc: bool = True, anchor: bool = True) -> str: if withdoc: id = self.curfilestack[-1] + ':' + id - return ('\\phantomsection' if anchor else '') + \ - '\\label{%s}' % self.idescape(id) + return (r'\phantomsection' if anchor else '') + r'\label{%s}' % self.idescape(id) def hypertarget_to(self, node: Element, anchor: bool = False) -> str: labels = ''.join(self.hypertarget(node_id, anchor=False) for node_id in node['ids']) @@ -479,48 +473,48 @@ class LaTeXTranslator(SphinxTranslator): return labels def hyperlink(self, id: str) -> str: - return '{\\hyperref[%s]{' % self.idescape(id) + return r'{\hyperref[%s]{' % self.idescape(id) def hyperpageref(self, id: str) -> str: - return '\\autopageref*{%s}' % self.idescape(id) + return r'\autopageref*{%s}' % self.idescape(id) def escape(self, s: str) -> str: return texescape.escape(s, self.config.latex_engine) def idescape(self, id: str) -> str: - return '\\detokenize{%s}' % str(id).translate(tex_replace_map).\ + return r'\detokenize{%s}' % str(id).translate(tex_replace_map).\ encode('ascii', 'backslashreplace').decode('ascii').\ replace('\\', '_') def babel_renewcommand(self, command: str, definition: str) -> str: if self.elements['multilingual']: - prefix = '\\addto\\captions%s{' % self.babel.get_language() + prefix = r'\addto\captions%s{' % self.babel.get_language() suffix = '}' else: # babel is disabled (mainly for Japanese environment) prefix = '' suffix = '' - return '%s\\renewcommand{%s}{%s}%s' % (prefix, command, definition, suffix) + CR + return r'%s\renewcommand{%s}{%s}%s' % (prefix, command, definition, suffix) + CR def generate_indices(self) -> str: def generate(content: List[Tuple[str, List[IndexEntry]]], collapsed: bool) -> None: - ret.append('\\begin{sphinxtheindex}' + CR) - ret.append('\\let\\bigletter\\sphinxstyleindexlettergroup' + CR) + ret.append(r'\begin{sphinxtheindex}' + CR) + ret.append(r'\let\bigletter\sphinxstyleindexlettergroup' + CR) for i, (letter, entries) in enumerate(content): if i > 0: - ret.append('\\indexspace' + CR) - ret.append('\\bigletter{%s}' % self.escape(letter) + CR) + ret.append(r'\indexspace' + CR) + ret.append(r'\bigletter{%s}' % self.escape(letter) + CR) for entry in entries: if not entry[3]: continue - ret.append('\\item\\relax\\sphinxstyleindexentry{%s}' % + ret.append(r'\item\relax\sphinxstyleindexentry{%s}' % self.encode(entry[0])) if entry[4]: # add "extra" info - ret.append('\\sphinxstyleindexextra{%s}' % self.encode(entry[4])) - ret.append('\\sphinxstyleindexpageref{%s:%s}' % + ret.append(r'\sphinxstyleindexextra{%s}' % self.encode(entry[4])) + ret.append(r'\sphinxstyleindexpageref{%s:%s}' % (entry[2], self.idescape(entry[3])) + CR) - ret.append('\\end{sphinxtheindex}' + CR) + ret.append(r'\end{sphinxtheindex}' + CR) ret = [] # latex_domain_indices can be False/True or a list of index names @@ -536,7 +530,7 @@ class LaTeXTranslator(SphinxTranslator): self.builder.docnames) if not content: continue - ret.append('\\renewcommand{\\indexname}{%s}' % indexcls.localname + CR) + ret.append(r'\renewcommand{\indexname}{%s}' % indexcls.localname + CR) generate(content, collapsed) return ''.join(ret) @@ -566,7 +560,7 @@ class LaTeXTranslator(SphinxTranslator): self.first_document = 0 elif self.first_document == 0: # ... and all others are the appendices - self.body.append(CR + '\\appendix' + CR) + self.body.append(CR + r'\appendix' + CR) self.first_document = -1 if 'docname' in node: self.body.append(self.hypertarget(':doc')) @@ -599,11 +593,11 @@ class LaTeXTranslator(SphinxTranslator): def visit_topic(self, node: Element) -> None: self.in_minipage = 1 - self.body.append(CR + '\\begin{sphinxShadowBox}' + CR) + self.body.append(CR + r'\begin{sphinxShadowBox}' + CR) def depart_topic(self, node: Element) -> None: self.in_minipage = 0 - self.body.append('\\end{sphinxShadowBox}' + CR) + self.body.append(r'\end{sphinxShadowBox}' + CR) visit_sidebar = visit_topic depart_sidebar = depart_topic @@ -615,20 +609,20 @@ class LaTeXTranslator(SphinxTranslator): def visit_productionlist(self, node: Element) -> None: self.body.append(BLANKLINE) - self.body.append('\\begin{productionlist}' + CR) + self.body.append(r'\begin{productionlist}' + CR) self.in_production_list = 1 def depart_productionlist(self, node: Element) -> None: - self.body.append('\\end{productionlist}' + BLANKLINE) + self.body.append(r'\end{productionlist}' + BLANKLINE) self.in_production_list = 0 def visit_production(self, node: Element) -> None: if node['tokenname']: tn = node['tokenname'] self.body.append(self.hypertarget('grammar-token-' + tn)) - self.body.append('\\production{%s}{' % self.encode(tn)) + self.body.append(r'\production{%s}{' % self.encode(tn)) else: - self.body.append('\\productioncont{') + self.body.append(r'\productioncont{') def depart_production(self, node: Element) -> None: self.body.append('}' + CR) @@ -683,7 +677,7 @@ class LaTeXTranslator(SphinxTranslator): logger.warning(__('encountered title node not in section, topic, table, ' 'admonition or sidebar'), location=node) - self.body.append('\\sphinxstyleothertitle{') + self.body.append(r'\sphinxstyleothertitle{') self.context.append('}' + CR) self.in_title = 1 @@ -696,7 +690,7 @@ class LaTeXTranslator(SphinxTranslator): def visit_subtitle(self, node: Element) -> None: if isinstance(node.parent, nodes.sidebar): - self.body.append('\\sphinxstylesidebarsubtitle{') + self.body.append(r'\sphinxstylesidebarsubtitle{') self.context.append('}' + CR) else: self.context.append('') @@ -707,18 +701,18 @@ class LaTeXTranslator(SphinxTranslator): def visit_desc(self, node: Element) -> None: if self.config.latex_show_urls == 'footnote': self.body.append(BLANKLINE) - self.body.append('\\begin{savenotes}\\begin{fulllineitems}' + CR) + self.body.append(r'\begin{savenotes}\begin{fulllineitems}' + CR) else: self.body.append(BLANKLINE) - self.body.append('\\begin{fulllineitems}' + CR) + self.body.append(r'\begin{fulllineitems}' + CR) if self.table: self.table.has_problematic = True def depart_desc(self, node: Element) -> None: if self.config.latex_show_urls == 'footnote': - self.body.append(CR + '\\end{fulllineitems}\\end{savenotes}' + BLANKLINE) + self.body.append(CR + r'\end{fulllineitems}\end{savenotes}' + BLANKLINE) else: - self.body.append(CR + '\\end{fulllineitems}' + BLANKLINE) + self.body.append(CR + r'\end{fulllineitems}' + BLANKLINE) def _visit_signature_line(self, node: Element) -> None: for child in node: @@ -741,14 +735,14 @@ class LaTeXTranslator(SphinxTranslator): self._visit_signature_line(node) else: self.body.append('%' + CR) - self.body.append('\\pysigstartmultiline' + CR) + self.body.append(r'\pysigstartmultiline' + CR) def depart_desc_signature(self, node: Element) -> None: if not node.get('is_multiline'): self._depart_signature_line(node) else: self.body.append('%' + CR) - self.body.append('\\pysigstopmultiline') + self.body.append(r'\pysigstopmultiline') def visit_desc_signature_line(self, node: Element) -> None: self._visit_signature_line(node) @@ -827,8 +821,8 @@ class LaTeXTranslator(SphinxTranslator): def visit_seealso(self, node: Element) -> None: self.body.append(BLANKLINE) - self.body.append('\\sphinxstrong{%s:}' % admonitionlabels['seealso'] + CR) - self.body.append('\\nopagebreak' + BLANKLINE) + self.body.append(r'\sphinxstrong{%s:}' % admonitionlabels['seealso'] + CR) + self.body.append(r'\nopagebreak' + BLANKLINE) def depart_seealso(self, node: Element) -> None: self.body.append(BLANKLINE) @@ -836,7 +830,7 @@ class LaTeXTranslator(SphinxTranslator): def visit_rubric(self, node: Element) -> None: if len(node) == 1 and node.astext() in ('Footnotes', _('Footnotes')): raise nodes.SkipNode - self.body.append('\\subsubsection*{') + self.body.append(r'\subsubsection*{') self.context.append('}' + CR) self.in_title = 1 @@ -848,23 +842,23 @@ class LaTeXTranslator(SphinxTranslator): self.in_footnote += 1 label = cast(nodes.label, node[0]) if 'auto' not in node: - self.body.append('\\sphinxstepexplicit ') + self.body.append(r'\sphinxstepexplicit ') if self.in_parsed_literal: - self.body.append('\\begin{footnote}[%s]' % label.astext()) + self.body.append(r'\begin{footnote}[%s]' % label.astext()) else: self.body.append('%' + CR) - self.body.append('\\begin{footnote}[%s]' % label.astext()) + self.body.append(r'\begin{footnote}[%s]' % label.astext()) if 'auto' not in node: - self.body.append('\\phantomsection' - '\\label{\\thesphinxscope.%s}%%' % label.astext() + CR) - self.body.append('\\sphinxAtStartFootnote' + CR) + self.body.append(r'\phantomsection' + r'\label{\thesphinxscope.%s}%%' % label.astext() + CR) + self.body.append(r'\sphinxAtStartFootnote' + CR) def depart_footnote(self, node: Element) -> None: if self.in_parsed_literal: - self.body.append('\\end{footnote}') + self.body.append(r'\end{footnote}') else: self.body.append('%' + CR) - self.body.append('\\end{footnote}') + self.body.append(r'\end{footnote}') self.in_footnote -= 1 def visit_label(self, node: Element) -> None: @@ -952,25 +946,24 @@ class LaTeXTranslator(SphinxTranslator): self.body.append('&') if cell.width == 1: # insert suitable strut for equalizing row heights in given multirow - self.body.append('\\sphinxtablestrut{%d}' % cell.cell_id) + self.body.append(r'\sphinxtablestrut{%d}' % cell.cell_id) else: # use \multicolumn for wide multirow cell - self.body.append('\\multicolumn{%d}{|l|}' - '{\\sphinxtablestrut{%d}}' % + self.body.append(r'\multicolumn{%d}{|l|}\sphinxtablestrut{%d}}' % (cell.width, cell.cell_id)) def depart_row(self, node: Element) -> None: - self.body.append('\\\\' + CR) + self.body.append(r'\\' + CR) cells = [self.table.cell(self.table.row, i) for i in range(self.table.colcount)] underlined = [cell.row + cell.height == self.table.row + 1 for cell in cells] if all(underlined): - self.body.append('\\hline') + self.body.append(r'\hline') else: i = 0 underlined.extend([False]) # sentinel while i < len(underlined): if underlined[i] is True: j = underlined[i:].index(False) - self.body.append('\\cline{%d-%d}' % (i + 1, i + j)) + self.body.append(r'\cline{%d-%d}' % (i + 1, i + j)) i += j i += 1 self.table.row += 1 @@ -984,22 +977,22 @@ class LaTeXTranslator(SphinxTranslator): if cell.width > 1: if self.config.latex_use_latex_multicolumn: if self.table.col == 0: - self.body.append('\\multicolumn{%d}{|l|}{%%' % cell.width + CR) + self.body.append(r'\multicolumn{%d}{|l|}{%%' % cell.width + CR) else: - self.body.append('\\multicolumn{%d}{l|}{%%' % cell.width + CR) + self.body.append(r'\multicolumn{%d}{l|}{%%' % cell.width + CR) context = '}%' + CR else: - self.body.append('\\sphinxstartmulticolumn{%d}%%' % cell.width + CR) - context = '\\sphinxstopmulticolumn' + CR + self.body.append(r'\sphinxstartmulticolumn{%d}%%' % cell.width + CR) + context = r'\sphinxstopmulticolumn' + CR if cell.height > 1: # \sphinxmultirow 2nd arg "cell_id" will serve as id for LaTeX macros as well - self.body.append('\\sphinxmultirow{%d}{%d}{%%' % (cell.height, cell.cell_id) + CR) + self.body.append(r'\sphinxmultirow{%d}{%d}{%%' % (cell.height, cell.cell_id) + CR) context = '}%' + CR + context if cell.width > 1 or cell.height > 1: - self.body.append('\\begin{varwidth}[t]{\\sphinxcolwidth{%d}{%d}}' + self.body.append(r'\begin{varwidth}[t]{\sphinxcolwidth{%d}{%d}}' % (cell.width, self.table.colcount) + CR) - context = ('\\par' + CR + '\\vskip-\\baselineskip' - '\\vbox{\\hbox{\\strut}}\\end{varwidth}%' + CR + context) + context = (r'\par' + CR + r'\vskip-\baselineskip' + r'\vbox{\hbox{\strut}}\end{varwidth}%' + CR + context) self.needs_linetrimming = 1 if len(node.traverse(nodes.paragraph)) >= 2: self.table.has_oldproblematic = True @@ -1007,7 +1000,7 @@ class LaTeXTranslator(SphinxTranslator): if len(node) == 1 and isinstance(node[0], nodes.paragraph) and node.astext() == '': pass else: - self.body.append('\\sphinxstyletheadfamily ') + self.body.append(r'\sphinxstyletheadfamily ') if self.needs_linetrimming: self.pushbody([]) self.context.append(context) @@ -1038,11 +1031,10 @@ class LaTeXTranslator(SphinxTranslator): if nextcell.width == 1: # insert suitable strut for equalizing row heights in multirow # they also serve to clear colour panels which would hide the text - self.body.append('\\sphinxtablestrut{%d}' % nextcell.cell_id) + self.body.append(r'\sphinxtablestrut{%d}' % nextcell.cell_id) else: # use \multicolumn for wide multirow cell - self.body.append('\\multicolumn{%d}{l|}' - '{\\sphinxtablestrut{%d}}' % + self.body.append(r'\multicolumn{%d}{l|}{\sphinxtablestrut{%d}}' % (nextcell.width, nextcell.cell_id)) def visit_acks(self, node: Element) -> None: @@ -1057,13 +1049,13 @@ class LaTeXTranslator(SphinxTranslator): def visit_bullet_list(self, node: Element) -> None: if not self.compact_list: - self.body.append('\\begin{itemize}' + CR) + self.body.append(r'\begin{itemize}' + CR) if self.table: self.table.has_problematic = True def depart_bullet_list(self, node: Element) -> None: if not self.compact_list: - self.body.append('\\end{itemize}' + CR) + self.body.append(r'\end{itemize}' + CR) def visit_enumerated_list(self, node: Element) -> None: def get_enumtype(node: Element) -> str: @@ -1088,16 +1080,16 @@ class LaTeXTranslator(SphinxTranslator): prefix = node.get('prefix', '') suffix = node.get('suffix', '.') - self.body.append('\\begin{enumerate}' + CR) - self.body.append('\\sphinxsetlistlabels{%s}{%s}{%s}{%s}{%s}%%' % + self.body.append(r'\begin{enumerate}' + CR) + self.body.append(r'\sphinxsetlistlabels{%s}{%s}{%s}{%s}{%s}%%' % (style, enum, enumnext, prefix, suffix) + CR) if 'start' in node: - self.body.append('\\setcounter{%s}{%d}' % (enum, node['start'] - 1) + CR) + self.body.append(r'\setcounter{%s}{%d}' % (enum, node['start'] - 1) + CR) if self.table: self.table.has_problematic = True def depart_enumerated_list(self, node: Element) -> None: - self.body.append('\\end{enumerate}' + CR) + self.body.append(r'\end{enumerate}' + CR) def visit_list_item(self, node: Element) -> None: # Append "{}" in case the next character is "[", which would break @@ -1108,12 +1100,12 @@ class LaTeXTranslator(SphinxTranslator): self.body.append(CR) def visit_definition_list(self, node: Element) -> None: - self.body.append('\\begin{description}' + CR) + self.body.append(r'\begin{description}' + CR) if self.table: self.table.has_problematic = True def depart_definition_list(self, node: Element) -> None: - self.body.append('\\end{description}' + CR) + self.body.append(r'\end{description}' + CR) def visit_definition_list_item(self, node: Element) -> None: pass @@ -1125,11 +1117,11 @@ class LaTeXTranslator(SphinxTranslator): self.in_term += 1 ctx = '' if node.get('ids'): - ctx = '\\phantomsection' + ctx = r'\phantomsection' for node_id in node['ids']: ctx += self.hypertarget(node_id, anchor=False) - ctx += '}] \\leavevmode' - self.body.append('\\item[{') + ctx += r'}] \leavevmode' + self.body.append(r'\item[{') self.context.append(ctx) def depart_term(self, node: Element) -> None: @@ -1149,12 +1141,12 @@ class LaTeXTranslator(SphinxTranslator): self.body.append(CR) def visit_field_list(self, node: Element) -> None: - self.body.append('\\begin{quote}\\begin{description}' + CR) + self.body.append(r'\begin{quote}\begin{description}' + CR) if self.table: self.table.has_problematic = True def depart_field_list(self, node: Element) -> None: - self.body.append('\\end{description}\\end{quote}' + CR) + self.body.append(r'\end{description}\end{quote}' + CR) def visit_field(self, node: Element) -> None: pass @@ -1174,7 +1166,7 @@ class LaTeXTranslator(SphinxTranslator): not isinstance(node.parent[index - 1], nodes.paragraph) and not isinstance(node.parent[index - 1], nodes.compound)): # insert blank line, if the paragraph follows a non-paragraph node in a compound - self.body.append('\\noindent' + CR) + self.body.append(r'\noindent' + CR) elif index == 1 and isinstance(node.parent, (nodes.footnote, footnotetext)): # don't insert blank line, if the paragraph is second child of a footnote # (first one is label node) @@ -1183,33 +1175,33 @@ class LaTeXTranslator(SphinxTranslator): # 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([CR, '\\sphinxAtStartPar' + CR]) + self.body.extend([CR, r'\sphinxAtStartPar' + CR]) def depart_paragraph(self, node: Element) -> None: self.body.append(CR) def visit_centered(self, node: Element) -> None: - self.body.append(CR + '\\begin{center}') + self.body.append(CR + r'\begin{center}') if self.table: self.table.has_problematic = True def depart_centered(self, node: Element) -> None: - self.body.append(CR + '\\end{center}') + self.body.append(CR + r'\end{center}') def visit_hlist(self, node: Element) -> None: self.compact_list += 1 ncolumns = node['ncolumns'] if self.compact_list > 1: - self.body.append('\\setlength{\\multicolsep}{0pt}' + CR) - self.body.append('\\begin{multicols}{' + ncolumns + '}\\raggedright' + CR) - self.body.append('\\begin{itemize}\\setlength{\\itemsep}{0pt}' - '\\setlength{\\parskip}{0pt}' + CR) + self.body.append(r'\setlength{\multicolsep}{0pt}' + CR) + self.body.append(r'\begin{multicols}{' + ncolumns + '}\raggedright' + CR) + self.body.append(r'\begin{itemize}\setlength{\itemsep}{0pt}' + r'\setlength{\parskip}{0pt}' + CR) if self.table: self.table.has_problematic = True def depart_hlist(self, node: Element) -> None: self.compact_list -= 1 - self.body.append('\\end{itemize}\\raggedcolumns\\end{multicols}' + CR) + self.body.append(r'\end{itemize}\raggedcolumns\end{multicols}' + CR) def visit_hlistcol(self, node: Element) -> None: pass @@ -1219,7 +1211,7 @@ class LaTeXTranslator(SphinxTranslator): # 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') + # self.body.append(r'\columnbreak\n') pass def latex_image_length(self, width_str: str, scale: int = 100) -> str: @@ -1234,9 +1226,8 @@ class LaTeXTranslator(SphinxTranslator): return isinstance(node.parent, nodes.TextElement) def visit_image(self, node: Element) -> None: - pre = [] # type: List[str] - # in reverse order - post = [] # type: List[str] + pre: List[str] = [] # in reverse order + post: List[str] = [] include_graphics_options = [] has_hyperlink = isinstance(node.parent, nodes.reference) if has_hyperlink: @@ -1267,15 +1258,14 @@ class LaTeXTranslator(SphinxTranslator): align_prepost = { # By default latex aligns the top of an image. (1, 'top'): ('', ''), - (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'), - (1, 'bottom'): ('\\raisebox{-\\height}{', '}'), - (0, 'center'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'), - (0, 'default'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'), + (1, 'middle'): (r'\raisebox{-0.5\height}{', '}'), + (1, 'bottom'): (r'\raisebox{-\height}{', '}'), + (0, 'center'): (r'{\hspace*{\fill}', r'\hspace*{\fill}}'), # These 2 don't exactly do the right thing. The image should # be floated alongside the paragraph. See # https://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG - (0, 'left'): ('{', '\\hspace*{\\fill}}'), - (0, 'right'): ('{\\hspace*{\\fill}', '}'), + (0, 'left'): ('{', r'\hspace*{\fill}}'), + (0, 'right'): (r'{\hspace*{\fill}', '}'), } try: pre.append(align_prepost[is_inline, node['align']][0]) @@ -1283,10 +1273,10 @@ class LaTeXTranslator(SphinxTranslator): except KeyError: pass if self.in_parsed_literal: - pre.append('{\\sphinxunactivateextrasandspace ') + pre.append(r'{\sphinxunactivateextrasandspace ') post.append('}') if not is_inline and not has_hyperlink: - pre.append(CR + '\\noindent') + pre.append(CR + r'\noindent') post.append(CR) pre.reverse() if node['uri'] in self.builder.images: @@ -1307,10 +1297,10 @@ class LaTeXTranslator(SphinxTranslator): if self.in_title and base: # Lowercase tokens forcely because some fncychap themes capitalize # the options of \sphinxincludegraphics unexpectly (ex. WIDTH=...). - self.body.append('\\lowercase{\\sphinxincludegraphics%s}{{%s}%s}' % + self.body.append(r'\lowercase{\sphinxincludegraphics%s}{{%s}%s}' % (options, base, ext)) else: - self.body.append('\\sphinxincludegraphics%s{{%s}%s}' % + self.body.append(r'\sphinxincludegraphics%s{{%s}%s}' % (options, base, ext)) self.body.extend(post) @@ -1326,14 +1316,14 @@ class LaTeXTranslator(SphinxTranslator): if 'width' in node: length = self.latex_image_length(node['width']) if length: - self.body.append('\\begin{sphinxfigure-in-table}[%s]' % length + CR) - self.body.append('\\centering' + CR) + self.body.append(r'\begin{sphinxfigure-in-table}[%s]' % length + CR) + self.body.append(r'\centering' + CR) else: - self.body.append('\\begin{sphinxfigure-in-table}' + CR) - self.body.append('\\centering' + CR) + self.body.append(r'\begin{sphinxfigure-in-table}' + CR) + self.body.append(r'\centering' + CR) if any(isinstance(child, nodes.caption) for child in node): - self.body.append('\\capstart') - self.context.append('\\end{sphinxfigure-in-table}\\relax' + CR) + self.body.append(r'\capstart') + self.context.append(r'\end{sphinxfigure-in-table}\relax' + CR) elif node.get('align', '') in ('left', 'right'): length = None if 'width' in node: @@ -1342,19 +1332,19 @@ class LaTeXTranslator(SphinxTranslator): length = self.latex_image_length(node[0]['width']) self.body.append(BLANKLINE) # Insert a blank line to prevent infinite loop # https://github.com/sphinx-doc/sphinx/issues/7059 - self.body.append('\\begin{wrapfigure}{%s}{%s}' % + self.body.append(r'\begin{wrapfigure}{%s}{%s}' % ('r' if node['align'] == 'right' else 'l', length or '0pt') + CR) - self.body.append('\\centering') - self.context.append('\\end{wrapfigure}' + CR) + self.body.append(r'\centering') + self.context.append(r'\end{wrapfigure}' + CR) elif self.in_minipage: - self.body.append(CR + '\\begin{center}') - self.context.append('\\end{center}' + CR) + self.body.append(CR + r'\begin{center}') + self.context.append(r'\end{center}' + CR) else: - self.body.append(CR + '\\begin{figure}[%s]' % align + CR) - self.body.append('\\centering' + CR) + self.body.append(CR + r'\begin{figure}[%s]' % align + CR) + self.body.append(r'\centering' + CR) if any(isinstance(child, nodes.caption) for child in node): - self.body.append('\\capstart' + CR) - self.context.append('\\end{figure}' + CR) + self.body.append(r'\capstart' + CR) + self.context.append(r'\end{figure}' + CR) def depart_figure(self, node: Element) -> None: self.body.append(self.context.pop()) @@ -1362,13 +1352,13 @@ class LaTeXTranslator(SphinxTranslator): def visit_caption(self, node: Element) -> None: self.in_caption += 1 if isinstance(node.parent, captioned_literal_block): - self.body.append('\\sphinxSetupCaptionForVerbatim{') + self.body.append(r'\sphinxSetupCaptionForVerbatim{') elif self.in_minipage and isinstance(node.parent, nodes.figure): - self.body.append('\\captionof{figure}{') + self.body.append(r'\captionof{figure}{') elif self.table and node.parent.tagname == 'figure': - self.body.append('\\sphinxfigcaption{') + self.body.append(r'\sphinxfigcaption{') else: - self.body.append('\\caption{') + self.body.append(r'\caption{') def depart_caption(self, node: Element) -> None: self.body.append('}') @@ -1378,27 +1368,27 @@ class LaTeXTranslator(SphinxTranslator): self.in_caption -= 1 def visit_legend(self, node: Element) -> None: - self.body.append(CR + '\\begin{sphinxlegend}') + self.body.append(CR + r'\begin{sphinxlegend}') def depart_legend(self, node: Element) -> None: - self.body.append('\\end{sphinxlegend}' + CR) + self.body.append(r'\end{sphinxlegend}' + CR) def visit_admonition(self, node: Element) -> None: - self.body.append(CR + '\\begin{sphinxadmonition}{note}') + self.body.append(CR + r'\begin{sphinxadmonition}{note}') self.no_latex_floats += 1 def depart_admonition(self, node: Element) -> None: - self.body.append('\\end{sphinxadmonition}' + CR) + self.body.append(r'\end{sphinxadmonition}' + CR) self.no_latex_floats -= 1 def _visit_named_admonition(self, node: Element) -> None: label = admonitionlabels[node.tagname] - self.body.append(CR + '\\begin{sphinxadmonition}{%s}{%s:}' % + self.body.append(CR + r'\begin{sphinxadmonition}{%s}{%s:}' % (node.tagname, label)) self.no_latex_floats += 1 def _depart_named_admonition(self, node: Element) -> None: - self.body.append('\\end{sphinxadmonition}' + CR) + self.body.append(r'\end{sphinxadmonition}' + CR) self.no_latex_floats -= 1 visit_attention = _visit_named_admonition @@ -1448,7 +1438,7 @@ class LaTeXTranslator(SphinxTranslator): self.body.append(self.hypertarget(id, anchor=anchor)) # skip if visitor for next node supports hyperlink - next_node = node # type: nodes.Node + next_node: Node = node while isinstance(next_node, nodes.target): next_node = next_node.next_node(ascend=True) @@ -1476,11 +1466,11 @@ class LaTeXTranslator(SphinxTranslator): pass def visit_attribution(self, node: Element) -> None: - self.body.append(CR + '\\begin{flushright}' + CR) + self.body.append(CR + r'\begin{flushright}' + CR) self.body.append('---') def depart_attribution(self, node: Element) -> None: - self.body.append(CR + '\\end{flushright}' + CR) + self.body.append(CR + r'\end{flushright}' + CR) def visit_index(self, node: Element) -> None: def escape(value: str) -> str: @@ -1498,7 +1488,7 @@ class LaTeXTranslator(SphinxTranslator): if match: return match.expand(r'\\spxentry{\1}\\spxextra{\2}') else: - return '\\spxentry{%s}' % string + return r'\spxentry{%s}' % string if not node.get('inline', True): self.body.append(CR) @@ -1545,7 +1535,7 @@ class LaTeXTranslator(SphinxTranslator): except ValueError as err: logger.warning(str(err)) if not node.get('inline', True): - self.body.append('\\ignorespaces ') + self.body.append(r'\ignorespaces ') raise nodes.SkipNode def visit_raw(self, node: Element) -> None: @@ -1605,12 +1595,12 @@ class LaTeXTranslator(SphinxTranslator): else: if len(node) == 1 and uri == node[0]: if node.get('nolinkurl'): - self.body.append('\\sphinxnolinkurl{%s}' % self.encode_uri(uri)) + self.body.append(r'\sphinxnolinkurl{%s}' % self.encode_uri(uri)) else: - self.body.append('\\sphinxurl{%s}' % self.encode_uri(uri)) + self.body.append(r'\sphinxurl{%s}' % self.encode_uri(uri)) raise nodes.SkipNode else: - self.body.append('\\sphinxhref{%s}{' % self.encode_uri(uri)) + self.body.append(r'\sphinxhref{%s}{' % self.encode_uri(uri)) self.context.append('}') def depart_reference(self, node: Element) -> None: @@ -1624,16 +1614,16 @@ class LaTeXTranslator(SphinxTranslator): else: id = node.get('refuri', '')[1:].replace('#', ':') - title = self.escape(node.get('title', '%s')).replace('\\%s', '%s') - if '\\{name\\}' in title or '\\{number\\}' in title: + title = self.escape(node.get('title', '%s')).replace(r'\%s', '%s') + if r'\{name\}' in title or r'\{number\}' in title: # new style format (cf. "Fig.%{number}") - title = title.replace('\\{name\\}', '{name}').replace('\\{number\\}', '{number}') - text = escape_abbr(title).format(name='\\nameref{%s}' % self.idescape(id), - number='\\ref{%s}' % self.idescape(id)) + title = title.replace(r'\{name\}', '{name}').replace(r'\{number\}', '{number}') + text = escape_abbr(title).format(name=r'\nameref{%s}' % self.idescape(id), + number=r'\ref{%s}' % self.idescape(id)) else: # old style format (cf. "Fig.%{number}") - text = escape_abbr(title) % ('\\ref{%s}' % self.idescape(id)) - hyperref = '\\hyperref[%s]{%s}' % (self.idescape(id), text) + text = escape_abbr(title) % (r'\ref{%s}' % self.idescape(id)) + hyperref = r'\hyperref[%s]{%s}' % (self.idescape(id), text) self.body.append(hyperref) raise nodes.SkipNode @@ -1707,15 +1697,15 @@ class LaTeXTranslator(SphinxTranslator): # adjust max width of citation labels not to break the layout longest_label = longest_label[:MAX_CITATION_LABEL_LENGTH] - self.body.append(CR + '\\begin{sphinxthebibliography}{%s}' % + self.body.append(CR + r'\begin{sphinxthebibliography}{%s}' % self.encode(longest_label) + CR) def depart_thebibliography(self, node: Element) -> None: - self.body.append('\\end{sphinxthebibliography}' + CR) + self.body.append(r'\end{sphinxthebibliography}' + CR) def visit_citation(self, node: Element) -> None: label = cast(nodes.label, node[0]) - self.body.append('\\bibitem[%s]{%s:%s}' % (self.encode(label.astext()), + self.body.append(r'\bibitem[%s]{%s:%s}' % (self.encode(label.astext()), node['docname'], node['ids'][0])) def depart_citation(self, node: Element) -> None: @@ -1725,7 +1715,7 @@ class LaTeXTranslator(SphinxTranslator): if self.in_title: pass else: - self.body.append('\\sphinxcite{%s:%s}' % (node['docname'], node['refname'])) + self.body.append(r'\sphinxcite{%s:%s}' % (node['docname'], node['refname'])) raise nodes.SkipNode def depart_citation_reference(self, node: Element) -> None: @@ -1746,7 +1736,7 @@ class LaTeXTranslator(SphinxTranslator): raise nodes.SkipNode def visit_footnotemark(self, node: Element) -> None: - self.body.append('\\sphinxfootnotemark[') + self.body.append(r'\sphinxfootnotemark[') def depart_footnotemark(self, node: Element) -> None: self.body.append(']') @@ -1754,15 +1744,15 @@ class LaTeXTranslator(SphinxTranslator): def visit_footnotetext(self, node: Element) -> None: label = cast(nodes.label, node[0]) self.body.append('%' + CR) - self.body.append('\\begin{footnotetext}[%s]' - '\\phantomsection\\label{\\thesphinxscope.%s}%%' + self.body.append(r'\begin{footnotetext}[%s]' + r'\phantomsection\label{\thesphinxscope.%s}%%' % (label.astext(), label.astext()) + CR) - self.body.append('\\sphinxAtStartFootnote' + CR) + self.body.append(r'\sphinxAtStartFootnote' + CR) def depart_footnotetext(self, node: Element) -> None: # the \ignorespaces in particular for after table header use self.body.append('%' + CR) - self.body.append('\\end{footnotetext}\\ignorespaces ') + self.body.append(r'\end{footnotetext}\ignorespaces ') def visit_captioned_literal_block(self, node: Element) -> None: pass @@ -1774,13 +1764,13 @@ class LaTeXTranslator(SphinxTranslator): if node.rawsource != node.astext(): # most probably a parsed-literal block -- don't highlight self.in_parsed_literal += 1 - self.body.append('\\begin{sphinxalltt}' + CR) + self.body.append(r'\begin{sphinxalltt}' + CR) else: labels = self.hypertarget_to(node) if isinstance(node.parent, captioned_literal_block): labels += self.hypertarget_to(node.parent) if labels and not self.in_footnote: - self.body.append(CR + '\\def\\sphinxLiteralBlockLabel{' + labels + '}') + self.body.append(CR + r'\def\sphinxLiteralBlockLabel{' + labels + '}') lang = node.get('language', 'default') linenos = node.get('linenos', False) @@ -1793,57 +1783,57 @@ class LaTeXTranslator(SphinxTranslator): location=node, **highlight_args ) if self.in_footnote: - self.body.append(CR + '\\sphinxSetupCodeBlockInFootnote') - hlcode = hlcode.replace('\\begin{Verbatim}', - '\\begin{sphinxVerbatim}') + self.body.append(CR + r'\sphinxSetupCodeBlockInFootnote') + hlcode = hlcode.replace(r'\begin{Verbatim}', + r'\begin{sphinxVerbatim}') # if in table raise verbatim flag to avoid "tabulary" environment # and opt for sphinxVerbatimintable to handle caption & long lines elif self.table: self.table.has_problematic = True self.table.has_verbatim = True - hlcode = hlcode.replace('\\begin{Verbatim}', - '\\begin{sphinxVerbatimintable}') + hlcode = hlcode.replace(r'\begin{Verbatim}', + r'\begin{sphinxVerbatimintable}') else: - hlcode = hlcode.replace('\\begin{Verbatim}', - '\\begin{sphinxVerbatim}') + hlcode = hlcode.replace(r'\begin{Verbatim}', + r'\begin{sphinxVerbatim}') # get consistent trailer hlcode = hlcode.rstrip()[:-14] # strip \end{Verbatim} if self.table and not self.in_footnote: - hlcode += '\\end{sphinxVerbatimintable}' + hlcode += r'\end{sphinxVerbatimintable}' else: - hlcode += '\\end{sphinxVerbatim}' + hlcode += r'\end{sphinxVerbatim}' hllines = str(highlight_args.get('hl_lines', []))[1:-1] if hllines: - self.body.append(CR + '\\fvset{hllines={, %s,}}%%' % hllines) + self.body.append(CR + r'\fvset{hllines={, %s,}}%%' % hllines) self.body.append(CR + hlcode + CR) if hllines: - self.body.append('\\sphinxresetverbatimhllines' + CR) + self.body.append(r'\sphinxresetverbatimhllines' + CR) raise nodes.SkipNode def depart_literal_block(self, node: Element) -> None: - self.body.append(CR + '\\end{sphinxalltt}' + CR) + self.body.append(CR + r'\end{sphinxalltt}' + CR) self.in_parsed_literal -= 1 visit_doctest_block = visit_literal_block depart_doctest_block = depart_literal_block def visit_line(self, node: Element) -> None: - self.body.append('\\item[] ') + self.body.append(r'\item[] ') def depart_line(self, node: Element) -> None: self.body.append(CR) def visit_line_block(self, node: Element) -> None: if isinstance(node.parent, nodes.line_block): - self.body.append('\\item[]' + CR) - self.body.append('\\begin{DUlineblock}{\\DUlineblockindent}' + CR) + self.body.append(r'\item[]' + CR) + self.body.append(r'\begin{DUlineblock}{\DUlineblockindent}' + CR) else: - self.body.append(CR + '\\begin{DUlineblock}{0em}' + CR) + self.body.append(CR + r'\begin{DUlineblock}{0em}' + CR) if self.table: self.table.has_problematic = True def depart_line_block(self, node: Element) -> None: - self.body.append('\\end{DUlineblock}' + CR) + self.body.append(r'\end{DUlineblock}' + CR) def visit_block_quote(self, node: Element) -> None: # If the block quote contains a single object and that object @@ -1856,7 +1846,7 @@ class LaTeXTranslator(SphinxTranslator): isinstance(child, nodes.enumerated_list): done = 1 if not done: - self.body.append('\\begin{quote}' + CR) + self.body.append(r'\begin{quote}' + CR) if self.table: self.table.has_problematic = True @@ -1868,7 +1858,7 @@ class LaTeXTranslator(SphinxTranslator): isinstance(child, nodes.enumerated_list): done = 1 if not done: - self.body.append('\\end{quote}' + CR) + self.body.append(r'\end{quote}' + CR) # option node handling copied from docutils' latex writer @@ -1889,7 +1879,7 @@ class LaTeXTranslator(SphinxTranslator): pass def visit_option_group(self, node: Element) -> None: - self.body.append('\\item [') + self.body.append(r'\item [') # flag for first option self.context.append(0) @@ -1898,12 +1888,12 @@ class LaTeXTranslator(SphinxTranslator): self.body.append('] ') def visit_option_list(self, node: Element) -> None: - self.body.append('\\begin{optionlist}{3cm}' + CR) + self.body.append(r'\begin{optionlist}{3cm}' + CR) if self.table: self.table.has_problematic = True def depart_option_list(self, node: Element) -> None: - self.body.append('\\end{optionlist}' + CR) + self.body.append(r'\end{optionlist}' + CR) def visit_option_list_item(self, node: Element) -> None: pass @@ -1923,13 +1913,13 @@ class LaTeXTranslator(SphinxTranslator): pass def visit_superscript(self, node: Element) -> None: - self.body.append('$^{\\text{') + self.body.append(r'$^{\text{') def depart_superscript(self, node: Element) -> None: self.body.append('}}$') def visit_subscript(self, node: Element) -> None: - self.body.append('$_{\\text{') + self.body.append(r'$_{\text{') def depart_subscript(self, node: Element) -> None: self.body.append('}}$') @@ -1996,7 +1986,7 @@ class LaTeXTranslator(SphinxTranslator): if self.literal_whitespace: # Insert a blank before the newline, to avoid # ! LaTeX Error: There's no line here to end. - text = text.replace(CR, '~\\\\' + CR).replace(' ', '~') + text = text.replace(CR, r'~\\' + CR).replace(' ', '~') return text def encode_uri(self, text: str) -> str: @@ -2004,9 +1994,9 @@ class LaTeXTranslator(SphinxTranslator): # this must be checked against hyperref package exact dealings # mainly, %, #, {, } and \ need escaping via a \ escape # in \href, the tilde is allowed and must be represented literally - return self.encode(text).replace('\\textasciitilde{}', '~').\ - replace('\\sphinxhyphen{}', '-').\ - replace('\\textquotesingle{}', "'") + return self.encode(text).replace(r'\textasciitilde{}', '~').\ + replace(r'\sphinxhyphen{}', '-').\ + replace(r'\textquotesingle{}', "'") def visit_Text(self, node: Text) -> None: text = self.encode(node.astext()) @@ -2071,120 +2061,6 @@ class LaTeXTranslator(SphinxTranslator): def unknown_visit(self, node: Node) -> None: raise NotImplementedError('Unknown node: ' + node.__class__.__name__) - # --------- METHODS FOR COMPATIBILITY -------------------------------------- - - def collect_footnotes(self, node: Element) -> Dict[str, List[Union["collected_footnote", bool]]]: # NOQA - def footnotes_under(n: Element) -> Iterator[nodes.footnote]: - if isinstance(n, nodes.footnote): - yield n - else: - for c in n.children: - if isinstance(c, addnodes.start_of_file): - continue - elif isinstance(c, nodes.Element): - yield from footnotes_under(c) - - warnings.warn('LaTeXWriter.collected_footnote() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - fnotes = {} # type: Dict[str, List[Union[collected_footnote, bool]]] - for fn in footnotes_under(node): - label = cast(nodes.label, fn[0]) - num = label.astext().strip() - newnode = collected_footnote('', *fn.children, number=num) - fnotes[num] = [newnode, False] - return fnotes - - @property - def no_contractions(self) -> int: - warnings.warn('LaTeXTranslator.no_contractions is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - return 0 - - def babel_defmacro(self, name: str, definition: str) -> str: - warnings.warn('babel_defmacro() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - - if self.elements['babel']: - prefix = '\\addto\\extras%s{' % self.babel.get_language() - suffix = '}' - else: # babel is disabled (mainly for Japanese environment) - prefix = '' - suffix = '' - - return ('%s\\def%s{%s}%s' % (prefix, name, definition, suffix) + CR) - - def generate_numfig_format(self, builder: "LaTeXBuilder") -> str: - warnings.warn('generate_numfig_format() is deprecated.', - RemovedInSphinx40Warning, stacklevel=2) - ret = [] # type: List[str] - figure = self.config.numfig_format['figure'].split('%s', 1) - if len(figure) == 1: - ret.append('\\def\\fnum@figure{%s}' % self.escape(figure[0]).strip() + CR) - else: - definition = escape_abbr(self.escape(figure[0])) - ret.append(self.babel_renewcommand('\\figurename', definition)) - ret.append('\\makeatletter' + CR) - ret.append('\\def\\fnum@figure{\\figurename\\thefigure{}%s}' % - self.escape(figure[1]) + CR) - ret.append('\\makeatother' + CR) - - table = self.config.numfig_format['table'].split('%s', 1) - if len(table) == 1: - ret.append('\\def\\fnum@table{%s}' % self.escape(table[0]).strip() + CR) - else: - definition = escape_abbr(self.escape(table[0])) - ret.append(self.babel_renewcommand('\\tablename', definition)) - ret.append('\\makeatletter' + CR) - ret.append('\\def\\fnum@table{\\tablename\\thetable{}%s}' % - self.escape(table[1]) + CR) - ret.append('\\makeatother' + CR) - - codeblock = self.config.numfig_format['code-block'].split('%s', 1) - if len(codeblock) == 1: - pass # FIXME - else: - definition = self.escape(codeblock[0]).strip() - ret.append(self.babel_renewcommand('\\literalblockname', definition)) - if codeblock[1]: - pass # FIXME - - return ''.join(ret) - - -# Import old modules here for compatibility -from sphinx.builders.latex import constants # NOQA -from sphinx.builders.latex.util import ExtBabel # NOQA - -deprecated_alias('sphinx.writers.latex', - { - 'ADDITIONAL_SETTINGS': constants.ADDITIONAL_SETTINGS, - 'DEFAULT_SETTINGS': constants.DEFAULT_SETTINGS, - 'LUALATEX_DEFAULT_FONTPKG': constants.LUALATEX_DEFAULT_FONTPKG, - 'PDFLATEX_DEFAULT_FONTPKG': constants.PDFLATEX_DEFAULT_FONTPKG, - 'SHORTHANDOFF': constants.SHORTHANDOFF, - 'XELATEX_DEFAULT_FONTPKG': constants.XELATEX_DEFAULT_FONTPKG, - 'XELATEX_GREEK_DEFAULT_FONTPKG': constants.XELATEX_GREEK_DEFAULT_FONTPKG, - 'ExtBabel': ExtBabel, - }, - RemovedInSphinx40Warning, - { - 'ADDITIONAL_SETTINGS': - 'sphinx.builders.latex.constants.ADDITIONAL_SETTINGS', - 'DEFAULT_SETTINGS': - 'sphinx.builders.latex.constants.DEFAULT_SETTINGS', - 'LUALATEX_DEFAULT_FONTPKG': - 'sphinx.builders.latex.constants.LUALATEX_DEFAULT_FONTPKG', - 'PDFLATEX_DEFAULT_FONTPKG': - 'sphinx.builders.latex.constants.PDFLATEX_DEFAULT_FONTPKG', - 'SHORTHANDOFF': - 'sphinx.builders.latex.constants.SHORTHANDOFF', - 'XELATEX_DEFAULT_FONTPKG': - 'sphinx.builders.latex.constants.XELATEX_DEFAULT_FONTPKG', - 'XELATEX_GREEK_DEFAULT_FONTPKG': - 'sphinx.builders.latex.constants.XELATEX_GREEK_DEFAULT_FONTPKG', - 'ExtBabel': 'sphinx.builders.latex.util.ExtBabel', - }) # FIXME: Workaround to avoid circular import # refs: https://github.com/sphinx-doc/sphinx/issues/5433 diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 9ef429ba3..c11c49892 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -8,7 +8,6 @@ :license: BSD, see LICENSE for details. """ -import warnings from typing import Any, Dict, Iterable, cast from docutils import nodes @@ -18,7 +17,6 @@ from docutils.writers.manpage import Writer from sphinx import addnodes from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import _, admonitionlabels from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator @@ -75,16 +73,9 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator): Custom translator. """ - _docinfo = {} # type: Dict[str, Any] + _docinfo: Dict[str, Any] = {} - def __init__(self, *args: Any) -> None: - if isinstance(args[0], nodes.document) and isinstance(args[1], Builder): - document, builder = args - else: - warnings.warn('The order of arguments for ManualPageTranslator has been changed. ' - 'Please give "document" as 1st and "builder" as 2nd.', - RemovedInSphinx40Warning, stacklevel=2) - builder, document = args + def __init__(self, document: nodes.document, builder: Builder) -> None: super().__init__(document, builder) self.in_productionlist = 0 diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 6518d10da..cad6f3685 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -12,8 +12,8 @@ import re import textwrap import warnings from os import path -from typing import (Any, Dict, Iterable, Iterator, List, Optional, Pattern, Set, Tuple, Union, - cast) +from typing import (TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional, Pattern, Set, + Tuple, Union, cast) from docutils import nodes, writers from docutils.nodes import Element, Node, Text @@ -29,8 +29,7 @@ from sphinx.util.docutils import SphinxTranslator from sphinx.util.i18n import format_date from sphinx.writers.latex import collected_footnote -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.builders.texinfo import TexinfoBuilder @@ -118,17 +117,17 @@ class TexinfoWriter(writers.Writer): """Texinfo writer for generating Texinfo documents.""" supported = ('texinfo', 'texi') - settings_spec = ( + settings_spec: Tuple[str, Any, Tuple[Tuple[str, List[str], Dict[str, str]], ...]] = ( 'Texinfo Specific Options', None, ( ("Name of the Info file", ['--texinfo-filename'], {'default': ''}), ('Dir entry', ['--texinfo-dir-entry'], {'default': ''}), ('Description', ['--texinfo-dir-description'], {'default': ''}), ('Category', ['--texinfo-dir-category'], {'default': - 'Miscellaneous'}))) # type: Tuple[str, Any, Tuple[Tuple[str, List[str], Dict[str, str]], ...]] # NOQA + 'Miscellaneous'}))) - settings_defaults = {} # type: Dict + settings_defaults: Dict = {} - output = None # type: str + output: str = None visitor_attributes = ('output', 'fragment') @@ -147,7 +146,7 @@ class TexinfoWriter(writers.Writer): class TexinfoTranslator(SphinxTranslator): - builder = None # type: TexinfoBuilder + builder: "TexinfoBuilder" = None ignore_missing_images = False default_elements = { @@ -169,40 +168,34 @@ class TexinfoTranslator(SphinxTranslator): super().__init__(document, builder) self.init_settings() - self.written_ids = set() # type: Set[str] - # node names and anchors in output + self.written_ids: Set[str] = set() # node names and anchors in output # node names and anchors that should be in output - self.referenced_ids = set() # type: Set[str] - self.indices = [] # type: List[Tuple[str, str]] - # (node name, content) - self.short_ids = {} # type: Dict[str, str] - # anchors --> short ids - self.node_names = {} # type: Dict[str, str] - # node name --> node's name to display - self.node_menus = {} # type: Dict[str, List[str]] - # node name --> node's menu entries - self.rellinks = {} # type: Dict[str, List[str]] - # node name --> (next, previous, up) + self.referenced_ids: Set[str] = set() + self.indices: List[Tuple[str, str]] = [] # (node name, content) + self.short_ids: Dict[str, str] = {} # anchors --> short ids + self.node_names: Dict[str, str] = {} # node name --> node's name to display + self.node_menus: Dict[str, List[str]] = {} # node name --> node's menu entries + self.rellinks: Dict[str, List[str]] = {} # node name --> (next, previous, up) self.collect_indices() self.collect_node_names() self.collect_node_menus() self.collect_rellinks() - self.body = [] # type: List[str] - self.context = [] # type: List[str] - self.descs = [] # type: List[addnodes.desc] - self.previous_section = None # type: nodes.section + self.body: List[str] = [] + self.context: List[str] = [] + self.descs: List[addnodes.desc] = [] + self.previous_section: nodes.section = None self.section_level = 0 self.seen_title = False - self.next_section_ids = set() # type: Set[str] + self.next_section_ids: Set[str] = set() self.escape_newlines = 0 self.escape_hyphens = 0 - self.curfilestack = [] # type: List[str] - self.footnotestack = [] # type: List[Dict[str, List[Union[collected_footnote, bool]]]] # NOQA + self.curfilestack: List[str] = [] + self.footnotestack: List[Dict[str, List[Union[collected_footnote, bool]]]] = [] # NOQA self.in_footnote = 0 - self.handled_abbrs = set() # type: Set[str] - self.colwidths = None # type: List[int] + self.handled_abbrs: Set[str] = set() + self.colwidths: List[int] = None def finish(self) -> None: if self.previous_section is None: @@ -241,7 +234,7 @@ class TexinfoTranslator(SphinxTranslator): language=self.config.language)) }) # title - title = self.settings.title # type: str + title: str = self.settings.title if not title: title_node = self.document.next_node(nodes.title) title = title_node.astext() if title_node else '<untitled>' @@ -300,7 +293,7 @@ class TexinfoTranslator(SphinxTranslator): def collect_node_menus(self) -> None: """Collect the menu entries for each "node" section.""" node_menus = self.node_menus - targets = [self.document] # type: List[Element] + targets: List[Element] = [self.document] targets.extend(self.document.traverse(nodes.section)) for node in targets: assert 'node_name' in node and node['node_name'] @@ -518,7 +511,7 @@ class TexinfoTranslator(SphinxTranslator): continue elif isinstance(c, nodes.Element): yield from footnotes_under(c) - fnotes = {} # type: Dict[str, List[Union[collected_footnote, bool]]] + fnotes: Dict[str, List[Union[collected_footnote, bool]]] = {} for fn in footnotes_under(node): label = cast(nodes.label, fn[0]) num = label.astext().strip() diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index c0ebe32a2..5bd96de76 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -12,7 +12,8 @@ import os import re import textwrap from itertools import chain, groupby -from typing import Any, Dict, Generator, Iterable, List, Optional, Set, Tuple, Union, cast +from typing import (TYPE_CHECKING, Any, Dict, Generator, Iterable, List, Optional, Set, Tuple, + Union, cast) from docutils import nodes, writers from docutils.nodes import Element, Node, Text @@ -22,8 +23,7 @@ from sphinx import addnodes from sphinx.locale import _, admonitionlabels from sphinx.util.docutils import SphinxTranslator -if False: - # For type annotation +if TYPE_CHECKING: from sphinx.builders.text import TextBuilder @@ -33,11 +33,11 @@ class Cell: """ def __init__(self, text: str = "", rowspan: int = 1, colspan: int = 1) -> None: self.text = text - self.wrapped = [] # type: List[str] + self.wrapped: List[str] = [] self.rowspan = rowspan self.colspan = colspan - self.col = None # type: Optional[int] - self.row = None # type: Optional[int] + self.col: Optional[int] = None + self.row: Optional[int] = None def __repr__(self) -> str: return "<Cell {!r} {}v{}/{}>{}>".format( @@ -98,10 +98,9 @@ class Table: """ def __init__(self, colwidth: List[int] = None) -> None: - self.lines = [] # type: List[List[Cell]] + self.lines: List[List[Cell]] = [] self.separator = 0 - self.colwidth = (colwidth if colwidth is not None - else []) # type: List[int] + self.colwidth: List[int] = (colwidth if colwidth is not None else []) self.current_line = 0 self.current_col = 0 @@ -168,7 +167,7 @@ class Table: @property def cells(self) -> Generator[Cell, None, None]: - seen = set() # type: Set[Cell] + seen: Set[Cell] = set() for lineno, line in enumerate(self.lines): for colno, cell in enumerate(line): if cell and cell not in seen: @@ -205,7 +204,7 @@ class Table: """Called on the line *before* lineno. Called with no *lineno* for the last sep. """ - out = [] # type: List[str] + out: List[str] = [] for colno, width in enumerate(self.measured_widths): if ( lineno is not None and @@ -267,7 +266,7 @@ class TextWrapper(textwrap.TextWrapper): The original _wrap_chunks uses len() to calculate width. This method respects wide/fullwidth characters for width adjustment. """ - lines = [] # type: List[str] + lines: List[str] = [] if self.width <= 0: raise ValueError("invalid width %r (must be > 0)" % self.width) @@ -328,7 +327,7 @@ class TextWrapper(textwrap.TextWrapper): """ def split(t: str) -> List[str]: return super(TextWrapper, self)._split(t) - chunks = [] # type: List[str] + chunks: List[str] = [] for chunk in split(text): for w, g in groupby(chunk, column_width): if w == 1: @@ -367,9 +366,9 @@ def my_wrap(text: str, width: int = MAXWIDTH, **kwargs: Any) -> List[str]: class TextWriter(writers.Writer): supported = ('text',) settings_spec = ('No options here.', '', ()) - settings_defaults = {} # type: Dict + settings_defaults: Dict = {} - output = None # type: str + output: str = None def __init__(self, builder: "TextBuilder") -> None: super().__init__() @@ -382,7 +381,7 @@ class TextWriter(writers.Writer): class TextTranslator(SphinxTranslator): - builder = None # type: TextBuilder + builder: "TextBuilder" = None def __init__(self, document: nodes.document, builder: "TextBuilder") -> None: super().__init__(document, builder) @@ -397,12 +396,12 @@ class TextTranslator(SphinxTranslator): self.sectionchars = self.config.text_sectionchars self.add_secnumbers = self.config.text_add_secnumbers self.secnumber_suffix = self.config.text_secnumber_suffix - self.states = [[]] # type: List[List[Tuple[int, Union[str, List[str]]]]] + self.states: List[List[Tuple[int, Union[str, List[str]]]]] = [[]] self.stateindent = [0] - self.list_counter = [] # type: List[int] + self.list_counter: List[int] = [] self.sectionlevel = 0 self.lineblocklevel = 0 - self.table = None # type: Table + self.table: Table = None def add_text(self, text: str) -> None: self.states[-1].append((-1, text)) @@ -415,8 +414,8 @@ class TextTranslator(SphinxTranslator): content = self.states.pop() maxindent = sum(self.stateindent) indent = self.stateindent.pop() - result = [] # type: List[Tuple[int, List[str]]] - toformat = [] # type: List[str] + result: List[Tuple[int, List[str]]] = [] + toformat: List[str] = [] def do_format() -> None: if not toformat: diff --git a/tests/roots/test-directive-csv-table/conf.py b/tests/roots/test-directive-csv-table/conf.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/roots/test-directive-csv-table/conf.py diff --git a/tests/roots/test-directive-csv-table/example.csv b/tests/roots/test-directive-csv-table/example.csv new file mode 100644 index 000000000..eb039aa9b --- /dev/null +++ b/tests/roots/test-directive-csv-table/example.csv @@ -0,0 +1 @@ +foo,bar,baz diff --git a/tests/roots/test-directive-csv-table/subdir/example.csv b/tests/roots/test-directive-csv-table/subdir/example.csv new file mode 100644 index 000000000..32fe56fad --- /dev/null +++ b/tests/roots/test-directive-csv-table/subdir/example.csv @@ -0,0 +1 @@ +FOO,BAR,BAZ diff --git a/tests/roots/test-domain-c/anon-dup-decl.rst b/tests/roots/test-domain-c/anon-dup-decl.rst index 5f6c3bdfe..743ae2f6a 100644 --- a/tests/roots/test-domain-c/anon-dup-decl.rst +++ b/tests/roots/test-domain-c/anon-dup-decl.rst @@ -1,3 +1,5 @@ +.. c:namespace:: anon_dup_decl_ns + .. c:struct:: anon_dup_decl .. c:struct:: @a.A diff --git a/tests/roots/test-domain-c/function_param_target.rst b/tests/roots/test-domain-c/function_param_target.rst index 05de01445..d316d7bcd 100644 --- a/tests/roots/test-domain-c/function_param_target.rst +++ b/tests/roots/test-domain-c/function_param_target.rst @@ -1,3 +1,5 @@ +.. c:namespace:: function_param_target + .. c:function:: void f(int i) - :c:var:`i` diff --git a/tests/roots/test-domain-c/index.rst b/tests/roots/test-domain-c/index.rst index 7e2c18be9..4febd63ef 100644 --- a/tests/roots/test-domain-c/index.rst +++ b/tests/roots/test-domain-c/index.rst @@ -1,3 +1,5 @@ +.. c:namespace:: index + test-domain-c ============= diff --git a/tests/roots/test-domain-c/semicolon.rst b/tests/roots/test-domain-c/semicolon.rst deleted file mode 100644 index 14ba17756..000000000 --- a/tests/roots/test-domain-c/semicolon.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. c:member:: int member; -.. c:var:: int var; -.. c:function:: void f(); -.. .. c:macro:: NO_SEMICOLON; -.. c:struct:: Struct; -.. c:union:: Union; -.. c:enum:: Enum; -.. c:enumerator:: Enumerator; -.. c:type:: Type; -.. c:type:: int TypeDef; diff --git a/tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py b/tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py new file mode 100644 index 000000000..c81bfe4c7 --- /dev/null +++ b/tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py @@ -0,0 +1 @@ +python_use_unqualified_type_names = True diff --git a/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst b/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst new file mode 100644 index 000000000..599206d8c --- /dev/null +++ b/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst @@ -0,0 +1,8 @@ +domain-py-smart_reference +========================= + +.. py:class:: Name + :module: foo + + +.. py:function:: hello(name: foo.Name, age: foo.Age) diff --git a/tests/roots/test-domain-py/module.rst b/tests/roots/test-domain-py/module.rst index dce3fa5ac..4a2806812 100644 --- a/tests/roots/test-domain-py/module.rst +++ b/tests/roots/test-domain-py/module.rst @@ -18,8 +18,7 @@ module * Link to :py:meth:`module_a.submodule.ModTopLevel.mod_child_1` -.. py:method:: ModTopLevel.prop - :property: +.. py:property:: ModTopLevel.prop * Link to :py:attr:`prop attribute <.prop>` * Link to :py:meth:`prop method <.prop>` diff --git a/tests/roots/test-ext-autodoc/target/canonical/__init__.py b/tests/roots/test-ext-autodoc/target/canonical/__init__.py new file mode 100644 index 000000000..4ca2b339c --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/canonical/__init__.py @@ -0,0 +1 @@ +from target.canonical.original import Bar, Foo diff --git a/tests/roots/test-ext-autodoc/target/canonical/original.py b/tests/roots/test-ext-autodoc/target/canonical/original.py new file mode 100644 index 000000000..42049b216 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/canonical/original.py @@ -0,0 +1,15 @@ +class Foo: + """docstring""" + + def meth(self): + """docstring""" + + +def bar(): + class Bar: + """docstring""" + + return Bar + + +Bar = bar() diff --git a/tests/roots/test-ext-autodoc/target/docstring_signature.py b/tests/roots/test-ext-autodoc/target/docstring_signature.py index 244109629..d9deb6244 100644 --- a/tests/roots/test-ext-autodoc/target/docstring_signature.py +++ b/tests/roots/test-ext-autodoc/target/docstring_signature.py @@ -23,3 +23,9 @@ class E: def __init__(self): """E(foo: int, bar: int, baz: int) -> None \\ E(foo: str, bar: str, baz: str) -> None""" + + +class F: + def __init__(self): + """F(foo: int, bar: int, baz: int) -> None + F(foo: str, bar: str, baz: str) -> None""" diff --git a/tests/roots/test-ext-autodoc/target/preserve_defaults.py b/tests/roots/test-ext-autodoc/target/preserve_defaults.py new file mode 100644 index 000000000..c927fa035 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/preserve_defaults.py @@ -0,0 +1,19 @@ +from datetime import datetime +from typing import Any + +CONSTANT = 'foo' +SENTINEL = object() + + +def foo(name: str = CONSTANT, + sentinal: Any = SENTINEL, + now: datetime = datetime.now()) -> None: + """docstring""" + + +class Class: + """docstring""" + + def meth(self, name: str = CONSTANT, sentinal: Any = SENTINEL, + now: datetime = datetime.now()) -> None: + """docstring""" diff --git a/tests/roots/test-ext-autodoc/target/properties.py b/tests/roots/test-ext-autodoc/target/properties.py new file mode 100644 index 000000000..409fc2b5d --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/properties.py @@ -0,0 +1,6 @@ +class Foo: + """docstring""" + + @property + def prop(self) -> int: + """docstring""" diff --git a/tests/roots/test-ext-autodoc/target/typehints.py b/tests/roots/test-ext-autodoc/target/typehints.py index 2c9039650..bb56054c3 100644 --- a/tests/roots/test-ext-autodoc/target/typehints.py +++ b/tests/roots/test-ext-autodoc/target/typehints.py @@ -68,3 +68,13 @@ def missing_attr(c, ): # type: (...) -> str return a + (b or "") + + +class _ClassWithDocumentedInit: + """Class docstring.""" + + def __init__(self, x: int) -> None: + """Init docstring. + + :param x: Some integer + """ diff --git a/tests/roots/test-ext-autodoc/target/typevar.py b/tests/roots/test-ext-autodoc/target/typevar.py index 864fea20c..c330e2d88 100644 --- a/tests/roots/test-ext-autodoc/target/typevar.py +++ b/tests/roots/test-ext-autodoc/target/typevar.py @@ -17,6 +17,9 @@ T5 = TypeVar("T5", contravariant=True) #: T6 T6 = NewType("T6", int) +#: T7 +T7 = TypeVar("T7", bound=int) + class Class: #: T1 diff --git a/tests/roots/test-ext-doctest-skipif/conf.py b/tests/roots/test-ext-doctest-skipif/conf.py index c863dbc01..6f5498256 100644 --- a/tests/roots/test-ext-doctest-skipif/conf.py +++ b/tests/roots/test-ext-doctest-skipif/conf.py @@ -1,7 +1,7 @@ extensions = ['sphinx.ext.doctest'] project = 'test project for the doctest :skipif: directive' -master_doc = 'skipif' +root_doc = 'skipif' source_suffix = '.txt' exclude_patterns = ['_build'] diff --git a/tests/roots/test-ext-doctest/conf.py b/tests/roots/test-ext-doctest/conf.py index fcf6a6cda..d0e8b107b 100644 --- a/tests/roots/test-ext-doctest/conf.py +++ b/tests/roots/test-ext-doctest/conf.py @@ -1,6 +1,6 @@ extensions = ['sphinx.ext.doctest'] project = 'test project for doctest' -master_doc = 'doctest' +root_doc = 'doctest' source_suffix = '.txt' exclude_patterns = ['_build'] diff --git a/tests/roots/test-intl/xx/LC_MESSAGES/figure.po b/tests/roots/test-intl/xx/LC_MESSAGES/figure.po index 449b15e3f..64bbdf763 100644 --- a/tests/roots/test-intl/xx/LC_MESSAGES/figure.po +++ b/tests/roots/test-intl/xx/LC_MESSAGES/figure.po @@ -37,19 +37,17 @@ msgstr "BLOCK" msgid "image url and alt" msgstr "IMAGE URL AND ALT" -msgid "" -".. image:: img.png\n" -" :alt: img" -msgstr "" -".. image:: i18n.png\n" -" :alt: IMG -> I18N" +msgid "img" +msgstr "IMG -> I18N" -msgid "" -".. image:: i18n.png\n" -" :alt: i18n" -msgstr "" -".. image:: img.png\n" -" :alt: I18N -> IMG" +msgid ".. image:: img.png" +msgstr ".. image:: i18n.png" + +msgid "i18n" +msgstr "I18N -> IMG" + +msgid ".. image:: i18n.png" +msgstr ".. image:: img.png" msgid "image on substitution" msgstr "IMAGE ON SUBSTITUTION" diff --git a/tests/roots/test-latex-equations/conf.py b/tests/roots/test-latex-equations/conf.py index 0957543e6..d851892ba 100644 --- a/tests/roots/test-latex-equations/conf.py +++ b/tests/roots/test-latex-equations/conf.py @@ -1,2 +1,2 @@ -master_doc = 'equations' +root_doc = 'equations' extensions = ['sphinx.ext.imgmath'] diff --git a/tests/roots/test-latex-includegraphics/conf.py b/tests/roots/test-latex-includegraphics/conf.py index b53df86a2..65c19ab85 100644 --- a/tests/roots/test-latex-includegraphics/conf.py +++ b/tests/roots/test-latex-includegraphics/conf.py @@ -1,5 +1,3 @@ -master_doc = 'index' - exclude_patterns = ['_build'] latex_elements = { diff --git a/tests/roots/test-linkcheck/conf.py b/tests/roots/test-linkcheck/conf.py index ae8ef24b7..ac54f73b0 100644 --- a/tests/roots/test-linkcheck/conf.py +++ b/tests/roots/test-linkcheck/conf.py @@ -1,4 +1,4 @@ -master_doc = 'links' +root_doc = 'links' source_suffix = '.txt' exclude_patterns = ['_build'] linkcheck_anchors = True diff --git a/tests/test_build.py b/tests/test_build.py index bd2523b3d..b0d1896b9 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -47,12 +47,12 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir): ======================= """)) - master_doc = srcdir / 'index.txt' - master_doc.write_text(master_doc.read_text() + dedent(""" - .. toctree:: + root_doc = srcdir / 'index.txt' + root_doc.write_text(root_doc.read_text() + dedent(""" + .. toctree:: - %(test_name)s/%(test_name)s - """ % {'test_name': test_name})) + %(test_name)s/%(test_name)s + """ % {'test_name': test_name})) return srcdir @@ -70,7 +70,7 @@ def test_build_all(requests_head, make_app, nonascii_srcdir, buildername): app.build() -def test_master_doc_not_found(tempdir, make_app): +def test_root_doc_not_found(tempdir, make_app): (tempdir / 'conf.py').write_text('') assert tempdir.listdir() == ['conf.py'] diff --git a/tests/test_build_epub.py b/tests/test_build_epub.py index 982f4c1b9..c468b093d 100644 --- a/tests/test_build_epub.py +++ b/tests/test_build_epub.py @@ -15,8 +15,6 @@ from xml.etree import ElementTree import pytest -from sphinx.util import docutils - # check given command is runnable def runnable(command): @@ -355,8 +353,6 @@ def test_epub_css_files(app): 'href="https://example.com/custom.css" />' not in content) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('epub', testroot='roles-download') def test_html_download_role(app, status, warning): app.build() diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 31c73c25f..b01d6f4eb 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -419,8 +419,6 @@ def test_html4_output(app, status, warning): (".//a[@href='_sources/otherext.foo.txt']", ''), ] })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', tags=['testtag'], confoverrides={'html_context.hckey_co': 'hcval_co'}) @pytest.mark.test_params(shared_result='test_build_html_output') @@ -435,8 +433,6 @@ def test_html_parallel(app): app.build() -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_build_html_output') def test_html_download(app): @@ -461,8 +457,6 @@ def test_html_download(app): assert matched.group(1) == filename -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='roles-download') def test_html_download_role(app, status, warning): app.build() @@ -541,8 +535,6 @@ def test_html_translator(app): (".//h1//span[@class='section-number']", '2.1.1. ', True), ], })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='tocdepth') @pytest.mark.test_params(shared_result='test_build_html_tocdepth') def test_tocdepth(app, cached_etree_parse, fname, expect): @@ -588,8 +580,6 @@ def test_tocdepth(app, cached_etree_parse, fname, expect): (".//h4//span[@class='section-number']", '2.1.1. ', True), ], })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('singlehtml', testroot='tocdepth') @pytest.mark.test_params(shared_result='test_build_html_tocdepth') def test_tocdepth_singlehtml(app, cached_etree_parse, fname, expect): @@ -643,8 +633,6 @@ def test_numfig_disabled_warn(app, warning): "span[@class='caption-number']", None, True), ], })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='numfig') @pytest.mark.test_params(shared_result='test_build_html_numfig') def test_numfig_disabled(app, cached_etree_parse, fname, expect): @@ -741,8 +729,6 @@ def test_numfig_without_numbered_toctree_warn(app, warning): "span[@class='caption-number']", '^Listing 6 $', True), ], })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx( 'html', testroot='numfig', srcdir='test_numfig_without_numbered_toctree', @@ -839,8 +825,6 @@ def test_numfig_with_numbered_toctree_warn(app, warning): "span[@class='caption-number']", '^Listing 2.2 $', True), ], })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) @pytest.mark.test_params(shared_result='test_build_html_numfig_on') def test_numfig_with_numbered_toctree(app, cached_etree_parse, fname, expect): @@ -934,8 +918,6 @@ def test_numfig_with_prefix_warn(app, warning): "span[@class='caption-number']", '^Code-2.2 $', True), ], })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True, 'numfig_format': {'figure': 'Figure:%s', @@ -1030,8 +1012,6 @@ def test_numfig_with_secnum_depth_warn(app, warning): "span[@class='caption-number']", '^Listing 2.1.2 $', True), ], })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True, 'numfig_secnum_depth': 2}) @@ -1105,8 +1085,6 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect): "span[@class='caption-number']", '^Listing 2.2 $', True), ], })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('singlehtml', testroot='numfig', confoverrides={'numfig': True}) @pytest.mark.test_params(shared_result='test_build_html_numfig_on') def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect): @@ -1128,8 +1106,6 @@ def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect): (".//li/p/a/span", 'No.2', True), ], })) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='add_enumerable_node', srcdir='test_enumerable_node') def test_enumerable_node(app, cached_etree_parse, fname, expect): @@ -1196,7 +1172,7 @@ def test_assets_order(app): content = (app.outdir / 'index.html').read_text() # css_files - expected = ['_static/pygments.css', '_static/alabaster.css', '_static/early.css', + expected = ['_static/early.css', '_static/pygments.css', '_static/alabaster.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) @@ -1339,8 +1315,8 @@ def test_alternate_stylesheets(app, cached_etree_parse, fname, expect): def test_html_style(app, status, warning): app.build() result = (app.outdir / 'index.html').read_text() - assert '<link rel="stylesheet" href="_static/default.css" type="text/css" />' in result - assert ('<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />' + assert '<link rel="stylesheet" type="text/css" href="_static/default.css" />' in result + assert ('<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />' not in result) diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index cc79580f6..bb1904d2c 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -21,7 +21,6 @@ from sphinx.builders.latex import default_latex_documents from sphinx.config import Config from sphinx.errors import SphinxError from sphinx.testing.util import strip_escseq -from sphinx.util import docutils from sphinx.util.osutil import cd, ensuredir from sphinx.writers.latex import LaTeXTranslator @@ -198,7 +197,7 @@ def test_latex_basic_manual_ja(app, status, warning): app.builder.build_all() result = (app.outdir / 'test.tex').read_text(encoding='utf8') print(result) - assert r'\def\sphinxdocclass{jsbook}' in result + assert r'\def\sphinxdocclass{ujbook}' in result assert r'\documentclass[letterpaper,10pt,dvipdfmx]{sphinxmanual}' in result @@ -211,7 +210,7 @@ def test_latex_basic_howto_ja(app, status, warning): app.builder.build_all() result = (app.outdir / 'test.tex').read_text(encoding='utf8') print(result) - assert r'\def\sphinxdocclass{jreport}' in result + assert r'\def\sphinxdocclass{ujreport}' in result assert r'\documentclass[letterpaper,10pt,dvipdfmx]{sphinxhowto}' in result @@ -278,14 +277,14 @@ def test_latex_title_after_admonitions(app, status, warning): @pytest.mark.sphinx('latex', testroot='basic', - confoverrides={'release': '1.0'}) + confoverrides={'release': '1.0_0'}) def test_latex_release(app, status, warning): app.builder.build_all() result = (app.outdir / 'test.tex').read_text() print(result) print(status.getvalue()) print(warning.getvalue()) - assert r'\release{1.0}' in result + assert r'\release{1.0\_0}' in result assert r'\renewcommand{\releasename}{Release}' in result @@ -525,7 +524,7 @@ def test_babel_with_no_language_settings(app, status, warning): print(warning.getvalue()) assert '\\documentclass[letterpaper,10pt,english]{sphinxmanual}' in result assert '\\usepackage{babel}' in result - assert '\\usepackage{times}' in result + assert '\\usepackage{tgtermes}' in result assert '\\usepackage[Bjarne]{fncychap}' in result assert ('\\addto\\captionsenglish{\\renewcommand{\\contentsname}{Table of content}}\n' in result) @@ -550,7 +549,7 @@ def test_babel_with_language_de(app, status, warning): print(warning.getvalue()) assert '\\documentclass[letterpaper,10pt,ngerman]{sphinxmanual}' in result assert '\\usepackage{babel}' in result - assert '\\usepackage{times}' in result + assert '\\usepackage{tgtermes}' in result assert '\\usepackage[Sonny]{fncychap}' in result assert ('\\addto\\captionsngerman{\\renewcommand{\\contentsname}{Table of content}}\n' in result) @@ -575,7 +574,7 @@ def test_babel_with_language_ru(app, status, warning): print(warning.getvalue()) assert '\\documentclass[letterpaper,10pt,russian]{sphinxmanual}' in result assert '\\usepackage{babel}' in result - assert '\\usepackage{times}' not in result + assert '\\usepackage{tgtermes}' not in result assert '\\usepackage[Sonny]{fncychap}' in result assert ('\\addto\\captionsrussian{\\renewcommand{\\contentsname}{Table of content}}\n' in result) @@ -600,7 +599,7 @@ def test_babel_with_language_tr(app, status, warning): print(warning.getvalue()) assert '\\documentclass[letterpaper,10pt,turkish]{sphinxmanual}' in result assert '\\usepackage{babel}' in result - assert '\\usepackage{times}' in result + assert '\\usepackage{tgtermes}' in result assert '\\usepackage[Sonny]{fncychap}' in result assert ('\\addto\\captionsturkish{\\renewcommand{\\contentsname}{Table of content}}\n' in result) @@ -625,7 +624,7 @@ def test_babel_with_language_ja(app, status, warning): print(warning.getvalue()) assert '\\documentclass[letterpaper,10pt,dvipdfmx]{sphinxmanual}' in result assert '\\usepackage{babel}' not in result - assert '\\usepackage{times}' in result + assert '\\usepackage{tgtermes}' in result assert '\\usepackage[Sonny]{fncychap}' not in result assert '\\renewcommand{\\contentsname}{Table of content}\n' in result assert '\\shorthandoff' not in result @@ -649,7 +648,7 @@ def test_babel_with_unknown_language(app, status, warning): print(warning.getvalue()) assert '\\documentclass[letterpaper,10pt,english]{sphinxmanual}' in result assert '\\usepackage{babel}' in result - assert '\\usepackage{times}' in result + assert '\\usepackage{tgtermes}' in result assert '\\usepackage[Sonny]{fncychap}' in result assert ('\\addto\\captionsenglish{\\renewcommand{\\contentsname}{Table of content}}\n' in result) @@ -677,7 +676,7 @@ def test_polyglossia_with_language_de(app, status, warning): assert '\\documentclass[letterpaper,10pt,german]{sphinxmanual}' in result assert '\\usepackage{polyglossia}' in result assert '\\setmainlanguage[spelling=new]{german}' in result - assert '\\usepackage{times}' not in result + assert '\\usepackage{tgtermes}' not in result assert '\\usepackage[Sonny]{fncychap}' in result assert ('\\addto\\captionsgerman{\\renewcommand{\\contentsname}{Table of content}}\n' in result) @@ -703,7 +702,7 @@ def test_polyglossia_with_language_de_1901(app, status, warning): assert '\\documentclass[letterpaper,10pt,german]{sphinxmanual}' in result assert '\\usepackage{polyglossia}' in result assert '\\setmainlanguage[spelling=old]{german}' in result - assert '\\usepackage{times}' not in result + assert '\\usepackage{tgtermes}' not in result assert '\\usepackage[Sonny]{fncychap}' in result assert ('\\addto\\captionsgerman{\\renewcommand{\\contentsname}{Table of content}}\n' in result) @@ -1038,7 +1037,7 @@ def test_toctree_maxdepth_howto(app, status, warning): @pytest.mark.sphinx( 'latex', testroot='toctree-maxdepth', - confoverrides={'master_doc': 'foo'}) + confoverrides={'root_doc': 'foo'}) def test_toctree_not_found(app, status, warning): app.builder.build_all() result = (app.outdir / 'python.tex').read_text() @@ -1052,7 +1051,7 @@ def test_toctree_not_found(app, status, warning): @pytest.mark.sphinx( 'latex', testroot='toctree-maxdepth', - confoverrides={'master_doc': 'bar'}) + confoverrides={'root_doc': 'bar'}) def test_toctree_without_maxdepth(app, status, warning): app.builder.build_all() result = (app.outdir / 'python.tex').read_text() @@ -1065,7 +1064,7 @@ def test_toctree_without_maxdepth(app, status, warning): @pytest.mark.sphinx( 'latex', testroot='toctree-maxdepth', - confoverrides={'master_doc': 'qux'}) + confoverrides={'root_doc': 'qux'}) def test_toctree_with_deeper_maxdepth(app, status, warning): app.builder.build_all() result = (app.outdir / 'python.tex').read_text() @@ -1171,8 +1170,6 @@ def test_maxlistdepth_at_ten(app, status, warning): compile_latex_document(app, 'python.tex') -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('latex', testroot='latex-table') @pytest.mark.test_params(shared_result='latex-table') def test_latex_table_tabulars(app, status, warning): @@ -1242,8 +1239,6 @@ def test_latex_table_tabulars(app, status, warning): assert actual == expected -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('latex', testroot='latex-table') @pytest.mark.test_params(shared_result='latex-table') def test_latex_table_longtable(app, status, warning): @@ -1303,8 +1298,6 @@ def test_latex_table_longtable(app, status, warning): assert actual == expected -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('latex', testroot='latex-table') @pytest.mark.test_params(shared_result='latex-table') def test_latex_table_complex_tables(app, status, warning): @@ -1539,7 +1532,7 @@ def test_latex_figure_in_admonition(app, status, warning): def test_default_latex_documents(): from sphinx.util import texescape texescape.init() - config = Config({'master_doc': 'index', + config = Config({'root_doc': 'index', 'project': 'STASI™ Documentation', 'author': "Wolfgang Schäuble & G'Beckstein."}) config.init_values() diff --git a/tests/test_build_manpage.py b/tests/test_build_manpage.py index a017abc69..2affb8ba3 100644 --- a/tests/test_build_manpage.py +++ b/tests/test_build_manpage.py @@ -17,9 +17,9 @@ from sphinx.config import Config @pytest.mark.sphinx('man') def test_all(app, status, warning): app.builder.build_all() - assert (app.outdir / 'sphinxtests.1').exists() + assert (app.outdir / '1' / 'sphinxtests.1').exists() - content = (app.outdir / 'sphinxtests.1').read_text() + content = (app.outdir / '1' / 'sphinxtests.1').read_text() assert r'\fBprint \fP\fIi\fP\fB\en\fP' in content assert r'\fBmanpage\en\fP' in content @@ -31,16 +31,16 @@ def test_all(app, status, warning): @pytest.mark.sphinx('man', testroot='basic', - confoverrides={'man_make_section_directory': True}) + confoverrides={'man_make_section_directory': False}) def test_man_make_section_directory(app, status, warning): app.build() - assert (app.outdir / '1' / 'python.1').exists() + assert (app.outdir / 'python.1').exists() @pytest.mark.sphinx('man', testroot='directive-code') def test_captioned_code_block(app, status, warning): app.builder.build_all() - content = (app.outdir / 'python.1').read_text() + content = (app.outdir / '1' / 'python.1').read_text() assert ('.sp\n' 'caption \\fItest\\fP rb\n' @@ -71,5 +71,5 @@ def test_default_man_pages(): @pytest.mark.sphinx('man', testroot='markup-rubric') def test_rubric(app, status, warning): app.build() - content = (app.outdir / 'python.1').read_text() + content = (app.outdir / '1' / 'python.1').read_text() assert 'This is a rubric\n' in content diff --git a/tests/test_config.py b/tests/test_config.py index 9a0b617d5..a48e7ce30 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -20,7 +20,7 @@ from sphinx.testing.path import path @pytest.mark.sphinx(testroot='config', confoverrides={ - 'master_doc': 'master', + 'root_doc': 'root', 'nonexisting_value': 'True', 'latex_elements.maketitle': 'blah blah blah', 'modindex_common_prefix': 'path1,path2'}) @@ -33,7 +33,7 @@ def test_core_config(app, status, warning): assert cfg.templates_path == ['_templates'] # overrides - assert cfg.master_doc == 'master' + assert cfg.root_doc == 'root' assert cfg.latex_elements['maketitle'] == 'blah blah blah' assert cfg.modindex_common_prefix == ['path1', 'path2'] @@ -78,11 +78,11 @@ def test_extension_values(): config = Config() # check standard settings - assert config.master_doc == 'index' + assert config.root_doc == 'index' # can't override it by add_config_value() with pytest.raises(ExtensionError) as excinfo: - config.add('master_doc', 'index', 'env', None) + config.add('root_doc', 'index', 'env', None) assert 'already present' in str(excinfo.value) # add a new config value @@ -201,13 +201,13 @@ def test_config_eol(logger, tempdir): assert logger.called is False -@pytest.mark.sphinx(confoverrides={'master_doc': 123, +@pytest.mark.sphinx(confoverrides={'root_doc': 123, 'language': 'foo', 'primary_domain': None}) def test_builtin_conf(app, status, warning): warnings = warning.getvalue() - assert 'master_doc' in warnings, ( - 'override on builtin "master_doc" should raise a type warning') + assert 'root_doc' in warnings, ( + 'override on builtin "root_doc" should raise a type warning') assert 'language' not in warnings, ( 'explicitly permitted override on builtin "language" should NOT raise ' 'a type warning') diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index ee67b738a..a011a31e8 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -9,9 +9,7 @@ """ import os -from distutils.version import LooseVersion -import pygments import pytest from docutils import nodes @@ -423,87 +421,18 @@ def test_literal_include_block_start_with_comment_or_brank(app, status, warning) def test_literal_include_linenos(app, status, warning): app.builder.build(['linenos']) html = (app.outdir / 'linenos.html').read_text() - pygments_version = tuple(LooseVersion(pygments.__version__).version) # :linenos: - if pygments_version >= (2, 8): - assert ('<td class="linenos"><div class="linenodiv"><pre>' - '<span class="normal"> 1</span>\n' - '<span class="normal"> 2</span>\n' - '<span class="normal"> 3</span>\n' - '<span class="normal"> 4</span>\n' - '<span class="normal"> 5</span>\n' - '<span class="normal"> 6</span>\n' - '<span class="normal"> 7</span>\n' - '<span class="normal"> 8</span>\n' - '<span class="normal"> 9</span>\n' - '<span class="normal">10</span>\n' - '<span class="normal">11</span>\n' - '<span class="normal">12</span>\n' - '<span class="normal">13</span></pre></div></td>' in html) - else: - assert ('<td class="linenos"><div class="linenodiv"><pre>' - ' 1\n' - ' 2\n' - ' 3\n' - ' 4\n' - ' 5\n' - ' 6\n' - ' 7\n' - ' 8\n' - ' 9\n' - '10\n' - '11\n' - '12\n' - '13</pre></div></td>' in html) + assert ('<span class="linenos"> 1</span><span class="c1">' + '# Literally included file using Python highlighting</span>' in html) # :lineno-start: - if pygments_version >= (2, 8): - assert ('<td class="linenos"><div class="linenodiv"><pre>' - '<span class="normal">200</span>\n' - '<span class="normal">201</span>\n' - '<span class="normal">202</span>\n' - '<span class="normal">203</span>\n' - '<span class="normal">204</span>\n' - '<span class="normal">205</span>\n' - '<span class="normal">206</span>\n' - '<span class="normal">207</span>\n' - '<span class="normal">208</span>\n' - '<span class="normal">209</span>\n' - '<span class="normal">210</span>\n' - '<span class="normal">211</span>\n' - '<span class="normal">212</span></pre></div></td>' in html) - else: - assert ('<td class="linenos"><div class="linenodiv"><pre>' - '200\n' - '201\n' - '202\n' - '203\n' - '204\n' - '205\n' - '206\n' - '207\n' - '208\n' - '209\n' - '210\n' - '211\n' - '212</pre></div></td>' in html) - - # :lineno-match: - if pygments_version >= (2, 8): - assert ('<td class="linenos"><div class="linenodiv"><pre>' - '<span class="normal">5</span>\n' - '<span class="normal">6</span>\n' - '<span class="normal">7</span>\n' - '<span class="normal">8</span>\n' - '<span class="normal">9</span></pre></div></td>' in html) - else: - assert ('<td class="linenos"><div class="linenodiv"><pre>' - '5\n' - '6\n' - '7\n' - '8\n' - '9</pre></div></td>' in html) + assert ('<span class="linenos">200</span><span class="c1">' + '# Literally included file using Python highlighting</span>' in html) + + # :lines: 5-9 + assert ('<span class="linenos">5</span><span class="k">class</span> ' + '<span class="nc">Foo</span><span class="p">:</span>' in html) @pytest.mark.sphinx('latex', testroot='directive-code') @@ -637,82 +566,17 @@ def test_linenothreshold(app, status, warning): app.builder.build(['linenothreshold']) html = (app.outdir / 'linenothreshold.html').read_text() - pygments_version = tuple(LooseVersion(pygments.__version__).version) - lineos_head = '<td class="linenos"><div class="linenodiv"><pre>' - lineos_tail = '</pre></div></td>' - # code-block using linenothreshold - if pygments_version >= (2, 8): - _, matched, html = html.partition(lineos_head + - '<span class="normal">1</span>\n' - '<span class="normal">2</span>\n' - '<span class="normal">3</span>\n' - '<span class="normal">4</span>\n' - '<span class="normal">5</span>\n' - '<span class="normal">6</span>' + lineos_tail) - else: - _, matched, html = html.partition(lineos_head + - '1\n' - '2\n' - '3\n' - '4\n' - '5\n' - '6' + lineos_tail) - assert matched - - # code-block not using linenothreshold - if pygments_version >= (2, 8): - html, matched, _ = html.partition(lineos_head + - '<span class="normal">1</span>\n' - '<span class="normal">2</span>' + lineos_tail) - else: - html, matched, _ = html.partition(lineos_head + - '1\n' - '2' + lineos_tail) - assert not matched + assert ('<span class="linenos">1</span><span class="k">class</span> ' + '<span class="nc">Foo</span><span class="p">:</span>' in html) + + # code-block not using linenothreshold (no line numbers) + assert '<span></span><span class="c1"># comment</span>' in html # literal include using linenothreshold - if pygments_version >= (2, 8): - _, matched, html = html.partition(lineos_head + - '<span class="normal"> 1</span>\n' - '<span class="normal"> 2</span>\n' - '<span class="normal"> 3</span>\n' - '<span class="normal"> 4</span>\n' - '<span class="normal"> 5</span>\n' - '<span class="normal"> 6</span>\n' - '<span class="normal"> 7</span>\n' - '<span class="normal"> 8</span>\n' - '<span class="normal"> 9</span>\n' - '<span class="normal">10</span>\n' - '<span class="normal">11</span>\n' - '<span class="normal">12</span>\n' - '<span class="normal">13</span>' + lineos_tail) - else: - _, matched, html = html.partition(lineos_head + - ' 1\n' - ' 2\n' - ' 3\n' - ' 4\n' - ' 5\n' - ' 6\n' - ' 7\n' - ' 8\n' - ' 9\n' - '10\n' - '11\n' - '12\n' - '13' + lineos_tail) - assert matched - - # literal include not using linenothreshold - if pygments_version >= (2, 8): - html, matched, _ = html.partition(lineos_head + - '<span class="normal">1</span>\n' - '<span class="normal">2</span>\n' - '<span class="normal">3</span>' + lineos_tail) - else: - html, matched, _ = html.partition(lineos_head + - '1\n' - '2\n' - '3' + lineos_tail) - assert not matched + assert ('<span class="linenos"> 1</span><span class="c1">' + '# Literally included file using Python highlighting</span>' in html) + + # literal include not using linenothreshold (no line numbers) + assert ('<span></span><span class="c1"># Very small literal include ' + '(linenothreshold check)</span>' in html) diff --git a/tests/test_directive_patch.py b/tests/test_directive_patch.py index 7dc568b1d..b28e0f1e5 100644 --- a/tests/test_directive_patch.py +++ b/tests/test_directive_patch.py @@ -8,6 +8,7 @@ :license: BSD, see LICENSE for details. """ +import pytest from docutils import nodes from sphinx.testing import restructuredtext @@ -54,6 +55,37 @@ def test_code_directive(app): assert_node(doctree[0], language="python", linenos=True, highlight_args={'linenostart': 5}) +@pytest.mark.sphinx(testroot='directive-csv-table') +def test_csv_table_directive(app): + # relative path from current document + text = ('.. csv-table::\n' + ' :file: example.csv\n') + doctree = restructuredtext.parse(app, text, docname="subdir/index") + assert_node(doctree, + ([nodes.table, nodes.tgroup, (nodes.colspec, + nodes.colspec, + nodes.colspec, + [nodes.tbody, nodes.row])],)) + assert_node(doctree[0][0][3][0], + ([nodes.entry, nodes.paragraph, "FOO"], + [nodes.entry, nodes.paragraph, "BAR"], + [nodes.entry, nodes.paragraph, "BAZ"])) + + # absolute path from source directory + text = ('.. csv-table::\n' + ' :file: /example.csv\n') + doctree = restructuredtext.parse(app, text, docname="subdir/index") + assert_node(doctree, + ([nodes.table, nodes.tgroup, (nodes.colspec, + nodes.colspec, + nodes.colspec, + [nodes.tbody, nodes.row])],)) + assert_node(doctree[0][0][3][0], + ([nodes.entry, nodes.paragraph, "foo"], + [nodes.entry, nodes.paragraph, "bar"], + [nodes.entry, nodes.paragraph, "baz"])) + + def test_math_directive(app): # normal case text = '.. math:: E = mc^2' diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 2cfcf74fa..ef4858786 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -417,6 +417,12 @@ def test_function_definitions(): check('function', 'void f(int arr[const static volatile 42])', {1: 'f'}, output='void f(int arr[static volatile const 42])') + with pytest.raises(DefinitionError): + parse('function', 'void f(int for)') + + # from #8960 + check('function', 'void f(void (*p)(int, double), int i)', {1: 'f'}) + def test_nested_name(): check('struct', '{key}.A', {1: "A"}) @@ -523,8 +529,15 @@ def test_attributes(): # raise DefinitionError("") +def split_warnigns(warning): + ws = warning.getvalue().split("\n") + assert len(ws) >= 1 + assert ws[-1] == "" + return ws[:-1] + + def filter_warnings(warning, file): - lines = warning.getvalue().split("\n") + lines = split_warnigns(warning) res = [l for l in lines if "domain-c" in l and "{}.rst".format(file) in l and "WARNING: document isn't included in any toctree" not in l] print("Filtered warnings for file '{}':".format(file)) @@ -578,10 +591,22 @@ def test_build_domain_c_anon_dup_decl(app, status, warning): assert "WARNING: c:identifier reference target not found: @b" in ws[1] -@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) -def test_build_domain_c_semicolon(app, status, warning): - app.builder.build_all() - ws = filter_warnings(warning, "semicolon") +@pytest.mark.sphinx(confoverrides={'nitpicky': True}) +def test_build_domain_c_semicolon(app, warning): + text = """ +.. c:member:: int member; +.. c:var:: int var; +.. c:function:: void f(); +.. .. c:macro:: NO_SEMICOLON; +.. c:struct:: Struct; +.. c:union:: Union; +.. c:enum:: Enum; +.. c:enumerator:: Enumerator; +.. c:type:: Type; +.. c:type:: int TypeDef; +""" + restructuredtext.parse(app, text) + ws = split_warnigns(warning) assert len(ws) == 0 @@ -593,8 +618,8 @@ def test_build_function_param_target(app, warning): assert len(ws) == 0 entries = extract_role_links(app, "function_param_target.html") assert entries == [ - ('c.f', 'i', 'i'), - ('c.f', 'f.i', 'f.i'), + ('c.function_param_target.f', 'i', 'i'), + ('c.function_param_target.f', 'f.i', 'f.i'), ] @@ -656,6 +681,8 @@ def test_noindexentry(app): @pytest.mark.sphinx(testroot='domain-c-intersphinx', confoverrides={'nitpicky': True}) def test_intersphinx(tempdir, app, status, warning): + # a splitting of test_ids_vs_tags0 into the primary directives in a remote project, + # and then the references in the test project origSource = """\ .. c:member:: int _member .. c:var:: int _var diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 690093f5c..edd5ea5bc 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -21,7 +21,6 @@ from sphinx.domains.cpp import (DefinitionError, DefinitionParser, NoOldIdError, 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 def parse(name, string): @@ -290,13 +289,16 @@ def test_expressions(): exprCheck('5 == 42', 'eqL5EL42E') exprCheck('5 != 42', 'neL5EL42E') exprCheck('5 not_eq 42', 'neL5EL42E') - # ['<=', '>=', '<', '>'] + # ['<=', '>=', '<', '>', '<=>'] exprCheck('5 <= 42', 'leL5EL42E') exprCheck('A <= 42', 'le1AL42E') exprCheck('5 >= 42', 'geL5EL42E') exprCheck('5 < 42', 'ltL5EL42E') exprCheck('A < 42', 'lt1AL42E') exprCheck('5 > 42', 'gtL5EL42E') + exprCheck('A > 42', 'gt1AL42E') + exprCheck('5 <=> 42', 'ssL5EL42E') + exprCheck('A <=> 42', 'ss1AL42E') # ['<<', '>>'] exprCheck('5 << 42', 'lsL5EL42E') exprCheck('A << 42', 'ls1AL42E') @@ -617,6 +619,9 @@ def test_function_definitions(): # from breathe#441 check('function', 'auto MakeThingy() -> Thingy*', {1: 'MakeThingy', 2: '10MakeThingyv'}) + # from #8960 + check('function', 'void f(void (*p)(int, double), int i)', {2: '1fPFvidEi'}) + def test_operators(): check('function', 'void operator new()', {1: "new-operator", 2: "nwv"}) @@ -662,6 +667,7 @@ def test_operators(): check('function', 'void operator>()', {1: "gt-operator", 2: "gtv"}) check('function', 'void operator<=()', {1: "lte-operator", 2: "lev"}) check('function', 'void operator>=()', {1: "gte-operator", 2: "gev"}) + check('function', 'void operator<=>()', {1: None, 2: "ssv"}) check('function', 'void operator!()', {1: "not-operator", 2: "ntv"}) check('function', 'void operator not()', {2: "ntv"}) check('function', 'void operator&&()', {1: "sand-operator", 2: "aav"}) @@ -1081,8 +1087,6 @@ def test_build_domain_cpp_misuse_of_roles(app, status, warning): assert len(ws) == len(warn) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': True}) def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, warning): app.builder.build_all() @@ -1124,8 +1128,6 @@ def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, war check(s, t, f) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': False}) def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, warning): app.builder.build_all() diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index b09021073..f5df9084b 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -201,6 +201,10 @@ def test_resolve_xref_for_properties(app, status, warning): ' title="module_a.submodule.ModTopLevel.prop">' '<code class="xref py py-meth docutils literal notranslate"><span class="pre">' 'prop</span> <span class="pre">method</span></code></a>' in content) + assert ('Link to <a class="reference internal" href="#module_a.submodule.ModTopLevel.prop"' + ' title="module_a.submodule.ModTopLevel.prop">' + '<code class="xref py py-attr docutils literal notranslate"><span class="pre">' + 'prop</span> <span class="pre">attribute</span></code></a>' in content) @pytest.mark.sphinx('dummy', testroot='domain-py') @@ -214,20 +218,22 @@ def test_domain_py_find_obj(app, status, warning): assert (find_obj(None, None, 'NONEXISTANT', 'class') == []) assert (find_obj(None, None, 'NestedParentA', 'class') == - [('NestedParentA', ('roles', 'NestedParentA', 'class'))]) + [('NestedParentA', ('roles', 'NestedParentA', 'class', False))]) assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') == - [('NestedParentA.NestedChildA', ('roles', 'NestedParentA.NestedChildA', 'class'))]) + [('NestedParentA.NestedChildA', + ('roles', 'NestedParentA.NestedChildA', 'class', False))]) assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') == - [('NestedParentA.NestedChildA', ('roles', 'NestedParentA.NestedChildA', 'class'))]) + [('NestedParentA.NestedChildA', + ('roles', 'NestedParentA.NestedChildA', 'class', False))]) assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'meth') == [('NestedParentA.NestedChildA.subchild_1', - ('roles', 'NestedParentA.NestedChildA.subchild_1', 'method'))]) + ('roles', 'NestedParentA.NestedChildA.subchild_1', 'method', False))]) assert (find_obj(None, 'NestedParentA', 'NestedChildA.subchild_1', 'meth') == [('NestedParentA.NestedChildA.subchild_1', - ('roles', 'NestedParentA.NestedChildA.subchild_1', 'method'))]) + ('roles', 'NestedParentA.NestedChildA.subchild_1', 'method', False))]) assert (find_obj(None, 'NestedParentA.NestedChildA', 'subchild_1', 'meth') == [('NestedParentA.NestedChildA.subchild_1', - ('roles', 'NestedParentA.NestedChildA.subchild_1', 'method'))]) + ('roles', 'NestedParentA.NestedChildA.subchild_1', 'method', False))]) def test_get_full_qualified_name(): @@ -557,7 +563,7 @@ def test_pydata(app): [desc_content, ()])])) assert_node(doctree[3][0][2][1], pending_xref, **{"py:module": "example"}) assert 'example.var' in domain.objects - assert domain.objects['example.var'] == ('index', 'example.var', 'data') + assert domain.objects['example.var'] == ('index', 'example.var', 'data', False) def test_pyfunction(app): @@ -587,9 +593,9 @@ def test_pyfunction(app): entries=[('single', 'func2() (in module example)', 'example.func2', '', None)]) assert 'func1' in domain.objects - assert domain.objects['func1'] == ('index', 'func1', 'function') + assert domain.objects['func1'] == ('index', 'func1', 'function', False) assert 'example.func2' in domain.objects - assert domain.objects['example.func2'] == ('index', 'example.func2', 'function') + assert domain.objects['example.func2'] == ('index', 'example.func2', 'function', False) def test_pyclass_options(app): @@ -611,13 +617,13 @@ def test_pyclass_options(app): assert_node(doctree[0], addnodes.index, entries=[('single', 'Class1 (built-in class)', 'Class1', '', None)]) assert 'Class1' in domain.objects - assert domain.objects['Class1'] == ('index', 'Class1', 'class') + assert domain.objects['Class1'] == ('index', 'Class1', 'class', False) # :final: assert_node(doctree[2], addnodes.index, entries=[('single', 'Class2 (built-in class)', 'Class2', '', None)]) assert 'Class2' in domain.objects - assert domain.objects['Class2'] == ('index', 'Class2', 'class') + assert domain.objects['Class2'] == ('index', 'Class2', 'class', False) def test_pymethod_options(app): @@ -663,7 +669,7 @@ def test_pymethod_options(app): [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth1' in domain.objects - assert domain.objects['Class.meth1'] == ('index', 'Class.meth1', 'method') + assert domain.objects['Class.meth1'] == ('index', 'Class.meth1', 'method', False) # :classmethod: assert_node(doctree[1][1][2], addnodes.index, @@ -673,7 +679,7 @@ def test_pymethod_options(app): [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth2' in domain.objects - assert domain.objects['Class.meth2'] == ('index', 'Class.meth2', 'method') + assert domain.objects['Class.meth2'] == ('index', 'Class.meth2', 'method', False) # :staticmethod: assert_node(doctree[1][1][4], addnodes.index, @@ -683,7 +689,7 @@ def test_pymethod_options(app): [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth3' in domain.objects - assert domain.objects['Class.meth3'] == ('index', 'Class.meth3', 'method') + assert domain.objects['Class.meth3'] == ('index', 'Class.meth3', 'method', False) # :async: assert_node(doctree[1][1][6], addnodes.index, @@ -693,7 +699,7 @@ def test_pymethod_options(app): [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth4' in domain.objects - assert domain.objects['Class.meth4'] == ('index', 'Class.meth4', 'method') + assert domain.objects['Class.meth4'] == ('index', 'Class.meth4', 'method', False) # :property: assert_node(doctree[1][1][8], addnodes.index, @@ -702,7 +708,7 @@ def test_pymethod_options(app): [desc_name, "meth5"])], [desc_content, ()])) assert 'Class.meth5' in domain.objects - assert domain.objects['Class.meth5'] == ('index', 'Class.meth5', 'method') + assert domain.objects['Class.meth5'] == ('index', 'Class.meth5', 'method', False) # :abstractmethod: assert_node(doctree[1][1][10], addnodes.index, @@ -712,7 +718,7 @@ def test_pymethod_options(app): [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth6' in domain.objects - assert domain.objects['Class.meth6'] == ('index', 'Class.meth6', 'method') + assert domain.objects['Class.meth6'] == ('index', 'Class.meth6', 'method', False) # :final: assert_node(doctree[1][1][12], addnodes.index, @@ -722,7 +728,7 @@ def test_pymethod_options(app): [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth7' in domain.objects - assert domain.objects['Class.meth7'] == ('index', 'Class.meth7', 'method') + assert domain.objects['Class.meth7'] == ('index', 'Class.meth7', 'method', False) def test_pyclassmethod(app): @@ -743,7 +749,7 @@ def test_pyclassmethod(app): [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth' in domain.objects - assert domain.objects['Class.meth'] == ('index', 'Class.meth', 'method') + assert domain.objects['Class.meth'] == ('index', 'Class.meth', 'method', False) def test_pystaticmethod(app): @@ -764,7 +770,7 @@ def test_pystaticmethod(app): [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth' in domain.objects - assert domain.objects['Class.meth'] == ('index', 'Class.meth', 'method') + assert domain.objects['Class.meth'] == ('index', 'Class.meth', 'method', False) def test_pyattribute(app): @@ -793,7 +799,30 @@ def test_pyattribute(app): assert_node(doctree[1][1][1][0][1][1], pending_xref, **{"py:class": "Class"}) assert_node(doctree[1][1][1][0][1][3], pending_xref, **{"py:class": "Class"}) assert 'Class.attr' in domain.objects - assert domain.objects['Class.attr'] == ('index', 'Class.attr', 'attribute') + assert domain.objects['Class.attr'] == ('index', 'Class.attr', 'attribute', False) + + +def test_pyproperty(app): + text = (".. py:class:: Class\n" + "\n" + " .. py:property:: prop\n" + " :abstractmethod:\n" + " :type: str\n") + domain = app.env.get_domain('py') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_annotation, "class "], + [desc_name, "Class"])], + [desc_content, (addnodes.index, + desc)])])) + assert_node(doctree[1][1][0], addnodes.index, + entries=[('single', 'prop (Class property)', 'Class.prop', '', None)]) + assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "abstract property "], + [desc_name, "prop"], + [desc_annotation, ": str"])], + [desc_content, ()])) + assert 'Class.prop' in domain.objects + assert domain.objects['Class.prop'] == ('index', 'Class.prop', 'property', False) def test_pydecorator_signature(app): @@ -808,7 +837,7 @@ def test_pydecorator_signature(app): domain="py", objtype="function", noindex=False) assert 'deco' in domain.objects - assert domain.objects['deco'] == ('index', 'deco', 'function') + assert domain.objects['deco'] == ('index', 'deco', 'function', False) def test_pydecoratormethod_signature(app): @@ -823,7 +852,22 @@ def test_pydecoratormethod_signature(app): domain="py", objtype="method", noindex=False) assert 'deco' in domain.objects - assert domain.objects['deco'] == ('index', 'deco', 'method') + assert domain.objects['deco'] == ('index', 'deco', 'method', False) + + +def test_canonical(app): + text = (".. py:class:: io.StringIO\n" + " :canonical: _io.StringIO") + domain = app.env.get_domain('py') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_annotation, "class "], + [desc_addname, "io."], + [desc_name, "StringIO"])], + desc_content)])) + assert 'io.StringIO' in domain.objects + assert domain.objects['io.StringIO'] == ('index', 'io.StringIO', 'class', False) + assert domain.objects['_io.StringIO'] == ('index', 'io.StringIO', 'class', True) def test_info_field_list(app): @@ -873,6 +917,30 @@ def test_info_field_list(app): **{"py:module": "example", "py:class": "Class"}) +def test_info_field_list_var(app): + text = (".. py:class:: Class\n" + "\n" + " :var int attr: blah blah\n") + doctree = restructuredtext.parse(app, text) + + assert_node(doctree, (addnodes.index, + [desc, (desc_signature, + [desc_content, nodes.field_list, nodes.field])])) + assert_node(doctree[1][1][0][0], ([nodes.field_name, "Variables"], + [nodes.field_body, nodes.paragraph])) + + # :var int attr: + assert_node(doctree[1][1][0][0][1][0], + ([addnodes.literal_strong, "attr"], + " (", + [pending_xref, addnodes.literal_emphasis, "int"], + ")", + " -- ", + "blah blah")) + assert_node(doctree[1][1][0][0][1][0][2], pending_xref, + refdomain="py", reftype="class", reftarget="int", **{"py:class": "Class"}) + + @pytest.mark.sphinx(freshenv=True) def test_module_index(app): text = (".. py:module:: docutils\n" @@ -958,6 +1026,25 @@ def test_noindexentry(app): assert_node(doctree[2], addnodes.index, entries=[]) +@pytest.mark.sphinx('html', testroot='domain-py-python_use_unqualified_type_names') +def test_python_python_use_unqualified_type_names(app, status, warning): + app.build() + content = (app.outdir / 'index.html').read_text() + assert ('<span class="n"><a class="reference internal" href="#foo.Name" title="foo.Name">' + '<span class="pre">Name</span></a></span>' in content) + assert '<span class="n"><span class="pre">foo.Age</span></span>' in content + + +@pytest.mark.sphinx('html', testroot='domain-py-python_use_unqualified_type_names', + confoverrides={'python_use_unqualified_type_names': False}) +def test_python_python_use_unqualified_type_names_disabled(app, status, warning): + app.build() + content = (app.outdir / 'index.html').read_text() + assert ('<span class="n"><a class="reference internal" href="#foo.Name" title="foo.Name">' + '<span class="pre">foo.Name</span></a></span>' in content) + assert '<span class="n"><span class="pre">foo.Age</span></span>' in content + + @pytest.mark.sphinx('dummy', testroot='domain-py-xref-warning') def test_warn_missing_reference(app, status, warning): app.build() diff --git a/tests/test_domain_std.py b/tests/test_domain_std.py index cf32e7964..9d9e27bd0 100644 --- a/tests/test_domain_std.py +++ b/tests/test_domain_std.py @@ -21,7 +21,6 @@ from sphinx.addnodes import (desc, desc_addname, desc_content, desc_name, desc_s from sphinx.domains.std import StandardDomain from sphinx.testing import restructuredtext from sphinx.testing.util import assert_node -from sphinx.util import docutils def test_process_doc_handle_figure_caption(): @@ -349,8 +348,6 @@ def test_multiple_cmdoptions(app): assert domain.progoptions[('cmd', '--output')] == ('index', 'cmdoption-cmd-o') -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx(testroot='productionlist') def test_productionlist(app, status, warning): app.builder.build_all() diff --git a/tests/test_environment.py b/tests/test_environment.py index 9791c2d5b..5d3035ac0 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -35,14 +35,14 @@ def test_config_status(make_app, app_params): assert "0 added, 0 changed, 0 removed" in app2._status.getvalue() # incremental build (config entry changed) - app3 = make_app(*args, confoverrides={'master_doc': 'indexx'}, **kwargs) + app3 = make_app(*args, confoverrides={'root_doc': 'indexx'}, **kwargs) fname = os.path.join(app3.srcdir, 'index.rst') assert os.path.isfile(fname) shutil.move(fname, fname[:-4] + 'x.rst') assert app3.env.config_status == CONFIG_CHANGED app3.build() shutil.move(fname[:-4] + 'x.rst', fname) - assert "[config changed ('master_doc')] 1 added" in app3._status.getvalue() + assert "[config changed ('root_doc')] 1 added" in app3._status.getvalue() # incremental build (extension changed) app4 = make_app(*args, confoverrides={'extensions': ['sphinx.ext.autodoc']}, **kwargs) @@ -85,7 +85,7 @@ def test_object_inventory(app): refs = app.env.domaindata['py']['objects'] assert 'func_without_module' in refs - assert refs['func_without_module'] == ('objects', 'func_without_module', 'function') + assert refs['func_without_module'] == ('objects', 'func_without_module', 'function', False) assert 'func_without_module2' in refs assert 'mod.func_in_module' in refs assert 'mod.Cls' in refs diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py index 41b3f727c..85a98b61b 100644 --- a/tests/test_environment_toctree.py +++ b/tests/test_environment_toctree.py @@ -10,7 +10,7 @@ import pytest from docutils import nodes -from docutils.nodes import bullet_list, caption, comment, list_item, reference +from docutils.nodes import bullet_list, comment, list_item, reference, title from sphinx import addnodes from sphinx.addnodes import compact_paragraph, only @@ -211,7 +211,7 @@ def test_get_toctree_for(app): app.build() toctree = TocTree(app.env).get_toctree_for('index', app.builder, collapse=False) assert_node(toctree, - [compact_paragraph, ([caption, "Table of Contents"], + [compact_paragraph, ([title, "Table of Contents"], bullet_list, bullet_list, bullet_list)]) @@ -251,7 +251,7 @@ def test_get_toctree_for_collapse(app): app.build() toctree = TocTree(app.env).get_toctree_for('index', app.builder, collapse=True) assert_node(toctree, - [compact_paragraph, ([caption, "Table of Contents"], + [compact_paragraph, ([title, "Table of Contents"], bullet_list, bullet_list, bullet_list)]) @@ -283,7 +283,7 @@ def test_get_toctree_for_maxdepth(app): toctree = TocTree(app.env).get_toctree_for('index', app.builder, collapse=False, maxdepth=3) assert_node(toctree, - [compact_paragraph, ([caption, "Table of Contents"], + [compact_paragraph, ([title, "Table of Contents"], bullet_list, bullet_list, bullet_list)]) @@ -329,7 +329,7 @@ def test_get_toctree_for_includehidden(app): toctree = TocTree(app.env).get_toctree_for('index', app.builder, collapse=False, includehidden=False) assert_node(toctree, - [compact_paragraph, ([caption, "Table of Contents"], + [compact_paragraph, ([title, "Table of Contents"], bullet_list, bullet_list)]) diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 1dc1bfe76..8ace97813 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -68,7 +68,7 @@ def make_directive_bridge(env): env = env, genopt = options, result = ViewList(), - filename_set = set(), + record_dependencies = set(), state = Mock(), ) directive.state.document.settings.tab_width = 8 @@ -286,7 +286,6 @@ def test_format_signature(app): '(b, c=42, *d, **e)' -@pytest.mark.skipif(sys.version_info < (3, 5), reason='typing is available since python3.5.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_process_signature_typing_generic(app): actual = do_autodoc(app, 'class', 'target.generic_class.A', {}) @@ -1034,9 +1033,8 @@ def test_autodoc_descriptor(app): ' Descriptor instance docstring.', '', '', - ' .. py:method:: Class.prop', + ' .. py:property:: Class.prop', ' :module: target.descriptor', - ' :property:', '', ' Property.', '' @@ -1056,9 +1054,8 @@ def test_autodoc_cached_property(app): ' :module: target.cached_property', '', '', - ' .. py:method:: Foo.prop', + ' .. py:property:: Foo.prop', ' :module: target.cached_property', - ' :property:', '', ] @@ -1524,10 +1521,9 @@ def test_abstractmethods(app): ' :module: target.abstractmethods', '', '', - ' .. py:method:: Base.prop', + ' .. py:property:: Base.prop', ' :module: target.abstractmethods', ' :abstractmethod:', - ' :property:', '', '', ' .. py:method:: Base.staticmeth()', @@ -1719,7 +1715,6 @@ def test_partialmethod_undoc_members(app): assert list(actual) == expected -@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is available since python3.6.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_typed_instance_variables(app): options = {"members": None, @@ -1818,7 +1813,6 @@ def test_autodoc_typed_instance_variables(app): ] -@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is available since python3.6.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_typed_inherited_instance_variables(app): options = {"members": None, @@ -2003,6 +1997,14 @@ def test_autodoc_TypeVar(app): '', ' alias of :class:`int`', '', + '', + '.. py:data:: T7', + ' :module: target.typevar', + '', + ' T7', + '', + " alias of TypeVar('T7', bound=\\ :class:`int`)", + '', ] @@ -2024,7 +2026,6 @@ def test_autodoc_Annotated(app): ] -@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is required.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_TYPE_CHECKING(app): options = {"members": None, @@ -2073,26 +2074,19 @@ def test_autodoc_for_egged_code(app): def test_singledispatch(app): options = {"members": None} actual = do_autodoc(app, 'module', 'target.singledispatch', options) - if sys.version_info < (3, 6): - # check the result via "in" because the order of singledispatch signatures is - # usually changed (because dict is not OrderedDict yet!) - assert '.. py:function:: func(arg, kwarg=None)' in actual - assert ' func(arg: int, kwarg=None)' in actual - assert ' func(arg: str, kwarg=None)' in actual - else: - assert list(actual) == [ - '', - '.. py:module:: target.singledispatch', - '', - '', - '.. py:function:: func(arg, kwarg=None)', - ' func(arg: int, kwarg=None)', - ' func(arg: str, kwarg=None)', - ' :module: target.singledispatch', - '', - ' A function for general use.', - '', - ] + assert list(actual) == [ + '', + '.. py:module:: target.singledispatch', + '', + '', + '.. py:function:: func(arg, kwarg=None)', + ' func(arg: int, kwarg=None)', + ' func(arg: str, kwarg=None)', + ' :module: target.singledispatch', + '', + ' A function for general use.', + '', + ] @pytest.mark.skipif(sys.version_info < (3, 8), @@ -2487,3 +2481,34 @@ def test_hide_value(app): ' :meta hide-value:', '', ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_canonical(app): + options = {'members': None, + 'imported-members': None} + actual = do_autodoc(app, 'module', 'target.canonical', options) + assert list(actual) == [ + '', + '.. py:module:: target.canonical', + '', + '', + '.. py:class:: Bar()', + ' :module: target.canonical', + '', + ' docstring', + '', + '', + '.. py:class:: Foo()', + ' :module: target.canonical', + ' :canonical: target.canonical.original.Foo', + '', + ' docstring', + '', + '', + ' .. py:method:: Foo.meth()', + ' :module: target.canonical', + '', + ' docstring', + '', + ] diff --git a/tests/test_ext_autodoc_autoclass.py b/tests/test_ext_autodoc_autoclass.py index 538b36881..940263387 100644 --- a/tests/test_ext_autodoc_autoclass.py +++ b/tests/test_ext_autodoc_autoclass.py @@ -200,6 +200,27 @@ def test_decorators(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_properties(app): + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.properties.Foo', options) + assert list(actual) == [ + '', + '.. py:class:: Foo()', + ' :module: target.properties', + '', + ' docstring', + '', + '', + ' .. py:property:: Foo.prop', + ' :module: target.properties', + ' :type: int', + '', + ' docstring', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') def test_slots_attribute(app): options = {"members": None} actual = do_autodoc(app, 'class', 'target.slots.Bar', options) diff --git a/tests/test_ext_autodoc_autofunction.py b/tests/test_ext_autodoc_autofunction.py index aa0476687..615091889 100644 --- a/tests/test_ext_autodoc_autofunction.py +++ b/tests/test_ext_autodoc_autofunction.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for details. """ -import sys - import pytest from .test_ext_autodoc import do_autodoc @@ -118,23 +116,16 @@ def test_decorated(app): def test_singledispatch(app): options = {} actual = do_autodoc(app, 'function', 'target.singledispatch.func', options) - if sys.version_info < (3, 6): - # check the result via "in" because the order of singledispatch signatures is - # usually changed (because dict is not OrderedDict yet!) - assert '.. py:function:: func(arg, kwarg=None)' in actual - assert ' func(arg: int, kwarg=None)' in actual - assert ' func(arg: str, kwarg=None)' in actual - else: - assert list(actual) == [ - '', - '.. py:function:: func(arg, kwarg=None)', - ' func(arg: int, kwarg=None)', - ' func(arg: str, kwarg=None)', - ' :module: target.singledispatch', - '', - ' A function for general use.', - '', - ] + assert list(actual) == [ + '', + '.. py:function:: func(arg, kwarg=None)', + ' func(arg: int, kwarg=None)', + ' func(arg: str, kwarg=None)', + ' :module: target.singledispatch', + '', + ' A function for general use.', + '', + ] @pytest.mark.sphinx('html', testroot='ext-autodoc') diff --git a/tests/test_ext_autodoc_autoproperty.py b/tests/test_ext_autodoc_autoproperty.py new file mode 100644 index 000000000..ee25aa8b7 --- /dev/null +++ b/tests/test_ext_autodoc_autoproperty.py @@ -0,0 +1,28 @@ +""" + test_ext_autodoc_autoproperty + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + 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 pytest + +from .test_ext_autodoc import do_autodoc + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_properties(app): + actual = do_autodoc(app, 'property', 'target.properties.Foo.prop') + assert list(actual) == [ + '', + '.. py:property:: Foo.prop', + ' :module: target.properties', + ' :type: int', + '', + ' docstring', + '', + ] diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index 06bf39c24..bc8c01fbd 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -261,16 +261,14 @@ def test_autodoc_docstring_signature(app): ' indented line', '', '', - ' .. py:method:: DocstringSig.prop1', + ' .. py:property:: DocstringSig.prop1', ' :module: target', - ' :property:', '', ' First line of docstring', '', '', - ' .. py:method:: DocstringSig.prop2', + ' .. py:property:: DocstringSig.prop2', ' :module: target', - ' :property:', '', ' First line of docstring', ' Second line of docstring', @@ -305,17 +303,15 @@ def test_autodoc_docstring_signature(app): ' indented line', '', '', - ' .. py:method:: DocstringSig.prop1', + ' .. py:property:: DocstringSig.prop1', ' :module: target', - ' :property:', '', ' DocstringSig.prop1(self)', ' First line of docstring', '', '', - ' .. py:method:: DocstringSig.prop2', + ' .. py:property:: DocstringSig.prop2', ' :module: target', - ' :property:', '', ' First line of docstring', ' Second line of docstring', @@ -352,7 +348,11 @@ def test_autoclass_content_and_docstring_signature_class(app): '', '.. py:class:: E()', ' :module: target.docstring_signature', - '' + '', + '', + '.. py:class:: F()', + ' :module: target.docstring_signature', + '', ] @@ -386,7 +386,12 @@ def test_autoclass_content_and_docstring_signature_init(app): '.. py:class:: E(foo: int, bar: int, baz: int) -> None', ' E(foo: str, bar: str, baz: str) -> None', ' :module: target.docstring_signature', - '' + '', + '', + '.. py:class:: F(foo: int, bar: int, baz: int) -> None', + ' F(foo: str, bar: str, baz: str) -> None', + ' :module: target.docstring_signature', + '', ] @@ -425,6 +430,11 @@ def test_autoclass_content_and_docstring_signature_both(app): ' E(foo: str, bar: str, baz: str) -> None', ' :module: target.docstring_signature', '', + '', + '.. py:class:: F(foo: int, bar: int, baz: int) -> None', + ' F(foo: str, bar: str, baz: str) -> None', + ' :module: target.docstring_signature', + '', ] @@ -687,6 +697,90 @@ def test_autodoc_typehints_description(app): @pytest.mark.sphinx('text', testroot='ext-autodoc', + confoverrides={'autodoc_typehints': "description", + 'autodoc_typehints_description_target': 'documented'}) +def test_autodoc_typehints_description_no_undoc(app): + # No :type: or :rtype: will be injected for `incr`, which does not have + # a description for its parameters or its return. `tuple_args` does + # describe them, so :type: and :rtype: will be added. + (app.srcdir / 'index.rst').write_text( + '.. autofunction:: target.typehints.incr\n' + '\n' + '.. autofunction:: target.typehints.tuple_args\n' + '\n' + ' :param x: arg\n' + ' :return: another tuple\n' + ) + app.build() + context = (app.outdir / 'index.txt').read_text() + assert ('target.typehints.incr(a, b=1)\n' + '\n' + 'target.typehints.tuple_args(x)\n' + '\n' + ' Parameters:\n' + ' **x** (*Tuple**[**int**, **Union**[**int**, **str**]**]*) -- arg\n' + '\n' + ' Returns:\n' + ' another tuple\n' + '\n' + ' Return type:\n' + ' Tuple[int, int]\n' + in context) + + +@pytest.mark.sphinx('text', testroot='ext-autodoc', + confoverrides={'autodoc_typehints': "description"}) +def test_autodoc_typehints_description_with_documented_init(app): + (app.srcdir / 'index.rst').write_text( + '.. autoclass:: target.typehints._ClassWithDocumentedInit\n' + ' :special-members: __init__\n' + ) + app.build() + context = (app.outdir / 'index.txt').read_text() + assert ('class target.typehints._ClassWithDocumentedInit(x)\n' + '\n' + ' Class docstring.\n' + '\n' + ' Parameters:\n' + ' **x** (*int*) --\n' + '\n' + ' Return type:\n' + ' None\n' + '\n' + ' __init__(x)\n' + '\n' + ' Init docstring.\n' + '\n' + ' Parameters:\n' + ' **x** (*int*) -- Some integer\n' + '\n' + ' Return type:\n' + ' None\n' == context) + + +@pytest.mark.sphinx('text', testroot='ext-autodoc', + confoverrides={'autodoc_typehints': "description", + 'autodoc_typehints_description_target': 'documented'}) +def test_autodoc_typehints_description_with_documented_init_no_undoc(app): + (app.srcdir / 'index.rst').write_text( + '.. autoclass:: target.typehints._ClassWithDocumentedInit\n' + ' :special-members: __init__\n' + ) + app.build() + context = (app.outdir / 'index.txt').read_text() + assert ('class target.typehints._ClassWithDocumentedInit(x)\n' + '\n' + ' Class docstring.\n' + '\n' + ' __init__(x)\n' + '\n' + ' Init docstring.\n' + '\n' + ' Parameters:\n' + ' **x** (*int*) -- Some integer\n' == context) + + +@pytest.mark.sphinx('text', testroot='ext-autodoc', confoverrides={'autodoc_typehints': "description"}) def test_autodoc_typehints_description_for_invalid_node(app): text = ".. py:function:: hello; world" diff --git a/tests/test_ext_autodoc_preserve_defaults.py b/tests/test_ext_autodoc_preserve_defaults.py new file mode 100644 index 000000000..f9833c291 --- /dev/null +++ b/tests/test_ext_autodoc_preserve_defaults.py @@ -0,0 +1,45 @@ +""" + test_ext_autodoc_preserve_defaults + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Test the autodoc extension. + + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import pytest + +from .test_ext_autodoc import do_autodoc + + +@pytest.mark.sphinx('html', testroot='ext-autodoc', + confoverrides={'autodoc_preserve_defaults': True}) +def test_preserve_defaults(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.preserve_defaults', options) + assert list(actual) == [ + '', + '.. py:module:: target.preserve_defaults', + '', + '', + '.. py:class:: Class()', + ' :module: target.preserve_defaults', + '', + ' docstring', + '', + '', + ' .. py:method:: Class.meth(name: str = CONSTANT, sentinal: Any = SENTINEL, ' + 'now: datetime.datetime = datetime.now()) -> None', + ' :module: target.preserve_defaults', + '', + ' docstring', + '', + '', + '.. py:function:: foo(name: str = CONSTANT, sentinal: Any = SENTINEL, now: ' + 'datetime.datetime = datetime.now()) -> None', + ' :module: target.preserve_defaults', + '', + ' docstring', + '', + ] diff --git a/tests/test_ext_autosectionlabel.py b/tests/test_ext_autosectionlabel.py index 0afe13b41..96f81aacd 100644 --- a/tests/test_ext_autosectionlabel.py +++ b/tests/test_ext_autosectionlabel.py @@ -12,11 +12,7 @@ import re import pytest -from sphinx.util import docutils - -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='ext-autosectionlabel') def test_autosectionlabel_html(app, status, warning, skipped_labels=False): app.builder.build_all() @@ -55,15 +51,11 @@ def test_autosectionlabel_html(app, status, warning, skipped_labels=False): # Re-use test definition from above, just change the test root directory -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='ext-autosectionlabel-prefix-document') def test_autosectionlabel_prefix_document_html(app, status, warning): test_autosectionlabel_html(app, status, warning) -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='ext-autosectionlabel', confoverrides={'autosectionlabel_maxdepth': 3}) def test_autosectionlabel_maxdepth(app, status, warning): diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index a87677525..523ed2acc 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -196,6 +196,14 @@ def test_missing_reference_pydomain(tempdir, app, status, warning): rn = missing_reference(app, app.env, node, contnode) assert rn.astext() == 'Foo.bar' + # pending_xref_condition="resolved" + node = addnodes.pending_xref('', reftarget='Foo.bar', refdomain='py', reftype='attr') + node['py:module'] = 'module1' + node += addnodes.pending_xref_condition('', 'Foo.bar', condition='resolved') + node += addnodes.pending_xref_condition('', 'module1.Foo.bar', condition='*') + rn = missing_reference(app, app.env, node, nodes.Text('dummy-cont-node')) + assert rn.astext() == 'Foo.bar' + def test_missing_reference_stddomain(tempdir, app, status, warning): inv_file = tempdir / 'inventory' diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py index 10c8c4866..bd124c8c6 100644 --- a/tests/test_ext_math.py +++ b/tests/test_ext_math.py @@ -72,8 +72,8 @@ def test_mathjax_options(app, status, warning): content = (app.outdir / 'index.html').read_text() assert ('<script async="async" integrity="sha384-0123456789" ' - 'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?' - 'config=TeX-AMS-MML_HTMLorMML"></script>' in content) + 'src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">' + '</script>' in content) @pytest.mark.sphinx('html', testroot='ext-math', diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 362e6fb8e..acf000178 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -1482,12 +1482,18 @@ Parameters ---------- param1 : :class:`MyClass <name.space.MyClass>` instance +Other Parameters +---------------- +param2 : :class:`MyClass <name.space.MyClass>` instance + """ config = Config(napoleon_use_param=False) actual = str(NumpyDocstring(docstring, config)) expected = """\ :Parameters: **param1** (:class:`MyClass <name.space.MyClass>` instance) + +:Other Parameters: **param2** (:class:`MyClass <name.space.MyClass>` instance) """ self.assertEqual(expected, actual) @@ -1496,6 +1502,9 @@ param1 : :class:`MyClass <name.space.MyClass>` instance expected = """\ :param param1: :type param1: :class:`MyClass <name.space.MyClass>` instance + +:param param2: +:type param2: :class:`MyClass <name.space.MyClass>` instance """ self.assertEqual(expected, actual) diff --git a/tests/test_ext_todo.py b/tests/test_ext_todo.py index b6fb2549c..b4f969004 100644 --- a/tests/test_ext_todo.py +++ b/tests/test_ext_todo.py @@ -12,11 +12,7 @@ import re import pytest -from sphinx.util import docutils - -@pytest.mark.skipif(docutils.__version_info__ < (0, 13), - reason='docutils-0.13 or above is required') @pytest.mark.sphinx('html', testroot='ext-todo', freshenv=True, confoverrides={'todo_include_todos': True, 'todo_emit_warnings': True}) def test_todo(app, status, warning): diff --git a/tests/test_intl.py b/tests/test_intl.py index 3a704fd7d..bb493c2f3 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -336,9 +336,9 @@ def test_text_figure_captions(app): "14.2. IMAGE URL AND ALT\n" "=======================\n" "\n" - "[image: i18n][image]\n" + "[image: I18N -> IMG][image]\n" "\n" - " [image: img][image]\n" + " [image: IMG -> I18N][image]\n" "\n" "\n" "14.3. IMAGE ON SUBSTITUTION\n" @@ -622,11 +622,8 @@ def test_html_meta(app): assert expected_expr in result expected_expr = '<meta content="I18N, SPHINX, MARKUP" name="keywords" />' assert expected_expr in result - if docutils.__version_info__ < (0, 17): - expected_expr = '<p class="caption"><span class="caption-text">HIDDEN TOC</span></p>' - assert expected_expr in result - else: - expected_expr = '<p><span class="caption-text">HIDDEN TOC</span></p>' + expected_expr = '<p class="caption"><span class="caption-text">HIDDEN TOC</span></p>' + assert expected_expr in result @sphinx_intl @@ -1098,12 +1095,12 @@ def test_additional_targets_should_not_be_translated(app): result = (app.outdir / 'figure.html').read_text() - # alt and src for image block should not be translated - expected_expr = """<img alt="i18n" src="_images/i18n.png" />""" + # src for image block should not be translated (alt is translated) + expected_expr = """<img alt="I18N -> IMG" src="_images/i18n.png" />""" assert_count(expected_expr, result, 1) - # alt and src for figure block should not be translated - expected_expr = """<img alt="img" src="_images/img.png" />""" + # src for figure block should not be translated (alt is translated) + expected_expr = """<img alt="IMG -> I18N" src="_images/img.png" />""" assert_count(expected_expr, result, 1) diff --git a/tests/test_pycode_parser.py b/tests/test_pycode_parser.py index 9169315fc..e9099178d 100644 --- a/tests/test_pycode_parser.py +++ b/tests/test_pycode_parser.py @@ -8,10 +8,6 @@ :license: BSD, see LICENSE for details. """ -import sys - -import pytest - from sphinx.pycode.parser import Parser from sphinx.util.inspect import signature_from_str @@ -95,8 +91,7 @@ def test_comment_picker_location(): ('Foo', 'attr3'): 'comment for attr3(3)'} -@pytest.mark.skipif(sys.version_info < (3, 6), reason='tests for py36+ syntax') -def test_annotated_assignment_py36(): +def test_annotated_assignment(): source = ('a: str = "Sphinx" #: comment\n' 'b: int = 1\n' '"""string on next line"""\n' diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 11086f5f6..94144ef22 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -16,7 +16,6 @@ import pytest from sphinx import application from sphinx.cmd import quickstart as qs from sphinx.util.console import coloron, nocolor -from sphinx.util.pycompat import execfile_ warnfile = StringIO() @@ -108,7 +107,7 @@ def test_quickstart_defaults(tempdir): conffile = tempdir / 'conf.py' assert conffile.isfile() ns = {} - execfile_(conffile, ns) + exec(conffile.read_text(), ns) assert ns['extensions'] == [] assert ns['templates_path'] == ['_templates'] assert ns['project'] == 'Sphinx Test' @@ -158,13 +157,13 @@ def test_quickstart_all_answers(tempdir): conffile = tempdir / 'source' / 'conf.py' assert conffile.isfile() ns = {} - execfile_(conffile, ns) + exec(conffile.read_text(), ns) assert ns['extensions'] == [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo' ] assert ns['templates_path'] == ['.templates'] assert ns['source_suffix'] == '.txt' - assert ns['master_doc'] == 'contents' + assert ns['root_doc'] == 'contents' assert ns['project'] == 'STASI™' assert ns['copyright'] == '%s, Wolfgang Schäuble & G\'Beckstein' % \ time.strftime('%Y') @@ -239,7 +238,7 @@ def test_default_filename(tempdir): conffile = tempdir / 'conf.py' assert conffile.isfile() ns = {} - execfile_(conffile, ns) + exec(conffile.read_text(), ns) def test_extensions(tempdir): @@ -249,5 +248,5 @@ def test_extensions(tempdir): conffile = tempdir / 'conf.py' assert conffile.isfile() ns = {} - execfile_(conffile, ns) + exec(conffile.read_text(), ns) assert ns['extensions'] == ['foo', 'bar', 'baz'] diff --git a/tests/test_search.py b/tests/test_search.py index 1ceb6d55b..dc4f546ca 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -143,6 +143,7 @@ def test_IndexBuilder(): assert index._titles == {'docname': 'title', 'docname2': 'title2'} assert index._filenames == {'docname': 'filename', 'docname2': 'filename2'} assert index._mapping == { + 'ar': {'docname', 'docname2'}, 'fermion': {'docname', 'docname2'}, 'comment': {'docname', 'docname2'}, 'non': {'docname', 'docname2'}, @@ -161,7 +162,8 @@ def test_IndexBuilder(): 'objects': {'': {'objdispname': (0, 0, 1, '#anchor')}}, 'objnames': {0: ('dummy', 'objtype', 'objtype')}, 'objtypes': {0: 'dummy:objtype'}, - 'terms': {'comment': [0, 1], + 'terms': {'ar': [0, 1], + 'comment': [0, 1], 'fermion': [0, 1], 'index': [0, 1], 'non': [0, 1], @@ -197,6 +199,7 @@ def test_IndexBuilder(): assert index._titles == {'docname2': 'title2'} assert index._filenames == {'docname2': 'filename2'} assert index._mapping == { + 'ar': {'docname2'}, 'fermion': {'docname2'}, 'comment': {'docname2'}, 'non': {'docname2'}, @@ -215,7 +218,8 @@ def test_IndexBuilder(): 'objects': {}, 'objnames': {0: ('dummy', 'objtype', 'objtype')}, 'objtypes': {0: 'dummy:objtype'}, - 'terms': {'comment': 0, + 'terms': {'ar': 0, + 'comment': 0, 'fermion': 0, 'index': 0, 'non': 0, @@ -261,4 +265,4 @@ def test_nosearch(app): assert index['docnames'] == ['index', 'nosearch', 'tocitem'] assert 'latex' not in index['terms'] assert 'zfs' in index['terms'] - assert index['terms']['zfs'] == 0 # zfs on nosearch.rst is not registered to index + assert index['terms']['zfs'] == [] # zfs on nosearch.rst is not registered to index diff --git a/tests/test_setup_command.py b/tests/test_setup_command.py index 52dad14fd..0561b2fc4 100644 --- a/tests/test_setup_command.py +++ b/tests/test_setup_command.py @@ -93,12 +93,12 @@ def nonascii_srcdir(request, setup_command): ========================== """)) - master_doc = srcdir / 'index.txt' - master_doc.write_bytes((master_doc.read_text() + dedent(""" - .. toctree:: + root_doc = srcdir / 'index.txt' + root_doc.write_bytes((root_doc.read_text() + dedent(""" + .. toctree:: - %(mb_name)s/%(mb_name)s - """ % locals())).encode()) + %(mb_name)s/%(mb_name)s + """ % locals())).encode()) @pytest.mark.usefixtures('nonascii_srcdir') diff --git a/tests/test_smartquotes.py b/tests/test_smartquotes.py index 0cc0681dc..a8d4b1ed6 100644 --- a/tests/test_smartquotes.py +++ b/tests/test_smartquotes.py @@ -10,8 +10,6 @@ import pytest -from sphinx.util import docutils - @pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True) def test_basic(app, status, warning): @@ -33,7 +31,7 @@ def test_text_builder(app, status, warning): def test_man_builder(app, status, warning): app.build() - content = (app.outdir / 'python.1').read_text() + content = (app.outdir / '1' / 'python.1').read_text() assert '\\-\\- "Sphinx" is a tool that makes it easy ...' in content @@ -63,8 +61,6 @@ def test_smartquotes_disabled(app, status, warning): assert '<p>-- "Sphinx" is a tool that makes it easy ...</p>' in content -@pytest.mark.skipif(docutils.__version_info__ < (0, 14), - reason='docutils-0.14 or above is required') @pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True, confoverrides={'smartquotes_action': 'q'}) def test_smartquotes_action(app, status, warning): @@ -88,5 +84,5 @@ def test_smartquotes_excludes_language(app, status, warning): def test_smartquotes_excludes_builders(app, status, warning): app.build() - content = (app.outdir / 'python.1').read_text() + content = (app.outdir / '1' / 'python.1').read_text() assert '– “Sphinx” is a tool that makes it easy …' in content diff --git a/tests/test_theming.py b/tests/test_theming.py index 0dded0725..be0afe522 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -127,7 +127,7 @@ def test_dark_style(app, status, warning): assert (app.outdir / '_static' / 'pygments_dark.css').exists() result = (app.outdir / 'index.html').read_text() - assert '<link rel="stylesheet" href="_static/pygments.css" type="text/css" />' in result + assert '<link rel="stylesheet" type="text/css" href="_static/pygments.css" />' in result assert ('<link id="pygments_dark_css" media="(prefers-color-scheme: dark)" ' 'rel="stylesheet" type="text/css" ' 'href="_static/pygments_dark.css" />') in result diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 863f44921..7b86c6ade 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -142,7 +142,13 @@ def test_signature_annotations(): # TypeVars and generic types with TypeVars sig = inspect.signature(f2) - assert stringify_signature(sig) == '(x: List[T], y: List[T_co], z: T) -> List[T_contra]' + if sys.version_info < (3, 7): + assert stringify_signature(sig) == '(x: List[T], y: List[T_co], z: T) -> List[T_contra]' + else: + assert stringify_signature(sig) == ('(x: List[tests.typing_test_data.T],' + ' y: List[tests.typing_test_data.T_co],' + ' z: tests.typing_test_data.T' + ') -> List[tests.typing_test_data.T_contra]') # Union types sig = inspect.signature(f3) @@ -223,10 +229,7 @@ def test_signature_annotations(): # type hints by string sig = inspect.signature(Node.children) - if (3, 5, 0) <= sys.version_info < (3, 5, 3): - assert stringify_signature(sig) == '(self) -> List[Node]' - else: - assert stringify_signature(sig) == '(self) -> List[tests.typing_test_data.Node]' + assert stringify_signature(sig) == '(self) -> List[tests.typing_test_data.Node]' sig = inspect.signature(Node.__init__) assert stringify_signature(sig) == '(self, parent: Optional[tests.typing_test_data.Node]) -> None' diff --git a/tests/test_util_pycompat.py b/tests/test_util_pycompat.py deleted file mode 100644 index 0e395fa7a..000000000 --- a/tests/test_util_pycompat.py +++ /dev/null @@ -1,38 +0,0 @@ -""" - test_util_pycompat - ~~~~~~~~~~~~~~~~~~ - - Tests sphinx.util.pycompat functions. - - :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -from sphinx.testing.util import strip_escseq -from sphinx.util import logging -from sphinx.util.pycompat import execfile_ - - -def test_execfile_python2(capsys, app, status, warning, tempdir): - logging.setup(app, status, warning) - - conf_py = tempdir / 'conf.py' - conf_py.write_bytes(b'print "hello"\n') - execfile_(conf_py, {}) - - msg = ( - 'Support for evaluating Python 2 syntax is deprecated ' - 'and will be removed in Sphinx 4.0. ' - 'Convert %s to Python 3 syntax.\n' % conf_py) - assert msg in strip_escseq(warning.getvalue()) - captured = capsys.readouterr() - assert captured.out == 'hello\n' - - -def test_execfile(capsys, tempdir): - conf_py = tempdir / 'conf.py' - conf_py.write_bytes(b'print("hello")\n') - execfile_(conf_py, {}) - - captured = capsys.readouterr() - assert captured.out == 'hello\n' diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index 927db73fd..5a5808ac5 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -11,6 +11,7 @@ import sys from numbers import Integral from struct import Struct +from types import TracebackType from typing import (Any, Callable, Dict, Generator, List, NewType, Optional, Tuple, TypeVar, Union) @@ -45,6 +46,7 @@ def test_restify(): assert restify(None) == ":obj:`None`" assert restify(Integral) == ":class:`numbers.Integral`" assert restify(Struct) == ":class:`struct.Struct`" + assert restify(TracebackType) == ":class:`types.TracebackType`" assert restify(Any) == ":obj:`Any`" @@ -133,7 +135,8 @@ def test_stringify(): assert stringify(str) == "str" assert stringify(None) == "None" assert stringify(Integral) == "numbers.Integral" - assert restify(Struct) == ":class:`struct.Struct`" + assert stringify(Struct) == "struct.Struct" + assert stringify(TracebackType) == "types.TracebackType" assert stringify(Any) == "Any" @@ -194,10 +197,17 @@ def test_stringify_type_hints_typevars(): T_co = TypeVar('T_co', covariant=True) T_contra = TypeVar('T_contra', contravariant=True) - assert stringify(T) == "T" - assert stringify(T_co) == "T_co" - assert stringify(T_contra) == "T_contra" - assert stringify(List[T]) == "List[T]" + if sys.version_info < (3, 7): + assert stringify(T) == "T" + assert stringify(T_co) == "T_co" + assert stringify(T_contra) == "T_contra" + assert stringify(List[T]) == "List[T]" + else: + assert stringify(T) == "tests.test_util_typing.T" + assert stringify(T_co) == "tests.test_util_typing.T_co" + assert stringify(T_contra) == "tests.test_util_typing.T_contra" + assert stringify(List[T]) == "List[tests.test_util_typing.T]" + assert stringify(MyInt) == "MyInt" @@ -1,6 +1,6 @@ [tox] minversion = 2.4.0 -envlist = docs,flake8,mypy,twine,coverage,py{35,36,37,38,39},du{12,13,14,15} +envlist = docs,flake8,mypy,twine,coverage,py{36,37,38,39},du{14,15,16} [testenv] usedevelop = True @@ -14,11 +14,9 @@ passenv = EPUBCHECK_PATH TERM description = - py{35,36,37,38,39}: Run unit tests against {envname}. + py{36,37,38,39}: Run unit tests against {envname}. du{12,13,14}: Run unit tests with the given version of docutils. deps = - du12: docutils==0.12 - du13: docutils==0.13.1 du14: docutils==0.14 du15: docutils==0.15 du16: docutils==0.16 |