diff options
59 files changed, 709 insertions, 384 deletions
@@ -17,6 +17,7 @@ Dependencies which in Ubuntu xenial are provided by package ``fonts-freefont-otf``, and e.g. in Fedora 29 via package ``texlive-gnu-freefont``. * requests 2.5.0 or above +* The six package is no longer a dependency. Incompatible changes -------------------- @@ -45,6 +46,11 @@ Incompatible changes block. As a result, they are highlighted as python3 by default. * The order of argument for ``HTMLTranslator``, ``HTML5Translator`` and ``ManualPageTranslator`` are changed +* LaTeX: hard-coded redefinitions of ``\l@section`` and ``\l@subsection`` + formerly done during loading of ``'manual'`` docclass get executed later, at + time of ``\sphinxtableofcontents``. This means that custom user definitions + from LaTeX preamble now get overwritten. Use ``\sphinxtableofcontentshook`` + to insert custom user definitions. See :ref:`latex-macros`. Deprecated ---------- @@ -127,6 +133,10 @@ Features added * LaTeX: support rendering (not in math, yet) of Greek and Cyrillic Unicode letters in non-Cyrillic document even with ``'pdflatex'`` as :confval:`latex_engine` (refs: #5645) +* #5660: The ``versionadded``, ``versionchanged`` and ``deprecated`` directives + are now generated with their own specific CSS classes + (``added``, ``changed`` and ``deprecated``, respectively) in addition to the + generic ``versionmodified`` class. * #5841: apidoc: Add --extensions option to sphinx-apidoc Bugs fixed @@ -149,7 +159,7 @@ Bugs fixed Testing -------- -Release 1.8.3 (in development) +Release 1.8.4 (in development) ============================== Dependencies @@ -167,6 +177,22 @@ Features added Bugs fixed ---------- +Testing +-------- + +Release 1.8.3 (released Dec 26, 2018) +===================================== + +Features added +-------------- + +* LaTeX: it is possible to insert custom material to appear on back of title + page, see discussion of ``'maketitle'`` key of :confval:`latex_elements` + (``'manual'`` docclass only) + +Bugs fixed +---------- + * #5725: mathjax: Use CDN URL for "latest" version by default * #5460: html search does not work with some 3rd party themes * #5520: LaTeX, caption package incompatibility since Sphinx 1.6 @@ -179,9 +205,13 @@ Bugs fixed * #5636: C++, fix parsing of floating point literals. * #5496 (again): C++, fix assertion in partial builds with duplicates. * #5724: quickstart: sphinx-quickstart fails when $LC_ALL is empty - -Testing --------- +* #1956: Default conf.py is not PEP8-compliant +* #5849: LaTeX: document class ``\maketitle`` is overwritten with no + possibility to use original meaning in place of Sphinx custom one +* #5834: apidoc: wrong help for ``--tocfile`` +* #5800: todo: crashed if todo is defined in TextElement +* #5846: htmlhelp: convert hex escaping to decimal escaping in .hhc/.hhk files +* htmlhelp: broken .hhk file generated when title contains a double quote Release 1.8.2 (released Nov 11, 2018) ===================================== @@ -551,27 +581,6 @@ Documentation * #5083: Fix wrong make.bat option for internationalization. * #5115: napoleon: add admonitions added by #4613 to the docs. -Release 1.7.10 (in development) -=============================== - -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- - -Features added --------------- - -Bugs fixed ----------- - -Testing --------- - Release 1.7.9 (released Sep 05, 2018) ===================================== diff --git a/doc/contents.rst b/doc/contents.rst index dc9baf269..f3a1cd2f8 100644 --- a/doc/contents.rst +++ b/doc/contents.rst @@ -25,6 +25,7 @@ Sphinx documentation contents templating latex extdev/index + development/tutorials/index faq glossary diff --git a/doc/develop.rst b/doc/develop.rst index 60ccaf79b..d061aae61 100644 --- a/doc/develop.rst +++ b/doc/develop.rst @@ -100,7 +100,7 @@ This is the current list of contributed extensions in that repository: - zopeext: provide an ``autointerface`` directive for using `Zope interfaces`_ -See the :ref:`extension tutorial <exttut>` on getting started with writing your +See the :doc:`extension tutorials <../development/tutorials/index>` on getting started with writing your own extensions. diff --git a/doc/development/tutorials/helloworld.rst b/doc/development/tutorials/helloworld.rst new file mode 100644 index 000000000..5ce8db66c --- /dev/null +++ b/doc/development/tutorials/helloworld.rst @@ -0,0 +1,162 @@ +Developing a "Hello world" directive +==================================== + +The objective of this tutorial is to create a very basic extension that adds a new +directive that outputs a paragraph containing `hello world`. + +Only basic information is provided in this tutorial. For more information, +refer to the :doc:`other tutorials <index>` that go into more +details. + +.. warning:: For this extension, you will need some basic understanding of docutils_ + and Python. + +Creating a new extension file +----------------------------- + +Your extension file could be in any folder of your project. In our case, +let's do the following: + +#. Create an :file:`_ext` folder in :file:`source`. +#. Create a new Python file in the :file:`_ext` folder called + :file:`helloworld.py`. + + Here is an example of the folder structure you might obtain: + + .. code-block:: text + + └── source + ├── _ext + │ └── helloworld.py + ├── _static + ├── _themes + ├── conf.py + ├── somefolder + ├── somefile.rst + └── someotherfile.rst + +Writing the extension +--------------------- + +Open :file:`helloworld.py` and paste the following code in it: + +.. code-block:: python + + from docutils import nodes + from docutils.parsers.rst import Directive + + + class HelloWorld(Directive): + def run(self): + paragraph_node = nodes.paragraph(text='Hello World!') + return [paragraph_node] + + + def setup(app): + app.add_directive("helloworld", HelloWorld) + + +Some essential things are happening in this example, and you will see them +in all directives: + +.. rubric:: Directive declaration + +Our new directive is declared in the ``HelloWorld`` class, it extends +docutils_' ``Directive`` class. All extensions that create directives +should extend this class. + +.. rubric:: ``run`` method + +This method is a requirement and it is part of every directive. It contains +the main logic of the directive and it returns a list of docutils nodes to +be processed by Sphinx. + +.. seealso:: + + :doc:`todo` + +.. rubric:: docutils nodes + +The ``run`` method returns a list of nodes. Nodes are docutils' way of +representing the content of a document. There are many types of nodes +available: text, paragraph, reference, table, etc. + +.. seealso:: + + `The docutils documentation on nodes <docutils nodes>`_ + +The ``nodes.paragraph`` class creates a new paragraph node. A paragraph +node typically contains some text that we can set during instantiation using +the ``text`` parameter. + +.. rubric:: ``setup`` function + +This function is a requirement. We use it to plug our new directive into +Sphinx. +The simplest thing you can do it call the ``app.add_directive`` method. + +.. note:: + + The first argument is the name of the directive itself as used in an rST file. + + In our case, we would use ``helloworld``: + + .. code-block:: rst + + Some intro text here... + + .. helloworld:: + + Some more text here... + + +Updating the conf.py file +------------------------- + +The extension file has to be declared in your :file:`conf.py` file to make +Sphinx aware of it: + +#. Open :file:`conf.py`. It is in the :file:`source` folder by default. +#. Add ``sys.path.append(os.path.abspath("./_ext"))`` before + the ``extensions`` variable declaration (if it exists). +#. Update or create the ``extensions`` list and add the + extension file name to the list: + + .. code-block:: python + + extensions.append('helloworld') + +You can now use the extension. + +.. admonition:: Example + + .. code-block:: rst + + Some intro text here... + + .. helloworld:: + + Some more text here... + + The sample above would generate: + + .. code-block:: text + + Some intro text here... + + Hello World! + + Some more text here... + +This is the very basic principle of an extension that creates a new directive. + +For a more advanced example, refer to :doc:`todo`. + +Further reading +--------------- + +You can create your own nodes if needed, refer to the +:doc:`todo` for more information. + +.. _docutils: http://docutils.sourceforge.net/ +.. _`docutils nodes`: http://docutils.sourceforge.net/docs/ref/doctree.html
\ No newline at end of file diff --git a/doc/development/tutorials/index.rst b/doc/development/tutorials/index.rst new file mode 100644 index 000000000..cb8dce435 --- /dev/null +++ b/doc/development/tutorials/index.rst @@ -0,0 +1,11 @@ +Extension tutorials +=================== + +Refer to the following tutorials to get started with extension development. + +.. toctree:: + :caption: Directive tutorials + :maxdepth: 1 + + helloworld + todo diff --git a/doc/extdev/tutorial.rst b/doc/development/tutorials/todo.rst index 33b45035e..e68a39342 100644 --- a/doc/extdev/tutorial.rst +++ b/doc/development/tutorials/todo.rst @@ -1,7 +1,5 @@ -.. _exttut: - -Tutorial: Writing a simple extension -==================================== +Developing a "TODO" extension +============================= This section is intended as a walkthrough for the creation of custom extensions. It covers the basics of writing and activating an extension, as well as @@ -12,112 +10,12 @@ include todo entries in the documentation, and to collect these in a central place. (A similar "todo" extension is distributed with Sphinx.) -Important objects ------------------ - -There are several key objects whose API you will use while writing an -extension. These are: - -**Application** - The application object (usually called ``app``) is an instance of - :class:`.Sphinx`. It controls most high-level functionality, such as the - setup of extensions, event dispatching and producing output (logging). - - If you have the environment object, the application is available as - ``env.app``. - -**Environment** - The build environment object (usually called ``env``) is an instance of - :class:`.BuildEnvironment`. It is responsible for parsing the source - documents, stores all metadata about the document collection and is - serialized to disk after each build. - - Its API provides methods to do with access to metadata, resolving references, - etc. It can also be used by extensions to cache information that should - persist for incremental rebuilds. - - If you have the application or builder object, the environment is available - as ``app.env`` or ``builder.env``. - -**Builder** - The builder object (usually called ``builder``) is an instance of a specific - subclass of :class:`.Builder`. Each builder class knows how to convert the - parsed documents into an output format, or otherwise process them (e.g. check - external links). - - If you have the application object, the builder is available as - ``app.builder``. - -**Config** - The config object (usually called ``config``) provides the values of - configuration values set in :file:`conf.py` as attributes. It is an instance - of :class:`.Config`. - - The config is available as ``app.config`` or ``env.config``. - - -Build Phases ------------- - -One thing that is vital in order to understand extension mechanisms is the way -in which a Sphinx project is built: this works in several phases. - -**Phase 0: Initialization** - - In this phase, almost nothing of interest to us happens. The source - directory is searched for source files, and extensions are initialized. - Should a stored build environment exist, it is loaded, otherwise a new one is - created. - -**Phase 1: Reading** - - In Phase 1, all source files (and on subsequent builds, those that are new or - changed) are read and parsed. This is the phase where directives and roles - are encountered by docutils, and the corresponding code is executed. The - output of this phase is a *doctree* for each source file; that is a tree of - docutils nodes. For document elements that aren't fully known until all - existing files are read, temporary nodes are created. - - There are nodes provided by docutils, which are documented `in the docutils - documentation <http://docutils.sourceforge.net/docs/ref/doctree.html>`__. - Additional nodes are provided by Sphinx and :ref:`documented here <nodes>`. - - During reading, the build environment is updated with all meta- and cross - reference data of the read documents, such as labels, the names of headings, - described Python objects and index entries. This will later be used to - replace the temporary nodes. - - The parsed doctrees are stored on the disk, because it is not possible to - hold all of them in memory. - -**Phase 2: Consistency checks** - - Some checking is done to ensure no surprises in the built documents. - -**Phase 3: Resolving** - - Now that the metadata and cross-reference data of all existing documents is - known, all temporary nodes are replaced by nodes that can be converted into - output using components called tranform. For example, links are created for - object references that exist, and simple literal nodes are created for those - that don't. - -**Phase 4: Writing** - - This phase converts the resolved doctrees to the desired output format, such - as HTML or LaTeX. This happens via a so-called docutils writer that visits - the individual nodes of each doctree and produces some output in the process. - -.. note:: - - Some builders deviate from this general build plan, for example, the builder - that checks external links does not need anything more than the parsed - doctrees and therefore does not have phases 2--4. - - Extension Design ---------------- +.. note:: To understand the design this extension, refer to + :ref:`important-objects` and :ref:`build-phases`. + We want the extension to add the following to Sphinx: * A "todo" directive, containing some content that is marked with "TODO", and @@ -174,12 +72,13 @@ the individual calls do is the following: If the third argument was ``'html'``, HTML documents would be full rebuild if the config value changed its value. This is needed for config values that - influence reading (build phase 1). + influence reading (build :ref:`phase 1 <build-phases>`). * :meth:`~Sphinx.add_node` adds a new *node class* to the build system. It also can specify visitor functions for each supported output format. These visitor - functions are needed when the new nodes stay until phase 4 -- since the - ``todolist`` node is always replaced in phase 3, it doesn't need any. + functions are needed when the new nodes stay until :ref:`phase 4 <build-phases>` + -- since the ``todolist`` node is always replaced in :ref:`phase 3 <build-phases>`, + it doesn't need any. We need to create the two node classes ``todo`` and ``todolist`` later. @@ -276,7 +175,7 @@ The ``todo`` directive function looks like this:: return [targetnode, todo_node] Several important things are covered here. First, as you can see, you can refer -to the build environment instance using ``self.state.document.settings.env``. +to the :ref:`build environment instance <important-objects>` using ``self.state.document.settings.env``. Then, to act as a link target (from the todolist), the todo directive needs to return a target node in addition to the todo node. The target ID (in HTML, this @@ -340,7 +239,8 @@ Here we clear out all todos whose docname matches the given one from the added again during parsing. The other handler belongs to the :event:`doctree-resolved` event. This event is -emitted at the end of phase 3 and allows custom resolving to be done:: +emitted at the end of :ref:`phase 3 <build-phases>` and allows custom resolving +to be done:: def process_todo_nodes(app, doctree, fromdocname): if not app.config.todo_include_todos: diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index b5336373d..da0d0e2d0 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -52,6 +52,115 @@ Note that it is still necessary to register the builder using .. _entry points: https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins +.. _important-objects: + +Important objects +----------------- + +There are several key objects whose API you will use while writing an +extension. These are: + +**Application** + The application object (usually called ``app``) is an instance of + :class:`.Sphinx`. It controls most high-level functionality, such as the + setup of extensions, event dispatching and producing output (logging). + + If you have the environment object, the application is available as + ``env.app``. + +**Environment** + The build environment object (usually called ``env``) is an instance of + :class:`.BuildEnvironment`. It is responsible for parsing the source + documents, stores all metadata about the document collection and is + serialized to disk after each build. + + Its API provides methods to do with access to metadata, resolving references, + etc. It can also be used by extensions to cache information that should + persist for incremental rebuilds. + + If you have the application or builder object, the environment is available + as ``app.env`` or ``builder.env``. + +**Builder** + The builder object (usually called ``builder``) is an instance of a specific + subclass of :class:`.Builder`. Each builder class knows how to convert the + parsed documents into an output format, or otherwise process them (e.g. check + external links). + + If you have the application object, the builder is available as + ``app.builder``. + +**Config** + The config object (usually called ``config``) provides the values of + configuration values set in :file:`conf.py` as attributes. It is an instance + of :class:`.Config`. + + The config is available as ``app.config`` or ``env.config``. + +To see an example of use of these objects, refer to :doc:`../development/tutorials/index`. + +.. _build-phases: + +Build Phases +------------ + +One thing that is vital in order to understand extension mechanisms is the way +in which a Sphinx project is built: this works in several phases. + +**Phase 0: Initialization** + + In this phase, almost nothing of interest to us happens. The source + directory is searched for source files, and extensions are initialized. + Should a stored build environment exist, it is loaded, otherwise a new one is + created. + +**Phase 1: Reading** + + In Phase 1, all source files (and on subsequent builds, those that are new or + changed) are read and parsed. This is the phase where directives and roles + are encountered by docutils, and the corresponding code is executed. The + output of this phase is a *doctree* for each source file; that is a tree of + docutils nodes. For document elements that aren't fully known until all + existing files are read, temporary nodes are created. + + There are nodes provided by docutils, which are documented `in the docutils + documentation <http://docutils.sourceforge.net/docs/ref/doctree.html>`__. + Additional nodes are provided by Sphinx and :ref:`documented here <nodes>`. + + During reading, the build environment is updated with all meta- and cross + reference data of the read documents, such as labels, the names of headings, + described Python objects and index entries. This will later be used to + replace the temporary nodes. + + The parsed doctrees are stored on the disk, because it is not possible to + hold all of them in memory. + +**Phase 2: Consistency checks** + + Some checking is done to ensure no surprises in the built documents. + +**Phase 3: Resolving** + + Now that the metadata and cross-reference data of all existing documents is + known, all temporary nodes are replaced by nodes that can be converted into + output using components called tranform. For example, links are created for + object references that exist, and simple literal nodes are created for those + that don't. + +**Phase 4: Writing** + + This phase converts the resolved doctrees to the desired output format, such + as HTML or LaTeX. This happens via a so-called docutils writer that visits + the individual nodes of each doctree and produces some output in the process. + +.. note:: + + Some builders deviate from this general build plan, for example, the builder + that checks external links does not need anything more than the parsed + doctrees and therefore does not have phases 2--4. + +To see an example of application, refer to :doc:`../development/tutorials/todo`. + .. _ext-metadata: Extension metadata @@ -82,8 +191,8 @@ APIs used for writing extensions -------------------------------- .. toctree:: + :maxdepth: 2 - tutorial appapi projectapi envapi diff --git a/doc/faq.rst b/doc/faq.rst index 920ef6651..4b608b780 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -30,7 +30,7 @@ How do I... ``sidebartoc`` block. ... write my own extension? - See the :ref:`extension tutorial <exttut>`. + See the :doc:`/development/tutorials/index`. ... convert from my existing docs using MoinMoin markup? The easiest way is to convert to xhtml, then convert `xhtml to reST`_. diff --git a/doc/latex.rst b/doc/latex.rst index 63e860a64..7a4d0be03 100644 --- a/doc/latex.rst +++ b/doc/latex.rst @@ -365,6 +365,8 @@ 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. +.. _latex-macros: + Macros ~~~~~~ @@ -390,13 +392,32 @@ Macros .. versionadded:: 1.6.3 ``\sphinxstylecodecontinued`` and ``\sphinxstylecodecontinues``. - the table of contents is typeset via ``\sphinxtableofcontents`` which is a - wrapper (whose definition can be found in :file:`sphinxhowto.cls` or in - :file:`sphinxmanual.cls`) of standard ``\tableofcontents``. + wrapper (defined differently in :file:`sphinxhowto.cls` and in + :file:`sphinxmanual.cls`) of standard ``\tableofcontents``. The macro + ``\sphinxtableofcontentshook`` is executed during its expansion right before + ``\tableofcontents`` itself. .. versionchanged:: 1.5 formerly, the meaning of ``\tableofcontents`` was modified by Sphinx. -- the ``\maketitle`` command is redefined by the class files - :file:`sphinxmanual.cls` and :file:`sphinxhowto.cls`. + .. versionchanged:: 2.0 + hard-coded redefinitions of ``\l@section`` and ``\l@subsection`` formerly + done during loading of ``'manual'`` docclass are now executed later via + ``\sphinxtableofcontentshook``. This macro is also executed by the + ``'howto'`` docclass, but defaults to empty with it. +- a custom ``\sphinxmaketitle`` is defined in the class files + :file:`sphinxmanual.cls` and :file:`sphinxhowto.cls` and is used as + default setting of ``'maketitle'`` :confval:`latex_elements` key. + + .. versionchanged:: 1.8.3 + formerly, ``\maketitle`` from LaTeX document class was modified by + Sphinx. +- for ``'manual'`` docclass a macro ``\sphinxbackoftitlepage``, if it is + defined, gets executed at end of ``\sphinxmaketitle``, before the final + ``\clearpage``. Use either the ``'maketitle'`` key or the ``'preamble'`` key + of :confval:`latex_elements` to add a custom definition of + ``\sphinxbackoftitlepage``. + + .. versionadded:: 1.8.3 - the citation reference is typeset via ``\sphinxcite`` which is a wrapper of standard ``\cite``. diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 3e20cf7dc..5400697eb 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -2279,10 +2279,22 @@ information. Previously this was done from inside :file:`sphinx.sty`. ``'maketitle'`` - "maketitle" call, default ``'\\maketitle'`` (but it has been - redefined by the Sphinx ``manual`` and ``howto`` classes.) Override - if you want to generate a differently-styled title page. + "maketitle" call, default ``'\\sphinxmaketitle'``. Override + if you want to generate a differently styled title page. + .. hint:: + + If the key value is set to + ``r'\newcommand\sphinxbackoftitlepage{<Extra + material>}\sphinxmaketitle'``, then ``<Extra material>`` will be + typeset on back of title page (``'manual'`` docclass only). + + .. versionchanged:: 1.8.3 + Original ``\maketitle`` from document class is not overwritten, + hence is re-usable as part of some custom setting for this key. + .. versionadded:: 1.8.3 + ``\sphinxbackoftitlepage`` optional macro. It can also be defined + inside ``'preamble'`` key rather than this one. ``'releasename'`` value that prefixes ``'release'`` element on title page, default ``'Release'``. As for *title* and *author* used in the tuples of diff --git a/doc/usage/extensions/viewcode.rst b/doc/usage/extensions/viewcode.rst index cc7dbb07a..d3c3c44fb 100644 --- a/doc/usage/extensions/viewcode.rst +++ b/doc/usage/extensions/viewcode.rst @@ -41,10 +41,9 @@ Configuration .. confval:: viewcode_follow_imported_members - If this is ``True``, viewcode extension will follow alias objects that - imported from another module such as functions, classes and attributes. As - side effects, this option else they produce nothing. The default is - ``True``. + If this is ``True``, viewcode extension will emit + :event:`viewcode-follow-imported` event to resolve the name of the module + by other extensions. The default is ``True``. .. versionadded:: 1.3 diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst index 7efb6735d..f51b3084e 100644 --- a/doc/usage/installation.rst +++ b/doc/usage/installation.rst @@ -113,8 +113,7 @@ Prompt* (:kbd:`⊞Win-r` and type :command:`cmd`). Once the command prompt is open, type :command:`python --version` and press Enter. If Python is available, you will see the version of Python printed to the screen. If you do not have Python installed, refer to the `Hitchhikers Guide to Python's`__ -Python on Windows installation guides. You can install either `Python 3`__ or -`Python 2.7`__. Python 3 is recommended. +Python on Windows installation guides. You must install `Python 3`__. Once Python is installed, you can install Sphinx using :command:`pip`. Refer to the :ref:`pip installation instructions <install-pypi>` below for more @@ -122,7 +121,6 @@ information. __ https://docs.python-guide.org/ __ https://docs.python-guide.org/starting/install3/win/ -__ https://docs.python-guide.org/starting/install/win/ .. _install-pypi: diff --git a/doc/usage/markdown.rst b/doc/usage/markdown.rst index ed0cce013..7593ea6b0 100644 --- a/doc/usage/markdown.rst +++ b/doc/usage/markdown.rst @@ -22,26 +22,41 @@ Configuration To configure your Sphinx project for Markdown support, proceed as follows: -#. Install *recommonmark*:: +#. Install the Markdown parser *recommonmark* from its source on GitHub:: - pip install recommonmark + pip install git+https://github.com/rtfd/recommonmark -#. Add the Markdown parser to the ``source_parsers`` configuration variable in - your Sphinx configuration file:: + .. note:: - source_parsers = { - '.md': 'recommonmark.parser.CommonMarkParser', - } + The configuration as explained here requires recommonmark version + 0.5.0.dev or higher, which is at the time of writing not available on + PyPI. If you want to use a released recommonmark version, follow the + instructions in the `Sphinx 1.8 documentation`__. + +__ https://www.sphinx-doc.org/en/1.8/usage/markdown.html + +#. Add *recommonmark* to the + :confval:`list of configured extensions <extensions>`:: - You can replace ``.md`` with a filename extension of your choice. + extensions = ['recommonmark'] -#. Add the Markdown filename extension to the ``source_suffix`` configuration - variable:: + .. versionchanged:: 1.8 + Version 1.8 deprecates and version 3.0 removes the ``source_parsers`` + configuration variable that was used by older *recommonmark* versions. - source_suffix = ['.rst', '.md'] +#. If you want to use Markdown files with extensions other than ``.md``, adjust + the :confval:`source_suffix` variable. The following example configures + Sphinx to parse all files with the extensions ``.md`` and ``.txt`` as + Markdown:: + + source_suffix = { + '.rst': 'restructuredtext', + '.txt': 'markdown', + '.md': 'markdown', + } #. You can further configure *recommonmark* to allow custom syntax that - standard *CommonMark* doesn't support. Read more in the `recommonmark + standard *CommonMark* doesn't support. Read more in the `recommonmark documentation`__. __ https://recommonmark.readthedocs.io/en/latest/auto_structify.html @@ -15,7 +15,6 @@ if sys.version_info < (3, 5): sys.exit(1) install_requires = [ - 'six>=1.5', 'Jinja2>=2.3', 'Pygments>=2.0', 'docutils>=0.12', diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 075fec8e3..b8b03c072 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -25,7 +25,6 @@ from docutils.frontend import OptionParser from docutils.io import DocTreeInput, StringOutput from docutils.readers.doctree import Reader as DoctreeReader from docutils.utils import relative_path -from six import text_type from sphinx import package_dir, __display_version__ from sphinx.application import ENV_PICKLE_FILENAME @@ -86,10 +85,10 @@ def get_stable_hash(obj): return get_stable_hash(list(obj.items())) elif isinstance(obj, (list, tuple)): obj = sorted(get_stable_hash(o) for o in obj) - return md5(text_type(obj).encode()).hexdigest() + return md5(str(obj).encode()).hexdigest() -class Stylesheet(text_type): +class Stylesheet(str): """A metadata of stylesheet. To keep compatibility with old themes, an instance of stylesheet behaves as @@ -101,7 +100,7 @@ class Stylesheet(text_type): def __new__(cls, filename, *args, **attributes): # type: (str, str, str) -> None - self = text_type.__new__(cls, filename) # type: ignore + self = str.__new__(cls, filename) # type: ignore self.filename = filename self.attributes = attributes self.attributes.setdefault('rel', 'stylesheet') @@ -146,7 +145,7 @@ class JSContainer(list): return ret -class JavaScript(text_type): +class JavaScript(str): """A metadata of javascript file. To keep compatibility with old themes, an instance of javascript behaves as @@ -158,7 +157,7 @@ class JavaScript(text_type): def __new__(cls, filename, **attributes): # type: (str, **str) -> None - self = text_type.__new__(cls, filename) # type: ignore + self = str.__new__(cls, filename) # type: ignore self.filename = filename self.attributes = attributes self.attributes.setdefault('type', 'text/javascript') @@ -1633,7 +1632,7 @@ def setup(app): 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', '\u00B6', 'html') + app.add_config_value('html_add_permalinks', '¶', 'html') app.add_config_value('html_use_index', True, 'html') app.add_config_value('html_split_index', False, 'html') app.add_config_value('html_copy_source', True, 'html') diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py index 4a9c7e36b..641761b69 100644 --- a/sphinx/builders/htmlhelp.py +++ b/sphinx/builders/htmlhelp.py @@ -25,7 +25,7 @@ from sphinx.util.osutil import make_filename_from_project if False: # For type annotation - from typing import Any, Dict, IO, List, Tuple # NOQA + from typing import Any, Dict, IO, List, Match, Tuple # NOQA from sphinx.application import Sphinx # NOQA from sphinx.config import Config # NOQA @@ -169,6 +169,20 @@ chm_locales = { } +def chm_htmlescape(s, quote=True): + # type: (str, bool) -> str + """ + chm_htmlescape() is a wrapper of html.escape(). + .hhc/.hhk files don't recognize hex escaping, we need convert + hex escaping to decimal escaping. for example: ``'`` -> ``'`` + html.escape() may generates a hex escaping ``'`` for single + quote ``'``, this wrapper fixes this. + """ + s = html.escape(s, quote) + s = s.replace(''', ''') # re-escape as decimal + return s + + class HTMLHelpBuilder(StandaloneHTMLBuilder): """ Builder that also outputs Windows HTML help project, contents and @@ -278,7 +292,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): write_toc(subnode, ullevel) elif isinstance(node, nodes.reference): link = node['refuri'] - title = html.escape(node.astext()).replace('"', '"') + title = chm_htmlescape(node.astext(), True) f.write(object_sitemap % (title, link)) elif isinstance(node, nodes.bullet_list): if ullevel != 0: @@ -305,10 +319,9 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): # type: (str, List[Tuple[str, str]], List[Tuple[str, List[Tuple[str, str]]]]) -> None # NOQA def write_param(name, value): # type: (str, str) -> None - item = ' <param name="%s" value="%s">\n' % \ - (name, value) + item = ' <param name="%s" value="%s">\n' % (name, value) f.write(item) - title = html.escape(title) + title = chm_htmlescape(title, True) f.write('<LI> <OBJECT type="text/sitemap">\n') write_param('Keyword', title) if len(refs) == 0: diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index a69e8f914..2d423a1dd 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -12,7 +12,6 @@ import os from os import path from docutils.frontend import OptionParser -from six import text_type from sphinx import package_dir, addnodes, highlighting from sphinx.builders import Builder @@ -24,7 +23,7 @@ from sphinx.builders.latex.transforms import ( from sphinx.config import ENUM from sphinx.environment import NoUri from sphinx.environment.adapters.asset import ImageAdapter -from sphinx.errors import SphinxError, ConfigError +from sphinx.errors import SphinxError from sphinx.locale import _, __ from sphinx.transforms import SphinxTransformer from sphinx.util import texescape, logging, status_iterator @@ -413,23 +412,6 @@ class LaTeXBuilder(Builder): def validate_config_values(app, config): # type: (Sphinx, Config) -> None - for document in config.latex_documents: - try: - text_type(document[2]) - except UnicodeDecodeError: - raise ConfigError( - __('Invalid latex_documents.title found (might contain non-ASCII chars. ' - 'Please use u"..." notation instead): %r') % (document,) - ) - - try: - text_type(document[3]) - except UnicodeDecodeError: - raise ConfigError( - __('Invalid latex_documents.author found (might contain non-ASCII chars. ' - 'Please use u"..." notation instead): %r') % (document,) - ) - for key in list(config.latex_elements): if key not in DEFAULT_SETTINGS: msg = __("Unknown configure key: latex_elements[%r]. ignored.") diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index f2efa9638..15c6827ff 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -16,7 +16,6 @@ import sys import traceback from docutils.utils import SystemMessage -from six import text_type import sphinx.locale from sphinx import __display_version__, package_dir @@ -53,17 +52,17 @@ def handle_exception(app, args, exception, stderr=sys.stderr): print(terminal_safe(exception.args[0]), file=stderr) elif isinstance(exception, SphinxError): print(red('%s:' % exception.category), file=stderr) - print(terminal_safe(text_type(exception)), file=stderr) + print(terminal_safe(str(exception)), file=stderr) elif isinstance(exception, UnicodeError): print(red(__('Encoding error:')), file=stderr) - print(terminal_safe(text_type(exception)), file=stderr) + print(terminal_safe(str(exception)), file=stderr) tbpath = save_traceback(app) print(red(__('The full traceback has been saved in %s, if you want ' 'to report the issue to the developers.') % tbpath), file=stderr) elif isinstance(exception, RuntimeError) and 'recursion depth' in str(exception): print(red(__('Recursion error:')), file=stderr) - print(terminal_safe(text_type(exception)), file=stderr) + print(terminal_safe(str(exception)), file=stderr) print(file=stderr) print(__('This can happen with very large or deeply nested source ' 'files. You can carefully increase the default Python ' diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index b3db8900e..d3e41aa54 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -32,7 +32,6 @@ except ImportError: USE_LIBEDIT = False from docutils.utils import column_width -from six import text_type import sphinx.locale from sphinx import __display_version__, package_dir @@ -158,7 +157,7 @@ def term_decode(text): warnings.warn('term_decode() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) - if isinstance(text, text_type): + if isinstance(text, str): return text # Use the known encoding, if possible @@ -391,10 +390,9 @@ def generate(d, overwrite=True, silent=False, templatedir=None): d['project_underline'] = column_width(d['project']) * '=' d.setdefault('extensions', []) d['copyright'] = time.strftime('%Y') + ', ' + d['author'] - d['author_texescaped'] = text_type(d['author']).\ - translate(texescape.tex_escape_map) + d['author_texescaped'] = d['author'].translate(texescape.tex_escape_map) d['project_doc'] = d['project'] + ' Documentation' - d['project_doc_texescaped'] = text_type(d['project'] + ' Documentation').\ + d['project_doc_texescaped'] = (d['project'] + ' Documentation').\ translate(texescape.tex_escape_map) # escape backslashes and single quotes in strings that are put into diff --git a/sphinx/config.py b/sphinx/config.py index 002b2a559..338de770c 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -16,8 +16,6 @@ from collections import OrderedDict from os import path, getenv from typing import Any, NamedTuple, Union -from six import text_type - from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning from sphinx.errors import ConfigError, ExtensionError from sphinx.locale import _, __ @@ -41,7 +39,7 @@ copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])') ConfigValue = NamedTuple('ConfigValue', [('name', str), ('value', Any), - ('rebuild', Union[bool, text_type])]) + ('rebuild', Union[bool, str])]) def is_serializable(obj): @@ -78,7 +76,7 @@ class ENUM: # RemovedInSphinx40Warning -string_classes = [text_type] # type: List +string_classes = [str] # type: List class Config: diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py index 21626ba35..4a515d2cc 100644 --- a/sphinx/domains/changeset.py +++ b/sphinx/domains/changeset.py @@ -34,6 +34,12 @@ versionlabels = { 'deprecated': _('Deprecated since version %s'), } +versionlabel_classes = { + 'versionadded': 'added', + 'versionchanged': 'changed', + 'deprecated': 'deprecated', +} + locale.versionlabels = DeprecatedDict( versionlabels, 'sphinx.locale.versionlabels is deprecated. ' @@ -78,6 +84,7 @@ class VersionChange(SphinxDirective): messages = [] if self.content: self.state.nested_parse(self.content, self.content_offset, node) + classes = ['versionmodified', versionlabel_classes[self.name]] if len(node): if isinstance(node[0], nodes.paragraph) and node[0].rawsource: content = nodes.inline(node[0].rawsource, translatable=True) @@ -87,11 +94,11 @@ class VersionChange(SphinxDirective): node[0].replace_self(nodes.paragraph('', '', content, translatable=False)) para = cast(nodes.paragraph, node[0]) - para.insert(0, nodes.inline('', '%s: ' % text, classes=['versionmodified'])) + para.insert(0, nodes.inline('', '%s: ' % text, classes=classes)) else: para = nodes.paragraph('', '', nodes.inline('', '%s.' % text, - classes=['versionmodified']), + classes=classes), translatable=False) node.append(para) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 70f1208df..51ebe35f1 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -14,7 +14,6 @@ from copy import deepcopy from docutils import nodes, utils from docutils.parsers.rst import directives -from six import text_type from sphinx import addnodes from sphinx.deprecation import RemovedInSphinx40Warning @@ -635,7 +634,7 @@ class ASTBase: def __str__(self): # type: () -> str - return self._stringify(lambda ast: text_type(ast)) + return self._stringify(lambda ast: str(ast)) def get_display_string(self): # type: () -> str @@ -666,7 +665,7 @@ class ASTCPPAttribute(ASTBase): def describe_signature(self, signode): # type: (addnodes.desc_signature) -> None - txt = text_type(self) + txt = str(self) signode.append(nodes.Text(txt, txt)) @@ -705,7 +704,7 @@ class ASTGnuAttributeList(ASTBase): def describe_signature(self, signode): # type: (addnodes.desc_signature) -> None - txt = text_type(self) + txt = str(self) signode.append(nodes.Text(txt, txt)) @@ -739,7 +738,7 @@ class ASTParenAttribute(ASTBase): def describe_signature(self, signode): # type: (addnodes.desc_signature) -> None - txt = text_type(self) + txt = str(self) signode.append(nodes.Text(txt, txt)) @@ -779,7 +778,7 @@ class ASTBooleanLiteral(ASTBase): return 'L0E' def describe_signature(self, signode, mode, env, symbol): - signode.append(nodes.Text(text_type(self))) + signode.append(nodes.Text(str(self))) class ASTNumberLiteral(ASTBase): @@ -796,7 +795,7 @@ class ASTNumberLiteral(ASTBase): return "L%sE" % self.data def describe_signature(self, signode, mode, env, symbol): - txt = text_type(self) + txt = str(self) signode.append(nodes.Text(txt, txt)) @@ -835,7 +834,7 @@ class ASTCharLiteral(ASTBase): return self.type + str(self.value) def describe_signature(self, signode, mode, env, symbol): - txt = text_type(self) + txt = str(self) signode.append(nodes.Text(txt, txt)) @@ -854,7 +853,7 @@ class ASTStringLiteral(ASTBase): return "LA%d_KcE" % (len(self.data) - 2) def describe_signature(self, signode, mode, env, symbol): - txt = text_type(self) + txt = str(self) signode.append(nodes.Text(txt, txt)) @@ -917,7 +916,7 @@ class ASTFoldExpr(ASTBase): # type: (int) -> str assert version >= 3 if version == 3: - return text_type(self) + return str(self) # TODO: find the right mangling scheme assert False @@ -1473,7 +1472,7 @@ class ASTFallbackExpr(ASTBase): def get_id(self, version): # type: (int) -> str - return text_type(self.expr) + return str(self.expr) def describe_signature(self, signode, mode, env, symbol): signode += nodes.Text(self.expr) @@ -1511,7 +1510,7 @@ class ASTIdentifier(ASTBase): if self.is_anon(): return 'Ut%d_%s' % (len(self.identifier) - 1, self.identifier[1:]) else: - return text_type(len(self.identifier)) + self.identifier + return str(len(self.identifier)) + self.identifier # and this is where we finally make a difference between __str__ and the display string @@ -1994,7 +1993,7 @@ class ASTOperator(ASTBase): def describe_signature(self, signode, mode, env, prefix, templateArgs, symbol): # type: (addnodes.desc_signature, str, Any, str, str, Symbol) -> None _verify_description_mode(mode) - identifier = text_type(self) + identifier = str(self) if mode == 'lastIsName': signode += addnodes.desc_name(identifier, identifier) else: @@ -2043,7 +2042,7 @@ class ASTOperatorType(ASTOperator): def get_name_no_template(self): # type: () -> str - return text_type(self) + return str(self) class ASTOperatorLiteral(ASTOperator): @@ -2078,9 +2077,9 @@ class ASTTemplateArgConstant(ASTBase): def get_id(self, version): # type: (int) -> str if version == 1: - return text_type(self).replace(' ', '-') + return str(self).replace(' ', '-') if version == 2: - return 'X' + text_type(self) + 'E' + return 'X' + str(self) + 'E' return 'X' + self.value.get_id(version) + 'E' def describe_signature(self, signode, mode, env, symbol): @@ -2155,7 +2154,7 @@ class ASTNestedNameElement(ASTBase): def describe_signature(self, signode, mode, env, prefix, symbol): # type: (addnodes.desc_signature, str, BuildEnvironment, str, Symbol) -> None - tArgs = text_type(self.templateArgs) if self.templateArgs is not None else '' + tArgs = str(self.templateArgs) if self.templateArgs is not None else '' self.identOrOp.describe_signature(signode, mode, env, prefix, tArgs, symbol) if self.templateArgs is not None: self.templateArgs.describe_signature(signode, mode, env, symbol) @@ -2188,7 +2187,7 @@ class ASTNestedName(ASTBase): def get_id(self, version, modifiers=''): # type: (int, str) -> str if version == 1: - tt = text_type(self) + tt = str(self) if tt in _id_shorthands_v1: return _id_shorthands_v1[tt] else: @@ -2223,9 +2222,9 @@ class ASTNestedName(ASTBase): _verify_description_mode(mode) # just print the name part, with template args, not template params if mode == 'noneIsName': - signode += nodes.Text(text_type(self)) + signode += nodes.Text(str(self)) elif mode == 'param': - name = text_type(self) + name = str(self) signode += nodes.emphasis(name, name) elif mode == 'markType' or mode == 'lastIsName': # Each element should be a pending xref targeting the complete @@ -2258,10 +2257,10 @@ class ASTNestedName(ASTBase): if template: dest += nodes.Text("template ") first = False - txt_nne = text_type(nne) + txt_nne = str(nne) if txt_nne != '': if nne.templateArgs and iTemplateParams < len(templateParams): - templateParamsPrefix += text_type(templateParams[iTemplateParams]) + templateParamsPrefix += str(templateParams[iTemplateParams]) iTemplateParams += 1 nne.describe_signature(dest, 'markType', env, templateParamsPrefix + prefix, symbol) @@ -2306,7 +2305,7 @@ class ASTTrailingTypeSpecFundamental(ASTBase): def describe_signature(self, signode, mode, env, symbol): # type: (addnodes.desc_signature, str, BuildEnvironment, Symbol) -> None - signode += nodes.Text(text_type(self.name)) + signode += nodes.Text(str(self.name)) class ASTTrailingTypeSpecName(ASTBase): @@ -2354,7 +2353,7 @@ class ASTTrailingTypeSpecDecltypeAuto(ASTBase): def describe_signature(self, signode, mode, env, symbol): # type: (addnodes.desc_signature, str, BuildEnvironment, Symbol) -> None - signode.append(nodes.Text(text_type(self))) + signode.append(nodes.Text(str(self))) class ASTTrailingTypeSpecDecltype(ASTBase): @@ -2467,7 +2466,7 @@ class ASTParametersQualifiers(ASTBase): if not first: res.append(', ') first = False - res.append(text_type(a)) + res.append(str(a)) res.append(')') if self.volatile: res.append(' volatile') @@ -2478,7 +2477,7 @@ class ASTParametersQualifiers(ASTBase): res.append(self.refQual) if self.exceptionSpec: res.append(' ') - res.append(text_type(self.exceptionSpec)) + res.append(str(self.exceptionSpec)) if self.final: res.append(' final') if self.override: @@ -2515,13 +2514,13 @@ class ASTParametersQualifiers(ASTBase): if self.refQual: _add_text(signode, self.refQual) if self.exceptionSpec: - _add_anno(signode, text_type(self.exceptionSpec)) + _add_anno(signode, str(self.exceptionSpec)) if self.final: _add_anno(signode, 'final') if self.override: _add_anno(signode, 'override') if self.initializer: - _add_text(signode, '= ' + text_type(self.initializer)) + _add_text(signode, '= ' + str(self.initializer)) class ASTDeclSpecsSimple(ASTBase): @@ -2653,7 +2652,7 @@ class ASTDeclSpecs(ASTBase): if len(res) > 0: res.append(" ") res.append(transform(self.trailingTypeSpec)) - r = text_type(self.rightSpecs) + r = str(self.rightSpecs) if len(r) > 0: if len(res) > 0: res.append(" ") @@ -2704,7 +2703,7 @@ class ASTArray(ASTBase): return 'A' if version == 2: if self.size: - return 'A' + text_type(self.size) + '_' + return 'A' + str(self.size) + '_' else: return 'A_' if self.size: @@ -3321,7 +3320,7 @@ class ASTType(ASTBase): _verify_description_mode(mode) self.declSpecs.describe_signature(signode, 'markType', env, symbol) if (self.decl.require_space_after_declSpecs() and - len(text_type(self.declSpecs)) > 0): + len(str(self.declSpecs)) > 0): signode += nodes.Text(' ') # for parameters that don't really declare new names we get 'markType', # this should not be propagated, but be 'noneIsName'. @@ -3929,8 +3928,8 @@ class Symbol: param = templateParams.params[i] arg = templateArgs.args[i] # TODO: doing this by string manipulation is probably not the most efficient - paramName = text_type(param.name) - argTxt = text_type(arg) + paramName = str(param.name) + argTxt = str(arg) isArgPackExpansion = argTxt.endswith('...') if param.isPack != isArgPackExpansion: return True @@ -3958,13 +3957,13 @@ class Symbol: return False if templateParams: # TODO: do better comparison - if text_type(s.templateParams) != text_type(templateParams): + if str(s.templateParams) != str(templateParams): return False if (s.templateArgs is None) != (templateArgs is None): return False if s.templateArgs: # TODO: do better comparison - if text_type(s.templateArgs) != text_type(templateArgs): + if str(s.templateArgs) != str(templateArgs): return False return True if matchSelf and matches(self): @@ -4253,7 +4252,7 @@ class Symbol: if not ourChild.declaration: ourChild._fill_empty(otherChild.declaration, otherChild.docname) elif ourChild.docname != otherChild.docname: - name = text_type(ourChild.declaration) + name = str(ourChild.declaration) msg = __("Duplicate declaration, also defined in '%s'.\n" "Declaration is '%s'.") msg = msg % (ourChild.docname, name) @@ -4401,20 +4400,20 @@ class Symbol: res.append('::') else: if self.templateParams: - res.append(text_type(self.templateParams)) + res.append(str(self.templateParams)) res.append('\n') res.append('\t' * indent) if self.identOrOp: - res.append(text_type(self.identOrOp)) + res.append(str(self.identOrOp)) else: - res.append(text_type(self.declaration)) + res.append(str(self.declaration)) if self.templateArgs: - res.append(text_type(self.templateArgs)) + res.append(str(self.templateArgs)) if self.declaration: res.append(": ") if self.isRedeclaration: res.append('!!duplicate!! ') - res.append(text_type(self.declaration)) + res.append(str(self.declaration)) if self.docname: res.append('\t(') res.append(self.docname) @@ -6151,7 +6150,7 @@ class DefinitionParser: msg += " Declaration:\n\t" if templatePrefix: msg += "%s\n\t" % templatePrefix - msg += text_type(nestedName) + msg += str(nestedName) self.warn(msg) newTemplates = [] @@ -6445,7 +6444,7 @@ class CPPObject(ObjectDescription): parentDecl = parentSymbol.declaration if parentDecl is not None and parentDecl.objectType == 'function': self.warn("C++ declarations inside functions are not supported." + - " Parent function is " + text_type(parentSymbol.get_full_nested_name())) + " Parent function is " + str(parentSymbol.get_full_nested_name())) name = _make_phony_error_name() symbol = parentSymbol.add_name(name) env.temp_data['cpp:last_symbol'] = symbol @@ -6928,7 +6927,7 @@ class CPPDomain(Domain): templateShorthand=True, matchSelf=True, recurseInAnon=True) if s is None or s.declaration is None: - txtName = text_type(name) + txtName = str(name) if txtName.startswith('std::') or txtName == 'std': raise NoUri() return None, None @@ -7031,7 +7030,7 @@ class CPPDomain(Domain): continue assert symbol.docname fullNestedName = symbol.get_full_nested_name() - name = text_type(fullNestedName).lstrip(':') + name = str(fullNestedName).lstrip(':') dispname = fullNestedName.get_display_string().lstrip(':') objectType = symbol.declaration.objectType docname = symbol.docname @@ -7050,7 +7049,7 @@ class CPPDomain(Domain): rootSymbol = self.data['root_symbol'] parentSymbol = rootSymbol.direct_lookup(parentKey) parentName = parentSymbol.get_full_nested_name() - return '::'.join([text_type(parentName), target]) + return '::'.join([str(parentName), target]) def setup(app): diff --git a/sphinx/environment/adapters/indexentries.py b/sphinx/environment/adapters/indexentries.py index 772a72715..4ca1c71f6 100644 --- a/sphinx/environment/adapters/indexentries.py +++ b/sphinx/environment/adapters/indexentries.py @@ -12,8 +12,6 @@ import re import unicodedata from itertools import groupby -from six import text_type - from sphinx.locale import _, __ from sphinx.util import split_into, logging @@ -44,7 +42,7 @@ class IndexEntries: # Force the word to be unicode if it's a ASCII bytestring. # This will solve problems with unicode normalization later. # For instance the RFC role will add bytestrings at the moment - word = text_type(word) + word = str(word) entry = dic.get(word) if not entry: dic[word] = entry = [[], {}, key] diff --git a/sphinx/environment/collectors/indexentries.py b/sphinx/environment/collectors/indexentries.py index 5ab309407..27d2c771b 100644 --- a/sphinx/environment/collectors/indexentries.py +++ b/sphinx/environment/collectors/indexentries.py @@ -8,8 +8,6 @@ :license: BSD, see LICENSE for details. """ -from six import text_type - from sphinx import addnodes from sphinx.environment.collectors import EnvironmentCollector from sphinx.util import split_index_msg, logging @@ -45,7 +43,7 @@ class IndexEntriesCollector(EnvironmentCollector): for entry in node['entries']: split_index_msg(entry[0], entry[1]) except ValueError as exc: - logger.warning(text_type(exc), location=node) + logger.warning(str(exc), location=node) node.parent.remove(node) else: for entry in node['entries']: diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index 2bb7384f4..6fe4921ab 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -336,7 +336,7 @@ Note: By default this script will not overwrite already created files.""")) dest='includeprivate', help=__('include "_private" modules')) parser.add_argument('--tocfile', action='store', dest='tocfile', default='modules', - help=__("don't create a table of contents file")) + help=__("filename of table of contents (default: modules)")) parser.add_argument('-T', '--no-toc', action='store_false', dest='tocfile', help=__("don't create a table of contents file")) parser.add_argument('-E', '--no-headings', action='store_true', diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 900f4a378..ebd3a37b7 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -12,12 +12,10 @@ import inspect import re -import sys import warnings from typing import Any from docutils.statemachine import StringList -from six import text_type import sphinx from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning @@ -461,13 +459,7 @@ class Documenter: def get_sourcename(self): # type: () -> str if self.analyzer: - # prevent encoding errors when the file name is non-ASCII - if not isinstance(self.analyzer.srcname, text_type): - filename = text_type(self.analyzer.srcname, - sys.getfilesystemencoding(), 'replace') - else: - filename = self.analyzer.srcname - return '%s:docstring of %s' % (filename, self.fullname) + return '%s:docstring of %s' % (self.analyzer.srcname, self.fullname) return 'docstring of %s' % self.fullname def add_content(self, more_content, no_docstring=False): diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index b0ffe093c..45e66e911 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -65,7 +65,6 @@ from docutils import nodes from docutils.parsers.rst import directives from docutils.parsers.rst.states import RSTStateMachine, state_classes from docutils.statemachine import StringList -from six import text_type import sphinx from sphinx import addnodes @@ -160,8 +159,7 @@ def autosummary_table_visit_html(self, node): par = cast(nodes.paragraph, col1_entry[0]) for j, subnode in enumerate(list(par)): if isinstance(subnode, nodes.Text): - new_text = text_type(subnode.astext()) - new_text = new_text.replace(" ", "\u00a0") + new_text = subnode.astext().replace(" ", "\u00a0") par[j] = nodes.Text(new_text) except IndexError: pass diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index d93d3fb50..377da12f1 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -17,7 +17,6 @@ from os import path from subprocess import Popen, PIPE from docutils import nodes -from six import text_type import sphinx from sphinx.errors import SphinxError @@ -290,7 +289,7 @@ def html_visit_math(self, node): try: fname, depth = render_math(self, '$' + node.astext() + '$') except MathExtError as exc: - msg = text_type(exc) + msg = str(exc) sm = nodes.system_message(msg, type='WARNING', level=2, backrefs=[], source=node.astext()) sm.walkabout(self) @@ -317,7 +316,7 @@ def html_visit_displaymath(self, node): try: fname, depth = render_math(self, latex) except MathExtError as exc: - msg = text_type(exc) + msg = str(exc) sm = nodes.system_message(msg, type='WARNING', level=2, backrefs=[], source=node.astext()) sm.walkabout(self) diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 62fea7df8..1d8f9cb60 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -173,7 +173,10 @@ def process_todo_nodes(app, doctree, fromdocname): try: newnode['refuri'] = app.builder.get_relative_uri( fromdocname, todo_info['docname']) - newnode['refuri'] += '#' + todo_info['target']['refid'] + if 'refid' in todo_info['target']: + newnode['refuri'] += '#' + todo_info['target']['refid'] + else: + newnode['refuri'] += '#' + todo_info['target']['ids'][0] except NoUri: # ignore if no URI can be determined, e.g. for LaTeX output pass diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 12fec930e..5fac63963 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -20,7 +20,6 @@ from pygments.lexers import PythonLexer, Python3Lexer, PythonConsoleLexer, \ CLexer, TextLexer, RstLexer from pygments.styles import get_style_by_name from pygments.util import ClassNotFound -from six import text_type from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.ext import doctest @@ -113,7 +112,7 @@ class PygmentsBridge: def highlight_block(self, source, lang, opts=None, location=None, force=False, **kwargs): # type: (str, str, Any, Any, bool, Any) -> str - if not isinstance(source, text_type): + if not isinstance(source, str): source = source.decode() # find out which lexer to use diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index 8d4058972..79d601460 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -14,8 +14,6 @@ import warnings from collections import UserString, defaultdict from gettext import NullTranslations -from six import text_type - from sphinx.deprecation import RemovedInSphinx30Warning if False: @@ -41,7 +39,7 @@ class _TranslationProxy(UserString): # type: (Callable, str) -> object if not args: # not called with "function" and "arguments", but a plain string - return text_type(func) + return str(func) return object.__new__(cls) def __getnewargs__(self): @@ -73,7 +71,7 @@ class _TranslationProxy(UserString): def __dir__(self): # type: () -> List[str] - return dir(text_type) + return dir(str) def __str__(self): # type: () -> str @@ -124,7 +122,7 @@ class _TranslationProxy(UserString): def __repr__(self): # type: () -> str try: - return 'i' + repr(text_type(self.data)) + return 'i' + repr(str(self.data)) except Exception: return '<%s broken>' % self.__class__.__name__ diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index ec04aecab..8aa2815d0 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -16,8 +16,6 @@ import tokenize from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING from tokenize import COMMENT, NL -from six import text_type - if False: # For type annotation from typing import Any, Dict, IO, List, Tuple # NOQA @@ -349,7 +347,7 @@ class VariableCommentPicker(ast.NodeVisitor): targets = get_assign_targets(self.previous) varnames = get_lvar_names(targets[0], self.get_self()) for varname in varnames: - if isinstance(node.value.s, text_type): + if isinstance(node.value.s, str): docstring = node.value.s else: docstring = node.value.s.decode(self.encoding or 'utf-8') diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index 2fd43ca4f..6100fc453 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -13,8 +13,6 @@ import re import warnings from os import path -from six import text_type - from docutils import nodes from sphinx import addnodes @@ -351,9 +349,9 @@ class IndexBuilder: otypes[domainname, type] = typeindex otype = domain.object_types.get(type) if otype: - # use unicode() to fire translation proxies + # use str() to fire translation proxies onames[typeindex] = (domainname, type, - text_type(domain.get_type_name(otype))) + str(domain.get_type_name(otype))) else: onames[typeindex] = (domainname, type, type) if anchor == fullname: diff --git a/sphinx/templates/quickstart/conf.py_t b/sphinx/templates/quickstart/conf.py_t index c43b219a3..90f3113ed 100644 --- a/sphinx/templates/quickstart/conf.py_t +++ b/sphinx/templates/quickstart/conf.py_t @@ -1,7 +1,7 @@ # Configuration file for the Sphinx documentation builder. # -# This file does only contain a selection of the most common options. For a -# full list see the documentation: +# This file only contains a selection of the most common options. For a full +# list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- @@ -167,3 +167,4 @@ intersphinx_mapping = {'https://docs.python.org/': None} # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True {%- endif %} + diff --git a/sphinx/testing/path.py b/sphinx/testing/path.py index d80bb0786..9477391b4 100644 --- a/sphinx/testing/path.py +++ b/sphinx/testing/path.py @@ -9,8 +9,6 @@ import os import shutil import sys -from six import text_type - if False: # For type annotation import builtins # NOQA @@ -20,7 +18,7 @@ if False: FILESYSTEMENCODING = sys.getfilesystemencoding() or sys.getdefaultencoding() -class path(text_type): +class path(str): """ Represents a path which behaves like a string. """ @@ -222,4 +220,4 @@ class path(text_type): def __repr__(self): # type: () -> str - return '%s(%s)' % (self.__class__.__name__, text_type.__repr__(self)) + return '%s(%s)' % (self.__class__.__name__, super().__repr__()) diff --git a/sphinx/texinputs/sphinxhowto.cls b/sphinx/texinputs/sphinxhowto.cls index 64c2feb64..f2572b3b4 100644 --- a/sphinx/texinputs/sphinxhowto.cls +++ b/sphinx/texinputs/sphinxhowto.cls @@ -3,7 +3,7 @@ % \NeedsTeXFormat{LaTeX2e}[1995/12/01] -\ProvidesClass{sphinxhowto}[2018/09/18 v1.8.1 Document class (Sphinx HOWTO)] +\ProvidesClass{sphinxhowto}[2018/12/23 v2.0 Document class (Sphinx howto)] % 'oneside' option overriding the 'twoside' default \newif\if@oneside @@ -30,7 +30,7 @@ % Change the title page to look a bit better, and fit in with the fncychap % ``Bjarne'' style a bit better. % -\renewcommand{\maketitle}{% +\newcommand{\sphinxmaketitle}{% \noindent\rule{\textwidth}{1pt}\par \begingroup % for PDF information dictionary \def\endgraf{ }\def\and{\& }% @@ -57,15 +57,16 @@ %\gdef\@thanks{}\gdef\@author{}\gdef\@title{} } -\newcommand{\sphinxtableofcontents}{ +\newcommand{\sphinxtableofcontents}{% \begingroup - \parskip = 0mm + \parskip \z@skip + \sphinxtableofcontentshook \tableofcontents \endgroup - \rule{\textwidth}{1pt} - \vspace{12pt} + \noindent\rule{\textwidth}{1pt}\par + \vspace{12pt}% } - +\newcommand\sphinxtableofcontentshook{} \pagenumbering{arabic} % Fix the bibliography environment to add an entry to the Table of diff --git a/sphinx/texinputs/sphinxmanual.cls b/sphinx/texinputs/sphinxmanual.cls index ef8ce01a1..eabc1af7b 100644 --- a/sphinx/texinputs/sphinxmanual.cls +++ b/sphinx/texinputs/sphinxmanual.cls @@ -3,7 +3,7 @@ % \NeedsTeXFormat{LaTeX2e}[1995/12/01] -\ProvidesClass{sphinxmanual}[2018/09/18 v1.8.1 Document class (Sphinx manual)] +\ProvidesClass{sphinxmanual}[2018/12/23 v2.0 Document class (Sphinx manual)] % chapters starting at odd pages (overridden by 'openany' document option) \PassOptionsToClass{openright}{\sphinxdocclass} @@ -33,9 +33,9 @@ % Change the title page to look a bit better, and fit in with the fncychap % ``Bjarne'' style a bit better. % -\renewcommand{\maketitle}{% - \let\spx@tempa\relax - \ifHy@pageanchor\def\spx@tempa{\Hy@pageanchortrue}\fi +\newcommand{\sphinxmaketitle}{% + \let\sphinxrestorepageanchorsetting\relax + \ifHy@pageanchor\def\sphinxrestorepageanchorsetting{\Hy@pageanchortrue}\fi \hypersetup{pageanchor=false}% avoid duplicate destination warnings \begin{titlepage}% \let\footnotesize\small @@ -69,14 +69,17 @@ \setcounter{footnote}{0}% \let\thanks\relax\let\maketitle\relax %\gdef\@thanks{}\gdef\@author{}\gdef\@title{} + \clearpage + \ifdefined\sphinxbackoftitlepage\sphinxbackoftitlepage\fi \if@openright\cleardoublepage\else\clearpage\fi - \spx@tempa + \sphinxrestorepageanchorsetting } \newcommand{\sphinxtableofcontents}{% \pagenumbering{roman}% \begingroup \parskip \z@skip + \sphinxtableofcontentshook \tableofcontents \endgroup % before resetting page counter, let's do the right thing. @@ -87,8 +90,10 @@ % This is needed to get the width of the section # area wide enough in the % library reference. Doing it here keeps it the same for all the manuals. % -\renewcommand*\l@section{\@dottedtocline{1}{1.5em}{2.6em}} -\renewcommand*\l@subsection{\@dottedtocline{2}{4.1em}{3.5em}} +\newcommand{\sphinxtableofcontentshook}{% + \renewcommand*\l@section{\@dottedtocline{1}{1.5em}{2.6em}}% + \renewcommand*\l@subsection{\@dottedtocline{2}{4.1em}{3.5em}}% +} % Fix the bibliography environment to add an entry to the Table of % Contents. diff --git a/sphinx/transforms/post_transforms/code.py b/sphinx/transforms/post_transforms/code.py index a732842c2..610437ac6 100644 --- a/sphinx/transforms/post_transforms/code.py +++ b/sphinx/transforms/post_transforms/code.py @@ -13,7 +13,6 @@ from typing import NamedTuple from docutils import nodes from pygments.lexers import PythonConsoleLexer, guess_lexer -from six import text_type from sphinx import addnodes from sphinx.ext import doctest @@ -25,7 +24,7 @@ if False: from sphinx.application import Sphinx # NOQA -HighlightSetting = NamedTuple('HighlightSetting', [('language', text_type), +HighlightSetting = NamedTuple('HighlightSetting', [('language', str), ('lineno_threshold', int)]) diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 8f63dbda5..a863a00fa 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -13,7 +13,6 @@ from hashlib import sha1 from math import ceil from docutils import nodes -from six import text_type from sphinx.locale import __ from sphinx.transforms import SphinxTransform @@ -118,8 +117,7 @@ class ImageDownloader(BaseImageConverter): node['uri'] = path self.app.env.images.add_file(self.env.docname, path) except Exception as exc: - logger.warning(__('Could not fetch remote image: %s [%s]') % - (node['uri'], text_type(exc))) + logger.warning(__('Could not fetch remote image: %s [%s]') % (node['uri'], exc)) class DataURIExtractor(BaseImageConverter): diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 11a2a9327..26d19ac6a 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -26,7 +26,6 @@ from time import mktime, strptime from urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode from docutils.utils import relative_path -from six import text_type from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning from sphinx.errors import PycodeError, SphinxParallelError, ExtensionError @@ -70,9 +69,7 @@ def path_stabilize(filepath): # type: (str) -> str "normalize path separater and unicode string" newpath = filepath.replace(os.path.sep, SEP) - if isinstance(newpath, text_type): - newpath = unicodedata.normalize('NFC', newpath) - return newpath + return unicodedata.normalize('NFC', newpath) def get_matching_files(dirname, exclude_matchers=()): @@ -637,9 +634,9 @@ def display_chunk(chunk): # type: (Any) -> str if isinstance(chunk, (list, tuple)): if len(chunk) == 1: - return text_type(chunk[0]) + return str(chunk[0]) return '%s .. %s' % (chunk[0], chunk[-1]) - return text_type(chunk) + return str(chunk) def old_status_iterator(iterable, summary, color="darkgreen", stringify_func=display_chunk): @@ -696,15 +693,12 @@ def rfc1123_to_epoch(rfc1123): def xmlname_checker(): # type: () -> Pattern # https://www.w3.org/TR/REC-xml/#NT-Name - # Only Python 3.3 or newer support character code in regular expression name_start_chars = [ ':', ['A', 'Z'], '_', ['a', 'z'], ['\u00C0', '\u00D6'], ['\u00D8', '\u00F6'], ['\u00F8', '\u02FF'], ['\u0370', '\u037D'], ['\u037F', '\u1FFF'], ['\u200C', '\u200D'], ['\u2070', '\u218F'], ['\u2C00', '\u2FEF'], ['\u3001', '\uD7FF'], ['\uF900', '\uFDCF'], - ['\uFDF0', '\uFFFD']] - - name_start_chars.append(['\U00010000', '\U000EFFFF']) + ['\uFDF0', '\uFFFD'], ['\U00010000', '\U000EFFFF']] name_chars = [ "\\-", "\\.", ['0', '9'], '\u00B7', ['\u0300', '\u036F'], diff --git a/sphinx/util/images.py b/sphinx/util/images.py index c74394c81..eaa188496 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -17,7 +17,6 @@ from os import path from typing import NamedTuple import imagesize -from six import text_type from sphinx.deprecation import RemovedInSphinx30Warning @@ -42,8 +41,8 @@ mime_suffixes = OrderedDict([ ('.svgz', 'image/svg+xml'), ]) -DataURI = NamedTuple('DataURI', [('mimetype', text_type), - ('charset', text_type), +DataURI = NamedTuple('DataURI', [('mimetype', str), + ('charset', str), ('data', bytes)]) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 7a0b8c5ba..4973111cf 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -103,16 +103,12 @@ def getargspec(func): def isenumclass(x): # type: (Type) -> bool """Check if the object is subclass of enum.""" - if enum is None: - return False return inspect.isclass(x) and issubclass(x, enum.Enum) def isenumattribute(x): # type: (Any) -> bool """Check if the object is attribute of enum.""" - if enum is None: - return False return isinstance(x, enum.Enum) diff --git a/sphinx/util/jsonimpl.py b/sphinx/util/jsonimpl.py index eb4117f90..8054be0d7 100644 --- a/sphinx/util/jsonimpl.py +++ b/sphinx/util/jsonimpl.py @@ -11,8 +11,6 @@ import json from collections import UserString -from six import text_type - if False: # For type annotation from typing import Any, IO # NOQA @@ -23,7 +21,7 @@ class SphinxJSONEncoder(json.JSONEncoder): def default(self, obj): # type: (Any) -> str if isinstance(obj, UserString): - return text_type(obj) + return str(obj) return super().default(obj) diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index e95313745..ddf78185e 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -12,7 +12,6 @@ import re from typing import Any, cast from docutils import nodes -from six import text_type from sphinx import addnodes from sphinx.locale import __ @@ -115,7 +114,7 @@ def repr_domxml(node, length=80): try: text = node.asdom().toxml() except Exception: - text = text_type(node) + text = str(node) if length and len(text) > length: text = text[:length] + '...' return text @@ -398,7 +397,7 @@ def inline_all_toctrees(builder, docnameset, docname, tree, colorfunc, traversed tree = cast(nodes.document, tree.deepcopy()) for toctreenode in tree.traverse(addnodes.toctree): newnodes = [] - includefiles = map(text_type, toctreenode['includefiles']) + includefiles = map(str, toctreenode['includefiles']) for includefile in includefiles: if includefile not in traversed: try: diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 0256ce0c2..df310368e 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -14,8 +14,6 @@ from html import escape as htmlescape # NOQA from io import TextIOWrapper # NOQA from textwrap import indent # NOQA -from six import text_type - from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import __ from sphinx.util import logging @@ -64,7 +62,7 @@ def convert_with_2to3(filepath): lineno, offset = err.context[1] # try to match ParseError details with SyntaxError details raise SyntaxError(err.msg, (filepath, lineno, offset, err.value)) - return text_type(tree) + return str(tree) class UnicodeMixin: diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index cf6c49daf..ecfd61f01 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -12,7 +12,6 @@ from typing import Any, Callable, Dict, List, Tuple, Union from docutils import nodes from docutils.parsers.rst.states import Inliner -from six import text_type # An entry of Directive.option_spec @@ -22,11 +21,11 @@ DirectiveOption = Callable[[str], Any] TextlikeNode = Union[nodes.Text, nodes.TextElement] # common role functions -RoleFunction = Callable[[text_type, text_type, text_type, int, Inliner, Dict, List[text_type]], +RoleFunction = Callable[[str, str, str, int, Inliner, Dict, List[str]], Tuple[List[nodes.Node], List[nodes.system_message]]] # title getter functions for enumerable nodes (see sphinx.domains.std) -TitleGetter = Callable[[nodes.Node], text_type] +TitleGetter = Callable[[nodes.Node], str] # inventory data on memory Inventory = Dict[str, Dict[str, Tuple[str, str, str, str]]] diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index f7366767f..014c0272c 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -92,7 +92,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): self.permalink_text = self.config.html_add_permalinks # support backwards-compatible setting to a bool if not isinstance(self.permalink_text, str): - self.permalink_text = self.permalink_text and '\u00B6' or '' + self.permalink_text = self.permalink_text and '¶' or '' self.permalink_text = self.encode(self.permalink_text) self.secnumber_suffix = self.config.html_secnumber_suffix self.param_separator = '' diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index 2a8380d73..88e95ceff 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -62,7 +62,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): self.permalink_text = self.config.html_add_permalinks # support backwards-compatible setting to a bool if not isinstance(self.permalink_text, str): - self.permalink_text = self.permalink_text and '\u00B6' or '' + self.permalink_text = self.permalink_text and '¶' or '' self.permalink_text = self.encode(self.permalink_text) self.secnumber_suffix = self.config.html_secnumber_suffix self.param_separator = '' diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c7f4388d6..d88a6ab5e 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -20,7 +20,6 @@ from typing import Iterable, cast from docutils import nodes, writers from docutils.writers.latex2e import Babel -from six import text_type from sphinx import addnodes from sphinx import highlighting @@ -159,7 +158,7 @@ DEFAULT_SETTINGS = { 'releasename': '', 'makeindex': '\\makeindex', 'shorthandoff': '', - 'maketitle': '\\maketitle', + 'maketitle': '\\sphinxmaketitle', 'tableofcontents': '\\sphinxtableofcontents', 'atendofbody': '', 'printindex': '\\printindex', @@ -759,7 +758,7 @@ class LaTeXTranslator(SphinxTranslator): def idescape(self, id): # type: (str) -> str - return '\\detokenize{%s}' % text_type(id).translate(tex_replace_map).\ + return '\\detokenize{%s}' % str(id).translate(tex_replace_map).\ encode('ascii', 'backslashreplace').decode('ascii').\ replace('\\', '_') @@ -780,34 +779,34 @@ class LaTeXTranslator(SphinxTranslator): figure = self.builder.config.numfig_format['figure'].split('%s', 1) if len(figure) == 1: ret.append('\\def\\fnum@figure{%s}\n' % - text_type(figure[0]).strip().translate(tex_escape_map)) + str(figure[0]).strip().translate(tex_escape_map)) else: - definition = text_type(figure[0]).strip().translate(tex_escape_map) + definition = str(figure[0]).strip().translate(tex_escape_map) ret.append(self.babel_renewcommand('\\figurename', definition)) if figure[1]: ret.append('\\makeatletter\n') ret.append('\\def\\fnum@figure{\\figurename\\thefigure%s}\n' % - text_type(figure[1]).strip().translate(tex_escape_map)) + str(figure[1]).strip().translate(tex_escape_map)) ret.append('\\makeatother\n') table = self.builder.config.numfig_format['table'].split('%s', 1) if len(table) == 1: ret.append('\\def\\fnum@table{%s}\n' % - text_type(table[0]).strip().translate(tex_escape_map)) + str(table[0]).strip().translate(tex_escape_map)) else: - definition = text_type(table[0]).strip().translate(tex_escape_map) + definition = str(table[0]).strip().translate(tex_escape_map) ret.append(self.babel_renewcommand('\\tablename', definition)) if table[1]: ret.append('\\makeatletter\n') ret.append('\\def\\fnum@table{\\tablename\\thetable%s}\n' % - text_type(table[1]).strip().translate(tex_escape_map)) + str(table[1]).strip().translate(tex_escape_map)) ret.append('\\makeatother\n') codeblock = self.builder.config.numfig_format['code-block'].split('%s', 1) if len(codeblock) == 1: pass # FIXME else: - definition = text_type(codeblock[0]).strip().translate(tex_escape_map) + definition = str(codeblock[0]).strip().translate(tex_escape_map) ret.append(self.babel_renewcommand('\\literalblockname', definition)) if codeblock[1]: pass # FIXME @@ -824,7 +823,7 @@ class LaTeXTranslator(SphinxTranslator): if i > 0: ret.append('\\indexspace\n') ret.append('\\bigletter{%s}\n' % - text_type(letter).translate(tex_escape_map)) + str(letter).translate(tex_escape_map)) for entry in entries: if not entry[3]: continue @@ -2003,7 +2002,7 @@ class LaTeXTranslator(SphinxTranslator): id = node.get('refuri', '')[1:].replace('#', ':') title = node.get('title', '%s') - title = text_type(title).translate(tex_escape_map).replace('\\%s', '%s') + title = str(title).translate(tex_escape_map).replace('\\%s', '%s') if '\\{name\\}' in title or '\\{number\\}' in title: # new style format (cf. "Fig.%{number}") title = title.replace('\\{name\\}', '{name}').replace('\\{number\\}', '{number}') @@ -2451,7 +2450,7 @@ class LaTeXTranslator(SphinxTranslator): def encode(self, text): # type: (str) -> str - text = text_type(text).translate(tex_escape_map) + text = str(text).translate(tex_escape_map) if self.literal_whitespace: # Insert a blank before the newline, to avoid # ! LaTeX Error: There's no line here to end. diff --git a/tests/roots/test-build-htmlhelp/conf.py b/tests/roots/test-build-htmlhelp/conf.py new file mode 100644 index 000000000..95293e956 --- /dev/null +++ b/tests/roots/test-build-htmlhelp/conf.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +project = 'test' +master_doc = 'index' diff --git a/tests/roots/test-build-htmlhelp/index.rst b/tests/roots/test-build-htmlhelp/index.rst new file mode 100644 index 000000000..641c2467d --- /dev/null +++ b/tests/roots/test-build-htmlhelp/index.rst @@ -0,0 +1,19 @@ +Index markup +------------ + +.. index:: + single: entry + pair: entry; pair + double: entry; double + triple: index; entry; triple + keyword: with + see: from; to + seealso: fromalso; toalso + +.. index:: + !Main, !Other + !single: entry; pair + +.. index:: triple-quoted string, Unicode Consortium, raw string + single: """; string literal + single: '''; string literal
\ No newline at end of file diff --git a/tests/roots/test-build-htmlhelp/make.bat b/tests/roots/test-build-htmlhelp/make.bat new file mode 100644 index 000000000..333fd1439 --- /dev/null +++ b/tests/roots/test-build-htmlhelp/make.bat @@ -0,0 +1,64 @@ +@echo off +setlocal + +pushd %~dp0 + +set this=%~n0 + +if not defined PYTHON set PYTHON=py + +if not defined SPHINXBUILD ( + %PYTHON% -c "import sphinx" > nul 2> nul + if errorlevel 1 ( + echo Installing sphinx with %PYTHON% + %PYTHON% -m pip install sphinx + if errorlevel 1 exit /B + ) + set SPHINXBUILD=%PYTHON% -c "import sphinx.cmd.build, sys; sys.exit(sphinx.cmd.build.main())" +) + +rem Search for HHC in likely places +set HTMLHELP= +where hhc /q && set HTMLHELP=hhc && goto :skiphhcsearch +where /R ..\externals hhc > "%TEMP%\hhc.loc" 2> nul && set /P HTMLHELP= < "%TEMP%\hhc.loc" & del "%TEMP%\hhc.loc" +if not exist "%HTMLHELP%" where /R "%ProgramFiles(x86)%" hhc > "%TEMP%\hhc.loc" 2> nul && set /P HTMLHELP= < "%TEMP%\hhc.loc" & del "%TEMP%\hhc.loc" +if not exist "%HTMLHELP%" where /R "%ProgramFiles%" hhc > "%TEMP%\hhc.loc" 2> nul && set /P HTMLHELP= < "%TEMP%\hhc.loc" & del "%TEMP%\hhc.loc" +if not exist "%HTMLHELP%" ( + echo. + echo.The HTML Help Workshop was not found. Set the HTMLHELP variable + echo.to the path to hhc.exe or download and install it from + echo.http://msdn.microsoft.com/en-us/library/ms669985 + exit /B 1 +) +echo hhc.exe path: %HTMLHELP% + +if "%BUILDDIR%" EQU "" set BUILDDIR=build + +%SPHINXBUILD% >nul 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + popd + exit /B 1 +) + +set SPHINXOPTS=-D html_theme_options.body_max_width=none %SPHINXOPTS% + +cmd /S /C "%SPHINXBUILD% %SPHINXOPTS% -bhtmlhelp -dbuild\doctrees . "%BUILDDIR%\htmlhelp" + +"%HTMLHELP%" "%BUILDDIR%\htmlhelp\test.hhp" +rem hhc.exe seems to always exit with code 1, reset to 0 for less than 2 +if not errorlevel 2 cmd /C exit /b 0 + +echo. +if errorlevel 1 ( + echo.Build failed (exit code %ERRORLEVEL%^), check for error messages + echo.above. Any output will be found in %BUILDDIR%\%1 +) else ( + echo.Build succeeded. All output should be in %BUILDDIR%\%1 +) + +popd diff --git a/tests/roots/test-ext-todo/foo.rst b/tests/roots/test-ext-todo/foo.rst index 269199977..12e9f63c5 100644 --- a/tests/roots/test-ext-todo/foo.rst +++ b/tests/roots/test-ext-todo/foo.rst @@ -2,3 +2,9 @@ foo === .. todo:: todo in foo + +.. py:function:: hello() + + :param bug: #5800 + + .. todo:: todo in param field diff --git a/tests/test_build_htmlhelp.py b/tests/test_build_htmlhelp.py index 124462aa5..b56d42ee3 100644 --- a/tests/test_build_htmlhelp.py +++ b/tests/test_build_htmlhelp.py @@ -8,8 +8,12 @@ :license: BSD, see LICENSE for details. """ +import re + import pytest +from sphinx.builders.htmlhelp import chm_htmlescape + from sphinx.builders.htmlhelp import default_htmlhelp_basename from sphinx.config import Config @@ -29,3 +33,31 @@ def test_default_htmlhelp_basename(): config = Config({'project': 'Sphinx Documentation'}) config.init_values() assert default_htmlhelp_basename(config) == 'sphinxdoc' + + +@pytest.mark.sphinx('htmlhelp', testroot='build-htmlhelp') +def test_chm(app): + app.build() + + # check .hhk file + outname = app.builder.config.htmlhelp_basename + hhk_path = str(app.outdir / outname + '.hhk') + + with open(hhk_path, 'rb') as f: + data = f.read() + m = re.search(br'&#[xX][0-9a-fA-F]+;', data) + assert m is None, 'Hex escaping exists in .hhk file: ' + str(m.group(0)) + + +def test_chm_htmlescape(): + assert chm_htmlescape('Hello world') == 'Hello world' + assert chm_htmlescape(u'Unicode 文字') == u'Unicode 文字' + assert chm_htmlescape('E') == '&#x45' + + assert chm_htmlescape('<Hello> "world"') == '<Hello> "world"' + assert chm_htmlescape('<Hello> "world"', True) == '<Hello> "world"' + assert chm_htmlescape('<Hello> "world"', False) == '<Hello> "world"' + + assert chm_htmlescape("Hello 'world'") == "Hello 'world'" + assert chm_htmlescape("Hello 'world'", True) == "Hello 'world'" + assert chm_htmlescape("Hello 'world'", False) == "Hello 'world'" diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index ab9cf9be2..6da91df82 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -12,7 +12,6 @@ import re import sys import pytest -from six import text_type import sphinx.domains.cpp as cppDomain from sphinx import addnodes @@ -39,7 +38,7 @@ def check(name, input, idDict, output=None): if output is None: output = input ast = parse(name, input) - res = text_type(ast) + res = str(ast) if res != output: print("") print("Input: ", input) diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index bdb9f5fec..9d24d138e 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -11,7 +11,6 @@ import pytest from docutils import nodes from mock import Mock -from six import text_type from sphinx import addnodes from sphinx.domains.python import py_sig_re, _pseudo_parse_arglist, PythonDomain @@ -30,22 +29,22 @@ def parse(sig): def test_function_signatures(): rv = parse('func(a=1) -> int object') - assert text_type(rv) == 'a=1' + assert rv == 'a=1' rv = parse('func(a=1, [b=None])') - assert text_type(rv) == 'a=1, [b=None]' + assert rv == 'a=1, [b=None]' rv = parse('func(a=1[, b=None])') - assert text_type(rv) == 'a=1, [b=None]' + assert rv == 'a=1, [b=None]' rv = parse("compile(source : string, filename, symbol='file')") - assert text_type(rv) == "source : string, filename, symbol='file'" + assert rv == "source : string, filename, symbol='file'" rv = parse('func(a=[], [b=None])') - assert text_type(rv) == 'a=[], [b=None]' + assert rv == 'a=[], [b=None]' rv = parse('func(a=[][, b=None])') - assert text_type(rv) == 'a=[], [b=None]' + assert rv == 'a=[], [b=None]' @pytest.mark.sphinx('dummy', testroot='domain-py') diff --git a/tests/test_ext_todo.py b/tests/test_ext_todo.py index e5aff7598..5e7ade9f6 100644 --- a/tests/test_ext_todo.py +++ b/tests/test_ext_todo.py @@ -40,13 +40,19 @@ def test_todo(app, status, warning): '<p class="last">todo in foo</p>') assert re.search(html, content, re.S) + html = ('<p class="first admonition-title">Todo</p>\n' + '<p class="last">todo in param field</p>') + assert re.search(html, content, re.S) + # check emitted warnings assert 'WARNING: TODO entry found: todo in foo' in warning.getvalue() assert 'WARNING: TODO entry found: todo in bar' in warning.getvalue() # check handled event - assert len(todos) == 2 - assert set(todo[1].astext() for todo in todos) == set(['todo in foo', 'todo in bar']) + assert len(todos) == 3 + assert set(todo[1].astext() for todo in todos) == {'todo in foo', + 'todo in bar', + 'todo in param field'} @pytest.mark.sphinx('html', testroot='ext-todo', freshenv=True, @@ -81,8 +87,10 @@ def test_todo_not_included(app, status, warning): assert 'WARNING: TODO entry found: todo in bar' in warning.getvalue() # check handled event - assert len(todos) == 2 - assert set(todo[1].astext() for todo in todos) == set(['todo in foo', 'todo in bar']) + assert len(todos) == 3 + assert set(todo[1].astext() for todo in todos) == {'todo in foo', + 'todo in bar', + 'todo in param field'} @pytest.mark.sphinx('latex', testroot='ext-todo', freshenv=True, @@ -105,7 +113,7 @@ def test_todo_valid_link(app, status, warning): link = r'\{\\hyperref\[\\detokenize\{(.*?foo.*?)}]\{\\sphinxcrossref{' \ r'\\sphinxstyleemphasis{original entry}}}}' m = re.findall(link, content) - assert len(m) == 2 + assert len(m) == 4 target = m[0] # Look for the targets of this link. diff --git a/tests/test_intl.py b/tests/test_intl.py index ee4defa0e..4961d2550 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -705,20 +705,20 @@ def test_html_versionchanges(app): return '' expect1 = ( - """<p><span class="versionmodified">Deprecated since version 1.0: </span>""" + """<p><span class="versionmodified deprecated">Deprecated since version 1.0: </span>""" """THIS IS THE <em>FIRST</em> PARAGRAPH OF DEPRECATED.</p>\n""" """<p>THIS IS THE <em>SECOND</em> PARAGRAPH OF DEPRECATED.</p>\n""") matched_content = get_content(result, "deprecated") assert expect1 == matched_content expect2 = ( - """<p><span class="versionmodified">New in version 1.0: </span>""" + """<p><span class="versionmodified added">New in version 1.0: </span>""" """THIS IS THE <em>FIRST</em> PARAGRAPH OF VERSIONADDED.</p>\n""") matched_content = get_content(result, "versionadded") assert expect2 == matched_content expect3 = ( - """<p><span class="versionmodified">Changed in version 1.0: </span>""" + """<p><span class="versionmodified changed">Changed in version 1.0: </span>""" """THIS IS THE <em>FIRST</em> PARAGRAPH OF VERSIONCHANGED.</p>\n""") matched_content = get_content(result, "versionchanged") assert expect3 == matched_content diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index f990de857..0085f257e 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -12,7 +12,6 @@ import time from io import StringIO import pytest -from six import text_type from sphinx import application from sphinx.cmd import quickstart as qs @@ -35,7 +34,6 @@ def mock_input(answers, needanswer=False): raise AssertionError('answer for %r missing and no default ' 'present' % prompt) called.add(prompt) - prompt = text_type(prompt) for question in answers: if prompt.startswith(qs.PROMPT_PREFIX + question): return answers[question] |