summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml6
-rw-r--r--.github/ISSUE_TEMPLATE/question.md17
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md11
-rw-r--r--.gitignore1
-rw-r--r--CHANGES119
-rw-r--r--CODE_OF_CONDUCT76
-rw-r--r--CONTRIBUTING.rst16
-rw-r--r--EXAMPLES16
-rw-r--r--Makefile1
-rw-r--r--README.rst8
-rw-r--r--doc/_static/Makefile4
-rw-r--r--doc/_static/translation.pngbin13791 -> 20416 bytes
-rw-r--r--doc/_static/translation.puml16
-rw-r--r--doc/_templates/index.html6
-rw-r--r--doc/code_of_conduct.rst8
-rw-r--r--doc/conf.py5
-rw-r--r--doc/contents.rst1
-rw-r--r--doc/development/tutorials/recipe.rst17
-rw-r--r--doc/development/tutorials/todo.rst14
-rw-r--r--doc/extdev/appapi.rst2
-rw-r--r--doc/extdev/deprecated.rst35
-rw-r--r--doc/faq.rst11
-rw-r--r--doc/man/sphinx-apidoc.rst2
-rw-r--r--doc/usage/advanced/intl.rst4
-rw-r--r--doc/usage/advanced/setuptools.rst30
-rw-r--r--doc/usage/configuration.rst19
-rw-r--r--doc/usage/extensions/autodoc.rst37
-rw-r--r--doc/usage/extensions/autosummary.rst7
-rw-r--r--doc/usage/extensions/doctest.rst12
-rw-r--r--doc/usage/extensions/intersphinx.rst10
-rw-r--r--doc/usage/installation.rst58
-rw-r--r--doc/usage/quickstart.rst29
-rw-r--r--doc/usage/restructuredtext/basics.rst42
-rw-r--r--doc/usage/restructuredtext/directives.rst31
-rw-r--r--doc/usage/restructuredtext/domains.rst7
-rw-r--r--doc/usage/restructuredtext/field-lists.rst9
-rw-r--r--setup.cfg1
-rw-r--r--setup.py13
-rw-r--r--sphinx/__init__.py10
-rw-r--r--sphinx/addnodes.py56
-rw-r--r--sphinx/application.py125
-rw-r--r--sphinx/builders/html/__init__.py (renamed from sphinx/builders/html.py)66
-rw-r--r--sphinx/builders/latex/__init__.py10
-rw-r--r--sphinx/builders/latex/constants.py2
-rw-r--r--sphinx/builders/latex/transforms.py2
-rw-r--r--sphinx/builders/latex/util.py10
-rw-r--r--sphinx/builders/linkcheck.py39
-rw-r--r--sphinx/cmd/quickstart.py156
-rw-r--r--sphinx/cmdline.py43
-rw-r--r--sphinx/config.py36
-rw-r--r--sphinx/deprecation.py6
-rw-r--r--sphinx/directives/__init__.py18
-rw-r--r--sphinx/directives/other.py4
-rw-r--r--sphinx/domains/__init__.py20
-rw-r--r--sphinx/domains/c.py1
-rw-r--r--sphinx/domains/changeset.py9
-rw-r--r--sphinx/domains/cpp.py495
-rw-r--r--sphinx/domains/index.py17
-rw-r--r--sphinx/domains/javascript.py118
-rw-r--r--sphinx/domains/math.py2
-rw-r--r--sphinx/domains/python.py91
-rw-r--r--sphinx/domains/rst.py105
-rw-r--r--sphinx/domains/std.py185
-rw-r--r--sphinx/environment/__init__.py128
-rw-r--r--sphinx/environment/adapters/indexentries.py15
-rw-r--r--sphinx/events.py28
-rw-r--r--sphinx/ext/autodoc/__init__.py103
-rw-r--r--sphinx/ext/autodoc/importer.py3
-rw-r--r--sphinx/ext/autodoc/mock.py46
-rw-r--r--sphinx/ext/autodoc/type_comment.py3
-rw-r--r--sphinx/ext/autosummary/__init__.py4
-rw-r--r--sphinx/ext/autosummary/generate.py35
-rw-r--r--sphinx/ext/coverage.py2
-rw-r--r--sphinx/ext/duration.py4
-rw-r--r--sphinx/ext/inheritance_diagram.py6
-rw-r--r--sphinx/ext/mathbase.py153
-rw-r--r--sphinx/ext/napoleon/docstring.py4
-rw-r--r--sphinx/ext/napoleon/iterators.py4
-rw-r--r--sphinx/ext/viewcode.py19
-rw-r--r--sphinx/highlighting.py36
-rw-r--r--sphinx/io.py99
-rw-r--r--sphinx/locale/__init__.py29
-rw-r--r--sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po473
-rw-r--r--sphinx/make_mode.py38
-rw-r--r--sphinx/parsers.py11
-rw-r--r--sphinx/pycode/ast.py68
-rw-r--r--sphinx/pycode/parser.py2
-rw-r--r--sphinx/registry.py67
-rw-r--r--sphinx/search/ja.py16
-rw-r--r--sphinx/templates/latex/longtable.tex_t2
-rw-r--r--sphinx/testing/comparer.py14
-rw-r--r--sphinx/testing/fixtures.py81
-rw-r--r--sphinx/testing/path.py19
-rw-r--r--sphinx/testing/util.py6
-rw-r--r--sphinx/texinputs/Makefile_t16
-rw-r--r--sphinx/texinputs/make.bat_t5
-rw-r--r--sphinx/texinputs/sphinx.sty3
-rw-r--r--sphinx/themes/basic/search.html6
-rw-r--r--sphinx/themes/basic/searchresults.html36
-rw-r--r--sphinx/transforms/post_transforms/__init__.py2
-rw-r--r--sphinx/transforms/post_transforms/compat.py86
-rw-r--r--sphinx/util/__init__.py40
-rw-r--r--sphinx/util/compat.py16
-rw-r--r--sphinx/util/docstrings.py32
-rw-r--r--sphinx/util/docutils.py45
-rw-r--r--sphinx/util/i18n.py10
-rw-r--r--sphinx/util/images.py20
-rw-r--r--sphinx/util/inspect.py91
-rw-r--r--sphinx/util/nodes.py6
-rw-r--r--sphinx/util/osutil.py24
-rw-r--r--sphinx/util/pycompat.py2
-rw-r--r--sphinx/util/requests.py23
-rw-r--r--sphinx/util/typing.py5
-rw-r--r--sphinx/versioning.py11
-rw-r--r--sphinx/writers/html.py33
-rw-r--r--sphinx/writers/html5.py33
-rw-r--r--sphinx/writers/latex.py119
-rw-r--r--sphinx/writers/texinfo.py12
-rw-r--r--sphinx/writers/text.py52
-rw-r--r--tests/roots/test-add_source_parser/conf.py11
-rw-r--r--tests/roots/test-domain-cpp/lookup-key-overload.rst8
-rw-r--r--tests/roots/test-domain-cpp/multi-decl-lookup.rst24
-rw-r--r--tests/roots/test-domain-cpp/warn-template-param-qualified-name.rst11
-rw-r--r--tests/roots/test-ext-autodoc/target/__init__.py4
-rw-r--r--tests/roots/test-ext-autodoc/target/annotated.py6
-rw-r--r--tests/roots/test-ext-autodoc/target/private.py5
-rw-r--r--tests/roots/test-ext-math-compat/conf.py9
-rw-r--r--tests/roots/test-html_scaled_image_link/conf.py0
-rw-r--r--tests/roots/test-html_scaled_image_link/img.pngbin0 -> 66247 bytes
-rw-r--r--tests/roots/test-html_scaled_image_link/index.rst11
-rw-r--r--tests/roots/test-inheritance/diagram_w_nested_classes.rst5
-rw-r--r--tests/roots/test-inheritance/dummy/test_nested.py12
-rw-r--r--tests/roots/test-latex-table/expects/longtable.tex2
-rw-r--r--tests/roots/test-latex-table/expects/longtable_having_align.tex2
-rw-r--r--tests/roots/test-latex-table/expects/longtable_having_caption.tex2
-rw-r--r--tests/roots/test-latex-table/expects/longtable_having_problematic_cell.tex2
-rw-r--r--tests/roots/test-latex-table/expects/longtable_having_stub_columns_and_problematic_cell.tex2
-rw-r--r--tests/roots/test-latex-table/expects/longtable_having_verbatim.tex2
-rw-r--r--tests/roots/test-latex-table/expects/longtable_having_widths.tex2
-rw-r--r--tests/roots/test-latex-table/expects/longtable_having_widths_and_problematic_cell.tex2
-rw-r--r--tests/roots/test-latex-table/expects/longtable_with_tabularcolumn.tex2
-rw-r--r--tests/roots/test-productionlist/Bare.rst6
-rw-r--r--tests/roots/test-productionlist/Dup1.rst5
-rw-r--r--tests/roots/test-productionlist/Dup2.rst5
-rw-r--r--tests/roots/test-productionlist/LineContinuation.rst6
-rw-r--r--tests/roots/test-productionlist/P1.rst6
-rw-r--r--tests/roots/test-productionlist/P2.rst6
-rw-r--r--tests/roots/test-productionlist/conf.py1
-rw-r--r--tests/roots/test-productionlist/firstLineRule.rst5
-rw-r--r--tests/roots/test-productionlist/index.rst27
-rw-r--r--tests/roots/test-search/nosearch.rst7
-rw-r--r--tests/test_application.py9
-rw-r--r--tests/test_autodoc.py55
-rw-r--r--tests/test_build.py2
-rw-r--r--tests/test_build_changes.py2
-rw-r--r--tests/test_build_dirhtml.py7
-rw-r--r--tests/test_build_epub.py44
-rw-r--r--tests/test_build_gettext.py10
-rw-r--r--tests/test_build_html.py78
-rw-r--r--tests/test_build_latex.py162
-rw-r--r--tests/test_build_linkcheck.py60
-rw-r--r--tests/test_build_manpage.py6
-rw-r--r--tests/test_build_texinfo.py6
-rw-r--r--tests/test_build_text.py34
-rw-r--r--tests/test_correct_year.py2
-rw-r--r--tests/test_directive_code.py26
-rw-r--r--tests/test_domain_cpp.py29
-rw-r--r--tests/test_domain_js.py106
-rw-r--r--tests/test_domain_py.py94
-rw-r--r--tests/test_domain_rst.py8
-rw-r--r--tests/test_domain_std.py70
-rw-r--r--tests/test_environment_indexentries.py37
-rw-r--r--tests/test_events.py24
-rw-r--r--tests/test_ext_apidoc.py26
-rw-r--r--tests/test_ext_autodoc_configs.py36
-rw-r--r--tests/test_ext_autodoc_mock.py2
-rw-r--r--tests/test_ext_autodoc_private_members.py46
-rw-r--r--tests/test_ext_autosectionlabel.py4
-rw-r--r--tests/test_ext_autosummary.py43
-rw-r--r--tests/test_ext_coverage.py8
-rw-r--r--tests/test_ext_githubpages.py2
-rw-r--r--tests/test_ext_graphviz.py8
-rw-r--r--tests/test_ext_ifconfig.py2
-rw-r--r--tests/test_ext_imgconverter.py2
-rw-r--r--tests/test_ext_inheritance_diagram.py16
-rw-r--r--tests/test_ext_intersphinx.py2
-rw-r--r--tests/test_ext_math.py24
-rw-r--r--tests/test_ext_todo.py10
-rw-r--r--tests/test_ext_viewcode.py12
-rw-r--r--tests/test_highlighting.py2
-rw-r--r--tests/test_intl.py62
-rw-r--r--tests/test_pycode_ast.py12
-rw-r--r--tests/test_quickstart.py2
-rw-r--r--tests/test_search.py20
-rw-r--r--tests/test_setup_command.py2
-rw-r--r--tests/test_smartquotes.py18
-rw-r--r--tests/test_templating.py4
-rw-r--r--tests/test_theming.py14
-rw-r--r--tests/test_toctree.py2
-rw-r--r--tests/test_transforms_post_transforms_code.py4
-rw-r--r--tests/test_util.py12
-rw-r--r--tests/test_util_docstrings.py29
-rw-r--r--tests/test_util_fileutil.py12
-rw-r--r--tests/test_util_images.py38
-rw-r--r--tests/test_util_inspect.py155
-rw-r--r--tests/test_util_typing.py8
-rw-r--r--tox.ini15
-rwxr-xr-xutils/bump_docker.sh16
-rwxr-xr-xutils/bump_version.py5
-rw-r--r--utils/jssplitter_generator.py2
-rw-r--r--utils/release-checklist26
211 files changed, 3428 insertions, 2917 deletions
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..c9009b90e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,6 @@
+# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
+blank_issues_enabled: false # default: true
+contact_links:
+- name: Question
+ url: https://groups.google.com/forum/#!forum/sphinx-users
+ about: For Q&A purpose, please use sphinx-users mailing list.
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
deleted file mode 100644
index 712676950..000000000
--- a/.github/ISSUE_TEMPLATE/question.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-name: Question
-about: For Q&A purpose, please use https://groups.google.com/forum/#!forum/sphinx-users
-title: For Q&A purpose, please use sphinx-users group
-labels: 'question'
-assignees: ''
-
----
-
-# Important
-
-This is a list of issues for Sphinx, **not a forum**.
-If you'd like to post a question, please move to sphinx-users group.
-https://groups.google.com/forum/#!forum/sphinx-users
-
-Thanks,
-
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 073a57795..6f4fe672f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,9 +1,20 @@
Subject: <short purpose of this pull request>
+<!--
+ Before posting a pull request, please choose a appropriate branch:
+
+ - Breaking changes: master
+ - Critical or severe bugs: X.Y.Z
+ - Others: X.Y
+
+ For more details, see https://www.sphinx-doc.org/en/master/devguide.html#branch-model
+-->
+
### Feature or Bugfix
<!-- please choose -->
- Feature
- Bugfix
+- Refactoring
### Purpose
- <long purpose of this pull request>
diff --git a/.gitignore b/.gitignore
index d5dc0b030..b72664183 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ distribute-*
env/
build/
dist/
+docker/
Sphinx.egg-info/
doc/_build/
doc/locale/
diff --git a/CHANGES b/CHANGES
index 542f5fc09..b4710da92 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,116 @@
+Release 3.0.0 (in development)
+==============================
+
+Dependencies
+------------
+
+* LaTeX: drop dependency on :program:`extractbb` for image inclusion in
+ Japanese documents as ``.xbb`` files are unneeded by :program:`dvipdfmx`
+ since TeXLive2015 (refs: #6189)
+* babel-2.0 or above is available (Unpinned)
+
+Incompatible changes
+--------------------
+
+* Drop features and APIs deprecated in 1.8.x
+* #247: autosummary: stub files are overwritten automatically by default. see
+ :confval:`autosummary_generate_overwrite` to change the behavior
+* #5923: autodoc: the members of ``object`` class are not documented by default
+ when ``:inherited-members:`` and ``:special-members:`` are given.
+* #6830: py domain: ``meta`` fields in info-field-list becomes reserved. They
+ are not displayed on output document now
+* The structure of ``sphinx.events.EventManager.listeners`` has changed
+* Due to the scoping changes for :rst:dir:`productionlist` some uses of
+ :rst:role:`token` must be modified to include the scope which was previously
+ ignored.
+* #6903: js domain: Internal data structure has changed. Both objects and
+ modules have node_id for cross reference
+* #7210: js domain: Non intended behavior is removed such as ``parseInt_`` links
+ to ``.. js:function:: parseInt``
+* #6903: rst domain: Internal data structure has changed. Now objects have
+ node_id for cross reference
+* #7229: rst domain: Non intended behavior is removed such as ``numref_`` links
+ to ``.. rst:role:: numref``
+
+Deprecated
+----------
+
+* ``desc_signature['first']``
+* ``sphinx.directives.DescDirective``
+* ``sphinx.domains.std.StandardDomain.add_object()``
+* ``sphinx.parsers.Parser.app``
+* ``sphinx.testing.path.Path.text()``
+* ``sphinx.testing.path.Path.bytes()``
+* ``sphinx.util.inspect.getargspec()``
+
+Features added
+--------------
+
+* #247: autosummary: Add :confval:`autosummary_generate_overwrite` to overwrite
+ old stub file
+* #5923: autodoc: ``:inherited-members:`` option takes a name of anchestor class
+ not to document inherited members of the class and uppers
+* #6830: autodoc: consider a member private if docstring contains
+ ``:meta private:`` in info-field-list
+* #7165: autodoc: Support Annotated type (PEP-593)
+* #6558: glossary: emit a warning for duplicated glossary entry
+* #3106: domain: Register hyperlink target for index page automatically
+* #6558: std domain: emit a warning for duplicated generic objects
+* #6830: py domain: Add new event: :event:`object-description-transform`
+* py domain: Support lambda functions in function signature
+* Support priority of event handlers. For more detail, see
+ :py:meth:`.Sphinx.connect()`
+* #3077: Implement the scoping for :rst:dir:`productionlist` as indicated
+ in the documentation.
+* #1027: Support backslash line continuation in :rst:dir:`productionlist`.
+* #7108: config: Allow to show an error message from conf.py via ``ConfigError``
+* #7032: html: :confval:`html_scaled_image_link` will be disabled for images having
+ ``no-scaled-link`` class
+* #7144: Add CSS class indicating its domain for each desc node
+* #7211: latex: Use babel for Chinese document when using XeLaTeX
+* #7220: genindex: Show "main" index entries at first
+* #7103: linkcheck: writes all links to ``output.json``
+
+Bugs fixed
+----------
+
+* C++, fix cross reference lookup in certain cases involving
+ function overloads.
+* #5078: C++, fix cross reference lookup when a directive contains multiple
+ declarations.
+* C++, suppress warnings for directly dependent typenames in cross references
+ generated automatically in signatures.
+* #5637: autodoc: Incorrect handling of nested class names on show-inheritance
+* #5637: inheritance_diagram: Incorrect handling of nested class names
+* #7139: ``code-block:: guess`` does not work
+
+Testing
+--------
+
+Release 2.4.4 (in development)
+==============================
+
+Dependencies
+------------
+
+Incompatible changes
+--------------------
+
+Deprecated
+----------
+
+Features added
+--------------
+
+Bugs fixed
+----------
+
+* #7197: LaTeX: platex cause error to build image directive with target url
+* #7223: Sphinx builds has been slower since 2.4.0
+
+Testing
+--------
+
Release 2.4.3 (released Feb 22, 2020)
=====================================
@@ -81,6 +194,8 @@ Features added
* #6966: graphviz: Support ``:class:`` option
* #6696: html: ``:scale:`` option of image/figure directive not working for SVG
images (imagesize-1.2.0 or above is required)
+* #7025: html search: full text search can be disabled for individual document
+ using ``:nosearch:`` file-wide metadata
* #6994: imgconverter: Support illustrator file (.ai) to .png conversion
* autodoc: Support Positional-Only Argument separator (PEP-570 compliant)
* autodoc: Support type annotations for variables
@@ -2025,7 +2140,7 @@ Features removed
* ``termsep`` node
* defindex.html template
-* LDML format support in `today`, `today_fmt` and `html_last_updated_fmt`
+* LDML format support in ``today``, ``today_fmt`` and ``html_last_updated_fmt``
* ``:inline:`` option for the directives of sphinx.ext.graphviz extension
* sphinx.ext.pngmath extension
* ``sphinx.util.compat.make_admonition()``
@@ -4829,7 +4944,7 @@ Features added
- Added a "nitpicky" mode that emits warnings for all missing
references. It is activated by the :option:`sphinx-build -n` command-line
- switch or the `nitpicky` config value.
+ switch or the :confval:`nitpicky` config value.
- Added ``latexpdf`` target in quickstart Makefile.
* Markup:
diff --git a/CODE_OF_CONDUCT b/CODE_OF_CONDUCT
new file mode 100644
index 000000000..f820bd131
--- /dev/null
+++ b/CODE_OF_CONDUCT
@@ -0,0 +1,76 @@
+Like the technical community as a whole, the Sphinx team and community is made
+up of volunteers from all over the world.
+Diversity is a strength, but it can also lead to communication issues and
+unhappiness. To that end, we have a few ground rules that we ask people to
+adhere to.
+
+* **Be friendly and patient.**
+
+* **Be welcoming.**
+ We strive to be a community that welcomes and supports people of all
+ backgrounds and identities. This includes, but is not limited to members of
+ any race, ethnicity, culture, national origin, colour, immigration status,
+ social and economic class, educational level, sex, sexual orientation, gender
+ identity and expression, age, size, family status, political belief, religion,
+ and mental and physical ability.
+
+* **Be considerate.**
+ Your work will be used by other people, and you in turn will depend on the
+ work of others. Any decision you take will affect users and colleagues, and
+ you should take those consequences into account when making decisions.
+ Remember that we're a world-wide community, so you might not be communicating
+ in someone else's primary language.
+
+* **Be respectful.**
+ Not all of us will agree all the time, but disagreement is no excuse for poor
+ behavior and poor manners. We might all experience some frustration now and
+ then, but we cannot allow that frustration to turn into a personal attack.
+ It’s important to remember that a community where people feel uncomfortable or
+ threatened is not a productive one. Members of the Sphinx community should be
+ respectful when dealing with other members as well as with people outside the
+ Sphinx community.
+
+* **Be careful in the words that you choose.**
+ We are a community of professionals, and we conduct ourselves professionally.
+ Be kind to others. Do not insult or put down other participants. Harassment
+ and other exclusionary behavior aren't acceptable. This includes, but is not
+ limited to:
+
+ * Violent threats or language directed against another person.
+
+ * Discriminatory jokes and language.
+
+ * Posting sexually explicit or violent material.
+
+ * Posting (or threatening to post) other people's personally identifying
+ information ("doxing").
+
+ * Personal insults, especially those using racist or sexist terms.
+
+ * Unwelcome sexual attention.
+
+ * Advocating for, or encouraging, any of the above behavior.
+
+ * Repeated harassment of others. In general, if someone asks you to stop, then
+ stop.
+
+* **When we disagree, try to understand why.**
+ Disagreements, both social and technical, happen all the time and Sphinx is no
+ exception. It is important that we resolve disagreements and differing views
+ constructively. Remember that we’re different. Different people have different
+ perspectives on issues. Being unable to understand why someone holds a
+ viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to
+ err and blaming each other doesn’t get us anywhere. Instead, focus on helping
+ to resolve issues and learning from mistakes.
+
+This isn’t an exhaustive list of things that you can’t do.
+Rather, take it in the spirit in which it’s intended - a guide to make it easier
+to enrich all of us and the technical communities in which we participate.
+This code of conduct applies to all spaces of the Sphinx community.
+
+Attribution
+-----------
+
+Original text courtesy of the Speak Up! project:
+http://web.archive.org/web/20141109123859/http://speakup.io/coc.html.
+
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 8c74b184a..6fd56fb9d 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -61,7 +61,7 @@ of the core developers before it is merged into the main repository.
#. If you feel uncomfortable or uncertain about an issue or your changes, feel
free to email the *sphinx-dev* mailing list.
#. Fork `the repository`_ on GitHub to start making your changes to the
- ``master`` branch for next MAJOR version, or ``X.Y`` branch for next
+ ``master`` branch for next MAJOR version, or ``A.x`` branch for next
MINOR version (see `Branch Model`_).
#. Write a test which shows that the bug was fixed or that the feature works
as expected.
@@ -94,10 +94,10 @@ These are the basic steps needed to start developing on Sphinx.
Sphinx adopts Semantic Versioning 2.0.0 (refs: https://semver.org/ ).
For changes that preserves backwards-compatibility of API and features,
- they should be included in the next MINOR release, use the ``X.Y`` branch.
+ they should be included in the next MINOR release, use the ``A.x`` branch.
::
- git checkout X.Y
+ git checkout A.x
For incompatible or other substantial changes that should wait until the
next MAJOR release, use the ``master`` branch.
@@ -199,7 +199,7 @@ These are the basic steps needed to start developing on Sphinx.
git push origin feature-xyz
#. Submit a pull request from your branch to the respective branch (``master``
- or ``X.Y``).
+ or ``A.x``).
#. Wait for a core developer to review your changes.
@@ -325,8 +325,8 @@ Versioning 2.0.0 (refs: https://semver.org/ ).
All changes including incompatible behaviors and public API updates are
allowed.
-``X.Y``
- Where ``X.Y`` is the ``MAJOR.MINOR`` release. Used to maintain current
+``A.x`` (ex. ``2.x``)
+ Where ``A.x`` is the ``MAJOR.MINOR`` release. Used to maintain current
MINOR release. All changes are allowed if the change preserves
backwards-compatibility of API and features.
@@ -334,8 +334,8 @@ Versioning 2.0.0 (refs: https://semver.org/ ).
new MAJOR version is released, the old ``MAJOR.MINOR`` branch will be
deleted and replaced by an equivalent tag.
-``X.Y.Z``
- Where ``X.Y.Z`` is the ``MAJOR.MINOR.PATCH`` release. Only
+``A.B.x`` (ex. ``2.4.x``)
+ Where ``A.B.x`` is the ``MAJOR.MINOR.PATCH`` release. Only
backwards-compatible bug fixes are allowed. In Sphinx project, PATCH
version is used for urgent bug fix.
diff --git a/EXAMPLES b/EXAMPLES
index 4920967d9..1f6bc848b 100644
--- a/EXAMPLES
+++ b/EXAMPLES
@@ -52,7 +52,7 @@ Documentation using the classic theme
* `Arb <http://arblib.org/>`__
* `Bazaar <http://doc.bazaar.canonical.com/>`__ (customized)
* `Beautiful Soup <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>`__
-* `Blender <https://docs.blender.org/api/current/>`__
+* `Blender API <https://docs.blender.org/api/current/>`__
* `Bugzilla <https://bugzilla.readthedocs.io/>`__
* `Buildbot <https://docs.buildbot.net/latest/>`__
* `CMake <https://cmake.org/documentation/>`__ (customized)
@@ -114,6 +114,7 @@ Documentation using the sphinxdoc theme
* `ABRT <https://abrt.readthedocs.io/>`__
* `cartopy <https://scitools.org.uk/cartopy/docs/latest/>`__
* `Jython <http://www.jython.org/docs/>`__
+* `LLVM <https://llvm.org/docs/>`__
* `Matplotlib <https://matplotlib.org/>`__
* `MDAnalysis Tutorial <https://www.mdanalysis.org/MDAnalysisTutorial/>`__
* `NetworkX <https://networkx.github.io/>`__
@@ -123,6 +124,7 @@ Documentation using the sphinxdoc theme
* `Pysparse <http://pysparse.sourceforge.net/>`__
* `PyTango <https://www.esrf.eu/computing/cs/tango/tango_doc/kernel_doc/pytango/latest/>`__
* `Python Wild Magic <https://vmlaker.github.io/pythonwildmagic/>`__ (customized)
+* `RDKit <https://www.rdkit.org/docs/>`__
* `Reteisi <http://www.reteisi.org/contents.html>`__ (customized)
* `Sqlkit <http://sqlkit.argolinux.org/>`__ (customized)
* `Turbulenz <http://docs.turbulenz.com/>`__
@@ -167,6 +169,7 @@ Documentation using sphinx_rtd_theme
* `ASE <https://wiki.fysik.dtu.dk/ase/>`__
* `Autofac <http://docs.autofac.org/>`__
* `BigchainDB <https://docs.bigchaindb.com/>`__
+* `Blender Reference Manual <https://docs.blender.org/manual/>`__
* `Blocks <https://blocks.readthedocs.io/>`__
* `bootstrap-datepicker <https://bootstrap-datepicker.readthedocs.io/>`__
* `Certbot <https://letsencrypt.readthedocs.io/>`__
@@ -180,17 +183,20 @@ Documentation using sphinx_rtd_theme
* `Databricks <https://docs.databricks.com/>`__ (customized)
* `Dataiku DSS <https://doc.dataiku.com/>`__
* `DNF <https://dnf.readthedocs.io/>`__
+* `Django-cas-ng <https://djangocas.dev/docs/>`__
* `edX <https://docs.edx.org/>`__
* `Electrum <http://docs.electrum.org/>`__
* `Elemental <http://libelemental.org/documentation/dev/>`__
* `ESWP3 <https://eswp3.readthedocs.io/>`__
* `Ethereum Homestead <http://www.ethdocs.org/>`__
+* `Exhale <https://exhale.readthedocs.io/>`__
* `Faker <https://faker.readthedocs.io/>`__
* `Fidimag <https://fidimag.readthedocs.io/>`__
* `Flake8 <http://flake8.pycqa.org/>`__
* `Flatpak <http://docs.flatpak.org/>`__
* `FluidDyn <https://fluiddyn.readthedocs.io/>`__
* `Fluidsim <https://fluidsim.readthedocs.io/>`__
+* `Gallium <https://gallium.readthedocs.io/>`__
* `GeoNode <http://docs.geonode.org/>`__
* `Glances <https://glances.readthedocs.io/>`__
* `Godot <https://godot.readthedocs.io/>`__
@@ -206,11 +212,13 @@ Documentation using sphinx_rtd_theme
* `Jupyter Notebook <https://jupyter-notebook.readthedocs.io/>`__
* `Lasagne <https://lasagne.readthedocs.io/>`__
* `latexindent.pl <https://latexindentpl.readthedocs.io/>`__
+* `Learning Apache Spark with Python <https://runawayhorse001.github.io/LearningApacheSpark>`__
* `Linguistica <https://linguistica-uchicago.github.io/lxa5/>`__
* `Linux kernel <https://www.kernel.org/doc/html/latest/index.html>`__
+* `Mailman <http://docs.list.org/>`__
* `MathJax <https://docs.mathjax.org/>`__
* `MDTraj <http://mdtraj.org/latest/>`__ (customized)
-* `MICrobial Community Analysis (micca) <http://micca.org/docs/latest/>`__
+* `micca - MICrobial Community Analysis <https://micca.readthedocs.io/>`__
* `MicroPython <https://docs.micropython.org/>`__
* `Minds <https://www.minds.org/docs/>`__ (customized)
* `Mink <http://mink.behat.org/>`__
@@ -252,6 +260,7 @@ Documentation using sphinx_rtd_theme
* `Sphinx AutoAPI <https://sphinx-autoapi.readthedocs.io/>`__
* `sphinx-argparse <https://sphinx-argparse.readthedocs.io/>`__
* `Sphinx-Gallery <https://sphinx-gallery.readthedocs.io/>`__ (customized)
+* `Sphinx with Github Webpages <https://runawayhorse001.github.io/SphinxGithub>`__
* `SpotBugs <https://spotbugs.readthedocs.io/>`__
* `StarUML <https://docs.staruml.io/>`__
* `Sublime Text Unofficial Documentation <http://docs.sublimetext.info/>`__
@@ -285,6 +294,7 @@ Documentation using sphinx_bootstrap_theme
* `Hedge <https://documen.tician.de/hedge/>`__
* `ObsPy <https://docs.obspy.org/>`__
* `Open Dylan <https://opendylan.org/documentation/>`__
+* `OPNFV <https://docs.opnfv.org/>`__
* `Pootle <http://docs.translatehouse.org/projects/pootle/>`__
* `PyUblas <https://documen.tician.de/pyublas/>`__
* `seaborn <https://seaborn.pydata.org/>`__
@@ -321,6 +331,7 @@ Documentation using a custom theme or integrated in a website
* `MongoDB <https://docs.mongodb.com/>`__
* `Music21 <https://web.mit.edu/music21/doc/>`__
* `MyHDL <http://docs.myhdl.org/>`__
+* `ndnSIM <https://ndnsim.net/current/>`__
* `nose <https://nose.readthedocs.io/>`__
* `ns-3 <https://www.nsnam.org/documentation/>`__
* `NumPy <https://docs.scipy.org/doc/numpy/reference/>`__
@@ -394,6 +405,7 @@ Books produced using Sphinx
* `"Python Professional Programming" (in Japanese) <http://www.amazon.co.jp/dp/4798032948/>`__
* `"Python Professional Programming 2nd Edition" (in Japanese) <https://www.amazon.co.jp/dp/479804315X/>`__
* `"Python Professional Programming 3rd Edition" (in Japanese) <https://www.amazon.co.jp/dp/4798053821/>`__
+* `Python Course by Yuri Petrov (Russian) <https://www.yuripetrov.ru/edu/python>`__
* `"Real World HTTP -- Learning The Internet and Web Technology via its history and code (Japanese)" <https://www.oreilly.co.jp/books/9784873118048/>`__
* `"Redmine Primer 5th Edition (in Japanese)" <https://www.shuwasystem.co.jp/products/7980html/4825.html>`__
* `"The repoze.bfg Web Application Framework" <https://www.amazon.com/repoze-bfg-Web-Application-Framework-Version/dp/0615345379>`__
diff --git a/Makefile b/Makefile
index 542083c49..42fe22dd8 100644
--- a/Makefile
+++ b/Makefile
@@ -31,6 +31,7 @@ clean-backupfiles:
clean-generated:
find . -name '.DS_Store' -exec rm -f {} +
rm -rf Sphinx.egg-info/
+ rm -rf dists/
rm -rf doc/_build/
rm -f sphinx/pycode/*.pickle
rm -f utils/*3.py*
diff --git a/README.rst b/README.rst
index 5a566c300..68065ac85 100644
--- a/README.rst
+++ b/README.rst
@@ -26,6 +26,10 @@
:target: https://codecov.io/gh/sphinx-doc/sphinx
:alt: Code Coverage Status (Codecov)
+.. image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
+ :target: https://opensource.org/licenses/BSD-3-Clause
+ :alt: BSD 3 Clause
+
Sphinx is a tool that makes it easy to create intelligent and beautiful
documentation for Python projects (or other documents consisting of multiple
reStructuredText sources), written by Georg Brandl. It was originally created
@@ -90,6 +94,10 @@ Get in touch
.. _on GitHub: https://github.com/sphinx-doc/sphinx
.. _mailing list: https://groups.google.com/forum/#!forum/sphinx-users
+Please adhere to our `code of conduct`__.
+
+__ http://www.sphinx-doc.org/en/master/code_of_conduct.html
+
Testing
=======
diff --git a/doc/_static/Makefile b/doc/_static/Makefile
new file mode 100644
index 000000000..39ce65260
--- /dev/null
+++ b/doc/_static/Makefile
@@ -0,0 +1,4 @@
+translation.png: translation.puml
+ plantuml $<
+clean:
+ rm translation.png
diff --git a/doc/_static/translation.png b/doc/_static/translation.png
index 11f3d02cd..a47c19f8d 100644
--- a/doc/_static/translation.png
+++ b/doc/_static/translation.png
Binary files differ
diff --git a/doc/_static/translation.puml b/doc/_static/translation.puml
new file mode 100644
index 000000000..5c3a7350b
--- /dev/null
+++ b/doc/_static/translation.puml
@@ -0,0 +1,16 @@
+@startuml
+file "SphinxProject"
+file ".rst"
+database ".pot"
+database ".po"
+database ".mo"
+actor translator
+file TranslatedBuild
+translator -l-> .po
+SphinxProject -r-> .rst
+.rst -r-> .pot : sphinx-build gettext
+.pot -r-> .po : Pootle
+.po -d-> .mo : msgfmt
+.mo -l-> TranslatedBuild
+.rst -d-> TranslatedBuild : "sphinx-buid -Dlanguage="
+@enduml
diff --git a/doc/_templates/index.html b/doc/_templates/index.html
index be174317d..1fc4ee51c 100644
--- a/doc/_templates/index.html
+++ b/doc/_templates/index.html
@@ -97,6 +97,8 @@
<p>{%trans%}A Japanese book about Sphinx has been published by O'Reilly:
<a href="https://www.oreilly.co.jp/books/9784873116488/">Sphinxをはじめよう /
Learning Sphinx</a>.{%endtrans%}</p>
+ <p>{%trans%}In 2019 the second edition of a German book about Sphinx was published:
+ <a href="https://literatur.hasecke.com/post/software-dokumentation-mit-sphinx/">Software-Dokumentation mit Sphinx</a>.{%endtrans%}</p>
<!-- <p><img src="{{ pathto("_static/bookcover.png", 1) }}"/></p> -->
@@ -118,4 +120,8 @@
<li>{%trans path=pathto("authors")%}<a href="{{ path }}">Sphinx Authors</a></li>{%endtrans%}
</ul>
+ <h2>{%trans%}Code of Conduct{%endtrans%}</h2>
+
+ {%trans path=pathto("code_of_conduct")%}Please adhere to our <a href="{{ path }}">Code of Conduct</a>.{%endtrans%}
+
{% endblock %}
diff --git a/doc/code_of_conduct.rst b/doc/code_of_conduct.rst
new file mode 100644
index 000000000..c1af92ddc
--- /dev/null
+++ b/doc/code_of_conduct.rst
@@ -0,0 +1,8 @@
+:tocdepth: 2
+
+.. _code_of_conduct:
+
+Sphinx Code of Conduct
+======================
+
+.. include:: ../CODE_OF_CONDUCT
diff --git a/doc/conf.py b/doc/conf.py
index 16594f038..9951aed2d 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -14,7 +14,7 @@ templates_path = ['_templates']
exclude_patterns = ['_build']
project = 'Sphinx'
-copyright = '2007-2019, Georg Brandl and the Sphinx team'
+copyright = '2007-2020, Georg Brandl and the Sphinx team'
version = sphinx.__display_version__
release = version
show_authors = True
@@ -146,6 +146,9 @@ def setup(app):
app.add_object_type('confval', 'confval',
objname='configuration value',
indextemplate='pair: %s; configuration value')
+ app.add_object_type('setuptools-confval', 'setuptools-confval',
+ objname='setuptools configuration value',
+ indextemplate='pair: %s; setuptools configuration value')
fdesc = GroupedField('parameter', label='Parameters',
names=['param'], can_collapse=True)
app.add_object_type('event', 'event', 'pair: %s; event', parse_event,
diff --git a/doc/contents.rst b/doc/contents.rst
index 91ce55917..7e87cfffa 100644
--- a/doc/contents.rst
+++ b/doc/contents.rst
@@ -34,6 +34,7 @@ Sphinx documentation contents
changes
examples
authors
+ code_of_conduct
diff --git a/doc/development/tutorials/recipe.rst b/doc/development/tutorials/recipe.rst
index 2a3aa6408..dcfe42c1a 100644
--- a/doc/development/tutorials/recipe.rst
+++ b/doc/development/tutorials/recipe.rst
@@ -75,7 +75,7 @@ The first thing to examine is the ``RecipeDirective`` directive:
.. literalinclude:: examples/recipe.py
:language: python
:linenos:
- :lines: 17-37
+ :pyobject: RecipeDirective
Unlike :doc:`helloworld` and :doc:`todo`, this directive doesn't derive from
:class:`docutils.parsers.rst.Directive` and doesn't define a ``run`` method.
@@ -103,7 +103,12 @@ reStructuredText in the body.
.. literalinclude:: examples/recipe.py
:language: python
:linenos:
- :lines: 40-102
+ :pyobject: IngredientIndex
+
+.. literalinclude:: examples/recipe.py
+ :language: python
+ :linenos:
+ :pyobject: RecipeIndex
Both ``IngredientIndex`` and ``RecipeIndex`` are derived from :class:`Index`.
They implement custom logic to generate a tuple of values that define the
@@ -117,6 +122,10 @@ all it really is is a list of tuples like ``('tomato', 'TomatoSoup', 'test',
'rec-TomatoSoup',...)``. Refer to the :doc:`domain API guide
</extdev/domainapi>` for more information on this API.
+These index pages can be referred by combination of domain name and its
+``name`` using :rst:role:`ref` role. For example, ``RecipeIndex`` can be
+referred by ``:ref:`recipe-recipe```.
+
.. rubric:: The domain
A Sphinx domain is a specialized container that ties together roles,
@@ -126,7 +135,7 @@ creating here.
.. literalinclude:: examples/recipe.py
:language: python
:linenos:
- :lines: 105-155
+ :pyobject: RecipeDomain
There are some interesting things to note about this ``recipe`` domain and domains
in general. Firstly, we actually register our directives, roles and indices
@@ -164,7 +173,7 @@ hook the various parts of our extension into Sphinx. Let's look at the
.. literalinclude:: examples/recipe.py
:language: python
:linenos:
- :lines: 158-
+ :pyobject: setup
This looks a little different to what we're used to seeing. There are no calls
to :meth:`~Sphinx.add_directive` or even :meth:`~Sphinx.add_role`. Instead, we
diff --git a/doc/development/tutorials/todo.rst b/doc/development/tutorials/todo.rst
index f4ac85ac9..e27528b5a 100644
--- a/doc/development/tutorials/todo.rst
+++ b/doc/development/tutorials/todo.rst
@@ -107,6 +107,20 @@ is just a "general" node.
<http://docutils.sourceforge.net/docs/ref/doctree.html>`__ and :ref:`Sphinx
<nodes>`.
+.. attention::
+
+ It is important to know that while you can extend Sphinx without
+ leaving your ``conf.py``, if you declare an inherited node right
+ there, you'll hit an unobvious :py:class:`PickleError`. So if
+ something goes wrong, please make sure that you put inherited nodes
+ into a separate Python module.
+
+ For more details, see:
+
+ - https://github.com/sphinx-doc/sphinx/issues/6751
+ - https://github.com/sphinx-doc/sphinx/issues/1493
+ - https://github.com/sphinx-doc/sphinx/issues/1424
+
.. rubric:: The directive classes
A directive class is a class deriving usually from
diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst
index e89da7ce9..76ad65a88 100644
--- a/doc/extdev/appapi.rst
+++ b/doc/extdev/appapi.rst
@@ -54,8 +54,6 @@ package.
.. automethod:: Sphinx.add_domain(domain)
-.. automethod:: Sphinx.override_domain(domain)
-
.. method:: Sphinx.add_directive_to_domain(domain, name, func, content, arguments, \*\*options)
.. automethod:: Sphinx.add_directive_to_domain(domain, name, directiveclass)
diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst
index d009454ea..484c76aba 100644
--- a/doc/extdev/deprecated.rst
+++ b/doc/extdev/deprecated.rst
@@ -26,6 +26,41 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- Alternatives
+ * - ``desc_signature['first']``
+ -
+ - 3.0
+ - N/A
+
+ * - ``sphinx.directives.DescDirective``
+ - 3.0
+ - 5.0
+ - ``sphinx.directives.ObjectDescription``
+
+ * - ``sphinx.domains.std.StandardDomain.add_object()``
+ - 3.0
+ - 5.0
+ - ``sphinx.domains.std.StandardDomain.note_object()``
+
+ * - ``sphinx.parsers.Parser.app``
+ - 3.0
+ - 5.0
+ - N/A
+
+ * - ``sphinx.testing.path.Path.text()``
+ - 3.0
+ - 5.0
+ - ``sphinx.testing.path.Path.read_text()``
+
+ * - ``sphinx.testing.path.Path.bytes()``
+ - 3.0
+ - 5.0
+ - ``sphinx.testing.path.Path.read_bytes()``
+
+ * - ``sphinx.util.inspect.getargspec()``
+ - 3.0
+ - 5.0
+ - ``inspect.getargspec()``
+
* - ``decode`` argument of ``sphinx.pycode.ModuleAnalyzer()``
- 2.4
- 4.0
diff --git a/doc/faq.rst b/doc/faq.rst
index 741901dad..28d14d79e 100644
--- a/doc/faq.rst
+++ b/doc/faq.rst
@@ -48,12 +48,10 @@ Using Sphinx with...
--------------------
Read the Docs
- https://readthedocs.org is a documentation hosting service based around
- Sphinx. They will host sphinx documentation, along with supporting a number
- of other features including version support, PDF generation, and more. The
- `Getting Started
- <https://read-the-docs.readthedocs.io/en/latest/getting_started.html>`_
- guide is a good place to start.
+ `Read the Docs <https://readthedocs.org>`_ is a documentation hosting
+ service based around Sphinx. They will host sphinx documentation, along
+ with supporting a number of other features including version support, PDF
+ generation, and more. The `Getting Started`_ guide is a good place to start.
Epydoc
There's a third-party extension providing an `api role`_ which refers to
@@ -145,6 +143,7 @@ Google Search
3. Add ``searchbox.html`` to the :confval:`html_sidebars` configuration value.
+.. _Getting Started: https://docs.readthedocs.io/en/stable/intro/getting-started-with-sphinx.html
.. _api role: https://git.savannah.gnu.org/cgit/kenozooid.git/tree/doc/extapi.py
.. _xhtml to reST: http://docutils.sourceforge.net/sandbox/xhtml2rest/xhtml2rest.py
diff --git a/doc/man/sphinx-apidoc.rst b/doc/man/sphinx-apidoc.rst
index 30bfde0bb..725d2f169 100644
--- a/doc/man/sphinx-apidoc.rst
+++ b/doc/man/sphinx-apidoc.rst
@@ -5,7 +5,7 @@ Synopsis
--------
**sphinx-apidoc** [*OPTIONS*] -o <*OUTPUT_PATH*> <*MODULE_PATH*>
-[*EXCLUDE_PATTERN*, ...]
+[*EXCLUDE_PATTERN* ...]
Description
-----------
diff --git a/doc/usage/advanced/intl.rst b/doc/usage/advanced/intl.rst
index 0174078eb..0dd89b65f 100644
--- a/doc/usage/advanced/intl.rst
+++ b/doc/usage/advanced/intl.rst
@@ -12,8 +12,8 @@ in itself. See the :ref:`intl-options` for details on configuration.
.. figure:: /_static/translation.png
:width: 100%
- Workflow visualization of translations in Sphinx. (The stick-figure is taken
- from an `XKCD comic <https://xkcd.com/779/>`_.)
+ Workflow visualization of translations in Sphinx. (The figure is created by
+ `plantuml <http://plantuml.com>`_.)
.. contents::
:local:
diff --git a/doc/usage/advanced/setuptools.rst b/doc/usage/advanced/setuptools.rst
index 10cc6a77d..f4dfb7f66 100644
--- a/doc/usage/advanced/setuptools.rst
+++ b/doc/usage/advanced/setuptools.rst
@@ -57,7 +57,7 @@ Once configured, call this by calling the relevant command on ``setup.py``::
Options for setuptools integration
----------------------------------
-.. confval:: fresh-env
+.. setuptools-confval:: fresh-env
A boolean that determines whether the saved environment should be discarded
on build. Default is false.
@@ -68,7 +68,7 @@ Options for setuptools integration
$ python setup.py build_sphinx -E
-.. confval:: all-files
+.. setuptools-confval:: all-files
A boolean that determines whether all files should be built from scratch.
Default is false.
@@ -79,7 +79,7 @@ Options for setuptools integration
$ python setup.py build_sphinx -a
-.. confval:: source-dir
+.. setuptools-confval:: source-dir
The target source directory. This can be relative to the ``setup.py`` or
``setup.cfg`` file, or it can be absolute. It defaults to ``./doc`` or
@@ -92,12 +92,12 @@ Options for setuptools integration
$ python setup.py build_sphinx -s $SOURCE_DIR
-.. confval:: build-dir
+.. setuptools-confval:: build-dir
The target build directory. This can be relative to the ``setup.py`` or
``setup.cfg`` file, or it can be absolute. Default is ``./build/sphinx``.
-.. confval:: config-dir
+.. setuptools-confval:: config-dir
Location of the configuration directory. This can be relative to the
``setup.py`` or ``setup.cfg`` file, or it can be absolute. Default is to use
@@ -111,7 +111,7 @@ Options for setuptools integration
.. versionadded:: 1.0
-.. confval:: builder
+.. setuptools-confval:: builder
The builder or list of builders to use. Default is ``html``.
@@ -124,7 +124,7 @@ Options for setuptools integration
.. versionchanged:: 1.6
This can now be a comma- or space-separated list of builders
-.. confval:: warning-is-error
+.. setuptools-confval:: warning-is-error
A boolean that ensures Sphinx warnings will result in a failed build.
Default is false.
@@ -137,32 +137,32 @@ Options for setuptools integration
.. versionadded:: 1.5
-.. confval:: project
+.. setuptools-confval:: project
The documented project's name. Default is ``''``.
.. versionadded:: 1.0
-.. confval:: version
+.. setuptools-confval:: version
The short X.Y version. Default is ``''``.
.. versionadded:: 1.0
-.. confval:: release
+.. setuptools-confval:: release
The full version, including alpha/beta/rc tags. Default is ``''``.
.. versionadded:: 1.0
-.. confval:: today
+.. setuptools-confval:: today
How to format the current date, used as the replacement for ``|today|``.
Default is ``''``.
.. versionadded:: 1.0
-.. confval:: link-index
+.. setuptools-confval:: link-index
A boolean that ensures index.html will be linked to the master doc. Default
is false.
@@ -175,13 +175,13 @@ Options for setuptools integration
.. versionadded:: 1.0
-.. confval:: copyright
+.. setuptools-confval:: copyright
The copyright string. Default is ``''``.
.. versionadded:: 1.3
-.. confval:: nitpicky
+.. setuptools-confval:: nitpicky
Run in nit-picky mode. Currently, this generates warnings for all missing
references. See the config value :confval:`nitpick_ignore` for a way to
@@ -189,7 +189,7 @@ Options for setuptools integration
.. versionadded:: 1.8
-.. confval:: pdb
+.. setuptools-confval:: pdb
A boolean to configure ``pdb`` on exception. Default is false.
diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst
index 6a25ae9ec..cfc5db065 100644
--- a/doc/usage/configuration.rst
+++ b/doc/usage/configuration.rst
@@ -574,7 +574,7 @@ General configuration
A dictionary of options that modify how the lexer specified by
:confval:`highlight_language` generates highlighted source code. These are
lexer-specific; for the options understood by each, see the
- `Pygments documentation <http://pygments.org/docs/lexers/>`_.
+ `Pygments documentation <https://pygments.org/docs/lexers.html>`_.
.. versionadded:: 1.3
@@ -781,7 +781,7 @@ documentation on :ref:`intl` for details.
i18n additionally. You can specify below names:
:index: index terms
- :literal-block: literal blocks: ``::`` and ``code-block``.
+ :literal-block: literal blocks (``::`` annotation and ``code-block`` directive)
:doctest-block: doctest block
:raw: raw content
:image: image/figure uri and alt
@@ -949,7 +949,7 @@ that use Sphinx's HTMLWriter class.
Example::
- html_css_files = ['custom.css'
+ html_css_files = ['custom.css',
'https://example.com/css/custom.css',
('print.css', {'media': 'print'})]
@@ -1357,8 +1357,21 @@ that use Sphinx's HTMLWriter class.
'target' option or scale related options: 'scale', 'width', 'height'.
The default is ``True``.
+ Document authors can this feature manually with giving ``no-scaled-link``
+ class to the image:
+
+ .. code-block:: rst
+
+ .. image:: sphinx.png
+ :scale: 50%
+ :class: no-scaled-link
+
.. versionadded:: 1.3
+ .. versionchanged:: 2.4
+
+ It is disabled for images having ``no-scaled-link`` class
+
.. confval:: html_math_renderer
The name of math_renderer extension for HTML output. The default is
diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst
index b56e42d4d..ceef5f36a 100644
--- a/doc/usage/extensions/autodoc.rst
+++ b/doc/usage/extensions/autodoc.rst
@@ -140,6 +140,20 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
.. versionadded:: 1.1
+ * autodoc considers a member private if its docstring contains
+ ``:meta private:`` in its :ref:`info-field-lists`.
+ For example:
+
+ .. code-block:: rst
+
+ def my_function(my_arg, my_other_arg):
+ """blah blah blah
+
+ :meta private:
+ """
+
+ .. versionadded:: 3.0
+
* Python "special" members (that is, those named like ``__special__``) will
be included if the ``special-members`` flag option is given::
@@ -157,7 +171,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
* For classes and exceptions, members inherited from base classes will be
left out when documenting all members, unless you give the
- ``inherited-members`` flag option, in addition to ``members``::
+ ``inherited-members`` option, in addition to ``members``::
.. autoclass:: Noodle
:members:
@@ -166,11 +180,29 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
This can be combined with ``undoc-members`` to document *all* available
members of the class or module.
+ It can take an anchestor class not to document inherited members from it.
+ By default, members of ``object`` class are not documented. To show them
+ all, give ``None`` to the option.
+
+ For example; If your class ``Foo`` is derived from ``list`` class and
+ you don't want to document ``list.__len__()``, you should specify a
+ option ``:inherited-members: list`` to avoid special members of list
+ class.
+
+ Another example; If your class Foo has ``__str__`` special method and
+ autodoc directive has both ``inherited-members`` and ``special-members``,
+ ``__str__`` will be documented as in the past, but other special method
+ that are not implemented in your class ``Foo``.
+
Note: this will lead to markup errors if the inherited members come from a
module whose docstrings are not reST formatted.
.. versionadded:: 0.3
+ .. versionchanged:: 3.0
+
+ It takes an anchestor class name as an argument.
+
* It's possible to override the signature for explicitly documented callable
objects (functions, methods, classes) with the regular syntax that will
override the signature gained from introspection::
@@ -308,9 +340,6 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
a decorator replaces the decorated function with another, it must copy the
original ``__doc__`` to the new function.
- From Python 2.5, :func:`functools.wraps` can be used to create
- well-behaved decorating functions.
-
Configuration
-------------
diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst
index c5211639e..fedc0a3cf 100644
--- a/doc/usage/extensions/autosummary.rst
+++ b/doc/usage/extensions/autosummary.rst
@@ -148,6 +148,13 @@ also use these config values:
Emits :event:`autodoc-skip-member` event as :mod:`~sphinx.ext.autodoc`
does.
+.. confval:: autosummary_generate_overwrite
+
+ If true, autosummary already overwrites stub files by generated contents.
+ Defaults to true (enabled).
+
+ .. versionadded:: 3.0
+
.. confval:: autosummary_mock_imports
This value contains a list of modules to be mocked up. See
diff --git a/doc/usage/extensions/doctest.rst b/doc/usage/extensions/doctest.rst
index 79a75536e..62d8577eb 100644
--- a/doc/usage/extensions/doctest.rst
+++ b/doc/usage/extensions/doctest.rst
@@ -11,11 +11,15 @@
pair: testing; snippets
-This extension allows you to test snippets in the documentation in a natural
-way. It works by collecting specially-marked up code blocks and running them as
-doctest tests.
+It is often helpful to include snippets of code in your documentation and
+demonstrate the results of executing them. But it is important to ensure that
+the documentation stays up-to-date with the code.
-Within one document, test code is partitioned in *groups*, where each group
+This extension allows you to test such code snippets in the documentation in
+a natural way. If you mark the code blocks as shown here, the ``doctest``
+builder will collect them and run them as doctest tests.
+
+Within each document, you can assign each snippet to a *group*. Each group
consists of:
* zero or more *setup code* blocks (e.g. importing the module to test)
diff --git a/doc/usage/extensions/intersphinx.rst b/doc/usage/extensions/intersphinx.rst
index 0b2070400..619ec8c20 100644
--- a/doc/usage/extensions/intersphinx.rst
+++ b/doc/usage/extensions/intersphinx.rst
@@ -148,3 +148,13 @@ project. The following example prints the Intersphinx mapping of the Python 3
documentation::
$ python -msphinx.ext.intersphinx https://docs.python.org/3/objects.inv
+
+Using Intersphinx with inventory file under Basic Authorization
+---------------------------------------------------------------
+
+Intersphinx supports Basic Authorization like this::
+
+ intersphinx_mapping = {'python': ('https://user:password@docs.python.org/3',
+ None)}
+
+The user and password will be stripped from the URL when generating the links.
diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst
index f51b3084e..ba93bf192 100644
--- a/doc/usage/installation.rst
+++ b/doc/usage/installation.rst
@@ -23,8 +23,7 @@ Linux
Debian/Ubuntu
~~~~~~~~~~~~~
-Install either ``python3-sphinx`` (Python 3) or ``python-sphinx`` (Python 2)
-using :command:`apt-get`:
+Install either ``python3-sphinx`` using :command:`apt-get`:
::
@@ -77,23 +76,22 @@ __ https://formulae.brew.sh/formula/sphinx-doc
MacPorts
~~~~~~~~
-Install either ``python36-sphinx`` (Python 3) or ``python27-sphinx`` (Python 2)
-using :command:`port`:
+Install either ``python3x-sphinx`` using :command:`port`:
::
- $ sudo port install py36-sphinx
+ $ sudo port install py38-sphinx
To set up the executable paths, use the ``port select`` command:
::
- $ sudo port select --set python python36
- $ sudo port select --set sphinx py36-sphinx
+ $ sudo port select --set python python38
+ $ sudo port select --set sphinx py38-sphinx
For more information, refer to the `package overview`__.
-__ https://www.macports.org/ports.php?by=library&substr=py36-sphinx
+__ https://www.macports.org/ports.php?by=library&substr=py38-sphinx
Anaconda
~~~~~~~~
@@ -108,12 +106,13 @@ Windows
.. todo:: Could we start packaging this?
Most Windows users do not have Python installed by default, so we begin with
-the installation of Python itself. If you are unsure, open the *Command
-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 must install `Python 3`__.
+the installation of Python itself. To check if you already have Python
+installed, open the *Command Prompt* (:kbd:`⊞Win-r` and type :command:`cmd`).
+Once the command prompt is open, type :command:`python --version` and press
+Enter. If Python is installed, 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 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
@@ -160,6 +159,37 @@ the ``--pre`` flag.
$ pip install -U --pre sphinx
+Docker
+------
+
+Docker images for Sphinx are published on the `Docker Hub <https://hub.docker.com/>`_. There are two kind of images:
+
+- `sphinxdoc/sphinx <https://hub.docker.com/repository/docker/sphinxdoc/sphinx>`_
+- `sphinxdoc/sphinx-latexpdf <https://hub.docker.com/repository/docker/sphinxdoc/sphinx-latexpdf>`_
+
+Former one is used for standard usage of Sphinx, and latter one is mainly used for PDF builds using LaTeX.
+Please choose one for your purpose.
+
+.. note::
+
+ sphinxdoc/sphinx-latexpdf contains TeXLive packages. So the image is very large (over 2GB!).
+
+.. hint::
+
+ 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
+
+ And you can following command this to build HTML document::
+
+ $ docker run --rm -v /path/to/document:/docs sphinxdoc/sphinx make html
+
+For more details, please read `README file`__ of docker images.
+
+.. __: https://hub.docker.com/repository/docker/sphinxdoc/sphinx
+
+
Installation from source
------------------------
diff --git a/doc/usage/quickstart.rst b/doc/usage/quickstart.rst
index 5279d3f87..b5462a388 100644
--- a/doc/usage/quickstart.rst
+++ b/doc/usage/quickstart.rst
@@ -6,7 +6,7 @@ Once Sphinx is :doc:`installed </usage/installation>`, you can proceed with
setting up your first Sphinx project. To ease the process of getting started,
Sphinx provides a tool, :program:`sphinx-quickstart`, which will generate a
documentation source directory and populate it with some defaults. We're going
-to use the :program:`sphinx-quickstart` tool here, though it's use by no means
+to use the :program:`sphinx-quickstart` tool here, though its use is by no means
necessary.
@@ -26,9 +26,6 @@ configuration values from a few questions it asks you. To use this, run:
$ sphinx-quickstart
-Answer each question asked. Be sure to say yes to the ``autodoc`` extension, as
-we will use this later.
-
There is also an automatic "API documentation" generator called
:program:`sphinx-apidoc`; see :doc:`/man/sphinx-apidoc` for details.
@@ -37,12 +34,11 @@ Defining document structure
---------------------------
Let's assume you've run :program:`sphinx-quickstart`. It created a source
-directory with :file:`conf.py` and a master document, :file:`index.rst` (if you
-accepted the defaults). The main function of the :term:`master document` is to
-serve as a welcome page, and to contain the root of the "table of contents
-tree" (or *toctree*). This is one of the main things that Sphinx adds to
-reStructuredText, a way to connect multiple files to a single hierarchy of
-documents.
+directory with :file:`conf.py` and a master document, :file:`index.rst`. The
+main function of the :term:`master document` is to serve as a welcome page, and
+to contain the root of the "table of contents tree" (or *toctree*). This is one
+of the main things that Sphinx adds to reStructuredText, a way to connect
+multiple files to a single hierarchy of documents.
.. sidebar:: reStructuredText directives
@@ -103,7 +99,7 @@ In Sphinx source files, you can use most features of standard
For example, you can add cross-file references in a portable way (which works
for all output types) using the :rst:role:`ref` role.
-For an example, if you are viewing the HTML version you can look at the source
+For an example, if you are viewing the HTML version, you can look at the source
for this document -- use the "Show Source" link in the sidebar.
.. todo:: Update the below link when we add new guides on these.
@@ -233,8 +229,7 @@ customize a config value that is not automatically added by
Keep in mind that the file uses Python syntax for strings, numbers, lists and
so on. The file is saved in UTF-8 by default, as indicated by the encoding
-declaration in the first line. If you use non-ASCII characters in any string
-value, you need to use Python Unicode strings (like ``project = u'Exposé'``).
+declaration in the first line.
|more| See :doc:`/usage/configuration` for documentation of all available
config values.
@@ -252,10 +247,12 @@ module that provides additional features for Sphinx projects) called *autodoc*.
In order to use *autodoc*, you need to activate it in :file:`conf.py` by
putting the string ``'sphinx.ext.autodoc'`` into the list assigned to the
-:confval:`extensions` config value. Then, you have a few additional directives
-at your disposal.
+:confval:`extensions` config value::
+
+ extensions = ['sphinx.ext.autodoc']
-For example, to document the function ``io.open()``, reading its signature and
+Then, you have a few additional directives at your disposal. For example, to
+document the function ``io.open()``, reading its signature and
docstring from the source file, you'd write this::
.. autofunction:: io.open
diff --git a/doc/usage/restructuredtext/basics.rst b/doc/usage/restructuredtext/basics.rst
index 1f36cdc40..dffaf297c 100644
--- a/doc/usage/restructuredtext/basics.rst
+++ b/doc/usage/restructuredtext/basics.rst
@@ -372,7 +372,8 @@ Docutils supports the following directives:
* HTML specifics:
- - :dudir:`meta` (generation of HTML ``<meta>`` tags)
+ - :dudir:`meta`
+ (generation of HTML ``<meta>`` tags, see also :ref:`html-meta` below)
- :dudir:`title <metadata-document-title>` (override document title)
* Influencing markup:
@@ -538,6 +539,45 @@ You can indent text after a comment start to form multiline comments::
Still in the comment.
+.. _html-meta:
+
+HTML Metadata
+-------------
+
+The :rst:dir:`meta` directive (:dudir:`ref <meta>`) allows specifying the HTML
+`metadata element`_ of a Sphinx documentation page. For example, the
+directive::
+
+ .. meta::
+ :description: The Sphinx documentation builder
+ :keywords: Sphinx, documentation, builder
+
+will generate the following HTML output:
+
+.. code:: html
+
+ <meta name="description" content="The Sphinx documentation builder">
+ <meta name="keywords" content="Sphinx, documentation, builder">
+
+Also, Sphinx will add the keywords as specified in the meta directive to the
+search index. Thereby, the ``lang`` attribute of the meta element is
+considered. For example, the directive::
+
+ .. meta::
+ :keywords: backup
+ :keywords lang=en: pleasefindthiskey pleasefindthiskeytoo
+ :keywords lang=de: bittediesenkeyfinden
+
+adds the following words to the search indices of builds with different language
+configurations:
+
+* ``pleasefindthiskey``, ``pleasefindthiskeytoo`` to *English* builds;
+* ``bittediesenkeyfinden`` to *German* builds;
+* ``backup`` to builds in all languages.
+
+.. _metadata element: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
+
+
Source encoding
---------------
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index 584fff472..b5b0a2980 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -441,7 +441,7 @@ If highlighting with the selected language fails (i.e. Pygments emits an
want to ensure consistent highlighting, you should fix your version of
Pygments.
-__ http://pygments.org/docs/lexers/
+__ http://pygments.org/docs/lexers
.. rst:directive:: .. highlight:: language
@@ -874,6 +874,19 @@ mainly contained in information units, such as the language reference.
.. versionchanged:: 1.1
Added ``see`` and ``seealso`` types, as well as marking main entries.
+ .. rubric:: options
+
+ .. rst:directive:option:: name: a label for hyperlink
+ :type: text
+
+ Define implicit target name that can be referenced by using
+ :rst:role:`ref`. For example::
+
+ .. index:: Python
+ :name: py-index
+
+ .. versionadded:: 3.0
+
.. rst:role:: index
While the :rst:dir:`index` directive is a block-level markup and links to the
@@ -1139,7 +1152,7 @@ derived forms), but provides enough to allow context-free grammars to be
displayed in a way that causes uses of a symbol to be rendered as hyperlinks to
the definition of the symbol. There is this directive:
-.. rst:directive:: .. productionlist:: [name]
+.. rst:directive:: .. productionlist:: [productionGroup]
This directive is used to enclose a group of productions. Each production
is given on a single line and consists of a name, separated by a colon from
@@ -1147,16 +1160,24 @@ the definition of the symbol. There is this directive:
continuation line must begin with a colon placed at the same column as in
the first line.
- The argument to :rst:dir:`productionlist` serves to distinguish different
- sets of production lists that belong to different grammars.
+ The *productionGroup* argument to :rst:dir:`productionlist` serves to
+ distinguish different sets of production lists that belong to different
+ grammars. Multiple production lists with the same *productionGroup* thus
+ define rules in the same scope.
Blank lines are not allowed within ``productionlist`` directive arguments.
The definition can contain token names which are marked as interpreted text
- (e.g. ``sum ::= `integer` "+" `integer```) -- this generates
+ (e.g. "``sum ::= `integer` "+" `integer```") -- this generates
cross-references to the productions of these tokens. Outside of the
production list, you can reference to token productions using
:rst:role:`token`.
+ However, if you have given a *productionGroup* argument you must prefix the
+ token name in the cross-reference with the group name and a colon,
+ e.g., "``myGroup:sum``" instead of just "``sum``".
+ If the group should not be shown in the title of the link either
+ an explicit title can be given (e.g., "``myTitle <myGroup:sum>``"),
+ or the target can be prefixed with a tilde (e.g., "``~myGroup:sum``").
Note that no further reST parsing is done in the production, so that you
don't have to escape ``*`` or ``|`` characters.
diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst
index 870f1be63..9c69fe88f 100644
--- a/doc/usage/restructuredtext/domains.rst
+++ b/doc/usage/restructuredtext/domains.rst
@@ -378,6 +378,9 @@ Info field lists
~~~~~~~~~~~~~~~~
.. versionadded:: 0.4
+.. versionchanged:: 3.0
+
+ meta fields are added.
Inside Python object description directives, reST field lists with these fields
are recognized and formatted nicely:
@@ -391,6 +394,10 @@ are recognized and formatted nicely:
* ``vartype``: Type of a variable. Creates a link if possible.
* ``returns``, ``return``: Description of the return value.
* ``rtype``: Return type. Creates a link if possible.
+* ``meta``: Add metadata to description of the python object. The metadata will
+ not be shown on output document. For example, ``:meta private:`` indicates
+ the python object is private member. It is used in
+ :py:mod:`sphinx.ext.autodoc` for filtering members.
.. note::
diff --git a/doc/usage/restructuredtext/field-lists.rst b/doc/usage/restructuredtext/field-lists.rst
index b84d238ba..0d1a47628 100644
--- a/doc/usage/restructuredtext/field-lists.rst
+++ b/doc/usage/restructuredtext/field-lists.rst
@@ -51,3 +51,12 @@ At the moment, these metadata fields are recognized:
:orphan:
.. versionadded:: 1.0
+
+``nosearch``
+ If set, full text search for this file is disabled. ::
+
+ :nosearch:
+
+ .. note:: object search is still available even if `nosearch` option is set.
+
+ .. versionadded:: 2.4
diff --git a/setup.cfg b/setup.cfg
index fdd2fe327..8aa9f30ff 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -40,6 +40,7 @@ paths =
.
[mypy]
+python_version = 3.5
disallow_incomplete_defs = True
show_column_numbers = True
show_error_context = True
diff --git a/setup.py b/setup.py
index 55578350f..bb6273033 100644
--- a/setup.py
+++ b/setup.py
@@ -25,7 +25,7 @@ install_requires = [
'Pygments>=2.0',
'docutils>=0.12',
'snowballstemmer>=1.1',
- 'babel>=1.3,!=2.0',
+ 'babel>=1.3',
'alabaster>=0.7,<0.8',
'imagesize',
'requests>=2.5.0',
@@ -41,15 +41,18 @@ extras_require = {
'docs': [
'sphinxcontrib-websupport',
],
- 'test': [
- 'pytest < 5.3.3',
- 'pytest-cov',
- 'html5lib',
+ 'lint': [
'flake8>=3.5.0',
'flake8-import-order',
'mypy>=0.761',
'docutils-stubs',
],
+ 'test': [
+ 'pytest < 5.3.3',
+ 'pytest-cov',
+ 'html5lib',
+ 'typed_ast', # for py35-37
+ ],
}
# Provide a "compile_catalog" command that also creates the translated
diff --git a/sphinx/__init__.py b/sphinx/__init__.py
index 312e2b0c5..4889f35d5 100644
--- a/sphinx/__init__.py
+++ b/sphinx/__init__.py
@@ -32,8 +32,8 @@ if 'PYTHONWARNINGS' not in os.environ:
warnings.filterwarnings('ignore', "'U' mode is deprecated",
DeprecationWarning, module='docutils.io')
-__version__ = '2.4.3'
-__released__ = '2.4.3' # used when Sphinx builds its own docs
+__version__ = '3.0.0+'
+__released__ = '3.0.0' # used when Sphinx builds its own docs
#: Version info for better programmatic use.
#:
@@ -43,7 +43,7 @@ __released__ = '2.4.3' # used when Sphinx builds its own docs
#:
#: .. versionadded:: 1.2
#: Before version 1.2, check the string ``sphinx.__version__``.
-version_info = (2, 4, 3, 'final', 0)
+version_info = (3, 0, 0, 'beta', 0)
package_dir = path.abspath(path.dirname(__file__))
@@ -56,8 +56,8 @@ if __version__.endswith('+'):
__version__ = __version__[:-1] # remove '+' for PEP-440 version spec.
try:
ret = subprocess.run(['git', 'show', '-s', '--pretty=format:%h'],
- stdout=PIPE, stderr=PIPE, encoding='ascii')
+ stdout=PIPE, stderr=PIPE)
if ret.stdout:
- __display_version__ += '/' + ret.stdout.strip()
+ __display_version__ += '/' + ret.stdout.decode('ascii').strip()
except Exception:
pass
diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py
index 5dac63867..15d5fc46b 100644
--- a/sphinx/addnodes.py
+++ b/sphinx/addnodes.py
@@ -14,7 +14,7 @@ from typing import Any, Dict, List, Sequence
from docutils import nodes
from docutils.nodes import Node
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning
if False:
# For type annotation
@@ -199,59 +199,6 @@ class production(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a single grammar production rule."""
-# math nodes
-
-
-class math(nodes.math):
- """Node for inline equations.
-
- .. warning:: This node is provided to keep compatibility only.
- It will be removed in nearly future. Don't use this from your extension.
-
- .. deprecated:: 1.8
- Use ``docutils.nodes.math`` instead.
- """
-
- def __getitem__(self, key):
- """Special accessor for supporting ``node['latex']``."""
- if key == 'latex' and 'latex' not in self.attributes:
- warnings.warn("math node for Sphinx was replaced by docutils'. "
- "Therefore please use ``node.astext()`` to get an equation instead.",
- RemovedInSphinx30Warning, stacklevel=2)
- return self.astext()
- else:
- return super().__getitem__(key)
-
-
-class math_block(nodes.math_block):
- """Node for block level equations.
-
- .. warning:: This node is provided to keep compatibility only.
- It will be removed in nearly future. Don't use this from your extension.
-
- .. deprecated:: 1.8
- """
-
- def __getitem__(self, key):
- if key == 'latex' and 'latex' not in self.attributes:
- warnings.warn("displaymath node for Sphinx was replaced by docutils'. "
- "Therefore please use ``node.astext()`` to get an equation instead.",
- RemovedInSphinx30Warning, stacklevel=2)
- return self.astext()
- else:
- return super().__getitem__(key)
-
-
-class displaymath(math_block):
- """Node for block level equations.
-
- .. warning:: This node is provided to keep compatibility only.
- It will be removed in nearly future. Don't use this from your extension.
-
- .. deprecated:: 1.8
- """
-
-
# other directive-level nodes
class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
@@ -389,7 +336,6 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_node(seealso)
app.add_node(productionlist)
app.add_node(production)
- app.add_node(displaymath)
app.add_node(index)
app.add_node(centered)
app.add_node(acks)
diff --git a/sphinx/application.py b/sphinx/application.py
index 744e62a4e..fbc637e60 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -16,7 +16,6 @@ import platform
import sys
import warnings
from collections import deque
-from inspect import isclass
from io import StringIO
from os import path
from typing import Any, Callable, Dict, IO, List, Tuple, Union
@@ -30,9 +29,7 @@ from pygments.lexer import Lexer
import sphinx
from sphinx import package_dir, locale
from sphinx.config import Config
-from sphinx.deprecation import (
- RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
-)
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.domains import Domain, Index
from sphinx.environment import BuildEnvironment
from sphinx.environment.collectors import EnvironmentCollector
@@ -46,11 +43,10 @@ from sphinx.registry import SphinxComponentRegistry
from sphinx.roles import XRefRole
from sphinx.theming import Theme
from sphinx.util import docutils
-from sphinx.util import import_object, progress_message
from sphinx.util import logging
+from sphinx.util import progress_message
from sphinx.util.build_phase import BuildPhase
from sphinx.util.console import bold # type: ignore
-from sphinx.util.docutils import directive_helper
from sphinx.util.i18n import CatalogRepository
from sphinx.util.logging import prefixed_warnings
from sphinx.util.osutil import abspath, ensuredir, relpath
@@ -105,7 +101,6 @@ builtin_extensions = (
'sphinx.transforms.post_transforms',
'sphinx.transforms.post_transforms.code',
'sphinx.transforms.post_transforms.images',
- 'sphinx.transforms.post_transforms.compat',
'sphinx.util.compat',
'sphinx.versioning',
# collectors should be loaded by specific order
@@ -408,29 +403,26 @@ class Sphinx:
if version > sphinx.__display_version__[:3]:
raise VersionRequirementError(version)
- def import_object(self, objname: str, source: str = None) -> Any:
- """Import an object from a ``module.name`` string.
-
- .. deprecated:: 1.8
- Use ``sphinx.util.import_object()`` instead.
- """
- warnings.warn('app.import_object() is deprecated. '
- 'Use sphinx.util.add_object_type() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return import_object(objname, source=None)
-
# event interface
- def connect(self, event: str, callback: Callable) -> int:
+ def connect(self, event: str, callback: Callable, priority: int = 500) -> int:
"""Register *callback* to be called when *event* is emitted.
For details on available core events and the arguments of callback
functions, please see :ref:`events`.
+ Registered callbacks will be invoked on event in the order of *priority* and
+ registration. The priority is ascending order.
+
The method returns a "listener ID" that can be used as an argument to
:meth:`disconnect`.
+
+ .. versionchanged:: 3.0
+
+ Support *priority*
"""
- listener_id = self.events.connect(event, callback)
- logger.debug('[app] connecting event %r: %r [id=%s]', event, callback, listener_id)
+ listener_id = self.events.connect(event, callback, priority)
+ logger.debug('[app] connecting event %r (%d): %r [id=%s]',
+ event, priority, callback, listener_id)
return listener_id
def disconnect(self, listener_id: int) -> None:
@@ -592,36 +584,13 @@ class Sphinx:
self.registry.add_enumerable_node(node, figtype, title_getter, override=override)
self.add_node(node, override=override, **kwargs)
- @property
- def enumerable_nodes(self) -> Dict["Type[Node]", Tuple[str, TitleGetter]]:
- warnings.warn('app.enumerable_nodes() is deprecated. '
- 'Use app.get_domain("std").enumerable_nodes instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.registry.enumerable_nodes
-
- def add_directive(self, name: str, obj: Any, content: bool = None,
- arguments: Tuple[int, int, bool] = None, override: bool = False,
- **options: Any) -> None:
+ def add_directive(self, name: str, cls: "Type[Directive]", override: bool = False) -> None:
"""Register a Docutils directive.
- *name* must be the prospective directive name. There are two possible
- ways to write a directive:
-
- - In the docutils 0.4 style, *obj* is the directive function.
- *content*, *arguments* and *options* are set as attributes on the
- function and determine whether the directive has content, arguments
- and options, respectively. **This style is deprecated.**
-
- - In the docutils 0.5 style, *obj* is the directive class.
- It must already have attributes named *has_content*,
- *required_arguments*, *optional_arguments*,
- *final_argument_whitespace* and *option_spec* that correspond to the
- options for the function way. See `the Docutils docs
- <http://docutils.sourceforge.net/docs/howto/rst-directives.html>`_
- for details.
-
- The directive class must inherit from the class
- ``docutils.parsers.rst.Directive``.
+ *name* must be the prospective directive name. *cls* is a directive
+ class which inherits ``docutils.parsers.rst.Directive``. For more
+ details, see `the Docutils docs
+ <http://docutils.sourceforge.net/docs/howto/rst-directives.html>`_ .
For example, the (already existing) :rst:dir:`literalinclude` directive
would be added like this:
@@ -652,17 +621,12 @@ class Sphinx:
.. versionchanged:: 1.8
Add *override* keyword.
"""
- logger.debug('[app] adding directive: %r',
- (name, obj, content, arguments, options))
+ logger.debug('[app] adding directive: %r', (name, cls))
if not override and docutils.is_directive_registered(name):
logger.warning(__('directive %r is already registered, it will be overridden'),
name, type='app', subtype='add_directive')
- if not isclass(obj) or not issubclass(obj, Directive):
- directive = directive_helper(obj, content, arguments, **options)
- docutils.register_directive(name, directive)
- else:
- docutils.register_directive(name, obj)
+ docutils.register_directive(name, cls)
def add_role(self, name: str, role: Any, override: bool = False) -> None:
"""Register a Docutils role.
@@ -712,25 +676,8 @@ class Sphinx:
"""
self.registry.add_domain(domain, override=override)
- def override_domain(self, domain: "Type[Domain]") -> None:
- """Override a registered domain.
-
- Make the given *domain* class known to Sphinx, assuming that there is
- already a domain with its ``.name``. The new domain must be a subclass
- of the existing one.
-
- .. versionadded:: 1.0
- .. deprecated:: 1.8
- Integrated to :meth:`add_domain`.
- """
- warnings.warn('app.override_domain() is deprecated. '
- 'Use app.add_domain() with override option instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.registry.add_domain(domain, override=True)
-
- def add_directive_to_domain(self, domain: str, name: str, obj: Any,
- has_content: bool = None, argument_spec: Any = None,
- override: bool = False, **option_spec: Any) -> None:
+ def add_directive_to_domain(self, domain: str, name: str,
+ 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
@@ -740,9 +687,7 @@ class Sphinx:
.. versionchanged:: 1.8
Add *override* keyword.
"""
- self.registry.add_directive_to_domain(domain, name, obj,
- has_content, argument_spec, override=override,
- **option_spec)
+ self.registry.add_directive_to_domain(domain, name, cls, override=override)
def add_role_to_domain(self, domain: str, name: str, role: Union[RoleFunction, XRefRole],
override: bool = False) -> None:
@@ -924,8 +869,10 @@ class Sphinx:
Add *filename* to the list of JavaScript files that the default HTML
template will include. The filename must be relative to the HTML
- static path , or a full URI with scheme. The keyword arguments are
- also accepted for attributes of ``<script>`` tag.
+ static path , or a full URI with scheme. If the keyword argument
+ ``body`` is given, its value will be added between the
+ ``<script>`` tags. Extra keyword arguments are included as
+ attributes of the ``<script>`` tag.
Example::
@@ -935,6 +882,9 @@ class Sphinx:
app.add_js_file('example.js', async="async")
# => <script src="_static/example.js" async="async"></script>
+ app.add_js_file(None, body="var myVariable = 'foo';")
+ # => <script>var myVariable = 'foo';</script>
+
.. versionadded:: 0.5
.. versionchanged:: 1.8
@@ -1197,12 +1147,6 @@ class Sphinx:
return True
- @property
- def _setting_up_extension(self) -> List[str]:
- warnings.warn('app._setting_up_extension is deprecated.',
- RemovedInSphinx30Warning)
- return ['?']
-
class TemplateBridge:
"""
@@ -1239,12 +1183,3 @@ class TemplateBridge:
specified context (a Python dictionary).
"""
raise NotImplementedError('must be implemented in subclasses')
-
-
-from sphinx.config import CONFIG_FILENAME # NOQA
-
-deprecated_alias('sphinx.application',
- {
- 'CONFIG_FILENAME': CONFIG_FILENAME,
- },
- RemovedInSphinx30Warning)
diff --git a/sphinx/builders/html.py b/sphinx/builders/html/__init__.py
index b3d2f1da2..cf8cd56ce 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html/__init__.py
@@ -28,7 +28,7 @@ from sphinx import package_dir, __display_version__
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.config import Config
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+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
@@ -53,7 +53,7 @@ if False:
from typing import Type # for python3.5.1
-# HTML5 Writer is avialable or not
+# HTML5 Writer is available or not
if is_html5_writer_available():
from sphinx.writers.html5 import HTML5Translator
html5_ready = True
@@ -103,35 +103,6 @@ class Stylesheet(str):
return self
-class JSContainer(list):
- """The container for JavaScript scripts."""
- def insert(self, index: int, obj: str) -> None:
- warnings.warn('To modify script_files in the theme is deprecated. '
- 'Please insert a <script> tag directly in your theme instead.',
- RemovedInSphinx30Warning, stacklevel=3)
- super().insert(index, obj)
-
- def extend(self, other: List[str]) -> None: # type: ignore
- warnings.warn('To modify script_files in the theme is deprecated. '
- 'Please insert a <script> tag directly in your theme instead.',
- RemovedInSphinx30Warning, stacklevel=3)
- for item in other:
- self.append(item)
-
- def __iadd__(self, other: List[str]) -> "JSContainer": # type: ignore
- warnings.warn('To modify script_files in the theme is deprecated. '
- 'Please insert a <script> tag directly in your theme instead.',
- RemovedInSphinx30Warning, stacklevel=3)
- for item in other:
- self.append(item)
- return self
-
- def __add__(self, other: List[str]) -> "JSContainer":
- ret = JSContainer(self)
- ret += other
- return ret
-
-
class JavaScript(str):
"""A metadata of javascript file.
@@ -234,7 +205,7 @@ class StandaloneHTMLBuilder(Builder):
self.css_files = [] # type: List[Dict[str, str]]
# JS files
- self.script_files = JSContainer() # type: List[JavaScript]
+ self.script_files = [] # type: List[JavaScript]
def init(self) -> None:
self.build_info = self.create_build_info()
@@ -836,13 +807,17 @@ class StandaloneHTMLBuilder(Builder):
if self.config.html_scaled_image_link and self.html_scaled_image_link:
for node in doctree.traverse(nodes.image):
- scale_keys = ('scale', 'width', 'height')
- if not any((key in node) for key in scale_keys) or \
- isinstance(node.parent, nodes.reference):
- # docutils does unfortunately not preserve the
- # ``target`` attribute on images, so we need to check
- # the parent node here.
+ if not any((key in node) for key in ['scale', 'width', 'height']):
+ # resizing options are not given. scaled image link is available
+ # only for resized images.
+ continue
+ elif isinstance(node.parent, nodes.reference):
+ # A image having hyperlink target
continue
+ elif 'no-scaled-link' in node['classes']:
+ # scaled image link is disabled for this node
+ continue
+
uri = node['uri']
reference = nodes.reference('', '', internal=True)
if uri in self.images:
@@ -876,7 +851,11 @@ class StandaloneHTMLBuilder(Builder):
if self.indexer is not None and title:
filename = self.env.doc2path(pagename, base=None)
try:
- self.indexer.feed(pagename, filename, title, doctree)
+ 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
@@ -1000,15 +979,6 @@ class StandaloneHTMLBuilder(Builder):
return False
ctx['hasdoc'] = hasdoc
- def warn(*args: Any, **kwargs: Any) -> str:
- """Simple warn() wrapper for themes."""
- warnings.warn('The template function warn() was deprecated. '
- 'Use warning() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- logger.warning(*args, **kwargs)
- return '' # return empty string
- ctx['warn'] = warn
-
ctx['toctree'] = lambda **kwargs: self._get_local_toctree(pagename, **kwargs)
self.add_sidebars(pagename, ctx)
ctx.update(addctx)
diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py
index 6712ffc25..96e6d13f4 100644
--- a/sphinx/builders/latex/__init__.py
+++ b/sphinx/builders/latex/__init__.py
@@ -408,31 +408,31 @@ def patch_settings(settings: Any) -> Any:
class Values(type(settings)): # type: ignore
@property
- def author(self):
+ def author(self) -> str:
warnings.warn('settings.author is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
return self._author
@property
- def title(self):
+ def title(self) -> str:
warnings.warn('settings.title is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
return self._title
@property
- def contentsname(self):
+ def contentsname(self) -> str:
warnings.warn('settings.contentsname is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
return self._contentsname
@property
- def docname(self):
+ def docname(self) -> str:
warnings.warn('settings.docname is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
return self._docname
@property
- def docclass(self):
+ def docclass(self) -> str:
warnings.warn('settings.docclass is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
return self._docclass
diff --git a/sphinx/builders/latex/constants.py b/sphinx/builders/latex/constants.py
index 39fbe0195..70fd0b1d1 100644
--- a/sphinx/builders/latex/constants.py
+++ b/sphinx/builders/latex/constants.py
@@ -184,6 +184,8 @@ ADDITIONAL_SETTINGS = {
'babel': '\\usepackage{babel}',
},
('xelatex', 'zh'): {
+ 'polyglossia': '',
+ 'babel': '\\usepackage{babel}',
'fontenc': '\\usepackage{xeCJK}',
},
('xelatex', 'el'): {
diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py
index 3ef0ff5d9..28841ad77 100644
--- a/sphinx/builders/latex/transforms.py
+++ b/sphinx/builders/latex/transforms.py
@@ -591,7 +591,7 @@ class IndexInSectionTitleTransform(SphinxTransform):
"""
default_priority = 400
- def apply(self):
+ def apply(self, **kwargs: Any) -> None:
for node in self.document.traverse(nodes.title):
if isinstance(node.parent, nodes.section):
for i, index in enumerate(node.traverse(addnodes.index)):
diff --git a/sphinx/builders/latex/util.py b/sphinx/builders/latex/util.py
index 8155d1fd7..b7d79121c 100644
--- a/sphinx/builders/latex/util.py
+++ b/sphinx/builders/latex/util.py
@@ -8,12 +8,8 @@
:license: BSD, see LICENSE for details.
"""
-import warnings
-
from docutils.writers.latex2e import Babel
-from sphinx.deprecation import RemovedInSphinx30Warning
-
class ExtBabel(Babel):
cyrillic_languages = ('bulgarian', 'kazakh', 'mongolian', 'russian', 'ukrainian')
@@ -24,12 +20,6 @@ class ExtBabel(Babel):
self.supported = True
super().__init__(language_code or '')
- def get_shorthandoff(self) -> str:
- warnings.warn('ExtBabel.get_shorthandoff() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- from sphinx.writers.latex import SHORTHANDOFF
- return SHORTHANDOFF
-
def uses_cyrillic(self) -> bool:
return self.language in self.cyrillic_languages
diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py
index 1b0dd4011..9fe689ec9 100644
--- a/sphinx/builders/linkcheck.py
+++ b/sphinx/builders/linkcheck.py
@@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details.
"""
+import json
import queue
import re
import socket
@@ -90,6 +91,8 @@ class CheckExternalLinksBuilder(Builder):
socket.setdefaulttimeout(5.0)
# create output file
open(path.join(self.outdir, 'output.txt'), 'w').close()
+ # create JSON output file
+ open(path.join(self.outdir, 'output.json'), 'w').close()
# create queues and worker threads
self.wqueue = queue.Queue() # type: queue.Queue
@@ -225,9 +228,16 @@ class CheckExternalLinksBuilder(Builder):
def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None:
uri, docname, lineno, status, info, code = result
+
+ filename = self.env.doc2path(docname, None)
+ linkstat = dict(filename=filename, lineno=lineno,
+ status=status, code=code, uri=uri,
+ info=info)
if status == 'unchecked':
+ self.write_linkstat(linkstat)
return
if status == 'working' and info == 'old':
+ self.write_linkstat(linkstat)
return
if lineno:
logger.info('(line %4d) ', lineno, nonl=True)
@@ -236,18 +246,22 @@ class CheckExternalLinksBuilder(Builder):
logger.info(darkgray('-ignored- ') + uri + ': ' + info)
else:
logger.info(darkgray('-ignored- ') + uri)
+ self.write_linkstat(linkstat)
elif status == 'local':
logger.info(darkgray('-local- ') + uri)
- self.write_entry('local', docname, lineno, uri)
+ self.write_entry('local', docname, filename, lineno, uri)
+ self.write_linkstat(linkstat)
elif status == 'working':
logger.info(darkgreen('ok ') + uri + info)
+ self.write_linkstat(linkstat)
elif status == 'broken':
- self.write_entry('broken', docname, lineno, uri + ': ' + info)
if self.app.quiet or self.app.warningiserror:
logger.warning(__('broken link: %s (%s)'), uri, info,
- location=(self.env.doc2path(docname), lineno))
+ location=(filename, lineno))
else:
logger.info(red('broken ') + uri + red(' - ' + info))
+ self.write_entry('broken', docname, filename, lineno, uri + ': ' + info)
+ self.write_linkstat(linkstat)
elif status == 'redirected':
try:
text, color = {
@@ -259,9 +273,11 @@ class CheckExternalLinksBuilder(Builder):
}[code]
except KeyError:
text, color = ('with unknown code', purple)
- self.write_entry('redirected ' + text, docname, lineno,
- uri + ' to ' + info)
+ linkstat['text'] = text
logger.info(color('redirect ') + uri + color(' - ' + text + ' to ' + info))
+ self.write_entry('redirected ' + text, docname, filename,
+ lineno, uri + ' to ' + info)
+ self.write_linkstat(linkstat)
def get_target_uri(self, docname: str, typ: str = None) -> str:
return ''
@@ -301,10 +317,15 @@ class CheckExternalLinksBuilder(Builder):
if self.broken:
self.app.statuscode = 1
- def write_entry(self, what: str, docname: str, line: int, uri: str) -> None:
- with open(path.join(self.outdir, 'output.txt'), 'a', encoding='utf-8') as output:
- output.write("%s:%s: [%s] %s\n" % (self.env.doc2path(docname, None),
- line, what, uri))
+ def write_entry(self, what: str, docname: str, filename: str, line: int,
+ uri: str) -> None:
+ with open(path.join(self.outdir, 'output.txt'), 'a') as output:
+ output.write("%s:%s: [%s] %s\n" % (filename, line, what, uri))
+
+ def write_linkstat(self, data: dict) -> None:
+ with open(path.join(self.outdir, 'output.json'), 'a') as output:
+ output.write(json.dumps(data))
+ output.write('\n')
def finish(self) -> None:
for worker in self.workers:
diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py
index eb7e9eb02..8f8ae58a1 100644
--- a/sphinx/cmd/quickstart.py
+++ b/sphinx/cmd/quickstart.py
@@ -54,10 +54,8 @@ EXTENSIONS = OrderedDict([
('imgmath', __('include math, rendered as PNG or SVG images')),
('mathjax', __('include math, rendered in the browser by MathJax')),
('ifconfig', __('conditional inclusion of content based on config values')),
- ('viewcode',
- __('include links to the source code of documented Python objects')),
- ('githubpages',
- __('create .nojekyll file to publish the document on GitHub pages')),
+ ('viewcode', __('include links to the source code of documented Python objects')),
+ ('githubpages', __('create .nojekyll file to publish the document on GitHub pages')),
])
DEFAULTS = {
@@ -129,8 +127,7 @@ def boolean(x: str) -> bool:
def suffix(x: str) -> str:
if not (x[0:1] == '.' and len(x) > 1):
- raise ValidationError(__("Please enter a file suffix, "
- "e.g. '.rst' or '.txt'."))
+ raise ValidationError(__("Please enter a file suffix, e.g. '.rst' or '.txt'."))
return x
@@ -228,16 +225,16 @@ def ask_user(d: Dict) -> None:
"""
print(bold(__('Welcome to the Sphinx %s quickstart utility.')) % __display_version__)
- print(__('''
-Please enter values for the following settings (just press Enter to
-accept a default value, if one is given in brackets).'''))
+ print()
+ print(__('Please enter values for the following settings (just press Enter to\n'
+ 'accept a default value, if one is given in brackets).'))
if 'path' in d:
- print(bold(__('''
-Selected root path: %s''') % d['path']))
+ print()
+ print(bold(__('Selected root path: %s')) % d['path'])
else:
- print(__('''
-Enter the root path for documentation.'''))
+ print()
+ print(__('Enter the root path for documentation.'))
d['path'] = do_prompt(__('Root path for the documentation'), '.', is_path)
while path.isfile(path.join(d['path'], 'conf.py')) or \
@@ -247,70 +244,68 @@ Enter the root path for documentation.'''))
'selected root path.')))
print(__('sphinx-quickstart will not overwrite existing Sphinx projects.'))
print()
- d['path'] = do_prompt(__('Please enter a new root path (or just Enter '
- 'to exit)'), '', is_path)
+ d['path'] = do_prompt(__('Please enter a new root path (or just Enter to exit)'),
+ '', is_path)
if not d['path']:
sys.exit(1)
if 'sep' not in d:
- print(__('''
-You have two options for placing the build directory for Sphinx output.
-Either, you use a directory "_build" within the root path, or you separate
-"source" and "build" directories within the root path.'''))
- d['sep'] = do_prompt(__('Separate source and build directories (y/n)'),
- 'n', boolean)
+ print()
+ print(__('You have two options for placing the build directory for Sphinx output.\n'
+ 'Either, you use a directory "_build" within the root path, or you separate\n'
+ '"source" and "build" directories within the root path.'))
+ d['sep'] = do_prompt(__('Separate source and build directories (y/n)'), 'n', boolean)
if 'dot' not in d:
- print(__('''
-Inside the root directory, two more directories will be created; "_templates"
-for custom HTML templates and "_static" for custom stylesheets and other static
-files. You can enter another prefix (such as ".") to replace the underscore.'''))
+ print()
+ print(__('Inside the root directory, two more directories will be created; "_templates"\n' # NOQA
+ 'for custom HTML templates and "_static" for custom stylesheets and other static\n' # NOQA
+ 'files. You can enter another prefix (such as ".") to replace the underscore.')) # NOQA
d['dot'] = do_prompt(__('Name prefix for templates and static dir'), '_', ok)
if 'project' not in d:
- print(__('''
-The project name will occur in several places in the built documentation.'''))
+ print()
+ print(__('The project name will occur in several places in the built documentation.'))
d['project'] = do_prompt(__('Project name'))
if 'author' not in d:
d['author'] = do_prompt(__('Author name(s)'))
if 'version' not in d:
- print(__('''
-Sphinx has the notion of a "version" and a "release" for the
-software. Each version can have multiple releases. For example, for
-Python the version is something like 2.5 or 3.0, while the release is
-something like 2.5.1 or 3.0a1. If you don't need this dual structure,
-just set both to the same value.'''))
+ print()
+ print(__('Sphinx has the notion of a "version" and a "release" for the\n'
+ 'software. Each version can have multiple releases. For example, for\n'
+ 'Python the version is something like 2.5 or 3.0, while the release is\n'
+ 'something like 2.5.1 or 3.0a1. If you don\'t need this dual structure,\n'
+ 'just set both to the same value.'))
d['version'] = do_prompt(__('Project version'), '', allow_empty)
if 'release' not in d:
d['release'] = do_prompt(__('Project release'), d['version'], allow_empty)
if 'language' not in d:
- print(__('''
-If the documents are to be written in a language other than English,
-you can select a language here by its language code. Sphinx will then
-translate text that it generates into that language.
-
-For a list of supported codes, see
-https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language.'''))
+ print()
+ print(__('If the documents are to be written in a language other than English,\n'
+ 'you can select a language here by its language code. Sphinx will then\n'
+ 'translate text that it generates into that language.\n'
+ '\n'
+ 'For a list of supported codes, see\n'
+ 'https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language.')) # NOQA
d['language'] = do_prompt(__('Project language'), 'en')
if d['language'] == 'en':
d['language'] = None
if 'suffix' not in d:
- print(__('''
-The file name suffix for source files. Commonly, this is either ".txt"
-or ".rst". Only files with this suffix are considered documents.'''))
+ print()
+ print(__('The file name suffix for source files. Commonly, this is either ".txt"\n'
+ 'or ".rst". Only files with this suffix are considered documents.'))
d['suffix'] = do_prompt(__('Source file suffix'), '.rst', suffix)
if 'master' not in d:
- print(__('''
-One document is special in that it is considered the top node of the
-"contents tree", that is, it is the root of the hierarchical structure
-of the documents. Normally, this is "index", but if your "index"
-document is a custom template, you can also set this to another filename.'''))
- d['master'] = do_prompt(__('Name of your master document (without suffix)'),
- 'index')
+ print()
+ print(__('One document is special in that it is considered the top node of the\n'
+ '"contents tree", that is, it is the root of the hierarchical structure\n'
+ 'of the documents. Normally, this is "index", but if your "index"\n'
+ 'document is a custom template, you can also set this to another filename.'))
+ d['master'] = do_prompt(__('Name of your master document (without suffix)'), 'index')
while path.isfile(path.join(d['path'], d['master'] + d['suffix'])) or \
path.isfile(path.join(d['path'], 'source', d['master'] + d['suffix'])):
@@ -323,8 +318,7 @@ document is a custom template, you can also set this to another filename.'''))
'existing file and press Enter'), d['master'])
if 'extensions' not in d:
- print(__('Indicate which of the following Sphinx extensions should be '
- 'enabled:'))
+ print(__('Indicate which of the following Sphinx extensions should be enabled:'))
d['extensions'] = []
for name, description in EXTENSIONS.items():
if do_prompt('%s: %s (y/n)' % (name, description), 'n', boolean):
@@ -332,20 +326,19 @@ document is a custom template, you can also set this to another filename.'''))
# Handle conflicting options
if {'sphinx.ext.imgmath', 'sphinx.ext.mathjax'}.issubset(d['extensions']):
- print(__('Note: imgmath and mathjax cannot be enabled at the same '
- 'time. imgmath has been deselected.'))
+ print(__('Note: imgmath and mathjax cannot be enabled at the same time. '
+ 'imgmath has been deselected.'))
d['extensions'].remove('sphinx.ext.imgmath')
if 'makefile' not in d:
- print(__('''
-A Makefile and a Windows command file can be generated for you so that you
-only have to run e.g. `make html' instead of invoking sphinx-build
-directly.'''))
+ print()
+ print(__('A Makefile and a Windows command file can be generated for you so that you\n'
+ 'only have to run e.g. `make html\' instead of invoking sphinx-build\n'
+ 'directly.'))
d['makefile'] = do_prompt(__('Create Makefile? (y/n)'), 'y', boolean)
if 'batchfile' not in d:
- d['batchfile'] = do_prompt(__('Create Windows command file? (y/n)'),
- 'y', boolean)
+ d['batchfile'] = do_prompt(__('Create Windows command file? (y/n)'), 'y', boolean)
print()
@@ -428,17 +421,18 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir:
return
print()
print(bold(__('Finished: An initial directory structure has been created.')))
- print(__('''
-You should now populate your master file %s and create other documentation
-source files. ''') % masterfile + ((d['makefile'] or d['batchfile']) and __('''\
-Use the Makefile to build the docs, like so:
- make builder
-''') or __('''\
-Use the sphinx-build command to build the docs, like so:
- sphinx-build -b builder %s %s
-''') % (srcdir, builddir)) + __('''\
-where "builder" is one of the supported builders, e.g. html, latex or linkcheck.
-'''))
+ print()
+ print(__('You should now populate your master file %s and create other documentation\n'
+ 'source files. ') % masterfile, end='')
+ if d['makefile'] or d['batchfile']:
+ print(__('Use the Makefile to build the docs, like so:\n'
+ ' make builder'))
+ else:
+ print(__('Use the sphinx-build command to build the docs, like so:\n'
+ ' sphinx-build -b builder %s %s') % (srcdir, builddir))
+ print(__('where "builder" is one of the supported builders, '
+ 'e.g. html, latex or linkcheck.'))
+ print()
def valid_dir(d: Dict) -> bool:
@@ -471,16 +465,18 @@ def valid_dir(d: Dict) -> bool:
def get_parser() -> argparse.ArgumentParser:
+ description = __(
+ "\n"
+ "Generate required files for a Sphinx project.\n"
+ "\n"
+ "sphinx-quickstart is an interactive tool that asks some questions about your\n"
+ "project and then generates a complete documentation directory and sample\n"
+ "Makefile to be used with sphinx-build.\n"
+ )
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] <PROJECT_DIR>',
epilog=__("For more information, visit <http://sphinx-doc.org/>."),
- description=__("""
-Generate required files for a Sphinx project.
-
-sphinx-quickstart is an interactive tool that asks some questions about your
-project and then generates a complete documentation directory and sample
-Makefile to be used with sphinx-build.
-"""))
+ description=description)
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet',
default=None,
@@ -579,8 +575,8 @@ def main(argv: List[str] = sys.argv[1:]) -> int:
try:
if 'quiet' in d:
if not {'project', 'author'}.issubset(d):
- print(__('''"quiet" is specified, but any of "project" or \
-"author" is not specified.'''))
+ print(__('"quiet" is specified, but any of "project" or '
+ '"author" is not specified.'))
return 1
if {'quiet', 'project', 'author'}.issubset(d):
diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py
deleted file mode 100644
index f938f8234..000000000
--- a/sphinx/cmdline.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
- sphinx.cmdline
- ~~~~~~~~~~~~~~
-
- sphinx-build command-line handling.
-
- :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-"""
-
-import argparse
-import sys
-import warnings
-from typing import Any, IO, List, Union
-
-from sphinx.application import Sphinx
-from sphinx.cmd import build
-from sphinx.deprecation import RemovedInSphinx30Warning
-
-
-def handle_exception(app: Sphinx, args: Any, exception: Union[Exception, KeyboardInterrupt],
- stderr: IO = sys.stderr) -> None:
- warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- build.handle_exception(app, args, exception, stderr)
-
-
-def jobs_argument(value: str) -> int:
- warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return build.jobs_argument(value)
-
-
-def get_parser() -> argparse.ArgumentParser:
- warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return build.get_parser()
-
-
-def main(argv: List[str] = sys.argv[1:]) -> int:
- warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return build.main(argv)
diff --git a/sphinx/config.py b/sphinx/config.py
index bba994389..87007c33d 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -18,7 +18,7 @@ from typing import (
Any, Callable, Dict, Generator, Iterator, List, NamedTuple, Set, Tuple, Union
)
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.errors import ConfigError, ExtensionError
from sphinx.locale import _, __
from sphinx.util import logging
@@ -154,26 +154,7 @@ class Config:
'env', []),
} # type: Dict[str, Tuple]
- def __init__(self, *args: Any) -> None:
- if len(args) == 4:
- # old style arguments: (dirname, filename, overrides, tags)
- warnings.warn('The argument of Config() class has been changed. '
- 'Use Config.read() to read configuration from conf.py.',
- RemovedInSphinx30Warning, stacklevel=2)
- dirname, filename, overrides, tags = args
- if dirname is None:
- config = {} # type: Dict[str, Any]
- else:
- config = eval_config_file(path.join(dirname, filename), tags)
- else:
- # new style arguments: (config={}, overrides={})
- if len(args) == 0:
- config, overrides = {}, {}
- elif len(args) == 1:
- config, overrides = args[0], {}
- else:
- config, overrides = args[:2]
-
+ 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
@@ -193,16 +174,6 @@ class Config:
namespace = eval_config_file(filename, tags)
return cls(namespace, overrides or {})
- def check_types(self) -> None:
- warnings.warn('Config.check_types() is deprecated. Use check_confval_types() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- check_confval_types(None, self)
-
- def check_unicode(self) -> None:
- warnings.warn('Config.check_unicode() is deprecated. Use check_unicode() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- check_unicode(self)
-
def convert_overrides(self, name: str, value: Any) -> Any:
if not isinstance(value, str):
return value
@@ -353,6 +324,9 @@ def eval_config_file(filename: str, tags: Tags) -> Dict[str, Any]:
msg = __("The configuration file (or one of the modules it imports) "
"called sys.exit()")
raise ConfigError(msg)
+ except ConfigError:
+ # pass through ConfigError from conf.py as is. It will be shown in console.
+ raise
except Exception:
msg = __("There is a programmable error in your configuration file:\n\n%s")
raise ConfigError(msg % traceback.format_exc())
diff --git a/sphinx/deprecation.py b/sphinx/deprecation.py
index dec5efa85..5e5e673d2 100644
--- a/sphinx/deprecation.py
+++ b/sphinx/deprecation.py
@@ -15,15 +15,15 @@ from typing import Any, Dict
from typing import Type # for python3.5.1
-class RemovedInSphinx30Warning(DeprecationWarning):
+class RemovedInSphinx40Warning(DeprecationWarning):
pass
-class RemovedInSphinx40Warning(PendingDeprecationWarning):
+class RemovedInSphinx50Warning(PendingDeprecationWarning):
pass
-RemovedInNextVersionWarning = RemovedInSphinx30Warning
+RemovedInNextVersionWarning = RemovedInSphinx40Warning
def deprecated_alias(modname: str, objects: Dict, warning: Type[Warning]) -> None:
diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py
index 9a2fb4412..0d9490f31 100644
--- a/sphinx/directives/__init__.py
+++ b/sphinx/directives/__init__.py
@@ -18,7 +18,9 @@ from docutils.parsers.rst import directives, roles
from sphinx import addnodes
from sphinx.addnodes import desc_signature
-from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
+from sphinx.deprecation import (
+ RemovedInSphinx40Warning, RemovedInSphinx50Warning, deprecated_alias
+)
from sphinx.util import docutils
from sphinx.util.docfields import DocFieldTransformer, Field, TypedField
from sphinx.util.docutils import SphinxDirective
@@ -34,7 +36,7 @@ nl_escape_re = re.compile(r'\\\n')
strip_backslash_re = re.compile(r'\\(.)')
-def optional_int(argument):
+def optional_int(argument: str) -> int:
"""
Check for an integer argument or None value; raise ``ValueError`` if not.
"""
@@ -160,6 +162,8 @@ class ObjectDescription(SphinxDirective):
# 'desctype' is a backwards compatible attribute
node['objtype'] = node['desctype'] = self.objtype
node['noindex'] = noindex = ('noindex' in self.options)
+ if self.domain:
+ node['classes'].append(self.domain)
self.names = [] # type: List[Any]
signatures = self.get_signatures()
@@ -167,7 +171,7 @@ class ObjectDescription(SphinxDirective):
# add a signature node for each signature in the current unit
# and add a reference target for it
signode = addnodes.desc_signature(sig, '')
- signode['first'] = False
+ self.set_source_info(signode)
node.append(signode)
try:
# name can also be a tuple, e.g. (classname, objname);
@@ -285,9 +289,11 @@ deprecated_alias('sphinx.directives',
},
RemovedInSphinx40Warning)
-
-# backwards compatible old name (will be marked deprecated in 3.0)
-DescDirective = ObjectDescription
+deprecated_alias('sphinx.directives',
+ {
+ 'DescDirective': ObjectDescription,
+ },
+ RemovedInSphinx50Warning)
def setup(app: "Sphinx") -> Dict[str, Any]:
diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py
index 87d769b41..e4fcc0f5c 100644
--- a/sphinx/directives/other.py
+++ b/sphinx/directives/other.py
@@ -85,14 +85,14 @@ class TocTree(SphinxDirective):
ret.append(wrappernode)
return ret
- def parse_content(self, toctree):
+ def parse_content(self, toctree: addnodes.toctree) -> List[Node]:
suffixes = self.config.source_suffix
# glob target documents
all_docnames = self.env.found_docs.copy()
all_docnames.remove(self.env.docname) # remove current document
- ret = []
+ ret = [] # type: List[Node]
excluded = Matcher(self.config.exclude_patterns)
for entry in self.content:
if not entry:
diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py
index b521cd025..11b3a4604 100644
--- a/sphinx/domains/__init__.py
+++ b/sphinx/domains/__init__.py
@@ -11,6 +11,7 @@
import copy
from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, Union
+from typing import cast
from docutils import nodes
from docutils.nodes import Element, Node, system_message
@@ -70,6 +71,9 @@ class Index:
a domain, subclass Index, overriding the three name attributes:
* `name` is an identifier used for generating file names.
+ It is also used for a hyperlink target for the index. Therefore, users can
+ refer the index page using ``ref`` role and a string which is combined
+ domain name and ``name`` attribute (ex. ``:ref:`py-modindex```).
* `localname` is the section title for the index.
* `shortname` is a short name for the index, for use in the relation bar in
HTML output. Can be empty to disable entries in the relation bar.
@@ -77,6 +81,11 @@ class Index:
and providing a :meth:`generate()` method. Then, add the index class to
your domain's `indices` list. Extensions can add indices to existing
domains using :meth:`~sphinx.application.Sphinx.add_index_to_domain()`.
+
+ .. versionchanged:: 3.0
+
+ Index pages can be referred by domain name and index name via
+ :rst:role:`ref` role.
"""
name = None # type: str
@@ -219,6 +228,17 @@ class Domain:
self.objtypes_for_role = self._role2type.get # type: Callable[[str], List[str]]
self.role_for_objtype = self._type2role.get # type: Callable[[str], str]
+ def setup(self) -> None:
+ """Set up domain object."""
+ from sphinx.domains.std import StandardDomain
+
+ # Add special hyperlink target for index pages (ex. py-modindex)
+ std = cast(StandardDomain, self.env.get_domain('std'))
+ for index in self.indices:
+ if index.name and index.localname:
+ docname = "%s-%s" % (self.name, index.name)
+ std.note_hyperlink_target(docname, docname, '', index.localname)
+
def add_object_type(self, name: str, objtype: ObjType) -> None:
"""Add an object type."""
self.object_types[name] = objtype
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index cc24df47e..539a52e59 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -203,7 +203,6 @@ class CObject(ObjectDescription):
if targetname not in self.state.document.ids:
signode['names'].append(targetname)
signode['ids'].append(targetname)
- signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
domain = cast(CDomain, self.env.get_domain('c'))
diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py
index f3f1d8aef..a07944db8 100644
--- a/sphinx/domains/changeset.py
+++ b/sphinx/domains/changeset.py
@@ -16,8 +16,6 @@ from docutils import nodes
from docutils.nodes import Node
from sphinx import addnodes
-from sphinx import locale
-from sphinx.deprecation import DeprecatedDict, RemovedInSphinx30Warning
from sphinx.domains import Domain
from sphinx.locale import _
from sphinx.util.docutils import SphinxDirective
@@ -41,13 +39,6 @@ versionlabel_classes = {
'deprecated': 'deprecated',
}
-locale.versionlabels = DeprecatedDict(
- versionlabels,
- 'sphinx.locale.versionlabels is deprecated. '
- 'Please use sphinx.domains.changeset.versionlabels instead.',
- RemovedInSphinx30Warning
-)
-
# TODO: move to typing.NamedTuple after dropping py35 support (see #5958)
ChangeSet = namedtuple('ChangeSet',
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 53c958601..3a23ff15a 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -367,6 +367,8 @@ _keywords = [
_max_id = 4
_id_prefix = [None, '', '_CPPv2', '_CPPv3', '_CPPv4']
+# Ids are used in lookup keys which are used across pickled files,
+# so when _max_id changes, make sure to update the ENV_VERSION.
# ------------------------------------------------------------------------------
# Id v1 constants
@@ -1790,7 +1792,8 @@ class ASTTemplateIntroduction(ASTBase):
class ASTTemplateDeclarationPrefix(ASTBase):
- def __init__(self, templates: List[Any]) -> None:
+ def __init__(self, templates: List[Union[ASTTemplateParams, ASTTemplateIntroduction]])\
+ -> None:
# templates is None means it's an explicit instantiation of a variable
self.templates = templates
@@ -3547,10 +3550,25 @@ class SymbolLookupResult:
self.templateArgs = templateArgs
+class LookupKey:
+ def __init__(self, data: List[Tuple[ASTNestedNameElement,
+ Union[ASTTemplateParams,
+ ASTTemplateIntroduction],
+ str]]) -> None:
+ self.data = data
+
+
class Symbol:
+ debug_indent = 0
+ debug_indent_string = " "
debug_lookup = False
debug_show_tree = False
+ @staticmethod
+ def debug_print(*args):
+ print(Symbol.debug_indent_string * Symbol.debug_indent, end="")
+ print(*args)
+
def _assert_invariants(self) -> None:
if not self.parent:
# parent == None means global scope, so declaration means a parent
@@ -3570,9 +3588,12 @@ class Symbol:
return super().__setattr__(key, value)
def __init__(self, parent: "Symbol", identOrOp: Union[ASTIdentifier, ASTOperator],
- templateParams: Any, templateArgs: Any, declaration: ASTDeclaration,
- docname: str) -> None:
+ templateParams: Union[ASTTemplateParams, ASTTemplateIntroduction],
+ templateArgs: Any, declaration: ASTDeclaration, docname: str) -> None:
self.parent = parent
+ # declarations in a single directive are linked together
+ self.siblingAbove = None # type: Symbol
+ self.siblingBelow = None # type: Symbol
self.identOrOp = identOrOp
self.templateParams = templateParams # template<templateParams>
self.templateArgs = templateArgs # identifier<templateArgs>
@@ -3607,6 +3628,9 @@ class Symbol:
self._add_template_and_function_params()
def _add_template_and_function_params(self):
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_add_template_and_function_params:")
# Note: we may be called from _fill_empty, so the symbols we want
# to add may actually already be present (as empty symbols).
@@ -3636,6 +3660,8 @@ class Symbol:
assert not nn.rooted
assert len(nn.names) == 1
self._add_symbols(nn, [], decl, self.docname)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
def remove(self):
if self.parent is None:
@@ -3645,12 +3671,18 @@ class Symbol:
self.parent = None
def clear_doc(self, docname: str) -> None:
- newChildren = []
+ newChildren = [] # type: List[Symbol]
for sChild in self._children:
sChild.clear_doc(docname)
if sChild.declaration and sChild.docname == docname:
sChild.declaration = None
sChild.docname = None
+ if sChild.siblingAbove is not None:
+ sChild.siblingAbove.siblingBelow = sChild.siblingBelow
+ if sChild.siblingBelow is not None:
+ sChild.siblingBelow.siblingAbove = sChild.siblingAbove
+ sChild.siblingAbove = None
+ sChild.siblingBelow = None
newChildren.append(sChild)
self._children = newChildren
@@ -3669,7 +3701,11 @@ class Symbol:
yield from c.children_recurse_anon
- def get_lookup_key(self) -> List[Tuple[ASTNestedNameElement, Any]]:
+ def get_lookup_key(self) -> "LookupKey":
+ # The pickle files for the environment and for each document are distinct.
+ # The environment has all the symbols, but the documents has xrefs that
+ # must know their scope. A lookup key is essentially a specification of
+ # how to find a specific symbol.
symbols = []
s = self
while s.parent:
@@ -3679,14 +3715,23 @@ class Symbol:
key = []
for s in symbols:
nne = ASTNestedNameElement(s.identOrOp, s.templateArgs)
- key.append((nne, s.templateParams))
- return key
+ if s.declaration is not None:
+ key.append((nne, s.templateParams, s.declaration.get_newest_id()))
+ else:
+ key.append((nne, s.templateParams, None))
+ return LookupKey(key)
def get_full_nested_name(self) -> ASTNestedName:
+ symbols = []
+ s = self
+ while s.parent:
+ symbols.append(s)
+ s = s.parent
+ symbols.reverse()
names = []
templates = []
- for nne, templateParams in self.get_lookup_key():
- names.append(nne)
+ for s in symbols:
+ names.append(ASTNestedNameElement(s.identOrOp, s.templateArgs))
templates.append(False)
return ASTNestedName(names, templates, rooted=False)
@@ -3695,9 +3740,12 @@ class Symbol:
templateShorthand: bool, matchSelf: bool,
recurseInAnon: bool, correctPrimaryTemplateArgs: bool
) -> "Symbol":
+ if Symbol.debug_lookup:
+ Symbol.debug_print("_find_first_named_symbol ->")
res = self._find_named_symbols(identOrOp, templateParams, templateArgs,
templateShorthand, matchSelf, recurseInAnon,
- correctPrimaryTemplateArgs)
+ correctPrimaryTemplateArgs,
+ searchInSiblings=False)
try:
return next(res)
except StopIteration:
@@ -3706,8 +3754,22 @@ class Symbol:
def _find_named_symbols(self, identOrOp: Union[ASTIdentifier, ASTOperator],
templateParams: Any, templateArgs: ASTTemplateArgs,
templateShorthand: bool, matchSelf: bool,
- recurseInAnon: bool, correctPrimaryTemplateArgs: bool
- ) -> Iterator["Symbol"]:
+ recurseInAnon: bool, correctPrimaryTemplateArgs: bool,
+ searchInSiblings: bool) -> Iterator["Symbol"]:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_find_named_symbols:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("self:")
+ print(self.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_print("identOrOp: ", identOrOp)
+ Symbol.debug_print("templateParams: ", templateParams)
+ Symbol.debug_print("templateArgs: ", templateArgs)
+ Symbol.debug_print("templateShorthand: ", templateShorthand)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("correctPrimaryTemplateAargs:", correctPrimaryTemplateArgs)
+ Symbol.debug_print("searchInSiblings: ", searchInSiblings)
def isSpecialization():
# the names of the template parameters must be given exactly as args
@@ -3759,20 +3821,64 @@ class Symbol:
if str(s.templateArgs) != str(templateArgs):
return False
return True
- if matchSelf and matches(self):
- yield self
- children = self.children_recurse_anon if recurseInAnon else self._children
- for s in children:
+
+ def candidates():
+ s = self
+ if Symbol.debug_lookup:
+ Symbol.debug_print("searching in self:")
+ print(s.to_string(Symbol.debug_indent + 1), end="")
+ while True:
+ if matchSelf:
+ yield s
+ if recurseInAnon:
+ yield from s.children_recurse_anon
+ else:
+ yield from s._children
+
+ if s.siblingAbove is None:
+ break
+ s = s.siblingAbove
+ if Symbol.debug_lookup:
+ Symbol.debug_print("searching in sibling:")
+ print(s.to_string(Symbol.debug_indent + 1), end="")
+
+ for s in candidates():
+ if Symbol.debug_lookup:
+ Symbol.debug_print("candidate:")
+ print(s.to_string(Symbol.debug_indent + 1), end="")
if matches(s):
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("matches")
+ Symbol.debug_indent -= 3
yield s
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 2
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
def _symbol_lookup(self, nestedName: ASTNestedName, templateDecls: List[Any],
onMissingQualifiedSymbol: Callable[["Symbol", Union[ASTIdentifier, ASTOperator], Any, ASTTemplateArgs], "Symbol"], # NOQA
strictTemplateParamArgLists: bool, ancestorLookupType: str,
templateShorthand: bool, matchSelf: bool,
- recurseInAnon: bool, correctPrimaryTemplateArgs: bool
- ) -> SymbolLookupResult:
+ recurseInAnon: bool, correctPrimaryTemplateArgs: bool,
+ searchInSiblings: bool) -> SymbolLookupResult:
# ancestorLookupType: if not None, specifies the target type of the lookup
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_symbol_lookup:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("self:")
+ print(self.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_print("nestedName: ", nestedName)
+ Symbol.debug_print("templateDecls: ", templateDecls)
+ Symbol.debug_print("strictTemplateParamArgLists:", strictTemplateParamArgLists)
+ Symbol.debug_print("ancestorLookupType:", ancestorLookupType)
+ Symbol.debug_print("templateShorthand: ", templateShorthand)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("correctPrimaryTemplateArgs: ", correctPrimaryTemplateArgs)
+ Symbol.debug_print("searchInSiblings: ", searchInSiblings)
if strictTemplateParamArgLists:
# Each template argument list must have a template parameter list.
@@ -3796,7 +3902,8 @@ class Symbol:
while parentSymbol.parent:
if parentSymbol.find_identifier(firstName.identOrOp,
matchSelf=matchSelf,
- recurseInAnon=recurseInAnon):
+ recurseInAnon=recurseInAnon,
+ searchInSiblings=searchInSiblings):
# if we are in the scope of a constructor but wants to
# reference the class we need to walk one extra up
if (len(names) == 1 and ancestorLookupType == 'class' and matchSelf and
@@ -3807,6 +3914,10 @@ class Symbol:
break
parentSymbol = parentSymbol.parent
+ if Symbol.debug_lookup:
+ Symbol.debug_print("starting point:")
+ print(parentSymbol.to_string(Symbol.debug_indent + 1), end="")
+
# and now the actual lookup
iTemplateDecl = 0
for name in names[:-1]:
@@ -3840,6 +3951,8 @@ class Symbol:
symbol = onMissingQualifiedSymbol(parentSymbol, identOrOp,
templateParams, templateArgs)
if symbol is None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
return None
# We have now matched part of a nested name, and need to match more
# so even if we should matchSelf before, we definitely shouldn't
@@ -3847,6 +3960,10 @@ class Symbol:
matchSelf = False
parentSymbol = symbol
+ if Symbol.debug_lookup:
+ Symbol.debug_print("handle last name from:")
+ print(parentSymbol.to_string(Symbol.debug_indent + 1), end="")
+
# handle the last name
name = names[-1]
identOrOp = name.identOrOp
@@ -3861,7 +3978,11 @@ class Symbol:
symbols = parentSymbol._find_named_symbols(
identOrOp, templateParams, templateArgs,
templateShorthand=templateShorthand, matchSelf=matchSelf,
- recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False)
+ recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False,
+ searchInSiblings=searchInSiblings)
+ if Symbol.debug_lookup:
+ symbols = list(symbols) # type: ignore
+ Symbol.debug_indent -= 2
return SymbolLookupResult(symbols, parentSymbol,
identOrOp, templateParams, templateArgs)
@@ -3871,21 +3992,26 @@ class Symbol:
# be an actual declaration.
if Symbol.debug_lookup:
- print("_add_symbols:")
- print(" tdecls:", templateDecls)
- print(" nn: ", nestedName)
- print(" decl: ", declaration)
- print(" doc: ", docname)
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_add_symbols:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("tdecls:", templateDecls)
+ Symbol.debug_print("nn: ", nestedName)
+ Symbol.debug_print("decl: ", declaration)
+ Symbol.debug_print("doc: ", docname)
def onMissingQualifiedSymbol(parentSymbol: "Symbol",
identOrOp: Union[ASTIdentifier, ASTOperator],
templateParams: Any, templateArgs: ASTTemplateArgs
) -> "Symbol":
if Symbol.debug_lookup:
- print(" _add_symbols, onMissingQualifiedSymbol:")
- print(" templateParams:", templateParams)
- print(" identOrOp: ", identOrOp)
- print(" templateARgs: ", templateArgs)
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_add_symbols, onMissingQualifiedSymbol:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("templateParams:", templateParams)
+ Symbol.debug_print("identOrOp: ", identOrOp)
+ Symbol.debug_print("templateARgs: ", templateArgs)
+ Symbol.debug_indent -= 2
return Symbol(parent=parentSymbol, identOrOp=identOrOp,
templateParams=templateParams,
templateArgs=templateArgs, declaration=None,
@@ -3898,32 +4024,40 @@ class Symbol:
templateShorthand=False,
matchSelf=False,
recurseInAnon=True,
- correctPrimaryTemplateArgs=True)
+ correctPrimaryTemplateArgs=True,
+ searchInSiblings=False)
assert lookupResult is not None # we create symbols all the way, so that can't happen
symbols = list(lookupResult.symbols)
if len(symbols) == 0:
if Symbol.debug_lookup:
- print(" _add_symbols, result, no symbol:")
- print(" templateParams:", lookupResult.templateParams)
- print(" identOrOp: ", lookupResult.identOrOp)
- print(" templateArgs: ", lookupResult.templateArgs)
- print(" declaration: ", declaration)
- print(" docname: ", docname)
+ Symbol.debug_print("_add_symbols, result, no symbol:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("templateParams:", lookupResult.templateParams)
+ Symbol.debug_print("identOrOp: ", lookupResult.identOrOp)
+ Symbol.debug_print("templateArgs: ", lookupResult.templateArgs)
+ Symbol.debug_print("declaration: ", declaration)
+ Symbol.debug_print("docname: ", docname)
+ Symbol.debug_indent -= 1
symbol = Symbol(parent=lookupResult.parentSymbol,
identOrOp=lookupResult.identOrOp,
templateParams=lookupResult.templateParams,
templateArgs=lookupResult.templateArgs,
declaration=declaration,
docname=docname)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
return symbol
if Symbol.debug_lookup:
- print(" _add_symbols, result, symbols:")
- print(" number symbols:", len(symbols))
+ Symbol.debug_print("_add_symbols, result, symbols:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("number symbols:", len(symbols))
+ Symbol.debug_indent -= 1
if not declaration:
if Symbol.debug_lookup:
- print(" no delcaration")
+ Symbol.debug_print("no delcaration")
+ Symbol.debug_indent -= 2
# good, just a scope creation
# TODO: what if we have more than one symbol?
return symbols[0]
@@ -3939,9 +4073,9 @@ class Symbol:
else:
withDecl.append(s)
if Symbol.debug_lookup:
- print(" #noDecl: ", len(noDecl))
- print(" #withDecl:", len(withDecl))
- print(" #dupDecl: ", len(dupDecl))
+ Symbol.debug_print("#noDecl: ", len(noDecl))
+ Symbol.debug_print("#withDecl:", len(withDecl))
+ Symbol.debug_print("#dupDecl: ", len(dupDecl))
# With partial builds we may start with a large symbol tree stripped of declarations.
# Essentially any combination of noDecl, withDecl, and dupDecls seems possible.
# TODO: make partial builds fully work. What should happen when the primary symbol gets
@@ -3952,7 +4086,7 @@ class Symbol:
# otherwise there should be only one symbol with a declaration.
def makeCandSymbol():
if Symbol.debug_lookup:
- print(" begin: creating candidate symbol")
+ Symbol.debug_print("begin: creating candidate symbol")
symbol = Symbol(parent=lookupResult.parentSymbol,
identOrOp=lookupResult.identOrOp,
templateParams=lookupResult.templateParams,
@@ -3960,7 +4094,7 @@ class Symbol:
declaration=declaration,
docname=docname)
if Symbol.debug_lookup:
- print(" end: creating candidate symbol")
+ Symbol.debug_print("end: creating candidate symbol")
return symbol
if len(withDecl) == 0:
candSymbol = None
@@ -3969,7 +4103,10 @@ class Symbol:
def handleDuplicateDeclaration(symbol, candSymbol):
if Symbol.debug_lookup:
- print(" redeclaration")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("redeclaration")
+ Symbol.debug_indent -= 1
+ Symbol.debug_indent -= 2
# Redeclaration of the same symbol.
# Let the new one be there, but raise an error to the client
# so it can use the real symbol as subscope.
@@ -3985,11 +4122,11 @@ class Symbol:
# a function, so compare IDs
candId = declaration.get_newest_id()
if Symbol.debug_lookup:
- print(" candId:", candId)
+ Symbol.debug_print("candId:", candId)
for symbol in withDecl:
oldId = symbol.declaration.get_newest_id()
if Symbol.debug_lookup:
- print(" oldId: ", oldId)
+ Symbol.debug_print("oldId: ", oldId)
if candId == oldId:
handleDuplicateDeclaration(symbol, candSymbol)
# (not reachable)
@@ -3997,14 +4134,16 @@ class Symbol:
# if there is an empty symbol, fill that one
if len(noDecl) == 0:
if Symbol.debug_lookup:
- print(" no match, no empty, candSybmol is not None?:", candSymbol is not None) # NOQA
+ Symbol.debug_print("no match, no empty, candSybmol is not None?:", candSymbol is not None) # NOQA
+ Symbol.debug_indent -= 2
if candSymbol is not None:
return candSymbol
else:
return makeCandSymbol()
else:
if Symbol.debug_lookup:
- print(" no match, but fill an empty declaration, candSybmol is not None?:", candSymbol is not None) # NOQA
+ Symbol.debug_print("no match, but fill an empty declaration, candSybmol is not None?:", candSymbol is not None) # NOQA
+ Symbol.debug_indent -= 2
if candSymbol is not None:
candSymbol.remove()
# assert len(noDecl) == 1
@@ -4021,6 +4160,9 @@ class Symbol:
def merge_with(self, other: "Symbol", docnames: List[str],
env: "BuildEnvironment") -> None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("merge_with:")
assert other is not None
for otherChild in other._children:
ourChild = self._find_first_named_symbol(
@@ -4050,17 +4192,28 @@ class Symbol:
# just ignore it, right?
pass
ourChild.merge_with(otherChild, docnames, env)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
def add_name(self, nestedName: ASTNestedName,
templatePrefix: ASTTemplateDeclarationPrefix = None) -> "Symbol":
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("add_name:")
if templatePrefix:
templateDecls = templatePrefix.templates
else:
templateDecls = []
- return self._add_symbols(nestedName, templateDecls,
- declaration=None, docname=None)
+ res = self._add_symbols(nestedName, templateDecls,
+ declaration=None, docname=None)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+ return res
def add_declaration(self, declaration: ASTDeclaration, docname: str) -> "Symbol":
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("add_declaration:")
assert declaration
assert docname
nestedName = declaration.name
@@ -4068,37 +4221,105 @@ class Symbol:
templateDecls = declaration.templatePrefix.templates
else:
templateDecls = []
- return self._add_symbols(nestedName, templateDecls, declaration, docname)
+ res = self._add_symbols(nestedName, templateDecls, declaration, docname)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+ return res
def find_identifier(self, identOrOp: Union[ASTIdentifier, ASTOperator],
- matchSelf: bool, recurseInAnon: bool) -> "Symbol":
- if matchSelf and self.identOrOp == identOrOp:
- return self
- children = self.children_recurse_anon if recurseInAnon else self._children
- for s in children:
- if s.identOrOp == identOrOp:
- return s
+ matchSelf: bool, recurseInAnon: bool, searchInSiblings: bool
+ ) -> "Symbol":
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("find_identifier:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("identOrOp: ", identOrOp)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("searchInSiblings:", searchInSiblings)
+ print(self.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_indent -= 2
+ current = self
+ while current is not None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 2
+ Symbol.debug_print("trying:")
+ print(current.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_indent -= 2
+ if matchSelf and current.identOrOp == identOrOp:
+ return current
+ children = current.children_recurse_anon if recurseInAnon else current._children
+ for s in children:
+ if s.identOrOp == identOrOp:
+ return s
+ if not searchInSiblings:
+ break
+ current = current.siblingAbove
return None
- def direct_lookup(self, key: List[Tuple[ASTNestedNameElement, Any]]) -> "Symbol":
+ def direct_lookup(self, key: "LookupKey") -> "Symbol":
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("direct_lookup:")
+ Symbol.debug_indent += 1
s = self
- for name, templateParams in key:
- identOrOp = name.identOrOp
- templateArgs = name.templateArgs
- s = s._find_first_named_symbol(identOrOp,
- templateParams, templateArgs,
- templateShorthand=False,
- matchSelf=False,
- recurseInAnon=False,
- correctPrimaryTemplateArgs=False)
- if not s:
+ for name, templateParams, id_ in key.data:
+ if id_ is not None:
+ res = None
+ for cand in s._children:
+ if cand.declaration is None:
+ continue
+ if cand.declaration.get_newest_id() == id_:
+ res = cand
+ break
+ s = res
+ else:
+ identOrOp = name.identOrOp
+ templateArgs = name.templateArgs
+ s = s._find_first_named_symbol(identOrOp,
+ templateParams, templateArgs,
+ templateShorthand=False,
+ matchSelf=False,
+ recurseInAnon=False,
+ correctPrimaryTemplateArgs=False)
+ if Symbol.debug_lookup:
+ Symbol.debug_print("name: ", name)
+ Symbol.debug_print("templateParams:", templateParams)
+ Symbol.debug_print("id: ", id_)
+ if s is not None:
+ print(s.to_string(Symbol.debug_indent + 1), end="")
+ else:
+ Symbol.debug_print("not found")
+ if s is None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
return None
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
return s
def find_name(self, nestedName: ASTNestedName, templateDecls: List[Any],
typ: str, templateShorthand: bool, matchSelf: bool,
- recurseInAnon: bool) -> List["Symbol"]:
+ recurseInAnon: bool, searchInSiblings: bool) -> Tuple[List["Symbol"], str]:
# templateShorthand: missing template parameter lists for templates is ok
+ # If the first component is None,
+ # then the second component _may_ be a string explaining why.
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("find_name:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("self:")
+ print(self.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_print("nestedName: ", nestedName)
+ Symbol.debug_print("templateDecls: ", templateDecls)
+ Symbol.debug_print("typ: ", typ)
+ Symbol.debug_print("templateShorthand:", templateShorthand)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("searchInSiblings: ", searchInSiblings)
+
+ class QualifiedSymbolIsTemplateParam(Exception):
+ pass
def onMissingQualifiedSymbol(parentSymbol: "Symbol",
identOrOp: Union[ASTIdentifier, ASTOperator],
@@ -4108,37 +4329,58 @@ class Symbol:
# Though, the correctPrimaryTemplateArgs does
# that for primary templates.
# Is there another case where it would be good?
+ if parentSymbol.declaration is not None:
+ if parentSymbol.declaration.objectType == 'templateParam':
+ raise QualifiedSymbolIsTemplateParam()
return None
- lookupResult = self._symbol_lookup(nestedName, templateDecls,
- onMissingQualifiedSymbol,
- strictTemplateParamArgLists=False,
- ancestorLookupType=typ,
- templateShorthand=templateShorthand,
- matchSelf=matchSelf,
- recurseInAnon=recurseInAnon,
- correctPrimaryTemplateArgs=False)
+ try:
+ lookupResult = self._symbol_lookup(nestedName, templateDecls,
+ onMissingQualifiedSymbol,
+ strictTemplateParamArgLists=False,
+ ancestorLookupType=typ,
+ templateShorthand=templateShorthand,
+ matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon,
+ correctPrimaryTemplateArgs=False,
+ searchInSiblings=searchInSiblings)
+ except QualifiedSymbolIsTemplateParam:
+ return None, "templateParamInQualified"
+
if lookupResult is None:
# if it was a part of the qualification that could not be found
- return None
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+ return None, None
res = list(lookupResult.symbols)
if len(res) != 0:
- return res
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+ return res, None
+
+ if lookupResult.parentSymbol.declaration is not None:
+ if lookupResult.parentSymbol.declaration.objectType == 'templateParam':
+ return None, "templateParamInQualified"
# try without template params and args
symbol = lookupResult.parentSymbol._find_first_named_symbol(
lookupResult.identOrOp, None, None,
templateShorthand=templateShorthand, matchSelf=matchSelf,
recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
if symbol is not None:
- return [symbol]
+ return [symbol], None
else:
- return None
+ return None, None
def find_declaration(self, declaration: ASTDeclaration, typ: str, templateShorthand: bool,
matchSelf: bool, recurseInAnon: bool) -> "Symbol":
# templateShorthand: missing template parameter lists for templates is ok
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("find_declaration:")
nestedName = declaration.name
if declaration.templatePrefix:
templateDecls = declaration.templatePrefix.templates
@@ -4158,8 +4400,10 @@ class Symbol:
templateShorthand=templateShorthand,
matchSelf=matchSelf,
recurseInAnon=recurseInAnon,
- correctPrimaryTemplateArgs=False)
-
+ correctPrimaryTemplateArgs=False,
+ searchInSiblings=False)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
if lookupResult is None:
return None
@@ -4185,14 +4429,14 @@ class Symbol:
return None
def to_string(self, indent: int) -> str:
- res = ['\t' * indent]
+ res = [Symbol.debug_indent_string * indent]
if not self.parent:
res.append('::')
else:
if self.templateParams:
res.append(str(self.templateParams))
res.append('\n')
- res.append('\t' * indent)
+ res.append(Symbol.debug_indent_string * indent)
if self.identOrOp:
res.append(str(self.identOrOp))
else:
@@ -5931,14 +6175,15 @@ class DefinitionParser:
def _parse_template_declaration_prefix(self, objectType: str
) -> ASTTemplateDeclarationPrefix:
- templates = [] # type: List[str]
+ templates = [] # type: 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]
pos = self.pos
if self.skip_word("template"):
try:
- params = self._parse_template_parameter_list() # type: Any
+ params = self._parse_template_parameter_list()
except DefinitionError as e:
if objectType == 'member' and len(templates) == 0:
return ASTTemplateDeclarationPrefix(None)
@@ -5990,7 +6235,7 @@ class DefinitionParser:
msg += str(nestedName)
self.warn(msg)
- newTemplates = []
+ newTemplates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]]
for i in range(numExtra):
newTemplates.append(ASTTemplateParams([]))
if templatePrefix and not isMemberInstantiation:
@@ -6176,7 +6421,8 @@ class CPPObject(ObjectDescription):
return
targetSymbol = parentSymbol.parent
- s = targetSymbol.find_identifier(symbol.identOrOp, matchSelf=False, recurseInAnon=True)
+ s = targetSymbol.find_identifier(symbol.identOrOp, matchSelf=False, recurseInAnon=True,
+ searchInSiblings=False)
if s is not None:
# something is already declared with that name
return
@@ -6244,7 +6490,6 @@ class CPPObject(ObjectDescription):
continue
if id not in self.state.document.ids:
signode['ids'].append(id)
- signode['first'] = (not self.names) # hmm, what is this about?
self.state.document.note_explicit_target(signode)
@property
@@ -6291,6 +6536,10 @@ class CPPObject(ObjectDescription):
symbol = parentSymbol.add_name(name)
env.temp_data['cpp:last_symbol'] = symbol
return []
+ # When multiple declarations are made in the same directive
+ # they need to know about each other to provide symbol lookup for function parameters.
+ # We use last_symbol to store the latest added declaration in a directive.
+ env.temp_data['cpp:last_symbol'] = None
return super().run()
def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration:
@@ -6311,6 +6560,13 @@ class CPPObject(ObjectDescription):
try:
symbol = parentSymbol.add_declaration(ast, docname=self.env.docname)
+ # append the new declaration to the sibling list
+ assert symbol.siblingAbove is None
+ assert symbol.siblingBelow is None
+ symbol.siblingAbove = self.env.temp_data['cpp:last_symbol']
+ if symbol.siblingAbove is not None:
+ assert symbol.siblingAbove.siblingBelow is None
+ symbol.siblingAbove.siblingBelow = symbol
self.env.temp_data['cpp:last_symbol'] = symbol
except _DuplicateSymbolError as e:
# Assume we are actually in the old symbol,
@@ -6329,10 +6585,10 @@ class CPPObject(ObjectDescription):
return ast
def before_content(self) -> None:
- lastSymbol = self.env.temp_data['cpp:last_symbol']
+ lastSymbol = self.env.temp_data['cpp:last_symbol'] # type: Symbol
assert lastSymbol
self.oldParentSymbol = self.env.temp_data['cpp:parent_symbol']
- self.oldParentKey = self.env.ref_context['cpp:parent_key']
+ self.oldParentKey = self.env.ref_context['cpp:parent_key'] # type: LookupKey
self.env.temp_data['cpp:parent_symbol'] = lastSymbol
self.env.ref_context['cpp:parent_key'] = lastSymbol.get_lookup_key()
@@ -6515,14 +6771,13 @@ class AliasTransform(SphinxTransform):
if ast is None:
# could not be parsed, so stop here
signode = addnodes.desc_signature(sig, '')
- signode['first'] = False
signode.clear()
signode += addnodes.desc_name(sig, sig)
node.replace_self(signode)
continue
- rootSymbol = self.env.domains['cpp'].data['root_symbol']
- parentSymbol = rootSymbol.direct_lookup(parentKey)
+ rootSymbol = self.env.domains['cpp'].data['root_symbol'] # type: Symbol
+ parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol
if not parentSymbol:
print("Target: ", sig)
print("ParentKey: ", parentKey)
@@ -6537,9 +6792,13 @@ class AliasTransform(SphinxTransform):
templateDecls = ns.templatePrefix.templates
else:
templateDecls = []
- symbols = parentSymbol.find_name(name, templateDecls, 'any',
- templateShorthand=True,
- matchSelf=True, recurseInAnon=True)
+ symbols, failReason = parentSymbol.find_name(
+ nestedName=name,
+ templateDecls=templateDecls,
+ typ='any',
+ templateShorthand=True,
+ matchSelf=True, recurseInAnon=True,
+ searchInSiblings=False)
if symbols is None:
symbols = []
else:
@@ -6555,7 +6814,6 @@ class AliasTransform(SphinxTransform):
if len(symbols) == 0:
signode = addnodes.desc_signature(sig, '')
- signode['first'] = False
node.append(signode)
signode.clear()
signode += addnodes.desc_name(sig, sig)
@@ -6569,7 +6827,6 @@ class AliasTransform(SphinxTransform):
options['tparam-line-spec'] = False
for s in symbols:
signode = addnodes.desc_signature(sig, '')
- signode['first'] = False
nodes.append(signode)
s.declaration.describe_signature(signode, 'markName', self.env, options)
node.replace_self(nodes)
@@ -6824,13 +7081,13 @@ class CPPDomain(Domain):
t, ex = findWarning(e)
warner.warn('Unparseable C++ cross-reference: %r\n%s' % (t, ex))
return None, None
- parentKey = node.get("cpp:parent_key", None)
+ parentKey = node.get("cpp:parent_key", None) # type: LookupKey
rootSymbol = self.data['root_symbol']
if parentKey:
- parentSymbol = rootSymbol.direct_lookup(parentKey)
+ parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol
if not parentSymbol:
print("Target: ", target)
- print("ParentKey: ", parentKey)
+ print("ParentKey: ", parentKey.data)
print(rootSymbol.dump(1))
assert parentSymbol # should be there
else:
@@ -6843,11 +7100,23 @@ class CPPDomain(Domain):
templateDecls = ns.templatePrefix.templates
else:
templateDecls = []
- symbols = parentSymbol.find_name(name, templateDecls, typ,
- templateShorthand=True,
- matchSelf=True, recurseInAnon=True)
- # just refer to the arbitrarily first symbol
- s = None if symbols is None else symbols[0]
+ # let's be conservative with the sibling lookup for now
+ searchInSiblings = (not name.rooted) and len(name.names) == 1
+ symbols, failReason = parentSymbol.find_name(
+ name, templateDecls, typ,
+ templateShorthand=True,
+ matchSelf=True, recurseInAnon=True,
+ searchInSiblings=searchInSiblings)
+ if symbols is None:
+ if typ == 'identifier':
+ if failReason == 'templateParamInQualified':
+ # this is an xref we created as part of a signature,
+ # so don't warn for names nested in template parameters
+ raise NoUri(str(name), typ)
+ s = None
+ else:
+ # just refer to the arbitrarily first symbol
+ s = symbols[0]
else:
decl = ast # type: ASTDeclaration
name = decl.name
@@ -6977,8 +7246,8 @@ class CPPDomain(Domain):
target = node.get('reftarget', None)
if target is None:
return None
- parentKey = node.get("cpp:parent_key", None)
- if parentKey is None or len(parentKey) <= 0:
+ parentKey = node.get("cpp:parent_key", None) # type: LookupKey
+ if parentKey is None or len(parentKey.data) <= 0:
return None
rootSymbol = self.data['root_symbol']
@@ -6996,7 +7265,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
return {
'version': 'builtin',
- 'env_version': 1,
+ 'env_version': 2,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/domains/index.py b/sphinx/domains/index.py
index d0bdbcfe0..18a256bac 100644
--- a/sphinx/domains/index.py
+++ b/sphinx/domains/index.py
@@ -12,6 +12,7 @@ from typing import Any, Dict, Iterable, List, Tuple
from docutils import nodes
from docutils.nodes import Node, system_message
+from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.domains import Domain
@@ -68,19 +69,27 @@ class IndexDirective(SphinxDirective):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
- option_spec = {} # type: Dict
+ option_spec = {
+ 'name': directives.unchanged,
+ }
def run(self) -> List[Node]:
arguments = self.arguments[0].split('\n')
- targetid = 'index-%s' % self.env.new_serialno('index')
- targetnode = nodes.target('', '', ids=[targetid])
+
+ if 'name' in self.options:
+ targetname = self.options['name']
+ targetnode = nodes.target('', '', names=[targetname])
+ else:
+ targetid = 'index-%s' % self.env.new_serialno('index')
+ targetnode = nodes.target('', '', ids=[targetid])
+
self.state.document.note_explicit_target(targetnode)
indexnode = addnodes.index()
indexnode['entries'] = []
indexnode['inline'] = False
self.set_source_info(indexnode)
for entry in arguments:
- indexnode['entries'].extend(process_index_entry(entry, targetid))
+ indexnode['entries'].extend(process_index_entry(entry, targetnode['ids'][0]))
return [indexnode, targetnode]
diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py
index feb39bc9d..d510d7903 100644
--- a/sphinx/domains/javascript.py
+++ b/sphinx/domains/javascript.py
@@ -28,7 +28,7 @@ from sphinx.roles import XRefRole
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_refnode
+from sphinx.util.nodes import make_id, make_refnode
logger = logging.getLogger(__name__)
@@ -106,21 +106,23 @@ class JSObject(ObjectDescription):
signode: desc_signature) -> None:
mod_name = self.env.ref_context.get('js:module')
fullname = (mod_name + '.' if mod_name else '') + name_obj[0]
- if fullname not in self.state.document.ids:
- signode['names'].append(fullname)
- signode['ids'].append(fullname.replace('$', '_S_'))
- signode['first'] = not self.names
- self.state.document.note_explicit_target(signode)
+ node_id = make_id(self.env, self.state.document, '', fullname)
+ signode['ids'].append(node_id)
- domain = cast(JavaScriptDomain, self.env.get_domain('js'))
- domain.note_object(fullname, self.objtype,
- location=(self.env.docname, self.lineno))
+ # Assign old styled node_id not to break old hyperlinks (if possible)
+ # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning)
+ old_node_id = self.make_old_id(fullname)
+ if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']:
+ signode['ids'].append(old_node_id)
+
+ self.state.document.note_explicit_target(signode)
+
+ domain = cast(JavaScriptDomain, self.env.get_domain('js'))
+ domain.note_object(fullname, self.objtype, node_id, location=signode)
indextext = self.get_index_text(mod_name, name_obj)
if indextext:
- self.indexnode['entries'].append(('single', indextext,
- fullname.replace('$', '_S_'),
- '', None))
+ self.indexnode['entries'].append(('single', indextext, node_id, '', None))
def get_index_text(self, objectname: str, name_obj: Tuple[str, str]) -> str:
name, obj = name_obj
@@ -191,6 +193,14 @@ class JSObject(ObjectDescription):
self.env.ref_context['js:object'] = (objects[-1] if len(objects) > 0
else None)
+ def make_old_id(self, fullname: str) -> str:
+ """Generate old styled node_id for JS objects.
+
+ .. note:: Old Styled node_id was used until Sphinx-3.0.
+ This will be removed in Sphinx-5.0.
+ """
+ return fullname.replace('$', '_S_')
+
class JSCallable(JSObject):
"""Description of a JavaScript function, method or constructor."""
@@ -251,21 +261,36 @@ class JSModule(SphinxDirective):
if not noindex:
domain = cast(JavaScriptDomain, self.env.get_domain('js'))
- domain.note_module(mod_name)
+ node_id = make_id(self.env, self.state.document, 'module', mod_name)
+ domain.note_module(mod_name, node_id)
# Make a duplicate entry in 'objects' to facilitate searching for
# the module in JavaScriptDomain.find_obj()
- domain.note_object(mod_name, 'module', location=(self.env.docname, self.lineno))
+ domain.note_object(mod_name, 'module', node_id,
+ location=(self.env.docname, self.lineno))
+
+ target = nodes.target('', '', ids=[node_id], ismod=True)
+
+ # Assign old styled node_id not to break old hyperlinks (if possible)
+ # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning)
+ old_node_id = self.make_old_id(mod_name)
+ if old_node_id not in self.state.document.ids and old_node_id not in target['ids']:
+ target['ids'].append(old_node_id)
- targetnode = nodes.target('', '', ids=['module-' + mod_name],
- ismod=True)
- self.state.document.note_explicit_target(targetnode)
- ret.append(targetnode)
+ self.state.document.note_explicit_target(target)
+ ret.append(target)
indextext = _('%s (module)') % mod_name
- inode = addnodes.index(entries=[('single', indextext,
- 'module-' + mod_name, '', None)])
+ inode = addnodes.index(entries=[('single', indextext, node_id, '', None)])
ret.append(inode)
return ret
+ def make_old_id(self, modname: str) -> str:
+ """Generate old styled node_id for JS modules.
+
+ .. note:: Old Styled node_id was used until Sphinx-3.0.
+ This will be removed in Sphinx-5.0.
+ """
+ return 'module-' + modname
+
class JSXRefRole(XRefRole):
def process_link(self, env: BuildEnvironment, refnode: Element,
@@ -317,47 +342,48 @@ class JavaScriptDomain(Domain):
'mod': JSXRefRole(),
}
initial_data = {
- 'objects': {}, # fullname -> docname, objtype
- 'modules': {}, # modname -> docname
+ '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]]:
- return self.data.setdefault('objects', {}) # fullname -> docname, objtype
+ def objects(self) -> Dict[str, Tuple[str, str, str]]:
+ return self.data.setdefault('objects', {}) # fullname -> docname, node_id, objtype
- def note_object(self, fullname: str, objtype: str, location: Any = None) -> None:
+ def note_object(self, fullname: str, objtype: str, node_id: str,
+ location: Any = None) -> None:
if fullname in self.objects:
docname = self.objects[fullname][0]
- logger.warning(__('duplicate object description of %s, other instance in %s'),
- fullname, docname, location=location)
- self.objects[fullname] = (self.env.docname, objtype)
+ logger.warning(__('duplicate %s description of %s, other %s in %s'),
+ objtype, fullname, objtype, docname, location=location)
+ self.objects[fullname] = (self.env.docname, node_id, objtype)
@property
- def modules(self) -> Dict[str, str]:
- return self.data.setdefault('modules', {}) # modname -> docname
+ def modules(self) -> Dict[str, Tuple[str, str]]:
+ return self.data.setdefault('modules', {}) # modname -> docname, node_id
- def note_module(self, modname: str) -> None:
- self.modules[modname] = self.env.docname
+ def note_module(self, modname: str, node_id: str) -> None:
+ self.modules[modname] = (self.env.docname, node_id)
def clear_doc(self, docname: str) -> None:
- for fullname, (pkg_docname, _l) in list(self.objects.items()):
+ for fullname, (pkg_docname, node_id, _l) in list(self.objects.items()):
if pkg_docname == docname:
del self.objects[fullname]
- for modname, pkg_docname in list(self.modules.items()):
+ for modname, (pkg_docname, node_id) in list(self.modules.items()):
if pkg_docname == docname:
del self.modules[modname]
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX check duplicates
- for fullname, (fn, objtype) in otherdata['objects'].items():
+ for fullname, (fn, node_id, objtype) in otherdata['objects'].items():
if fn in docnames:
- self.objects[fullname] = (fn, objtype)
- for mod_name, pkg_docname in otherdata['modules'].items():
+ self.objects[fullname] = (fn, node_id, objtype)
+ for mod_name, (pkg_docname, node_id) in otherdata['modules'].items():
if pkg_docname in docnames:
- self.modules[mod_name] = pkg_docname
+ self.modules[mod_name] = (pkg_docname, node_id)
def find_obj(self, env: BuildEnvironment, mod_name: str, prefix: str, name: str,
- typ: str, searchorder: int = 0) -> Tuple[str, Tuple[str, str]]:
+ typ: str, searchorder: int = 0) -> Tuple[str, Tuple[str, str, str]]:
if name[-2:] == '()':
name = name[:-2]
@@ -389,8 +415,7 @@ class JavaScriptDomain(Domain):
name, obj = self.find_obj(env, mod_name, prefix, target, typ, searchorder)
if not obj:
return None
- return make_refnode(builder, fromdocname, obj[0],
- name.replace('$', '_S_'), contnode, name)
+ return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name)
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
target: str, node: pending_xref, contnode: Element
@@ -400,13 +425,12 @@ class JavaScriptDomain(Domain):
name, obj = self.find_obj(env, mod_name, prefix, target, None, 1)
if not obj:
return []
- return [('js:' + self.role_for_objtype(obj[1]),
- make_refnode(builder, fromdocname, obj[0],
- name.replace('$', '_S_'), contnode, name))]
+ return [('js:' + self.role_for_objtype(obj[2]),
+ make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name))]
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
- for refname, (docname, type) in list(self.objects.items()):
- yield refname, refname, type, docname, refname.replace('$', '_S_'), 1
+ for refname, (docname, node_id, typ) in list(self.objects.items()):
+ yield refname, refname, typ, docname, node_id, 1
def get_full_qualified_name(self, node: Element) -> str:
modname = node.get('js:module')
@@ -423,7 +447,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
return {
'version': 'builtin',
- 'env_version': 1,
+ 'env_version': 2,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py
index b07b2603b..88b6e4eb8 100644
--- a/sphinx/domains/math.py
+++ b/sphinx/domains/math.py
@@ -15,7 +15,6 @@ from docutils import nodes
from docutils.nodes import Element, Node, system_message
from docutils.nodes import make_id
-from sphinx.addnodes import math_block as displaymath
from sphinx.addnodes import pending_xref
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.domains import Domain
@@ -54,7 +53,6 @@ class MathDomain(Domain):
'eq': 'equation not found: %(target)s',
}
enumerable_nodes = { # node_class -> (figtype, title_getter)
- displaymath: ('displaymath', None),
nodes.math_block: ('displaymath', None),
}
roles = {
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 3a49d6745..a9edd85b4 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -10,6 +10,7 @@
import re
import warnings
+from inspect import Parameter
from typing import Any, Dict, Iterable, Iterator, List, Tuple
from typing import cast
@@ -17,13 +18,11 @@ from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst import directives
-from sphinx import addnodes, locale
+from sphinx import addnodes
from sphinx.addnodes import pending_xref, desc_signature
from sphinx.application import Sphinx
from sphinx.builders import Builder
-from sphinx.deprecation import (
- DeprecatedDict, RemovedInSphinx30Warning, RemovedInSphinx40Warning
-)
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType, Index, IndexEntry
from sphinx.environment import BuildEnvironment
@@ -32,6 +31,7 @@ from sphinx.roles import XRefRole
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_refnode
from sphinx.util.typing import TextlikeNode
@@ -63,12 +63,46 @@ pairindextypes = {
'builtin': _('built-in function'),
}
-locale.pairindextypes = DeprecatedDict(
- pairindextypes,
- 'sphinx.locale.pairindextypes is deprecated. '
- 'Please use sphinx.domains.python.pairindextypes instead.',
- RemovedInSphinx30Warning
-)
+
+def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
+ """Parse a list of arguments using AST parser"""
+ params = addnodes.desc_parameterlist(arglist)
+ sig = signature_from_str('(%s)' % arglist)
+ last_kind = None
+ for param in sig.parameters.values():
+ if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY:
+ # PEP-570: Separator for Positional Only Parameter: /
+ params += addnodes.desc_parameter('', nodes.Text('/'))
+ if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
+ param.POSITIONAL_ONLY,
+ None):
+ # PEP-3102: Separator for Keyword Only Parameter: *
+ params += addnodes.desc_parameter('', nodes.Text('*'))
+
+ node = addnodes.desc_parameter()
+ if param.kind == param.VAR_POSITIONAL:
+ node += nodes.Text('*' + param.name)
+ elif param.kind == param.VAR_KEYWORD:
+ node += nodes.Text('**' + param.name)
+ else:
+ node += nodes.Text(param.name)
+
+ if param.annotation is not param.empty:
+ node += nodes.Text(': ' + param.annotation)
+ if param.default is not param.empty:
+ if param.annotation is not param.empty:
+ node += nodes.Text(' = ' + str(param.default))
+ else:
+ node += nodes.Text('=' + str(param.default))
+
+ params += node
+ last_kind = param.kind
+
+ if last_kind == Parameter.POSITIONAL_ONLY:
+ # PEP-570: Separator for Positional Only Parameter: /
+ params += addnodes.desc_parameter('', nodes.Text('/'))
+
+ return params
def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
@@ -293,7 +327,15 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_name(name, name)
if arglist:
- _pseudo_parse_arglist(signode, arglist)
+ try:
+ signode += _parse_arglist(arglist)
+ except SyntaxError:
+ # fallback to parse arglist original parser.
+ # it supports to represent optional arguments (ex. "func(foo [, bar])")
+ _pseudo_parse_arglist(signode, arglist)
+ except NotImplementedError as exc:
+ logger.warning(exc)
+ _pseudo_parse_arglist(signode, arglist)
else:
if self.needs_arglist():
# for callables, add an empty parameter list
@@ -320,12 +362,10 @@ class PyObject(ObjectDescription):
if fullname not in self.state.document.ids:
signode['names'].append(fullname)
signode['ids'].append(fullname)
- signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
domain = cast(PythonDomain, self.env.get_domain('py'))
- domain.note_object(fullname, self.objtype,
- location=(self.env.docname, self.lineno))
+ domain.note_object(fullname, self.objtype, location=signode)
indextext = self.get_index_text(modname, name_cls)
if indextext:
@@ -811,6 +851,21 @@ class PyXRefRole(XRefRole):
return title, target
+def filter_meta_fields(app: Sphinx, domain: str, objtype: str, content: Element) -> None:
+ """Filter ``:meta:`` field from its docstring."""
+ if domain != 'py':
+ return
+
+ for node in content:
+ if isinstance(node, nodes.field_list):
+ fields = cast(List[nodes.field], node)
+ for field in fields:
+ field_name = cast(nodes.field_body, field[0]).astext().strip()
+ if field_name == 'meta' or field_name.startswith('meta '):
+ node.remove(field)
+ break
+
+
class PythonModuleIndex(Index):
"""
Index subclass to provide the Python module index.
@@ -979,7 +1034,8 @@ class PythonDomain(Domain):
self.modules[modname] = data
def find_obj(self, env: BuildEnvironment, modname: str, classname: str,
- name: str, type: str, searchmode: int = 0) -> List[Tuple[str, Any]]:
+ name: str, type: str, searchmode: int = 0
+ ) -> List[Tuple[str, Tuple[str, str]]]:
"""Find a Python object for "name", perhaps using the given module
and/or classname. Returns a list of (name, object entry) tuples.
"""
@@ -990,7 +1046,7 @@ class PythonDomain(Domain):
if not name:
return []
- matches = [] # type: List[Tuple[str, Any]]
+ matches = [] # type: List[Tuple[str, Tuple[str, str]]]
newname = None
if searchmode == 1:
@@ -1119,7 +1175,10 @@ class PythonDomain(Domain):
def setup(app: Sphinx) -> Dict[str, Any]:
+ app.setup_extension('sphinx.directives')
+
app.add_domain(PythonDomain)
+ app.connect('object-description-transform', filter_meta_fields)
return {
'version': 'builtin',
diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py
index 81287c815..e25b31936 100644
--- a/sphinx/domains/rst.py
+++ b/sphinx/domains/rst.py
@@ -25,7 +25,7 @@ from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
-from sphinx.util.nodes import make_refnode
+from sphinx.util.nodes import make_id, make_refnode
logger = logging.getLogger(__name__)
@@ -39,24 +39,35 @@ class ReSTMarkup(ObjectDescription):
"""
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
- targetname = self.objtype + '-' + name
- if targetname not in self.state.document.ids:
- signode['names'].append(targetname)
- signode['ids'].append(targetname)
- signode['first'] = (not self.names)
- self.state.document.note_explicit_target(signode)
+ node_id = make_id(self.env, self.state.document, self.objtype, name)
+ signode['ids'].append(node_id)
- domain = cast(ReSTDomain, self.env.get_domain('rst'))
- domain.note_object(self.objtype, name, location=(self.env.docname, self.lineno))
+ # Assign old styled node_id not to break old hyperlinks (if possible)
+ # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning)
+ old_node_id = self.make_old_id(name)
+ if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']:
+ signode['ids'].append(old_node_id)
+
+ self.state.document.note_explicit_target(signode)
+
+ domain = cast(ReSTDomain, self.env.get_domain('rst'))
+ domain.note_object(self.objtype, name, node_id, location=signode)
indextext = self.get_index_text(self.objtype, name)
if indextext:
- self.indexnode['entries'].append(('single', indextext,
- targetname, '', None))
+ self.indexnode['entries'].append(('single', indextext, node_id, '', None))
def get_index_text(self, objectname: str, name: str) -> str:
return ''
+ def make_old_id(self, name: str) -> str:
+ """Generate old styled node_id for reST markups.
+
+ .. note:: Old Styled node_id was used until Sphinx-3.0.
+ This will be removed in Sphinx-5.0.
+ """
+ return self.objtype + '-' + name
+
def parse_directive(d: str) -> Tuple[str, str]:
"""Parse a directive signature.
@@ -128,27 +139,37 @@ class ReSTDirectiveOption(ReSTMarkup):
return name
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
+ domain = cast(ReSTDomain, self.env.get_domain('rst'))
+
directive_name = self.current_directive
- targetname = '-'.join([self.objtype, self.current_directive, name])
- if targetname not in self.state.document.ids:
- signode['names'].append(targetname)
- signode['ids'].append(targetname)
- signode['first'] = (not self.names)
- self.state.document.note_explicit_target(signode)
+ if directive_name:
+ prefix = '-'.join([self.objtype, directive_name])
+ objname = ':'.join([directive_name, name])
+ else:
+ prefix = self.objtype
+ objname = name
+
+ node_id = make_id(self.env, self.state.document, prefix, name)
+ signode['ids'].append(node_id)
+
+ # Assign old styled node_id not to break old hyperlinks (if possible)
+ # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning)
+ old_node_id = self.make_old_id(name)
+ if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']:
+ signode['ids'].append(old_node_id)
- objname = ':'.join(filter(None, [directive_name, name]))
- domain = cast(ReSTDomain, self.env.get_domain('rst'))
- domain.note_object(self.objtype, objname, location=(self.env.docname, self.lineno))
+ self.state.document.note_explicit_target(signode)
+ domain.note_object(self.objtype, objname, node_id, location=signode)
if directive_name:
key = name[0].upper()
pair = [_('%s (directive)') % directive_name,
_(':%s: (directive option)') % name]
- self.indexnode['entries'].append(('pair', '; '.join(pair), targetname, '', key))
+ self.indexnode['entries'].append(('pair', '; '.join(pair), node_id, '', key))
else:
key = name[0].upper()
text = _(':%s: (directive option)') % name
- self.indexnode['entries'].append(('single', text, targetname, '', key))
+ self.indexnode['entries'].append(('single', text, node_id, '', key))
@property
def current_directive(self) -> str:
@@ -158,6 +179,14 @@ class ReSTDirectiveOption(ReSTMarkup):
else:
return ''
+ def make_old_id(self, name: str) -> str:
+ """Generate old styled node_id for directive options.
+
+ .. note:: Old Styled node_id was used until Sphinx-3.0.
+ This will be removed in Sphinx-5.0.
+ """
+ return '-'.join([self.objtype, self.current_directive, name])
+
class ReSTRole(ReSTMarkup):
"""
@@ -195,37 +224,36 @@ class ReSTDomain(Domain):
} # type: Dict[str, Dict[Tuple[str, str], str]]
@property
- def objects(self) -> Dict[Tuple[str, str], str]:
- return self.data.setdefault('objects', {}) # (objtype, fullname) -> docname
+ def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]:
+ return self.data.setdefault('objects', {}) # (objtype, fullname) -> (docname, node_id)
- def note_object(self, objtype: str, name: str, location: Any = None) -> None:
+ def note_object(self, objtype: str, name: str, node_id: str, location: Any = None) -> None:
if (objtype, name) in self.objects:
- docname = self.objects[objtype, name]
+ docname, node_id = self.objects[objtype, name]
logger.warning(__('duplicate description of %s %s, other instance in %s') %
(objtype, name, docname), location=location)
- self.objects[objtype, name] = self.env.docname
+ self.objects[objtype, name] = (self.env.docname, node_id)
def clear_doc(self, docname: str) -> None:
- for (typ, name), doc in list(self.objects.items()):
+ for (typ, name), (doc, node_id) in list(self.objects.items()):
if doc == docname:
del self.objects[typ, name]
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX check duplicates
- for (typ, name), doc in otherdata['objects'].items():
+ for (typ, name), (doc, node_id) in otherdata['objects'].items():
if doc in docnames:
- self.objects[typ, name] = doc
+ self.objects[typ, name] = (doc, node_id)
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref, contnode: Element
) -> Element:
objtypes = self.objtypes_for_role(typ)
for objtype in objtypes:
- todocname = self.objects.get((objtype, target))
+ todocname, node_id = self.objects.get((objtype, target), (None, None))
if todocname:
- return make_refnode(builder, fromdocname, todocname,
- objtype + '-' + target,
+ return make_refnode(builder, fromdocname, todocname, node_id,
contnode, target + ' ' + objtype)
return None
@@ -234,17 +262,16 @@ class ReSTDomain(Domain):
) -> List[Tuple[str, Element]]:
results = [] # type: List[Tuple[str, Element]]
for objtype in self.object_types:
- todocname = self.objects.get((objtype, target))
+ todocname, node_id = self.objects.get((objtype, target), (None, None))
if todocname:
results.append(('rst:' + self.role_for_objtype(objtype),
- make_refnode(builder, fromdocname, todocname,
- objtype + '-' + target,
+ make_refnode(builder, fromdocname, todocname, node_id,
contnode, target + ' ' + objtype)))
return results
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
- for (typ, name), docname in self.data['objects'].items():
- yield name, name, typ, docname, typ + '-' + name, 1
+ for (typ, name), (docname, node_id) in self.data['objects'].items():
+ yield name, name, typ, docname, node_id, 1
def setup(app: Sphinx) -> Dict[str, Any]:
@@ -252,7 +279,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
return {
'version': 'builtin',
- 'env_version': 1,
+ 'env_version': 2,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 4e46c1551..dcf72efe8 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -22,10 +22,9 @@ from docutils.statemachine import StringList
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
-from sphinx.errors import NoUri
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import ws_re, logging, docname_join
@@ -67,9 +66,17 @@ class GenericObject(ObjectDescription):
return name
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
- targetname = '%s-%s' % (self.objtype, name)
- signode['ids'].append(targetname)
+ node_id = make_id(self.env, self.state.document, self.objtype, name)
+ signode['ids'].append(node_id)
+
+ # Assign old styled node_id not to break old hyperlinks (if possible)
+ # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning)
+ old_node_id = self.make_old_id(name)
+ if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']:
+ signode['ids'].append(old_node_id)
+
self.state.document.note_explicit_target(signode)
+
if self.indextemplate:
colon = self.indextemplate.find(':')
if colon != -1:
@@ -78,11 +85,18 @@ class GenericObject(ObjectDescription):
else:
indextype = 'single'
indexentry = self.indextemplate % (name,)
- self.indexnode['entries'].append((indextype, indexentry,
- targetname, '', None))
+ self.indexnode['entries'].append((indextype, indexentry, node_id, '', None))
std = cast(StandardDomain, self.env.get_domain('std'))
- std.add_object(self.objtype, name, self.env.docname, targetname)
+ std.note_object(self.objtype, name, node_id, location=signode)
+
+ def make_old_id(self, name: str) -> str:
+ """Generate old styled node_id for generic objects.
+
+ .. note:: Old Styled node_id was used until Sphinx-3.0.
+ This will be removed in Sphinx-5.0.
+ """
+ return self.objtype + '-' + name
class EnvVar(GenericObject):
@@ -125,8 +139,16 @@ class Target(SphinxDirective):
def run(self) -> List[Node]:
# normalize whitespace in fullname like XRefRole does
fullname = ws_re.sub(' ', self.arguments[0].strip())
- targetname = '%s-%s' % (self.name, fullname)
- node = nodes.target('', '', ids=[targetname])
+ node_id = make_id(self.env, self.state.document, self.name, fullname)
+ node = nodes.target('', '', ids=[node_id])
+ self.set_source_info(node)
+
+ # Assign old styled node_id not to break old hyperlinks (if possible)
+ # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning)
+ old_node_id = self.make_old_id(fullname)
+ if old_node_id not in self.state.document.ids and old_node_id not in node['ids']:
+ node['ids'].append(old_node_id)
+
self.state.document.note_explicit_target(node)
ret = [node] # type: List[Node]
if self.indextemplate:
@@ -136,18 +158,25 @@ class Target(SphinxDirective):
if colon != -1:
indextype = indexentry[:colon].strip()
indexentry = indexentry[colon + 1:].strip()
- inode = addnodes.index(entries=[(indextype, indexentry,
- targetname, '', None)])
+ inode = addnodes.index(entries=[(indextype, indexentry, node_id, '', None)])
ret.insert(0, inode)
name = self.name
if ':' in self.name:
_, name = self.name.split(':', 1)
std = cast(StandardDomain, self.env.get_domain('std'))
- std.add_object(name, fullname, self.env.docname, targetname)
+ std.note_object(name, fullname, node_id, location=node)
return ret
+ def make_old_id(self, name: str) -> str:
+ """Generate old styled node_id for targets.
+
+ .. note:: Old Styled node_id was used until Sphinx-3.0.
+ This will be removed in Sphinx-5.0.
+ """
+ return self.name + '-' + name
+
class Cmdoption(ObjectDescription):
"""
@@ -165,7 +194,7 @@ class Cmdoption(ObjectDescription):
logger.warning(__('Malformed option description %r, should '
'look like "opt", "-opt args", "--opt args", '
'"/opt args" or "+opt args"'), potential_option,
- location=(self.env.docname, self.lineno))
+ location=signode)
continue
optname, args = m.groups()
if count:
@@ -274,7 +303,7 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index
term['ids'].append(node_id)
std = cast(StandardDomain, env.get_domain('std'))
- std.add_object('term', termtext.lower(), env.docname, node_id)
+ std.note_object('term', termtext.lower(), node_id, location=term)
# add an index entry too
indexnode = addnodes.index()
@@ -406,7 +435,9 @@ class Glossary(SphinxDirective):
return messages + [node]
-def token_xrefs(text: str) -> List[Node]:
+def token_xrefs(text: str, productionGroup: str = '') -> List[Node]:
+ if len(productionGroup) != 0:
+ productionGroup += ':'
retnodes = [] # type: List[Node]
pos = 0
for m in token_re.finditer(text):
@@ -414,7 +445,7 @@ def token_xrefs(text: str) -> List[Node]:
txt = text[pos:m.start()]
retnodes.append(nodes.Text(txt, txt))
refnode = pending_xref(m.group(1), reftype='token', refdomain='std',
- reftarget=m.group(1))
+ reftarget=productionGroup + m.group(1))
refnode += nodes.literal(m.group(1), m.group(1), classes=['xref'])
retnodes.append(refnode)
pos = m.end()
@@ -437,11 +468,16 @@ class ProductionList(SphinxDirective):
def run(self) -> List[Node]:
domain = cast(StandardDomain, self.env.get_domain('std'))
node = addnodes.productionlist() # type: Element
- i = 0
+ self.set_source_info(node)
+ # The backslash handling is from ObjectDescription.get_signatures
+ nl_escape_re = re.compile(r'\\\n')
+ lines = nl_escape_re.sub('', self.arguments[0]).split('\n')
- for rule in self.arguments[0].split('\n'):
+ productionGroup = ""
+ i = 0
+ for rule in lines:
if i == 0 and ':' not in rule:
- # production group
+ productionGroup = rule.strip()
continue
i += 1
try:
@@ -451,16 +487,41 @@ class ProductionList(SphinxDirective):
subnode = addnodes.production(rule)
subnode['tokenname'] = name.strip()
if subnode['tokenname']:
- idname = nodes.make_id('grammar-token-%s' % subnode['tokenname'])
+ # nodes.make_id converts '_' to '-',
+ # so we can use '_' to delimit group from name,
+ # and make sure we don't clash with other IDs.
+ idname = 'grammar-token-%s_%s' \
+ % (nodes.make_id(productionGroup), nodes.make_id(name))
if idname not in self.state.document.ids:
subnode['ids'].append(idname)
+
+ idnameOld = nodes.make_id('grammar-token-' + name)
+ if idnameOld not in self.state.document.ids:
+ subnode['ids'].append(idnameOld)
self.state.document.note_implicit_target(subnode, subnode)
- domain.add_object('token', subnode['tokenname'], self.env.docname, idname)
- subnode.extend(token_xrefs(tokens))
+ if len(productionGroup) != 0:
+ objName = "%s:%s" % (productionGroup, name)
+ else:
+ objName = name
+ domain.note_object(objtype='token', name=objName, labelid=idname,
+ location=node)
+ subnode.extend(token_xrefs(tokens, productionGroup))
node.append(subnode)
return [node]
+class TokenXRefRole(XRefRole):
+ def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool,
+ title: str, target: str) -> Tuple[str, str]:
+ target = target.lstrip('~') # a title-specific thing
+ if not self.has_explicit_title and title[0] == '~':
+ if ':' in title:
+ _, title = title.split(':')
+ else:
+ title = title[1:]
+ return title, target
+
+
class StandardDomain(Domain):
"""
Domain for all objects that don't fit into another domain or are added
@@ -492,7 +553,7 @@ class StandardDomain(Domain):
'option': OptionXRefRole(warn_dangling=True),
'envvar': EnvVarXRefRole(),
# links to tokens in grammar productions
- 'token': XRefRole(),
+ 'token': TokenXRefRole(),
# links to terms in glossary
'term': XRefRole(lowercase=True, innernodeclass=nodes.inline,
warn_dangling=True),
@@ -547,10 +608,51 @@ class StandardDomain(Domain):
for node, settings in env.app.registry.enumerable_nodes.items():
self.enumerable_nodes[node] = settings
+ def note_hyperlink_target(self, name: str, docname: str, node_id: str,
+ title: str = '') -> None:
+ """Add a hyperlink target for cross reference.
+
+ .. warning::
+
+ This is only for internal use. Please don't use this from your extension.
+ ``document.note_explicit_target()`` or ``note_implicit_target()`` are recommended to
+ add a hyperlink target to the document.
+
+ This only adds a hyperlink target to the StandardDomain. And this does not add a
+ node_id to node. Therefore, it is very fragile to calling this without
+ understanding hyperlink target framework in both docutils and Sphinx.
+
+ .. versionadded:: 3.0
+ """
+ if name in self.anonlabels and self.anonlabels[name] != (docname, node_id):
+ logger.warning(__('duplicate label %s, other instance in %s'),
+ name, self.env.doc2path(self.anonlabels[name][0]))
+
+ self.anonlabels[name] = (docname, node_id)
+ if title:
+ self.labels[name] = (docname, node_id, title)
+
@property
def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]:
return self.data.setdefault('objects', {}) # (objtype, name) -> docname, labelid
+ def note_object(self, objtype: str, name: str, labelid: str, location: Any = None
+ ) -> None:
+ """Note a generic object for cross reference.
+
+ .. versionadded:: 3.0
+ """
+ if (objtype, name) in self.objects:
+ docname = self.objects[objtype, name][0]
+ logger.warning(__('duplicate %s description of %s, other instance in %s'),
+ objtype, name, docname, location=location)
+ self.objects[objtype, name] = (self.env.docname, labelid)
+
+ def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None:
+ warnings.warn('StandardDomain.add_object() is deprecated.',
+ RemovedInSphinx50Warning)
+ self.objects[objtype, name] = (docname, labelid)
+
@property
def progoptions(self) -> Dict[Tuple[str, str], Tuple[str, str]]:
return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid
@@ -632,9 +734,6 @@ class StandardDomain(Domain):
continue
self.labels[name] = docname, labelid, sectname
- def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None:
- self.objects[objtype, name] = (docname, labelid)
-
def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None:
self.progoptions[program, name] = (docname, labelid)
@@ -814,30 +913,6 @@ class StandardDomain(Domain):
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
- def _resolve_citation_xref(self, env: "BuildEnvironment", fromdocname: str,
- builder: "Builder", typ: str, target: str,
- node: pending_xref, contnode: Element) -> Element:
- warnings.warn('StandardDomain._resolve_citation_xref() is deprecated.',
- RemovedInSphinx30Warning)
- docname, labelid, lineno = self.data['citations'].get(target, ('', '', 0))
- if not docname:
- if 'ids' in node:
- # remove ids attribute that annotated at
- # transforms.CitationReference.apply.
- del node['ids'][:]
- return None
-
- try:
- return make_refnode(builder, fromdocname, docname,
- labelid, contnode)
- except NoUri:
- # remove the ids we added in the CitationReferences
- # transform since they can't be transfered to
- # the contnode (if it's a Text node)
- if not isinstance(contnode, Element):
- del node['ids'][:]
- raise
-
def _resolve_obj_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
@@ -934,16 +1009,6 @@ class StandardDomain(Domain):
figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None))
return figtype
- def get_figtype(self, node: Node) -> str:
- """Get figure type of nodes.
-
- .. deprecated:: 1.8
- """
- warnings.warn('StandardDomain.get_figtype() is deprecated. '
- 'Please use get_enumerable_node_type() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.get_enumerable_node_type(node)
-
def get_fignumber(self, env: "BuildEnvironment", builder: "Builder",
figtype: str, docname: str, target_node: Element) -> Tuple[int, ...]:
if figtype == 'section':
diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py
index f1ae6024d..d40a6cbb3 100644
--- a/sphinx/environment/__init__.py
+++ b/sphinx/environment/__init__.py
@@ -13,9 +13,8 @@ import pickle
import warnings
from collections import defaultdict
from copy import copy
-from io import BytesIO
from os import path
-from typing import Any, Callable, Dict, Generator, IO, Iterator, List, Set, Tuple, Union
+from typing import Any, Callable, Dict, Generator, Iterator, List, Set, Tuple, Union
from typing import cast
from docutils import nodes
@@ -23,9 +22,7 @@ from docutils.nodes import Node
from sphinx import addnodes
from sphinx.config import Config
-from sphinx.deprecation import (
- RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
-)
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.domains import Domain
from sphinx.environment.adapters.toctree import TocTree
from sphinx.errors import SphinxError, BuildEnvironmentError, DocumentError, ExtensionError
@@ -221,6 +218,10 @@ class BuildEnvironment:
for domain in app.registry.create_domains(self):
self.domains[domain.name] = domain
+ # setup domains (must do after all initialization)
+ for domain in self.domains.values():
+ domain.setup()
+
# initialize config
self._update_config(app.config)
@@ -640,113 +641,6 @@ class BuildEnvironment:
domain.check_consistency()
self.events.emit('env-check-consistency', self)
- # --------- METHODS FOR COMPATIBILITY --------------------------------------
-
- def update(self, config: Config, srcdir: str, doctreedir: str) -> List[str]:
- warnings.warn('env.update() is deprecated. Please use builder.read() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.app.builder.read()
-
- def _read_serial(self, docnames: List[str], app: "Sphinx") -> None:
- warnings.warn('env._read_serial() is deprecated. Please use builder.read() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.app.builder._read_serial(docnames)
-
- def _read_parallel(self, docnames: List[str], app: "Sphinx", nproc: int) -> None:
- warnings.warn('env._read_parallel() is deprecated. Please use builder.read() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.app.builder._read_parallel(docnames, nproc)
-
- def read_doc(self, docname: str, app: "Sphinx" = None) -> None:
- warnings.warn('env.read_doc() is deprecated. Please use builder.read_doc() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.app.builder.read_doc(docname)
-
- def write_doctree(self, docname: str, doctree: nodes.document) -> None:
- warnings.warn('env.write_doctree() is deprecated. '
- 'Please use builder.write_doctree() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.app.builder.write_doctree(docname, doctree)
-
- @property
- def _nitpick_ignore(self) -> List[str]:
- warnings.warn('env._nitpick_ignore is deprecated. '
- 'Please use config.nitpick_ignore instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.config.nitpick_ignore
-
- @staticmethod
- def load(f: IO, app: "Sphinx" = None) -> "BuildEnvironment":
- warnings.warn('BuildEnvironment.load() is deprecated. '
- 'Please use pickle.load() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- try:
- env = pickle.load(f)
- except Exception as exc:
- # This can happen for example when the pickle is from a
- # different version of Sphinx.
- raise OSError(exc)
- if app:
- env.app = app
- env.config.values = app.config.values
- return env
-
- @classmethod
- def loads(cls, string: bytes, app: "Sphinx" = None) -> "BuildEnvironment":
- warnings.warn('BuildEnvironment.loads() is deprecated. '
- 'Please use pickle.loads() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- io = BytesIO(string)
- return cls.load(io, app)
-
- @classmethod
- def frompickle(cls, filename: str, app: "Sphinx") -> "BuildEnvironment":
- warnings.warn('BuildEnvironment.frompickle() is deprecated. '
- 'Please use pickle.load() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- with open(filename, 'rb') as f:
- return cls.load(f, app)
-
- @staticmethod
- def dump(env: "BuildEnvironment", f: IO) -> None:
- warnings.warn('BuildEnvironment.dump() is deprecated. '
- 'Please use pickle.dump() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- pickle.dump(env, f, pickle.HIGHEST_PROTOCOL)
-
- @classmethod
- def dumps(cls, env: "BuildEnvironment") -> bytes:
- warnings.warn('BuildEnvironment.dumps() is deprecated. '
- 'Please use pickle.dumps() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- io = BytesIO()
- cls.dump(env, io)
- return io.getvalue()
-
- def topickle(self, filename: str) -> None:
- warnings.warn('env.topickle() is deprecated. '
- 'Please use pickle.dump() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- with open(filename, 'wb') as f:
- self.dump(self, f)
-
- @property
- def versionchanges(self) -> Dict[str, List[Tuple[str, str, int, str, str, str]]]:
- warnings.warn('env.versionchanges() is deprecated. '
- 'Please use ChangeSetDomain instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.domaindata['changeset']['changes']
-
- def note_versionchange(self, type: str, version: str,
- node: addnodes.versionmodified, lineno: int) -> None:
- warnings.warn('env.note_versionchange() is deprecated. '
- 'Please use ChangeSetDomain.note_changeset() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- node['type'] = type
- node['version'] = version
- node.line = lineno
- self.get_domain('changeset').note_changeset(node) # type: ignore
-
@property
def indexentries(self) -> Dict[str, List[Tuple[str, str, str, str, str]]]:
warnings.warn('env.indexentries() is deprecated. Please use IndexDomain instead.',
@@ -762,13 +656,3 @@ class BuildEnvironment:
from sphinx.domains.index import IndexDomain
domain = cast(IndexDomain, self.get_domain('index'))
domain.data['entries'] = entries
-
-
-from sphinx.errors import NoUri # NOQA
-
-
-deprecated_alias('sphinx.environment',
- {
- 'NoUri': NoUri,
- },
- RemovedInSphinx30Warning)
diff --git a/sphinx/environment/adapters/indexentries.py b/sphinx/environment/adapters/indexentries.py
index 1d135bd32..5af213932 100644
--- a/sphinx/environment/adapters/indexentries.py
+++ b/sphinx/environment/adapters/indexentries.py
@@ -7,7 +7,7 @@
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
-import bisect
+
import re
import unicodedata
from itertools import groupby
@@ -52,8 +52,7 @@ class IndexEntries:
except NoUri:
pass
else:
- # maintain links in sorted/deterministic order
- bisect.insort(entry[0], (main, uri))
+ entry[0].append((main, uri))
domain = cast(IndexDomain, self.env.get_domain('index'))
for fn, entries in domain.entries.items():
@@ -89,6 +88,16 @@ class IndexEntries:
except ValueError as err:
logger.warning(str(err), location=fn)
+ # sort the index entries for same keyword.
+ def keyfunc0(entry: Tuple[str, str]) -> Tuple[bool, str]:
+ main, uri = entry
+ return (not main, uri) # show main entries at first
+
+ for indexentry in new.values():
+ indexentry[0].sort(key=keyfunc0)
+ for subentry in indexentry[1].values():
+ subentry[0].sort(key=keyfunc0) # type: ignore
+
# sort the index entries; put all symbols at the front, even those
# following the letters in ASCII, this is where the chr(127) comes from
def keyfunc(entry: Tuple[str, List]) -> Tuple[str, str]:
diff --git a/sphinx/events.py b/sphinx/events.py
index e6ea379eb..ff49f290c 100644
--- a/sphinx/events.py
+++ b/sphinx/events.py
@@ -11,8 +11,9 @@
"""
import warnings
-from collections import OrderedDict, defaultdict
-from typing import Any, Callable, Dict, List
+from collections import defaultdict
+from operator import attrgetter
+from typing import Any, Callable, Dict, List, NamedTuple
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.errors import ExtensionError
@@ -26,6 +27,10 @@ if False:
logger = logging.getLogger(__name__)
+EventListener = NamedTuple('EventListener', [('id', int),
+ ('handler', Callable),
+ ('priority', int)])
+
# List of all known core events. Maps name to arguments description.
core_events = {
@@ -57,7 +62,7 @@ class EventManager:
RemovedInSphinx40Warning)
self.app = app
self.events = core_events.copy()
- self.listeners = defaultdict(OrderedDict) # type: Dict[str, Dict[int, Callable]]
+ self.listeners = defaultdict(list) # type: Dict[str, List[EventListener]]
self.next_listener_id = 0
def add(self, name: str) -> None:
@@ -66,20 +71,22 @@ class EventManager:
raise ExtensionError(__('Event %r already present') % name)
self.events[name] = ''
- def connect(self, name: str, callback: Callable) -> int:
+ def connect(self, name: str, callback: Callable, priority: int) -> int:
"""Connect a handler to specific event."""
if name not in self.events:
raise ExtensionError(__('Unknown event name: %s') % name)
listener_id = self.next_listener_id
self.next_listener_id += 1
- self.listeners[name][listener_id] = callback
+ self.listeners[name].append(EventListener(listener_id, callback, priority))
return listener_id
def disconnect(self, listener_id: int) -> None:
"""Disconnect a handler."""
- for event in self.listeners.values():
- event.pop(listener_id, None)
+ for listeners in self.listeners.values():
+ for listener in listeners[:]:
+ if listener.id == listener_id:
+ listeners.remove(listener)
def emit(self, name: str, *args: Any) -> List:
"""Emit a Sphinx event."""
@@ -91,12 +98,13 @@ class EventManager:
pass
results = []
- for callback in self.listeners[name].values():
+ listeners = sorted(self.listeners[name], key=attrgetter("priority"))
+ for listener in listeners:
if self.app is None:
# for compatibility; RemovedInSphinx40Warning
- results.append(callback(*args))
+ results.append(listener.handler(*args))
else:
- results.append(callback(self.app, *args))
+ results.append(listener.handler(self.app, *args))
return results
def emit_firstresult(self, name: str, *args: Any) -> Any:
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 08e4e5301..7abd6c879 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -20,10 +20,8 @@ from docutils.statemachine import StringList
import sphinx
from sphinx.application import Sphinx
-from sphinx.config import Config, ENUM
-from sphinx.deprecation import (
- RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
-)
+from sphinx.config import ENUM
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.environment import BuildEnvironment
from sphinx.ext.autodoc.importer import import_object, get_module_members, get_object_members
from sphinx.ext.autodoc.mock import mock
@@ -32,7 +30,7 @@ from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import inspect
from sphinx.util import logging
from sphinx.util import rpartition
-from sphinx.util.docstrings import prepare_docstring
+from sphinx.util.docstrings import extract_metadata, prepare_docstring
from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature
from sphinx.util.typing import stringify as stringify_typehint
@@ -84,6 +82,14 @@ def members_set_option(arg: Any) -> Union[object, Set[str]]:
return {x.strip() for x in arg.split(',') if x.strip()}
+def inherited_members_option(arg: Any) -> Union[object, Set[str]]:
+ """Used to convert the :members: option to auto directives."""
+ if arg is None:
+ return 'object'
+ else:
+ return arg
+
+
SUPPRESS = object()
@@ -516,6 +522,17 @@ class Documenter:
The user can override the skipping decision by connecting to the
``autodoc-skip-member`` event.
"""
+ def is_filtered_inherited_member(name: str) -> bool:
+ if inspect.isclass(self.object):
+ for cls in self.object.__mro__:
+ if cls.__name__ == self.options.inherited_members and cls != self.object:
+ # given member is a member of specified *super class*
+ return True
+ elif name in cls.__dict__:
+ return False
+
+ return False
+
ret = []
# search for members in source code too
@@ -545,32 +562,45 @@ class Documenter:
doc = None
has_doc = bool(doc)
+ metadata = extract_metadata(doc)
+ if 'private' in metadata:
+ # consider a member private if docstring has "private" metadata
+ isprivate = True
+ else:
+ isprivate = membername.startswith('_')
+
keep = False
if want_all and membername.startswith('__') and \
membername.endswith('__') and len(membername) > 4:
# special __methods__
- if self.options.special_members is ALL and \
- membername != '__doc__':
- keep = has_doc or self.options.undoc_members
- elif self.options.special_members and \
- self.options.special_members is not ALL and \
- membername in self.options.special_members:
- keep = has_doc or self.options.undoc_members
+ if self.options.special_members is ALL:
+ if membername == '__doc__':
+ keep = False
+ elif is_filtered_inherited_member(membername):
+ keep = False
+ else:
+ keep = has_doc or self.options.undoc_members
+ elif self.options.special_members:
+ if membername in self.options.special_members:
+ keep = has_doc or self.options.undoc_members
elif (namespace, membername) in attr_docs:
- if want_all and membername.startswith('_'):
+ if want_all and isprivate:
# ignore members whose name starts with _ by default
keep = self.options.private_members
else:
# keep documented attributes
keep = True
isattr = True
- elif want_all and membername.startswith('_'):
+ elif want_all and isprivate:
# ignore members whose name starts with _ by default
keep = self.options.private_members and \
(has_doc or self.options.undoc_members)
else:
- # ignore undocumented members if :undoc-members: is not given
- keep = has_doc or self.options.undoc_members
+ if self.options.members is ALL and is_filtered_inherited_member(membername):
+ keep = False
+ else:
+ # ignore undocumented members if :undoc-members: is not given
+ keep = has_doc or self.options.undoc_members
# give the user a chance to decide whether this member
# should be skipped
@@ -744,7 +774,7 @@ class ModuleDocumenter(Documenter):
option_spec = {
'members': members_option, 'undoc-members': bool_option,
- 'noindex': bool_option, 'inherited-members': bool_option,
+ 'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'synopsis': identity,
'platform': identity, 'deprecated': bool_option,
'member-order': identity, 'exclude-members': members_set_option,
@@ -1035,7 +1065,7 @@ class DecoratorDocumenter(FunctionDocumenter):
# must be lower than FunctionDocumenter
priority = -1
- def format_args(self, **kwargs):
+ def format_args(self, **kwargs: Any) -> Any:
args = super().format_args(**kwargs)
if ',' in args:
return args
@@ -1051,7 +1081,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
member_order = 20
option_spec = {
'members': members_option, 'undoc-members': bool_option,
- 'noindex': bool_option, 'inherited-members': bool_option,
+ 'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'member-order': identity,
'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': members_option,
@@ -1116,7 +1146,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
if hasattr(self.object, '__bases__') and len(self.object.__bases__):
bases = [':class:`%s`' % b.__name__
if b.__module__ in ('__builtin__', 'builtins')
- else ':class:`%s.%s`' % (b.__module__, b.__name__)
+ else ':class:`%s.%s`' % (b.__module__, b.__qualname__)
for b in self.object.__bases__]
self.add_line(' ' + _('Bases: %s') % ', '.join(bases),
sourcename)
@@ -1575,38 +1605,6 @@ def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any:
return safe_getattr(obj, name, *defargs)
-def merge_autodoc_default_flags(app: Sphinx, config: Config) -> None:
- """This merges the autodoc_default_flags to autodoc_default_options."""
- if not config.autodoc_default_flags:
- return
-
- # Note: this option will be removed in Sphinx-4.0. But I marked this as
- # RemovedInSphinx *30* Warning because we have to emit warnings for users
- # who will be still in use with Sphinx-3.x. So we should replace this by
- # logger.warning() on 3.0.0 release.
- warnings.warn('autodoc_default_flags is now deprecated. '
- 'Please use autodoc_default_options instead.',
- RemovedInSphinx30Warning, stacklevel=2)
-
- for option in config.autodoc_default_flags:
- if isinstance(option, str):
- config.autodoc_default_options[option] = None
- else:
- logger.warning(
- __("Ignoring invalid option in autodoc_default_flags: %r"),
- option, type='autodoc'
- )
-
-
-from sphinx.ext.autodoc.mock import _MockImporter # NOQA
-
-deprecated_alias('sphinx.ext.autodoc',
- {
- '_MockImporter': _MockImporter,
- },
- RemovedInSphinx40Warning)
-
-
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(ModuleDocumenter)
app.add_autodocumenter(ClassDocumenter)
@@ -1635,7 +1633,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_event('autodoc-process-signature')
app.add_event('autodoc-skip-member')
- app.connect('config-inited', merge_autodoc_default_flags)
app.setup_extension('sphinx.ext.autodoc.type_comment')
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index c0381d7b4..e98b97915 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -180,12 +180,11 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
from sphinx.ext.autodoc.mock import ( # NOQA
- _MockImporter, _MockModule, _MockObject, MockFinder, MockLoader, mock
+ _MockModule, _MockObject, MockFinder, MockLoader, mock
)
deprecated_alias('sphinx.ext.autodoc.importer',
{
- '_MockImporter': _MockImporter,
'_MockModule': _MockModule,
'_MockObject': _MockObject,
'MockFinder': MockFinder,
diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py
index 0ee0fddc1..25f50d27e 100644
--- a/sphinx/ext/autodoc/mock.py
+++ b/sphinx/ext/autodoc/mock.py
@@ -11,13 +11,11 @@
import contextlib
import os
import sys
-import warnings
from importlib.abc import Loader, MetaPathFinder
from importlib.machinery import ModuleSpec
from types import FunctionType, MethodType, ModuleType
from typing import Any, Generator, Iterator, List, Sequence, Tuple, Union
-from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.util import logging
logger = logging.getLogger(__name__)
@@ -81,15 +79,11 @@ class _MockModule(ModuleType):
"""Used by autodoc_mock_imports."""
__file__ = os.devnull
- def __init__(self, name: str, loader: "_MockImporter" = None) -> None:
+ def __init__(self, name: str) -> None:
super().__init__(name)
self.__all__ = [] # type: List[str]
self.__path__ = [] # type: List[str]
- if loader is not None:
- warnings.warn('The loader argument for _MockModule is deprecated.',
- RemovedInSphinx30Warning)
-
def __getattr__(self, name: str) -> _MockObject:
return _make_subclass(name, self.__name__)()
@@ -97,44 +91,6 @@ class _MockModule(ModuleType):
return self.__name__
-class _MockImporter(MetaPathFinder):
- def __init__(self, names: List[str]) -> None:
- self.names = names
- self.mocked_modules = [] # type: List[str]
- # enable hook by adding itself to meta_path
- sys.meta_path.insert(0, self)
-
- warnings.warn('_MockImporter is now deprecated.',
- RemovedInSphinx30Warning)
-
- def disable(self) -> None:
- # remove `self` from `sys.meta_path` to disable import hook
- sys.meta_path = [i for i in sys.meta_path if i is not self]
- # remove mocked modules from sys.modules to avoid side effects after
- # running auto-documenter
- for m in self.mocked_modules:
- if m in sys.modules:
- del sys.modules[m]
-
- def find_module(self, name: str, path: Sequence[Union[bytes, str]] = None) -> Any:
- # check if name is (or is a descendant of) one of our base_packages
- for n in self.names:
- if n == name or name.startswith(n + '.'):
- return self
- return None
-
- def load_module(self, name: str) -> ModuleType:
- if name in sys.modules:
- # module has already been imported, return it
- return sys.modules[name]
- else:
- logger.debug('[autodoc] adding a mock module %s!', name)
- module = _MockModule(name, self)
- sys.modules[name] = module
- self.mocked_modules.append(name)
- return module
-
-
class MockLoader(Loader):
"""A loader for mocking."""
def __init__(self, finder: "MockFinder") -> None:
diff --git a/sphinx/ext/autodoc/type_comment.py b/sphinx/ext/autodoc/type_comment.py
index fb0eebfd8..e6a77f24d 100644
--- a/sphinx/ext/autodoc/type_comment.py
+++ b/sphinx/ext/autodoc/type_comment.py
@@ -14,6 +14,7 @@ from typing import cast
import sphinx
from sphinx.application import Sphinx
+from sphinx.locale import __
from sphinx.pycode.ast import ast
from sphinx.pycode.ast import parse as ast_parse
from sphinx.pycode.ast import unparse as ast_unparse
@@ -128,7 +129,7 @@ def update_annotations_using_type_comments(app: Sphinx, obj: Any, bound_method:
if 'return' not in obj.__annotations__:
obj.__annotations__['return'] = type_sig.return_annotation
except NotImplementedError as exc: # failed to ast.unparse()
- logger.warning("Failed to parse type_comment for %r: %s", obj, exc)
+ logger.warning(__("Failed to parse type_comment for %r: %s"), obj, exc)
def setup(app: Sphinx) -> Dict[str, Any]:
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index 7a68552df..3d296cbde 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -745,7 +745,8 @@ def process_generate_options(app: Sphinx) -> None:
with mock(app.config.autosummary_mock_imports):
generate_autosummary_docs(genfiles, builder=app.builder,
suffix=suffix, base_path=app.srcdir,
- app=app, imported_members=imported_members)
+ app=app, imported_members=imported_members,
+ overwrite=app.config.autosummary_generate_overwrite)
def setup(app: Sphinx) -> Dict[str, Any]:
@@ -768,6 +769,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.connect('doctree-read', process_autosummary_toc)
app.connect('builder-inited', process_generate_options)
app.add_config_value('autosummary_generate', [], 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')
app.add_config_value('autosummary_imported_members', [], False, [bool])
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index a59a97ea7..ef623e2bd 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -223,7 +223,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
suffix: str = '.rst', warn: Callable = None,
info: Callable = None, base_path: str = None,
builder: Builder = None, template_dir: str = None,
- imported_members: bool = False, app: Any = None) -> None:
+ imported_members: bool = False, app: Any = None,
+ overwrite: bool = True) -> None:
if info:
warnings.warn('info argument for generate_autosummary_docs() is deprecated.',
RemovedInSphinx40Warning)
@@ -271,22 +272,27 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
try:
name, obj, parent, mod_name = import_by_name(name)
except ImportError as e:
- _warn('[autosummary] failed to import %r: %s' % (name, e))
+ _warn(__('[autosummary] failed to import %r: %s') % (name, e))
continue
- fn = os.path.join(path, name + suffix)
+ content = generate_autosummary_content(name, obj, parent, template, template_name,
+ imported_members, app)
- # skip it if it exists
- if os.path.isfile(fn):
- continue
-
- new_files.append(fn)
+ filename = os.path.join(path, name + suffix)
+ if os.path.isfile(filename):
+ with open(filename) as f:
+ old_content = f.read()
- with open(fn, 'w') as f:
- rendered = generate_autosummary_content(name, obj, parent,
- template, template_name,
- imported_members, app)
- f.write(rendered)
+ if content == old_content:
+ continue
+ elif overwrite: # content has changed
+ with open(filename, 'w') as f:
+ f.write(content)
+ new_files.append(filename)
+ else:
+ with open(filename, 'w') as f:
+ f.write(content)
+ new_files.append(filename)
# descend recursively to new files
if new_files:
@@ -294,7 +300,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
suffix=suffix, warn=warn, info=info,
base_path=base_path, builder=builder,
template_dir=template_dir,
- imported_members=imported_members, app=app)
+ imported_members=imported_members, app=app,
+ overwrite=overwrite)
# -- Finding documented entries in files ---------------------------------------
diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py
index 987cf2919..e8157848f 100644
--- a/sphinx/ext/coverage.py
+++ b/sphinx/ext/coverage.py
@@ -123,7 +123,7 @@ class CoverageBuilder(Builder):
op.write(' * %-50s [%9s]\n' % (name, typ))
op.write('\n')
- def ignore_pyobj(self, full_name):
+ def ignore_pyobj(self, full_name: str) -> bool:
for exp in self.py_ignorexps:
if exp.search(full_name):
return True
diff --git a/sphinx/ext/duration.py b/sphinx/ext/duration.py
index 02e60cf7e..669baf2f1 100644
--- a/sphinx/ext/duration.py
+++ b/sphinx/ext/duration.py
@@ -11,8 +11,8 @@
from datetime import datetime, timedelta
from itertools import islice
from operator import itemgetter
+from typing import Any, Dict, List
from typing import cast
-from typing import Dict, List
from docutils import nodes
@@ -82,7 +82,7 @@ def on_build_finished(app: Sphinx, error: Exception) -> None:
logger.info('%d.%03d %s', d.seconds, d.microseconds / 1000, docname)
-def setup(app):
+def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(DurationDomain)
app.connect('builder-inited', on_builder_inited)
app.connect('source-read', on_source_read)
diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py
index 01803708d..db2a15b14 100644
--- a/sphinx/ext/inheritance_diagram.py
+++ b/sphinx/ext/inheritance_diagram.py
@@ -229,7 +229,7 @@ class InheritanceGraph:
if module in ('__builtin__', 'builtins'):
fullname = cls.__name__
else:
- fullname = '%s.%s' % (module, cls.__name__)
+ fullname = '%s.%s' % (module, cls.__qualname__)
if parts == 0:
result = fullname
else:
@@ -247,6 +247,7 @@ class InheritanceGraph:
default_graph_attrs = {
'rankdir': 'LR',
'size': '"8.0, 12.0"',
+ 'bgcolor': 'transparent',
}
default_node_attrs = {
'shape': 'box',
@@ -254,7 +255,8 @@ class InheritanceGraph:
'height': 0.25,
'fontname': '"Vera Sans, DejaVu Sans, Liberation Sans, '
'Arial, Helvetica, sans"',
- 'style': '"setlinewidth(0.5)"',
+ 'style': '"setlinewidth(0.5),filled"',
+ 'fillcolor': 'white',
}
default_edge_attrs = {
'arrowsize': 0.5,
diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py
deleted file mode 100644
index 99f117bf7..000000000
--- a/sphinx/ext/mathbase.py
+++ /dev/null
@@ -1,153 +0,0 @@
-"""
- sphinx.ext.mathbase
- ~~~~~~~~~~~~~~~~~~~
-
- Set up math support in source files and LaTeX/text output.
-
- :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-"""
-
-import warnings
-from typing import Callable, List, Tuple
-
-from docutils import nodes
-from docutils.nodes import Element, Node
-from docutils.parsers.rst.roles import math_role as math_role_base
-
-from sphinx.addnodes import math, math_block as displaymath # NOQA # to keep compatibility
-from sphinx.application import Sphinx
-from sphinx.builders.latex.nodes import math_reference as eqref # NOQA # to keep compatibility
-from sphinx.deprecation import RemovedInSphinx30Warning
-from sphinx.directives.patches import MathDirective as MathDirectiveBase
-from sphinx.domains.math import MathDomain # NOQA # to keep compatibility
-from sphinx.domains.math import MathReferenceRole as EqXRefRole # NOQA # to keep compatibility
-from sphinx.writers.html import HTMLTranslator
-from sphinx.writers.latex import LaTeXTranslator
-from sphinx.writers.manpage import ManualPageTranslator
-from sphinx.writers.texinfo import TexinfoTranslator
-from sphinx.writers.text import TextTranslator
-
-
-class MathDirective(MathDirectiveBase):
- def run(self) -> List[Node]:
- warnings.warn('sphinx.ext.mathbase.MathDirective is moved to '
- 'sphinx.directives.patches package.',
- RemovedInSphinx30Warning, stacklevel=2)
- return super().run()
-
-
-def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
- warnings.warn('sphinx.ext.mathbase.math_role() is deprecated. '
- 'Please use docutils.parsers.rst.roles.math_role() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return math_role_base(role, rawtext, text, lineno, inliner, options, content)
-
-
-def get_node_equation_number(writer: HTMLTranslator, node: nodes.math_block) -> str:
- warnings.warn('sphinx.ext.mathbase.get_node_equation_number() is moved to '
- 'sphinx.util.math package.',
- RemovedInSphinx30Warning, stacklevel=2)
- from sphinx.util.math import get_node_equation_number
- return get_node_equation_number(writer, node)
-
-
-def wrap_displaymath(text: str, label: str, numbering: bool) -> str:
- warnings.warn('sphinx.ext.mathbase.wrap_displaymath() is moved to '
- 'sphinx.util.math package.',
- RemovedInSphinx30Warning, stacklevel=2)
- from sphinx.util.math import wrap_displaymath
- return wrap_displaymath(text, label, numbering)
-
-
-def is_in_section_title(node: Element) -> bool:
- """Determine whether the node is in a section title"""
- from sphinx.util.nodes import traverse_parent
-
- warnings.warn('is_in_section_title() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
-
- for ancestor in traverse_parent(node):
- if isinstance(ancestor, nodes.title) and \
- isinstance(ancestor.parent, nodes.section):
- return True
- return False
-
-
-def latex_visit_math(self: LaTeXTranslator, node: Element) -> None:
- warnings.warn('latex_visit_math() is deprecated. '
- 'Please use LaTeXTranslator.visit_math() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.visit_math(node)
-
-
-def latex_visit_displaymath(self: LaTeXTranslator, node: Element) -> None:
- warnings.warn('latex_visit_displaymath() is deprecated. '
- 'Please use LaTeXTranslator.visit_math_block() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.visit_math_block(node)
-
-
-def man_visit_math(self: ManualPageTranslator, node: Element) -> None:
- warnings.warn('man_visit_math() is deprecated. '
- 'Please use ManualPageTranslator.visit_math() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.visit_math(node)
-
-
-def man_visit_displaymath(self: ManualPageTranslator, node: Element) -> None:
- warnings.warn('man_visit_displaymath() is deprecated. '
- 'Please use ManualPageTranslator.visit_math_block() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.visit_math_block(node)
-
-
-def man_depart_displaymath(self: ManualPageTranslator, node: Element) -> None:
- warnings.warn('man_depart_displaymath() is deprecated. '
- 'Please use ManualPageTranslator.depart_math_block() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.depart_math_block(node)
-
-
-def texinfo_visit_math(self: TexinfoTranslator, node: Element) -> None:
- warnings.warn('texinfo_visit_math() is deprecated. '
- 'Please use TexinfoTranslator.visit_math() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.visit_math(node)
-
-
-def texinfo_visit_displaymath(self: TexinfoTranslator, node: Element) -> None:
- warnings.warn('texinfo_visit_displaymath() is deprecated. '
- 'Please use TexinfoTranslator.visit_math_block() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.visit_math_block(node)
-
-
-def texinfo_depart_displaymath(self: TexinfoTranslator, node: Element) -> None:
- warnings.warn('texinfo_depart_displaymath() is deprecated. '
- 'Please use TexinfoTranslator.depart_math_block() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
-
-
-def text_visit_math(self: TextTranslator, node: Element) -> None:
- warnings.warn('text_visit_math() is deprecated. '
- 'Please use TextTranslator.visit_math() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.visit_math(node)
-
-
-def text_visit_displaymath(self: TextTranslator, node: Element) -> None:
- warnings.warn('text_visit_displaymath() is deprecated. '
- 'Please use TextTranslator.visit_math_block() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.visit_math_block(node)
-
-
-def setup_math(app: Sphinx,
- htmlinlinevisitors: Tuple[Callable, Callable],
- htmldisplayvisitors: Tuple[Callable, Callable]) -> None:
- warnings.warn('setup_math() is deprecated. '
- 'Please use app.add_html_math_renderer() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
-
- app.add_html_math_renderer('unknown', htmlinlinevisitors, htmldisplayvisitors)
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py
index 81f2496cb..7f6ebe478 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -561,7 +561,7 @@ class GoogleDocstring:
lines = self._consume_to_next_section()
self._parsed_lines.extend(lines)
- def _parse_admonition(self, admonition, section):
+ def _parse_admonition(self, admonition: str, section: str) -> List[str]:
# type (str, str) -> List[str]
lines = self._consume_to_next_section()
return self._format_admonition(admonition, lines)
@@ -603,7 +603,7 @@ class GoogleDocstring:
label = labels.get(section.lower(), section)
return self._parse_generic_section(label, use_admonition)
- def _parse_custom_generic_section(self, section):
+ def _parse_custom_generic_section(self, section: str) -> List[str]:
# for now, no admonition for simple custom sections
return self._parse_generic_section(section, False)
diff --git a/sphinx/ext/napoleon/iterators.py b/sphinx/ext/napoleon/iterators.py
index 72ab74120..e91a3ec14 100644
--- a/sphinx/ext/napoleon/iterators.py
+++ b/sphinx/ext/napoleon/iterators.py
@@ -60,9 +60,7 @@ class peek_iter:
return self
def __next__(self, n: int = None) -> Any:
- # note: prevent 2to3 to transform self.next() in next(self) which
- # causes an infinite loop !
- return getattr(self, 'next')(n)
+ return self.next(n)
def _fillcache(self, n: int) -> None:
"""Cache `n` items. If `n` is 0 or None, then 1 item is cached."""
diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py
index 82c2111ef..dc24a1993 100644
--- a/sphinx/ext/viewcode.py
+++ b/sphinx/ext/viewcode.py
@@ -9,7 +9,6 @@
"""
import traceback
-import warnings
from typing import Any, Dict, Iterable, Iterator, Set, Tuple
from docutils import nodes
@@ -18,8 +17,6 @@ from docutils.nodes import Element, Node
import sphinx
from sphinx import addnodes
from sphinx.application import Sphinx
-from sphinx.config import Config
-from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
from sphinx.pycode import ModuleAnalyzer
@@ -57,10 +54,10 @@ def doctree_read(app: Sphinx, doctree: Node) -> None:
if app.builder.name.startswith("epub") and not env.config.viewcode_enable_epub:
return
- def has_tag(modname, fullname, docname, refname):
+ def has_tag(modname: str, fullname: str, docname: str, refname: str) -> bool:
entry = env._viewcode_modules.get(modname, None) # type: ignore
if entry is False:
- return
+ return False
code_tags = app.emit_firstresult('viewcode-find-source', modname)
if code_tags is None:
@@ -69,7 +66,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None:
analyzer.find_tags()
except Exception:
env._viewcode_modules[modname] = False # type: ignore
- return
+ return False
code = analyzer.code
tags = analyzer.tags
@@ -84,6 +81,8 @@ def doctree_read(app: Sphinx, doctree: Node) -> None:
used[fullname] = docname
return True
+ return False
+
for objnode in doctree.traverse(addnodes.desc):
if objnode.get('domain') != 'py':
continue
@@ -234,18 +233,10 @@ def collect_pages(app: Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]:
yield ('_modules/index', context, 'page.html')
-def migrate_viewcode_import(app: Sphinx, config: Config) -> None:
- if config.viewcode_import is not None:
- warnings.warn('viewcode_import was renamed to viewcode_follow_imported_members. '
- 'Please update your configuration.',
- RemovedInSphinx30Warning, stacklevel=2)
-
-
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('viewcode_import', None, False)
app.add_config_value('viewcode_enable_epub', False, False)
app.add_config_value('viewcode_follow_imported_members', True, False)
- app.connect('config-inited', migrate_viewcode_import)
app.connect('doctree-read', doctree_read)
app.connect('env-merge-info', env_merge_info)
app.connect('html-collect-pages', collect_pages)
diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py
index 49a4105bf..4dc9ce543 100644
--- a/sphinx/highlighting.py
+++ b/sphinx/highlighting.py
@@ -8,8 +8,6 @@
:license: BSD, see LICENSE for details.
"""
-import html
-import warnings
from functools import partial
from importlib import import_module
from typing import Any, Dict
@@ -26,8 +24,6 @@ from pygments.style import Style
from pygments.styles import get_style_by_name
from pygments.util import ClassNotFound
-from sphinx.deprecation import RemovedInSphinx30Warning
-from sphinx.ext import doctest
from sphinx.locale import __
from sphinx.pygments_styles import SphinxStyle, NoneStyle
from sphinx.util import logging, texescape
@@ -65,7 +61,7 @@ class PygmentsBridge:
latex_formatter = LatexFormatter
def __init__(self, dest: str = 'html', stylename: str = 'sphinx',
- trim_doctest_flags: bool = None, latex_engine: str = None) -> None:
+ latex_engine: str = None) -> None:
self.dest = dest
self.latex_engine = latex_engine
@@ -77,11 +73,6 @@ class PygmentsBridge:
self.formatter = self.latex_formatter
self.formatter_args['commandprefix'] = 'PYG'
- self.trim_doctest_flags = trim_doctest_flags
- if trim_doctest_flags is not None:
- warnings.warn('trim_doctest_flags option for PygmentsBridge is now deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
-
def get_style(self, stylename: str) -> Style:
if stylename is None or stylename == 'sphinx':
return SphinxStyle
@@ -97,19 +88,6 @@ class PygmentsBridge:
kwargs.update(self.formatter_args)
return self.formatter(**kwargs)
- def unhighlighted(self, source: str) -> str:
- warnings.warn('PygmentsBridge.unhighlighted() is now deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- if self.dest == 'html':
- return '<pre>' + html.escape(source) + '</pre>\n'
- else:
- # first, escape highlighting characters like Pygments does
- source = source.translate(escape_hl_chars)
- # then, escape all characters nonrepresentable in LaTeX
- source = texescape.escape(source, self.latex_engine)
- return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \
- source + '\\end{Verbatim}\n'
-
def get_lexer(self, source: str, lang: str, opts: Dict = None,
force: bool = False, location: Any = None) -> Lexer:
if not opts:
@@ -127,11 +105,6 @@ class PygmentsBridge:
lang = 'pycon3'
else:
lang = 'python3'
- elif lang == 'guess':
- try:
- lexer = guess_lexer(source)
- except Exception:
- lexer = lexers['none']
if lang in lexers:
# just return custom lexers here (without installing raiseonerror filter)
@@ -141,7 +114,7 @@ class PygmentsBridge:
else:
try:
if lang == 'guess':
- lexer = guess_lexer(lang, **opts)
+ lexer = guess_lexer(source, **opts)
else:
lexer = get_lexer_by_name(lang, **opts)
except ClassNotFound:
@@ -161,11 +134,6 @@ class PygmentsBridge:
lexer = self.get_lexer(source, lang, opts, force, location)
- # trim doctest options if wanted
- if isinstance(lexer, PythonConsoleLexer) and self.trim_doctest_flags:
- source = doctest.blankline_re.sub('', source)
- source = doctest.doctestopt_re.sub('', source)
-
# highlight via Pygments
formatter = self.get_formatter(**kwargs)
try:
diff --git a/sphinx/io.py b/sphinx/io.py
index b1290ae89..18b4f053e 100644
--- a/sphinx/io.py
+++ b/sphinx/io.py
@@ -9,7 +9,7 @@
"""
import codecs
import warnings
-from typing import Any, List, Tuple
+from typing import Any, List
from typing import Type # for python3.5.1
from docutils import nodes
@@ -19,14 +19,11 @@ from docutils.io import FileInput, Input, NullOutput
from docutils.parsers import Parser
from docutils.parsers.rst import Parser as RSTParser
from docutils.readers import standalone
-from docutils.statemachine import StringList, string2lines
from docutils.transforms import Transform
from docutils.transforms.references import DanglingReferences
from docutils.writers import UnfilteredWriter
-from sphinx.deprecation import (
- RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
-)
+from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.environment import BuildEnvironment
from sphinx.errors import FiletypeNotFoundError
from sphinx.transforms import (
@@ -39,7 +36,6 @@ from sphinx.transforms.references import SphinxDomains
from sphinx.util import logging, get_filetype
from sphinx.util import UnicodeDecodeErrorHandler
from sphinx.util.docutils import LoggingReporter
-from sphinx.util.rst import append_epilog, docinfo_re, prepend_prolog
from sphinx.versioning import UIDTransform
if False:
@@ -160,17 +156,6 @@ class SphinxI18nReader(SphinxBaseReader):
if transform in self.transforms:
self.transforms.remove(transform)
- def set_lineno_for_reporter(self, lineno: int) -> None:
- """Stores the source line number of original text."""
- warnings.warn('SphinxI18nReader.set_lineno_for_reporter() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
-
- @property
- def line(self) -> int:
- warnings.warn('SphinxI18nReader.line is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return 0
-
class SphinxDummyWriter(UnfilteredWriter):
"""Dummy writer module used for generating doctree."""
@@ -186,93 +171,13 @@ def SphinxDummySourceClass(source: Any, *args: Any, **kwargs: Any) -> Any:
return source
-class SphinxBaseFileInput(FileInput):
- """A base class of SphinxFileInput.
-
- It supports to replace unknown Unicode characters to '?'.
- """
-
- def __init__(self, app: "Sphinx", env: BuildEnvironment,
- *args: Any, **kwargs: Any) -> None:
- self.app = app
- self.env = env
-
- warnings.warn('%s is deprecated.' % self.__class__.__name__,
- RemovedInSphinx30Warning, stacklevel=2)
-
- kwargs['error_handler'] = 'sphinx' # py3: handle error on open.
- super().__init__(*args, **kwargs)
-
- def warn_and_replace(self, error: Any) -> Tuple:
- return UnicodeDecodeErrorHandler(self.env.docname)(error)
-
-
class SphinxFileInput(FileInput):
"""A basic FileInput for Sphinx."""
- supported = ('*',) # RemovedInSphinx30Warning
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
kwargs['error_handler'] = 'sphinx'
super().__init__(*args, **kwargs)
-class SphinxRSTFileInput(SphinxBaseFileInput):
- """A reST FileInput for Sphinx.
-
- This FileInput automatically prepends and appends text by :confval:`rst_prolog` and
- :confval:`rst_epilog`.
-
- .. important::
-
- This FileInput uses an instance of ``StringList`` as a return value of ``read()``
- method to indicate original source filename and line numbers after prepending and
- appending.
- For that reason, ``sphinx.parsers.RSTParser`` should be used with this to parse
- a content correctly.
- """
- supported = ('restructuredtext',)
-
- def prepend_prolog(self, text: StringList, prolog: str) -> None:
- docinfo = self.count_docinfo_lines(text)
- if docinfo:
- # insert a blank line after docinfo
- text.insert(docinfo, '', '<generated>', 0)
- docinfo += 1
-
- # insert prolog (after docinfo if exists)
- for lineno, line in enumerate(prolog.splitlines()):
- text.insert(docinfo + lineno, line, '<rst_prolog>', lineno)
-
- text.insert(docinfo + lineno + 1, '', '<generated>', 0)
-
- def append_epilog(self, text: StringList, epilog: str) -> None:
- # append a blank line and rst_epilog
- text.append('', '<generated>', 0)
- for lineno, line in enumerate(epilog.splitlines()):
- text.append(line, '<rst_epilog>', lineno)
-
- def read(self) -> StringList: # type: ignore
- inputstring = super().read()
- lines = string2lines(inputstring, convert_whitespace=True)
- content = StringList()
- for lineno, line in enumerate(lines):
- content.append(line, self.source_path, lineno)
-
- prepend_prolog(content, self.env.config.rst_prolog)
- append_epilog(content, self.env.config.rst_epilog)
-
- return content
-
- def count_docinfo_lines(self, content: StringList) -> int:
- if len(content) == 0:
- return 0
- else:
- for lineno, line in enumerate(content.data):
- if not docinfo_re.match(line):
- break
- return lineno
-
-
def read_doc(app: "Sphinx", env: BuildEnvironment, filename: str) -> nodes.document:
"""Parse a document and convert to doctree."""
# set up error_handler for the target document
diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py
index 262425a36..9812355ca 100644
--- a/sphinx/locale/__init__.py
+++ b/sphinx/locale/__init__.py
@@ -10,13 +10,10 @@
import gettext
import locale
-import warnings
from collections import UserString, defaultdict
from gettext import NullTranslations
from typing import Any, Callable, Dict, Iterable, List, Tuple, Union
-from sphinx.deprecation import RemovedInSphinx30Warning
-
class _TranslationProxy(UserString):
"""
@@ -106,24 +103,6 @@ class _TranslationProxy(UserString):
return '<%s broken>' % self.__class__.__name__
-def mygettext(string: str) -> str:
- """Used instead of _ when creating TranslationProxies, because _ is
- not bound yet at that time.
- """
- warnings.warn('sphinx.locale.mygettext() is deprecated. Please use `_()` instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return _(string)
-
-
-def lazy_gettext(string: str) -> str:
- """A lazy version of `gettext`."""
- # if isinstance(string, _TranslationProxy):
- # return string
- warnings.warn('sphinx.locale.laxy_gettext() is deprecated. Please use `_()` instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return _TranslationProxy(mygettext, string) # type: ignore
-
-
translators = defaultdict(NullTranslations) # type: Dict[Tuple[str, str], NullTranslations]
@@ -218,7 +197,7 @@ def _lazy_translate(catalog: str, namespace: str, message: str) -> str:
return translator.gettext(message)
-def get_translation(catalog, namespace='general'):
+def get_translation(catalog: str, namespace: str = 'general') -> Callable:
"""Get a translation function based on the *catalog* and *namespace*.
The extension can use this API to translate the messages on the
@@ -266,12 +245,6 @@ _ = get_translation('sphinx')
__ = get_translation('sphinx', 'console')
-def l_(*args):
- warnings.warn('sphinx.locale.l_() is deprecated. Please use `_()` instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return _(*args)
-
-
# labels
admonitionlabels = {
'attention': _('Attention'),
diff --git a/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po b/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po
index 0d402ede1..c651d7510 100644
--- a/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po
+++ b/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po
@@ -1,7 +1,7 @@
# Translations template for Sphinx.
# Copyright (C) 2019 ORGANIZATION
# This file is distributed under the same license as the Sphinx project.
-#
+#
# Translators:
# Yinian Chin <yinian1992@live.com>, 2015,2017-2018
# Hsiaoming Yang <me@lepture.com>, 2018
@@ -28,6 +28,7 @@ msgstr ""
"Generated-By: Babel 2.6.0\n"
"Language: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 2.2.4\n"
#: sphinx/application.py:153
#, python-format
@@ -50,9 +51,7 @@ msgstr "正在运行 Sphinx v%s"
#: sphinx/application.py:214
#, python-format
-msgid ""
-"This project needs at least Sphinx v%s and therefore cannot be built with "
-"this version."
+msgid "This project needs at least Sphinx v%s and therefore cannot be built with this version."
msgstr "该项目需要 Sphinx v%s 及以上版本,使用现有版本不能构建文档。"
#: sphinx/application.py:234
@@ -66,9 +65,8 @@ msgstr "同时设置扩展名 %s:"
#: sphinx/application.py:245
msgid ""
-"'setup' as currently defined in conf.py isn't a Python callable. Please "
-"modify its definition to make it a callable function. This is needed for "
-"conf.py to behave as a Sphinx extension."
+"'setup' as currently defined in conf.py isn't a Python callable. Please modify its definition to make it a callable function. This is needed for conf.py to "
+"behave as a Sphinx extension."
msgstr "当前 conf.py 中定义的 'setup' 不是一个可调用的 Python 对象。请把其定义改为一个可调用的函数。Sphinx 扩展的 conf.py 必须这样配置。"
#: sphinx/application.py:269
@@ -76,9 +74,8 @@ msgstr "当前 conf.py 中定义的 'setup' 不是一个可调用的 Python 对
msgid "loading translations [%s]... "
msgstr "正在加载翻译 [%s]... "
-#: sphinx/application.py:285 sphinx/builders/html.py:852
-#: sphinx/builders/html.py:870 sphinx/builders/html.py:1133
-#: sphinx/builders/html.py:1151 sphinx/util/__init__.py:702
+#: sphinx/application.py:285 sphinx/builders/html.py:852 sphinx/builders/html.py:870 sphinx/builders/html.py:1133 sphinx/builders/html.py:1151
+#: sphinx/util/__init__.py:702
msgid "done"
msgstr "完成"
@@ -134,18 +131,12 @@ msgstr "角色 %r 已注册,将被覆盖"
#: sphinx/application.py:1179
#, python-format
-msgid ""
-"the %s extension does not declare if it is safe for parallel reading, "
-"assuming it isn't - please ask the extension author to check and make it "
-"explicit"
+msgid "the %s extension does not declare if it is safe for parallel reading, assuming it isn't - please ask the extension author to check and make it explicit"
msgstr "扩展 %s 没有声明是否并行读取安全,默认假定为否 - 请联系扩展作者检查是否支持该特性并显式声明"
#: sphinx/application.py:1185
#, python-format
-msgid ""
-"the %s extension does not declare if it is safe for parallel writing, "
-"assuming it isn't - please ask the extension author to check and make it "
-"explicit"
+msgid "the %s extension does not declare if it is safe for parallel writing, assuming it isn't - please ask the extension author to check and make it explicit"
msgstr "%s 扩展没有声明是否并行写入安全,默认假定为否 - 请联系扩展作者检查是否支持该特性并显式声明"
#: sphinx/application.py:1196
@@ -155,9 +146,7 @@ msgstr "执行顺序 %s"
#: sphinx/config.py:220
#, python-format
-msgid ""
-"cannot override dictionary config setting %r, ignoring (use %r to set "
-"individual elements)"
+msgid "cannot override dictionary config setting %r, ignoring (use %r to set individual elements)"
msgstr "不能覆盖字典配置项 %r,已忽略 (请用 %r 设置单个字典元素)"
#: sphinx/config.py:229
@@ -191,8 +180,7 @@ msgid "There is a syntax error in your configuration file: %s\n"
msgstr "配置文件中存在语法错误: %s\n"
#: sphinx/config.py:366
-msgid ""
-"The configuration file (or one of the modules it imports) called sys.exit()"
+msgid "The configuration file (or one of the modules it imports) called sys.exit()"
msgstr "配置文件(或配置文件导入的模块)调用了 sys.exit()"
#: sphinx/config.py:370
@@ -201,7 +189,10 @@ msgid ""
"There is a programmable error in your configuration file:\n"
"\n"
"%s"
-msgstr "配置文件中有程序上的错误:\n\n%s"
+msgstr ""
+"配置文件中有程序上的错误:\n"
+"\n"
+"%s"
#: sphinx/config.py:397
#, python-format
@@ -231,9 +222,7 @@ msgid "Listing %s"
msgstr "列表 %s"
#: sphinx/config.py:447
-msgid ""
-"The config value `{name}` has to be a one of {candidates}, but `{current}` "
-"is given."
+msgid "The config value `{name}` has to be a one of {candidates}, but `{current}` is given."
msgstr "配置项 `{name}` 必须设置为 {candidates} 之一,但现在是 `{current}` 。"
#: sphinx/config.py:465
@@ -243,16 +232,12 @@ msgid ""
msgstr "配置值\"[name]\"的类型为\"[当前._name];但\"当前\"为\"当前\"。\"当前\"为\"当前\"。\"当前\"为\"当前\"。=================================预期 [允许]。"
#: sphinx/config.py:478
-msgid ""
-"The config value `{name}' has type `{current.__name__}', defaults to "
-"`{default.__name__}'."
+msgid "The config value `{name}' has type `{current.__name__}', defaults to `{default.__name__}'."
msgstr "配置项 `{name}' 的类型是 `{current.__name__}',默认为 `{default.__name__}'。"
#: sphinx/config.py:497
#, python-format
-msgid ""
-"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."
+msgid "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."
msgstr "配置项 %r 的值包含了非 ASCII 字符,这会导致 Unicode 错误。请使用 Unicode 字符串,例如 %r。"
#: sphinx/config.py:506
@@ -278,16 +263,12 @@ msgstr "未知事件名称:%s"
#: sphinx/extension.py:52
#, python-format
-msgid ""
-"The %s extension is required by needs_extensions settings, but it is not "
-"loaded."
+msgid "The %s extension is required by needs_extensions settings, but it is not loaded."
msgstr "未能加载 needs_extensions 配置项所需的 %s。"
#: sphinx/extension.py:57
#, python-format
-msgid ""
-"This project needs the extension %s at least in version %s and therefore "
-"cannot be built with the loaded version (%s)."
+msgid "This project needs the extension %s at least in version %s and therefore cannot be built with the loaded version (%s)."
msgstr "该项目所需扩展 %s 最低要求版本 %s ,当前加载版本 (%s) 无法构建文档。"
#: sphinx/highlighting.py:142
@@ -417,23 +398,17 @@ msgstr "无法导入扩展 %s"
#: sphinx/registry.py:479
#, python-format
-msgid ""
-"extension %r has no setup() function; is it really a Sphinx extension "
-"module?"
+msgid "extension %r has no setup() function; is it really a Sphinx extension module?"
msgstr "扩展 %r 未包含setup() 函数;它确实是一个 Sphinx 扩展模块吗?"
#: sphinx/registry.py:488
#, python-format
-msgid ""
-"The %s extension used by this project needs at least Sphinx v%s; it "
-"therefore cannot be built with this version."
+msgid "The %s extension used by this project needs at least Sphinx v%s; it therefore cannot be built with this version."
msgstr "该项目所用扩展 %s 需要 Sphinx 版本 %s 以上;当前版本无法构建文档。"
#: sphinx/registry.py:496
#, python-format
-msgid ""
-"extension %r returned an unsupported object from its setup() function; it "
-"should return None or a metadata dictionary"
+msgid "extension %r returned an unsupported object from its setup() function; it should return None or a metadata dictionary"
msgstr "扩展 %r 在其 setup() 函数中返回了一个不支持的对象;该函数应返回 None 或一个元数据字典"
#: sphinx/roles.py:221 sphinx/roles.py:272
@@ -472,9 +447,7 @@ msgid "file %r on theme path is not a valid zipfile or contains no theme"
msgstr "主题路径指定的文件 %r 是一个无效的或不包含主题的 zip 文件"
#: sphinx/theming.py:258
-msgid ""
-"sphinx_rtd_theme is no longer a hard dependency since version 1.4.0. Please "
-"install it manually.(pip install sphinx_rtd_theme)"
+msgid "sphinx_rtd_theme is no longer a hard dependency since version 1.4.0. Please install it manually.(pip install sphinx_rtd_theme)"
msgstr "sphinx_rtd_theme 从 1.4.0 版本开始不再作为强依赖。请手动安装。(pip install sphinx_rtd_theme)"
#: sphinx/theming.py:262
@@ -496,8 +469,7 @@ msgstr "没有找到适合 %s 构建器的图像:%s"
msgid "building [mo]: "
msgstr "构建 [mo]: "
-#: sphinx/builders/__init__.py:232 sphinx/builders/__init__.py:574
-#: sphinx/builders/__init__.py:602
+#: sphinx/builders/__init__.py:232 sphinx/builders/__init__.py:574 sphinx/builders/__init__.py:602
msgid "writing output... "
msgstr "写入输出... "
@@ -522,8 +494,7 @@ msgstr "所有源文件"
#: sphinx/builders/__init__.py:298
#, python-format
-msgid ""
-"file %r given on command line is not under the source directory, ignoring"
+msgid "file %r given on command line is not under the source directory, ignoring"
msgstr "源文件目录下没有命令行给出的 %r 文件,将被忽略"
#: sphinx/builders/__init__.py:303
@@ -602,8 +573,7 @@ msgstr "准备文件"
msgid "duplicated ToC entry found: %s"
msgstr "找到重复的ToC条目: %s"
-#: sphinx/builders/_epub_base.py:414 sphinx/builders/html.py:761
-#: sphinx/builders/latex/__init__.py:415 sphinx/builders/texinfo.py:190
+#: sphinx/builders/_epub_base.py:414 sphinx/builders/html.py:761 sphinx/builders/latex/__init__.py:415 sphinx/builders/texinfo.py:190
msgid "copying images... "
msgstr "复制图像... "
@@ -612,8 +582,7 @@ msgstr "复制图像... "
msgid "cannot read image file %r: copying it instead"
msgstr "无法读取图像文件 %r:直接复制"
-#: sphinx/builders/_epub_base.py:427 sphinx/builders/html.py:769
-#: sphinx/builders/latex/__init__.py:423 sphinx/builders/texinfo.py:199
+#: sphinx/builders/_epub_base.py:427 sphinx/builders/html.py:769 sphinx/builders/latex/__init__.py:423 sphinx/builders/texinfo.py:199
#, python-format
msgid "cannot copy image file %r: %s"
msgstr "无法复制图像文件 %r:%s"
@@ -627,8 +596,7 @@ msgstr "无法写入图像文件 %r:%s"
msgid "Pillow not found - copying image files"
msgstr "未找到Pillow - 复制图像文件"
-#: sphinx/builders/_epub_base.py:490 sphinx/builders/_epub_base.py:503
-#: sphinx/builders/_epub_base.py:539 sphinx/builders/_epub_base.py:724
+#: sphinx/builders/_epub_base.py:490 sphinx/builders/_epub_base.py:503 sphinx/builders/_epub_base.py:539 sphinx/builders/_epub_base.py:724
#: sphinx/builders/_epub_base.py:757 sphinx/builders/epub3.py:183
#, python-format
msgid "writing %s file..."
@@ -762,9 +730,7 @@ msgstr "HTML 页面保存在 %(outdir)s 目录。"
msgid "Failed to read build info file: %r"
msgstr "读取构建信息文件失败:%r"
-#: sphinx/builders/html.py:488 sphinx/builders/latex/__init__.py:206
-#: sphinx/transforms/__init__.py:121 sphinx/writers/manpage.py:118
-#: sphinx/writers/texinfo.py:243
+#: sphinx/builders/html.py:488 sphinx/builders/latex/__init__.py:206 sphinx/transforms/__init__.py:121 sphinx/writers/manpage.py:118 sphinx/writers/texinfo.py:243
#, python-format
msgid "%b %d, %Y"
msgstr "%Y 年 %m 月 %d 日"
@@ -850,9 +816,7 @@ msgid "Failed to write build info file: %r"
msgstr "写入构建信息文件失败:%r"
#: sphinx/builders/html.py:927
-msgid ""
-"search index couldn't be loaded, but not all documents will be built: the "
-"index will be incomplete."
+msgid "search index couldn't be loaded, but not all documents will be built: the index will be incomplete."
msgstr "无法加载搜索索引,不会构建所有文档:索引将不完整。"
#: sphinx/builders/html.py:996
@@ -862,9 +826,7 @@ msgstr "页面 %s 匹配了 html_sidebars 中的两条规则:%r 和 %r"
#: sphinx/builders/html.py:1094
#, python-format
-msgid ""
-"a Unicode error occurred when rendering the page %s. Please make sure all "
-"config values that contain non-ASCII content are Unicode strings."
+msgid "a Unicode error occurred when rendering the page %s. Please make sure all config values that contain non-ASCII content are Unicode strings."
msgstr "渲染页面 %s 时发生了 Unicode 错误。请确保所有包含非 ASCII 字符的配置项是 Unicode 字符串。"
#: sphinx/builders/html.py:1099
@@ -872,10 +834,11 @@ msgstr "渲染页面 %s 时发生了 Unicode 错误。请确保所有包含非 A
msgid ""
"An error happened in rendering the page %s.\n"
"Reason: %r"
-msgstr "渲染页面 %s 时发生了错误。\n原因:%r"
+msgstr ""
+"渲染页面 %s 时发生了错误。\n"
+"原因:%r"
-#: sphinx/builders/html.py:1111 sphinx/builders/text.py:85
-#: sphinx/builders/xml.py:99
+#: sphinx/builders/html.py:1111 sphinx/builders/text.py:85 sphinx/builders/xml.py:99
#, python-format
msgid "error writing file %s: %s"
msgstr "写入文件 %s 时发生错误:%s"
@@ -932,8 +895,7 @@ msgstr "手册页保存在 %(outdir)s 目录。"
msgid "no \"man_pages\" config value found; no manual pages will be written"
msgstr "未找到“man_pages”配置项,不会写入手册页"
-#: sphinx/builders/latex/__init__.py:272 sphinx/builders/manpage.py:64
-#: sphinx/builders/singlehtml.py:174 sphinx/builders/texinfo.py:120
+#: sphinx/builders/latex/__init__.py:272 sphinx/builders/manpage.py:64 sphinx/builders/singlehtml.py:174 sphinx/builders/texinfo.py:120
msgid "writing"
msgstr "写作"
@@ -965,7 +927,10 @@ msgid ""
"\n"
"Run 'make' in that directory to run these through makeinfo\n"
"(use 'make info' here to do that automatically)."
-msgstr "\n在该目录下运行“make”命令以通过 makeinfo 运行这些 Texinfo文件\n(在此处用“make info”即可自动执行)。"
+msgstr ""
+"\n"
+"在该目录下运行“make”命令以通过 makeinfo 运行这些 Texinfo文件\n"
+"(在此处用“make info”即可自动执行)。"
#: sphinx/builders/texinfo.py:85
msgid "no \"texinfo_documents\" config value found; no documents will be written"
@@ -1023,7 +988,10 @@ msgid ""
"\n"
"Run 'make' in that directory to run these through (pdf)latex\n"
"(use `make latexpdf' here to do that automatically)."
-msgstr "\n在该目录下运行“make”以通过 (pdf)latex 运行这些 LaTex 文件\n(在此处用“make latexpdf”即可自动执行)。"
+msgstr ""
+"\n"
+"在该目录下运行“make”以通过 (pdf)latex 运行这些 LaTex 文件\n"
+"(在此处用“make latexpdf”即可自动执行)。"
#: sphinx/builders/latex/__init__.py:165
msgid "no \"latex_documents\" config value found; no documents will be written"
@@ -1034,14 +1002,9 @@ msgstr "未找到“latex_documents”配置项,不会写入文档"
msgid "\"latex_documents\" config value references unknown document %s"
msgstr "配置项“latex_documents”引用了未知文档 %s"
-#: sphinx/builders/latex/__init__.py:213 sphinx/domains/std.py:501
-#: sphinx/templates/latex/latex.tex_t:79
-#: sphinx/themes/basic/genindex-single.html:30
-#: sphinx/themes/basic/genindex-single.html:55
-#: sphinx/themes/basic/genindex-split.html:11
-#: sphinx/themes/basic/genindex-split.html:14
-#: sphinx/themes/basic/genindex.html:30 sphinx/themes/basic/genindex.html:33
-#: sphinx/themes/basic/genindex.html:66 sphinx/themes/basic/layout.html:150
+#: sphinx/builders/latex/__init__.py:213 sphinx/domains/std.py:501 sphinx/templates/latex/latex.tex_t:79 sphinx/themes/basic/genindex-single.html:30
+#: sphinx/themes/basic/genindex-single.html:55 sphinx/themes/basic/genindex-split.html:11 sphinx/themes/basic/genindex-split.html:14
+#: sphinx/themes/basic/genindex.html:30 sphinx/themes/basic/genindex.html:33 sphinx/themes/basic/genindex.html:66 sphinx/themes/basic/layout.html:150
#: sphinx/writers/texinfo.py:522
msgid "Index"
msgstr "索引"
@@ -1090,9 +1053,7 @@ msgstr "编码错误:"
#: sphinx/cmd/build.py:59 sphinx/cmd/build.py:74
#, python-format
-msgid ""
-"The full traceback has been saved in %s, if you want to report the issue to "
-"the developers."
+msgid "The full traceback has been saved in %s, if you want to report the issue to the developers."
msgstr "如果你想向开发者报告问题,可以查阅已经保存在 %s 的完整 Traceback 信息 。"
#: sphinx/cmd/build.py:63
@@ -1100,10 +1061,7 @@ msgid "Recursion error:"
msgstr "递归错误:"
#: sphinx/cmd/build.py:66
-msgid ""
-"This can happen with very large or deeply nested source files. You can "
-"carefully increase the default Python recursion limit of 1000 in conf.py "
-"with e.g.:"
+msgid "This can happen with very large or deeply nested source files. You can carefully increase the default Python recursion limit of 1000 in conf.py with e.g.:"
msgstr "在源文件过大或嵌套层数过深时会出现此错误。你可以在 conf.py 中增大默认的Python 递归 1000 层限制,像这样:"
#: sphinx/cmd/build.py:69
@@ -1115,23 +1073,18 @@ msgid "Exception occurred:"
msgstr "抛出异常:"
#: sphinx/cmd/build.py:77
-msgid ""
-"Please also report this if it was a user error, so that a better error "
-"message can be provided next time."
+msgid "Please also report this if it was a user error, so that a better error message can be provided next time."
msgstr "如果此处抛出了用户的错误,也请向我们报告,这样以后可以显示更友好、更详细的错误信息。"
#: sphinx/cmd/build.py:80
-msgid ""
-"A bug report can be filed in the tracker at <https://github.com/sphinx-"
-"doc/sphinx/issues>. Thanks!"
+msgid "A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks!"
msgstr "请向 Bug 追踪系统 <https://github.com/sphinx-doc/sphinx/issues> 投递 Bug 报告。谢谢!"
#: sphinx/cmd/build.py:97
msgid "job number should be a positive number"
msgstr "工作编号应为正值"
-#: sphinx/cmd/build.py:106 sphinx/cmd/quickstart.py:497
-#: sphinx/ext/apidoc.py:298 sphinx/ext/autosummary/generate.py:363
+#: sphinx/cmd/build.py:106 sphinx/cmd/quickstart.py:497 sphinx/ext/apidoc.py:298 sphinx/ext/autosummary/generate.py:363
msgid "For more information, visit <http://sphinx-doc.org/>."
msgstr "更多信息请访问 <http://sphinx-doc.org/>。"
@@ -1152,7 +1105,20 @@ msgid ""
"\n"
"By default, everything that is outdated is built. Output only for selected\n"
"files can be built by specifying individual filenames.\n"
-msgstr "\n从源文件生成文档。\n\nsphinx-build 从 SOURCEDIR 中的文件生成文档,并保存在 OUTPUTDIR。\n它从 SOURCEDIR 的“conf.py” 中读取配置。“sphinx-quickstart”工具可以生\n成包括“conf.py”在内的模板文件。\n\nsphinx-build 可以生成多种格式的文档。在命令行中指定构建器名称即可\n选择文档格式,默认是 HTML。构建器也可以执行文档处理相关的其他\n任务。\n\n默认只会重新构建过期内容。如果指定了文件名,那么只会产生这些文件\n的输出。\n"
+msgstr ""
+"\n"
+"从源文件生成文档。\n"
+"\n"
+"sphinx-build 从 SOURCEDIR 中的文件生成文档,并保存在 OUTPUTDIR。\n"
+"它从 SOURCEDIR 的“conf.py” 中读取配置。“sphinx-quickstart”工具可以生\n"
+"成包括“conf.py”在内的模板文件。\n"
+"\n"
+"sphinx-build 可以生成多种格式的文档。在命令行中指定构建器名称即可\n"
+"选择文档格式,默认是 HTML。构建器也可以执行文档处理相关的其他\n"
+"任务。\n"
+"\n"
+"默认只会重新构建过期内容。如果指定了文件名,那么只会产生这些文件\n"
+"的输出。\n"
#: sphinx/cmd/build.py:128
msgid "path to documentation source files"
@@ -1183,21 +1149,15 @@ msgid "don't use a saved environment, always read all files"
msgstr "不使用已保存的环境,始终读取全部文件"
#: sphinx/cmd/build.py:146
-msgid ""
-"path for the cached environment and doctree files (default: "
-"OUTPUTDIR/.doctrees)"
+msgid "path for the cached environment and doctree files (default: OUTPUTDIR/.doctrees)"
msgstr "缓存环境和 doctree 文件路径(默认:OUTPUTDIR/.doctrees)"
#: sphinx/cmd/build.py:149
-msgid ""
-"build in parallel with N processes where possible (special value \"auto\" "
-"will set N to cpu-count)"
+msgid "build in parallel with N processes where possible (special value \"auto\" will set N to cpu-count)"
msgstr "如果可能,用 N 个进程并行构建文档(如果指定为“auto”,则 N 为 CPU 数量)"
#: sphinx/cmd/build.py:153
-msgid ""
-"path where configuration file (conf.py) is located (default: same as "
-"SOURCEDIR)"
+msgid "path where configuration file (conf.py) is located (default: same as SOURCEDIR)"
msgstr "配置文件(conf.py)所在目录路径(默认:与 SOURCEDIR 相同)"
#: sphinx/cmd/build.py:156
@@ -1348,9 +1308,7 @@ msgid "Please enter a file suffix, e.g. '.rst' or '.txt'."
msgstr "请输入文件后缀,例如:“.rst”或者“.txt”。"
#: sphinx/cmd/quickstart.py:169
-msgid ""
-"* Note: non-ASCII characters entered and terminal encoding unknown -- "
-"assuming UTF-8 or Latin-1."
+msgid "* Note: non-ASCII characters entered and terminal encoding unknown -- assuming UTF-8 or Latin-1."
msgstr "* 提示:输入了非 ASCII 字符并且终端编码未知——假定为 UTF-8 或 Latin-1。"
#: sphinx/cmd/quickstart.py:248
@@ -1363,20 +1321,27 @@ msgid ""
"\n"
"Please enter values for the following settings (just press Enter to\n"
"accept a default value, if one is given in brackets)."
-msgstr "\n请输入接下来各项设置的值(如果方括号中指定了默认值,直接\n按回车即可使用默认值)。"
+msgstr ""
+"\n"
+"请输入接下来各项设置的值(如果方括号中指定了默认值,直接\n"
+"按回车即可使用默认值)。"
#: sphinx/cmd/quickstart.py:254
#, python-format
msgid ""
"\n"
"Selected root path: %s"
-msgstr "\n已选择根路径:%s"
+msgstr ""
+"\n"
+"已选择根路径:%s"
#: sphinx/cmd/quickstart.py:257
msgid ""
"\n"
"Enter the root path for documentation."
-msgstr "\n输入文档的根路径。"
+msgstr ""
+"\n"
+"输入文档的根路径。"
#: sphinx/cmd/quickstart.py:259
msgid "Root path for the documentation"
@@ -1400,7 +1365,11 @@ msgid ""
"You have two options for placing the build directory for Sphinx output.\n"
"Either, you use a directory \"_build\" within the root path, or you separate\n"
"\"source\" and \"build\" directories within the root path."
-msgstr "\n布置用于保存 Sphinx 输出的构建目录,有两种选择。\n一是在根路径下创建“_build”目录,二是在根路径下创建“source”\n和“build”两个独立的目录。"
+msgstr ""
+"\n"
+"布置用于保存 Sphinx 输出的构建目录,有两种选择。\n"
+"一是在根路径下创建“_build”目录,二是在根路径下创建“source”\n"
+"和“build”两个独立的目录。"
#: sphinx/cmd/quickstart.py:278
msgid "Separate source and build directories (y/n)"
@@ -1412,7 +1381,11 @@ msgid ""
"Inside the root directory, two more directories will be created; \"_templates\"\n"
"for custom HTML templates and \"_static\" for custom stylesheets and other static\n"
"files. You can enter another prefix (such as \".\") to replace the underscore."
-msgstr "\n在根目录下,还会创建两个目录:“_templates”用于自定义 HTML 模板、\n“_static”用于自定义 CSS 和其他静态文件。你可以输入其他前缀(比如“.”)\n代替默认的下划线。"
+msgstr ""
+"\n"
+"在根目录下,还会创建两个目录:“_templates”用于自定义 HTML 模板、\n"
+"“_static”用于自定义 CSS 和其他静态文件。你可以输入其他前缀(比如“.”)\n"
+"代替默认的下划线。"
#: sphinx/cmd/quickstart.py:286
msgid "Name prefix for templates and static dir"
@@ -1422,7 +1395,9 @@ msgstr "模板目录名和静态目录名的前缀"
msgid ""
"\n"
"The project name will occur in several places in the built documentation."
-msgstr "\n项目名称会出现在文档的许多地方。"
+msgstr ""
+"\n"
+"项目名称会出现在文档的许多地方。"
#: sphinx/cmd/quickstart.py:291
msgid "Project name"
@@ -1440,7 +1415,12 @@ msgid ""
"Python the version is something like 2.5 or 3.0, while the release is\n"
"something like 2.5.1 or 3.0a1. If you don't need this dual structure,\n"
"just set both to the same value."
-msgstr "\n在 Sphinx 中,会区分“版本”和“发行版本”两个概念。同一版本可以\n有多个发行版本。例如,Python 版本可以是 2.5 或 3.0,而发行版\n本则是 2.5.1 或 3.0a1。如果你不需要这样的双重版本结构,请把这\n两个选项设置为相同值。"
+msgstr ""
+"\n"
+"在 Sphinx 中,会区分“版本”和“发行版本”两个概念。同一版本可以\n"
+"有多个发行版本。例如,Python 版本可以是 2.5 或 3.0,而发行版\n"
+"本则是 2.5.1 或 3.0a1。如果你不需要这样的双重版本结构,请把这\n"
+"两个选项设置为相同值。"
#: sphinx/cmd/quickstart.py:302
msgid "Project version"
@@ -1470,7 +1450,10 @@ msgid ""
"\n"
"The file name suffix for source files. Commonly, this is either \".txt\"\n"
"or \".rst\". Only files with this suffix are considered documents."
-msgstr "\n源文件的文件名后缀。一般是“.txt”或“.rst”。只有此后缀的文件才会\n被视为文档的源文件。"
+msgstr ""
+"\n"
+"源文件的文件名后缀。一般是“.txt”或“.rst”。只有此后缀的文件才会\n"
+"被视为文档的源文件。"
#: sphinx/cmd/quickstart.py:322
msgid "Source file suffix"
@@ -1483,7 +1466,11 @@ msgid ""
"\"contents tree\", that is, it is the root of the hierarchical structure\n"
"of the documents. Normally, this is \"index\", but if your \"index\"\n"
"document is a custom template, you can also set this to another filename."
-msgstr "\n有一个特殊的文档将被视为“目录树”的树顶节点,也即是文档层级\n结构的根。一般用“index”作为这个特殊文档,如果你的“index”文\n档使用了自定义模板,你也可以指定其他的文件名。"
+msgstr ""
+"\n"
+"有一个特殊的文档将被视为“目录树”的树顶节点,也即是文档层级\n"
+"结构的根。一般用“index”作为这个特殊文档,如果你的“index”文\n"
+"档使用了自定义模板,你也可以指定其他的文件名。"
#: sphinx/cmd/quickstart.py:330
msgid "Name of your master document (without suffix)"
@@ -1491,8 +1478,7 @@ msgstr "主文档文件名(不含后缀)"
#: sphinx/cmd/quickstart.py:336
#, python-format
-msgid ""
-"Error: the master file %s has already been found in the selected root path."
+msgid "Error: the master file %s has already been found in the selected root path."
msgstr "错误:选择的根目录下已存在主文档文件 %s。"
#: sphinx/cmd/quickstart.py:338
@@ -1500,8 +1486,7 @@ msgid "sphinx-quickstart will not overwrite the existing file."
msgstr "sphinx-quickstart 不会覆盖已有的文件。"
#: sphinx/cmd/quickstart.py:340
-msgid ""
-"Please enter a new file name, or rename the existing file and press Enter"
+msgid "Please enter a new file name, or rename the existing file and press Enter"
msgstr "请输入新文件名,若要重命名现有文件请按回车"
#: sphinx/cmd/quickstart.py:344
@@ -1509,9 +1494,7 @@ msgid "Indicate which of the following Sphinx extensions should be enabled:"
msgstr "启用 Sphinx 扩展:"
#: sphinx/cmd/quickstart.py:353
-msgid ""
-"Note: imgmath and mathjax cannot be enabled at the same time. imgmath has "
-"been deselected."
+msgid "Note: imgmath and mathjax cannot be enabled at the same time. imgmath has been deselected."
msgstr "注意:imgmath 和 mathjax 不能同时启用。已取消选择 imgmath。"
#: sphinx/cmd/quickstart.py:358
@@ -1520,7 +1503,10 @@ msgid ""
"A Makefile and a Windows command file can be generated for you so that you\n"
"only have to run e.g. `make html' instead of invoking sphinx-build\n"
"directly."
-msgstr "\n生成 Makefile 和 Windows 批处理文件,可以直接像“make html”这样\n运行,而不需要直接调用 sphinx-build。"
+msgstr ""
+"\n"
+"生成 Makefile 和 Windows 批处理文件,可以直接像“make html”这样\n"
+"运行,而不需要直接调用 sphinx-build。"
#: sphinx/cmd/quickstart.py:362
msgid "Create Makefile? (y/n)"
@@ -1556,19 +1542,21 @@ msgstr "\n你现在可以填写主文档文件 %s 并创建其他文档源文件
msgid ""
"Use the Makefile to build the docs, like so:\n"
" make builder\n"
-msgstr "用 Makefile 构建文档,像这样:\n make builder\n"
+msgstr ""
+"用 Makefile 构建文档,像这样:\n"
+" make builder\n"
#: sphinx/cmd/quickstart.py:455
#, python-format
msgid ""
"Use the sphinx-build command to build the docs, like so:\n"
" sphinx-build -b builder %s %s\n"
-msgstr "用 sphinx-build 命令构建文档,像这样:\n sphinx-build -b builder %s %s\n"
+msgstr ""
+"用 sphinx-build 命令构建文档,像这样:\n"
+" sphinx-build -b builder %s %s\n"
#: sphinx/cmd/quickstart.py:458
-msgid ""
-"where \"builder\" is one of the supported builders, e.g. html, latex or "
-"linkcheck.\n"
+msgid "where \"builder\" is one of the supported builders, e.g. html, latex or linkcheck.\n"
msgstr "此处的“builder”是支持的构建器名,比如 html、latex 或 linkcheck。\n"
#: sphinx/cmd/quickstart.py:498
@@ -1579,7 +1567,12 @@ msgid ""
"sphinx-quickstart is an interactive tool that asks some questions about your\n"
"project and then generates a complete documentation directory and sample\n"
"Makefile to be used with sphinx-build.\n"
-msgstr "\n生成 Sphinx 项目的必需文件。\n\nsphinx-quickstart 是一个交互式工具,询问一些关于项目的问题,生成\n完整的文档目录和用于 sphinx-build 的示例 Makefile。\n"
+msgstr ""
+"\n"
+"生成 Sphinx 项目的必需文件。\n"
+"\n"
+"sphinx-quickstart 是一个交互式工具,询问一些关于项目的问题,生成\n"
+"完整的文档目录和用于 sphinx-build 的示例 Makefile。\n"
#: sphinx/cmd/quickstart.py:508
msgid "quiet mode"
@@ -1695,14 +1688,11 @@ msgid "\"quiet\" is specified, but any of \"project\" or \"author\" is not speci
msgstr "指定了“quiet”,但是没有指定“project”和“author”。"
#: sphinx/cmd/quickstart.py:618
-msgid ""
-"Error: specified path is not a directory, or sphinx files already exist."
+msgid "Error: specified path is not a directory, or sphinx files already exist."
msgstr "错误:指定的路径不是一个目录,或是 Sphinx 文件已存在。"
#: sphinx/cmd/quickstart.py:620
-msgid ""
-"sphinx-quickstart only generate into a empty directory. Please specify a new"
-" root path."
+msgid "sphinx-quickstart only generate into a empty directory. Please specify a new root path."
msgstr "sphinx-quickstart 只会在空目录中生成文件。请指定一个新的根路径。"
#: sphinx/cmd/quickstart.py:635
@@ -1719,8 +1709,7 @@ msgstr "检测到过度的去缩进"
msgid "Invalid caption: %s"
msgstr "无效的标题:%s"
-#: sphinx/directives/code.py:140 sphinx/directives/code.py:292
-#: sphinx/directives/code.py:462
+#: sphinx/directives/code.py:140 sphinx/directives/code.py:292 sphinx/directives/code.py:462
#, python-format
msgid "line number spec is out of range(1-%d): %r"
msgstr "指定的行号超出范围(1-%d):%r"
@@ -1777,18 +1766,15 @@ msgstr "作者: "
msgid "%s %s"
msgstr "%s %s"
-#: sphinx/domains/c.py:64 sphinx/domains/cpp.py:6445
-#: sphinx/domains/python.py:210 sphinx/ext/napoleon/docstring.py:696
+#: sphinx/domains/c.py:64 sphinx/domains/cpp.py:6445 sphinx/domains/python.py:210 sphinx/ext/napoleon/docstring.py:696
msgid "Parameters"
msgstr "参数"
-#: sphinx/domains/c.py:67 sphinx/domains/cpp.py:6454
-#: sphinx/domains/javascript.py:209 sphinx/domains/python.py:222
+#: sphinx/domains/c.py:67 sphinx/domains/cpp.py:6454 sphinx/domains/javascript.py:209 sphinx/domains/python.py:222
msgid "Returns"
msgstr "返回"
-#: sphinx/domains/c.py:69 sphinx/domains/javascript.py:211
-#: sphinx/domains/python.py:224
+#: sphinx/domains/c.py:69 sphinx/domains/javascript.py:211 sphinx/domains/python.py:224
msgid "Return type"
msgstr "返回类型"
@@ -1817,8 +1803,7 @@ msgstr "%s (C 类型)"
msgid "%s (C variable)"
msgstr "%s (C 变量)"
-#: sphinx/domains/c.py:258 sphinx/domains/cpp.py:7029
-#: sphinx/domains/javascript.py:297 sphinx/domains/python.py:742
+#: sphinx/domains/c.py:258 sphinx/domains/cpp.py:7029 sphinx/domains/javascript.py:297 sphinx/domains/python.py:742
msgid "function"
msgstr "函数"
@@ -1858,7 +1843,9 @@ msgstr "%s 版后已移除"
msgid ""
"Duplicate declaration, also defined in '%s'.\n"
"Declaration is '%s'."
-msgstr "重复的声明,已经在“%s”处定义。\n定义为“%s”。"
+msgstr ""
+"重复的声明,已经在“%s”处定义。\n"
+"定义为“%s”。"
#: sphinx/domains/cpp.py:6448
msgid "Template Parameters"
@@ -1873,8 +1860,7 @@ msgstr "抛出"
msgid "%s (C++ %s)"
msgstr "%s (C++ %s)"
-#: sphinx/domains/cpp.py:7027 sphinx/domains/javascript.py:299
-#: sphinx/domains/python.py:744
+#: sphinx/domains/cpp.py:7027 sphinx/domains/javascript.py:299 sphinx/domains/python.py:744
msgid "class"
msgstr "类"
@@ -1899,7 +1885,9 @@ msgstr "枚举子"
msgid ""
"Duplicate declaration, also defined in '%s'.\n"
"Name of declaration is '%s'."
-msgstr "重复的声明,已经在“%s”处定义。\n声明名称为“%s”。"
+msgstr ""
+"重复的声明,已经在“%s”处定义。\n"
+"声明名称为“%s”。"
#: sphinx/domains/javascript.py:130 sphinx/domains/python.py:430
#, python-format
@@ -1947,8 +1935,7 @@ msgstr "数据"
msgid "attribute"
msgstr "属性"
-#: sphinx/domains/javascript.py:302 sphinx/domains/python.py:49
-#: sphinx/domains/python.py:750
+#: sphinx/domains/javascript.py:302 sphinx/domains/python.py:49 sphinx/domains/python.py:750
msgid "module"
msgstr "模块"
@@ -1994,8 +1981,7 @@ msgstr "变量"
msgid "Raises"
msgstr "引发"
-#: sphinx/domains/python.py:431 sphinx/domains/python.py:489
-#: sphinx/domains/python.py:501 sphinx/domains/python.py:514
+#: sphinx/domains/python.py:431 sphinx/domains/python.py:489 sphinx/domains/python.py:501 sphinx/domains/python.py:514
#, python-format
msgid "%s() (in module %s)"
msgstr "%s() (在 %s 模块中)"
@@ -2104,9 +2090,7 @@ msgstr "环境变量; %s"
#: sphinx/domains/std.py:166
#, python-format
-msgid ""
-"Malformed option description %r, should look like \"opt\", \"-opt args\", \""
-"--opt args\", \"/opt args\" or \"+opt args\""
+msgid "Malformed option description %r, should look like \"opt\", \"-opt args\", \"--opt args\", \"/opt args\" or \"+opt args\""
msgstr "畸形的选项描述 %r,应是“opt”、“-opt args”、“--opt args”、“/opt args”或“+opt args”形式"
#: sphinx/domains/std.py:207
@@ -2206,9 +2190,7 @@ msgid "source directory has changed"
msgstr "源文件目录已变化"
#: sphinx/environment/__init__.py:283
-msgid ""
-"This environment is incompatible with the selected builder, please choose "
-"another doctree directory."
+msgid "This environment is incompatible with the selected builder, please choose another doctree directory."
msgstr "本环境与选择的构建器不兼容,请选择其他的文档树目录。"
#: sphinx/environment/__init__.py:404
@@ -2244,8 +2226,7 @@ msgstr "参见 %s"
msgid "unknown index entry type %r"
msgstr "未知的索引条目类型 %r"
-#: sphinx/environment/adapters/indexentries.py:156
-#: sphinx/templates/latex/sphinxmessages.sty_t:11
+#: sphinx/environment/adapters/indexentries.py:156 sphinx/templates/latex/sphinxmessages.sty_t:11
msgid "Symbols"
msgstr "符号"
@@ -2256,9 +2237,7 @@ msgstr "在文档树中检测到循环引用,已忽略:%s <- %s"
#: sphinx/environment/adapters/toctree.py:172
#, python-format
-msgid ""
-"toctree contains reference to document %r that doesn't have a title: no link"
-" will be generated"
+msgid "toctree contains reference to document %r that doesn't have a title: no link will be generated"
msgstr "目录树引用的文档 %r 缺少标题:不会生成链接"
#: sphinx/environment/adapters/toctree.py:178
@@ -2306,15 +2285,21 @@ msgid ""
"excluded from generation.\n"
"\n"
"Note: By default this script will not overwrite already created files."
-msgstr "\n在 <MODULE_PATH> 中递归查找 Python 模块和包,然后在 <OUTPUT_PATH> 中为每个使用了\nautomodule 指令的包创建一个 reST 文件。\n\n<EXCLUDE_PATTERN> 可以排除生成符合规则的文件/目录的文档。\n\n提示:本脚本默认不会覆盖已有文件。"
+msgstr ""
+"\n"
+"在 <MODULE_PATH> 中递归查找 Python 模块和包,然后在 <OUTPUT_PATH> 中为每个使用了\n"
+"automodule 指令的包创建一个 reST 文件。\n"
+"\n"
+"<EXCLUDE_PATTERN> 可以排除生成符合规则的文件/目录的文档。\n"
+"\n"
+"提示:本脚本默认不会覆盖已有文件。"
#: sphinx/ext/apidoc.py:312
msgid "path to module to document"
msgstr "要生成文档的模块路径"
#: sphinx/ext/apidoc.py:314
-msgid ""
-"fnmatch-style file and/or directory patterns to exclude from generation"
+msgid "fnmatch-style file and/or directory patterns to exclude from generation"
msgstr "排除的文件/目录,fnmatch 风格的规则"
#: sphinx/ext/apidoc.py:319
@@ -2330,9 +2315,7 @@ msgid "overwrite existing files"
msgstr "覆盖已有文件"
#: sphinx/ext/apidoc.py:328
-msgid ""
-"follow symbolic links. Powerful when combined with "
-"collective.recipe.omelette."
+msgid "follow symbolic links. Powerful when combined with collective.recipe.omelette."
msgstr "遵循符号链接。配合 collective.recipe.omelette 使用尤其奏效。"
#: sphinx/ext/apidoc.py:331
@@ -2356,9 +2339,7 @@ msgid "don't create a table of contents file"
msgstr "不创建目录文件"
#: sphinx/ext/apidoc.py:344
-msgid ""
-"don't create headings for the module/package packages (e.g. when the "
-"docstrings already contain them)"
+msgid "don't create headings for the module/package packages (e.g. when the docstrings already contain them)"
msgstr "不创建模块/包的标题(比如当 Docstring 中已经有标题时,可以使用这个选项)"
#: sphinx/ext/apidoc.py:349
@@ -2366,9 +2347,7 @@ msgid "put module documentation before submodule documentation"
msgstr "模块文档先于子模块文档"
#: sphinx/ext/apidoc.py:353
-msgid ""
-"interpret module paths according to PEP-0420 implicit namespaces "
-"specification"
+msgid "interpret module paths according to PEP-0420 implicit namespaces specification"
msgstr "根据 PEP-0420 隐式命名空间规范解释模块路径"
#: sphinx/ext/apidoc.py:357
@@ -2415,9 +2394,7 @@ msgstr "无效的正则表达式 %r 在 %s"
#: sphinx/ext/coverage.py:55
#, python-format
-msgid ""
-"Testing of coverage in the sources finished, look at the results in "
-"%(outdir)spython.txt."
+msgid "Testing of coverage in the sources finished, look at the results in %(outdir)spython.txt."
msgstr "已完成源文件的覆盖率测试,请在 %(outdir)s/python.txt 中查看结果。"
#: sphinx/ext/coverage.py:70
@@ -2451,9 +2428,7 @@ msgstr "无效的 TestCode 类型"
#: sphinx/ext/doctest.py:283
#, python-format
-msgid ""
-"Testing of doctests in the sources finished, look at the results in "
-"%(outdir)s/output.txt."
+msgid "Testing of doctests in the sources finished, look at the results in %(outdir)s/output.txt."
msgstr "已完成源文件的文档测试,请在 %(outdir)s/output.txt 中查看结果。"
#: sphinx/ext/doctest.py:446
@@ -2491,9 +2466,7 @@ msgstr "dot没有生成输出文件:\n[stderr]\n%r\n[stdout]\n%r"
#: sphinx/ext/graphviz.py:254
#, python-format
-msgid ""
-"dot command %r cannot be run (needed for graphviz output), check the "
-"graphviz_dot setting"
+msgid "dot command %r cannot be run (needed for graphviz output), check the graphviz_dot setting"
msgstr "无法运行 Dot 命令 %r (Graphviz 输出所需),请检查 graphviz_dot 配置"
#: sphinx/ext/graphviz.py:261
@@ -2511,8 +2484,7 @@ msgstr "点退出错误:\n[stderr]\n%r\n[stdout]\n%r"
msgid "graphviz_output_format must be one of 'png', 'svg', but is %r"
msgstr "graphviz_output_format 必须是 'png' 或 'svg' 中之一,现为 %r"
-#: sphinx/ext/graphviz.py:275 sphinx/ext/graphviz.py:329
-#: sphinx/ext/graphviz.py:367
+#: sphinx/ext/graphviz.py:275 sphinx/ext/graphviz.py:329 sphinx/ext/graphviz.py:367
#, python-format
msgid "dot code %r: %s"
msgstr "点 代码 %r: %s"
@@ -2543,16 +2515,12 @@ msgstr "转换退出时出错:\n[stderr]\n%r\n[stdout]\n%r"
#: sphinx/ext/imgmath.py:139
#, python-format
-msgid ""
-"LaTeX command %r cannot be run (needed for math display), check the "
-"imgmath_latex setting"
+msgid "LaTeX command %r cannot be run (needed for math display), check the imgmath_latex setting"
msgstr "无法运行 LaTeX 命令 %r (数学公式显示必需),请检查 imgmath_latex 设置"
#: sphinx/ext/imgmath.py:154
#, python-format
-msgid ""
-"%s command %r cannot be run (needed for math display), check the imgmath_%s "
-"setting"
+msgid "%s command %r cannot be run (needed for math display), check the imgmath_%s setting"
msgstr "无法运行 %s 命令 %r (数学公式显示必需),请检查 imgmath_%s 设置"
#: sphinx/ext/imgmath.py:289
@@ -2680,14 +2648,15 @@ msgstr "属性 %s 不存在,在对象 %s 上"
msgid ""
"autodoc: failed to determine %r to be documented.the following exception was raised:\n"
"%s"
-msgstr "autodoc:无法判断是否生成 %r 的文档。抛出了下列异常:\n%s"
+msgstr ""
+"autodoc:无法判断是否生成 %r 的文档。抛出了下列异常:\n"
+"%s"
#: sphinx/ext/autodoc/__init__.py:692
#, python-format
msgid ""
-"don't know which module to import for autodocumenting %r (try placing a "
-"\"module\" or \"currentmodule\" directive in the document, or giving an "
-"explicit module name)"
+"don't know which module to import for autodocumenting %r (try placing a \"module\" or \"currentmodule\" directive in the document, or giving an explicit module "
+"name)"
msgstr "无法判断导入哪个模块来自动生成文档 %r(尝试在文档中使用“module”或“currentmodule”指令,或者显式给定模块名)"
#: sphinx/ext/autodoc/__init__.py:786
@@ -2701,15 +2670,12 @@ msgstr "automodule %s 给定了函数签名参数或返回类型标注"
#: sphinx/ext/autodoc/__init__.py:827
#, python-format
-msgid ""
-"__all__ should be a list of strings, not %r (in module %s) -- ignoring "
-"__all__"
+msgid "__all__ should be a list of strings, not %r (in module %s) -- ignoring __all__"
msgstr "__all__ 应是一个字符串列表,而不是 %r (出现在模块 %s 中) -- 已忽略__all__"
#: sphinx/ext/autodoc/__init__.py:842
#, python-format
-msgid ""
-"missing attribute mentioned in :members: or __all__: module %s, attribute %s"
+msgid "missing attribute mentioned in :members: or __all__: module %s, attribute %s"
msgstr ":members: 或 __all__ 提及的属性不存在:模块 %s,属性 %s"
#: sphinx/ext/autodoc/__init__.py:1126
@@ -2753,9 +2719,7 @@ msgid "failed to import object %s"
msgstr "无法导入对象 %s"
#: sphinx/ext/autosummary/__init__.py:702
-msgid ""
-"autosummary generats .rst files internally. But your source_suffix does not "
-"contain .rst. Skipped."
+msgid "autosummary generats .rst files internally. But your source_suffix does not contain .rst. Skipped."
msgstr "autosummary 内部生成 .rst 文件,但是 source_suffix 中不包含 .rst,已跳过。"
#: sphinx/ext/autosummary/generate.py:100
@@ -2781,7 +2745,17 @@ msgid ""
"``sphinx.ext.autosummary`` Python module and can be read using::\n"
"\n"
" pydoc sphinx.ext.autosummary\n"
-msgstr "\n用 autosummary 指令生成 ReStructuredText\n\nsphinx-autogen 是 sphinx.ext.autosummary.generate 的前端,它根据给定\n的输入文件中的 autosummary 指令生成 reStructuredText  文件\n\nautosummary 指令的格式见 Python 模块 ``sphinx.ext.autosummary`` 的文\n档,并且可以这样调出文档阅读::\n\n pydoc sphinx.ext.autosummary\n"
+msgstr ""
+"\n"
+"用 autosummary 指令生成 ReStructuredText\n"
+"\n"
+"sphinx-autogen 是 sphinx.ext.autosummary.generate 的前端,它根据给定\n"
+"的输入文件中的 autosummary 指令生成 reStructuredText  文件\n"
+"\n"
+"autosummary 指令的格式见 Python 模块 ``sphinx.ext.autosummary`` 的文\n"
+"档,并且可以这样调出文档阅读::\n"
+"\n"
+" pydoc sphinx.ext.autosummary\n"
#: sphinx/ext/autosummary/generate.py:381
msgid "source files to generate rST files for"
@@ -2878,8 +2852,7 @@ msgstr "小技巧"
msgid "Warning"
msgstr "警告"
-#: sphinx/templates/latex/longtable.tex_t:23
-#: sphinx/templates/latex/sphinxmessages.sty_t:8
+#: sphinx/templates/latex/longtable.tex_t:23 sphinx/templates/latex/sphinxmessages.sty_t:8
msgid "continued from previous page"
msgstr "续上页"
@@ -2903,13 +2876,11 @@ msgstr "数值"
msgid "page"
msgstr "页"
-#: sphinx/themes/agogo/layout.html:46 sphinx/themes/basic/globaltoc.html:10
-#: sphinx/themes/basic/localtoc.html:11 sphinx/themes/scrolls/layout.html:41
+#: sphinx/themes/agogo/layout.html:46 sphinx/themes/basic/globaltoc.html:10 sphinx/themes/basic/localtoc.html:11 sphinx/themes/scrolls/layout.html:41
msgid "Table of Contents"
msgstr "目录"
-#: sphinx/themes/agogo/layout.html:51 sphinx/themes/basic/layout.html:153
-#: sphinx/themes/basic/search.html:11 sphinx/themes/basic/search.html:21
+#: sphinx/themes/agogo/layout.html:51 sphinx/themes/basic/layout.html:153 sphinx/themes/basic/search.html:11 sphinx/themes/basic/search.html:21
#: sphinx/themes/basic/searchresults.html:10
msgid "Search"
msgstr "搜索"
@@ -2971,9 +2942,7 @@ msgstr "所的函数,类,术语"
msgid "Index &ndash; %(key)s"
msgstr "索引 &ndash; %(key)s"
-#: sphinx/themes/basic/genindex-single.html:61
-#: sphinx/themes/basic/genindex-split.html:24
-#: sphinx/themes/basic/genindex-split.html:38
+#: sphinx/themes/basic/genindex-single.html:61 sphinx/themes/basic/genindex-split.html:24 sphinx/themes/basic/genindex-split.html:38
#: sphinx/themes/basic/genindex.html:72
msgid "Full index on one page"
msgstr "一页的全部索引"
@@ -3020,9 +2989,7 @@ msgstr "最后更新于 %(last_updated)s."
#: sphinx/themes/basic/layout.html:210
#, python-format
-msgid ""
-"Created using <a href=\"http://sphinx-doc.org/\">Sphinx</a> "
-"%(sphinx_version)s."
+msgid "Created using <a href=\"http://sphinx-doc.org/\">Sphinx</a> %(sphinx_version)s."
msgstr "由 <a href=\"http://sphinx-doc.org/\">Sphinx</a> %(sphinx_version)s 创建。"
#: sphinx/themes/basic/opensearch.xml:4
@@ -3060,23 +3027,16 @@ msgid ""
" containing fewer words won't appear in the result list."
msgstr "在这儿,你可以对这些文档进行搜索。向搜索框中输入你所要搜索的关键字并点击“搜索”。注意:搜索引擎会自动搜索所有的关键字。将不会搜索到部分关键字的页面."
-#: sphinx/themes/basic/search.html:37
-#: sphinx/themes/basic/searchresults.html:17
+#: sphinx/themes/basic/search.html:37 sphinx/themes/basic/searchresults.html:17
msgid "search"
msgstr "搜索"
-#: sphinx/themes/basic/search.html:41
-#: sphinx/themes/basic/searchresults.html:21
-#: sphinx/themes/basic/static/searchtools.js:285
+#: sphinx/themes/basic/search.html:41 sphinx/themes/basic/searchresults.html:21 sphinx/themes/basic/static/searchtools.js:285
msgid "Search Results"
msgstr "搜索结果"
-#: sphinx/themes/basic/search.html:43
-#: sphinx/themes/basic/searchresults.html:23
-#: sphinx/themes/basic/static/searchtools.js:287
-msgid ""
-"Your search did not match any documents. Please make sure that all words are"
-" spelled correctly and that you've selected enough categories."
+#: sphinx/themes/basic/search.html:43 sphinx/themes/basic/searchresults.html:23 sphinx/themes/basic/static/searchtools.js:287
+msgid "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
msgstr "没有任何文档匹配您的搜索。请确保你输入的词拼写正确并选择了合适的分类。"
#: sphinx/themes/basic/searchbox.html:12
@@ -3087,8 +3047,7 @@ msgstr "快速搜索"
msgid "This Page"
msgstr "本页"
-#: sphinx/themes/basic/changes/frameset.html:5
-#: sphinx/themes/basic/changes/versionchanges.html:12
+#: sphinx/themes/basic/changes/frameset.html:5 sphinx/themes/basic/changes/versionchanges.html:12
#, python-format
msgid "Changes in Version %(version)s &#8212; %(docstitle)s"
msgstr "更改发生在版本 %(version)s&#8212; %(docstitle)s"
@@ -3115,15 +3074,11 @@ msgstr "C API 更改"
msgid "Other changes"
msgstr "其他更改"
-#: sphinx/themes/basic/static/doctools.js:194 sphinx/writers/html.py:454
-#: sphinx/writers/html.py:459 sphinx/writers/html5.py:400
-#: sphinx/writers/html5.py:405
+#: sphinx/themes/basic/static/doctools.js:194 sphinx/writers/html.py:454 sphinx/writers/html.py:459 sphinx/writers/html5.py:400 sphinx/writers/html5.py:405
msgid "Permalink to this headline"
msgstr "永久链接至标题"
-#: sphinx/themes/basic/static/doctools.js:200 sphinx/writers/html.py:134
-#: sphinx/writers/html.py:145 sphinx/writers/html5.py:103
-#: sphinx/writers/html5.py:114
+#: sphinx/themes/basic/static/doctools.js:200 sphinx/writers/html.py:134 sphinx/writers/html.py:145 sphinx/writers/html5.py:103 sphinx/writers/html5.py:114
msgid "Permalink to this definition"
msgstr "永久链接至目标"
@@ -3152,8 +3107,7 @@ msgstr ", 在 "
msgid "Expand sidebar"
msgstr "展开边栏"
-#: sphinx/themes/classic/static/sidebar.js_t:96
-#: sphinx/themes/classic/static/sidebar.js_t:124
+#: sphinx/themes/classic/static/sidebar.js_t:96 sphinx/themes/classic/static/sidebar.js_t:124
msgid "Collapse sidebar"
msgstr "折叠边栏"
@@ -3163,8 +3117,7 @@ msgstr "目录"
#: sphinx/transforms/__init__.py:261
#, python-format
-msgid ""
-"4 column based index found. It might be a bug of extensions you use: %r"
+msgid "4 column based index found. It might be a bug of extensions you use: %r"
msgstr "发现使用了 4 列布局的索引页。可能是你所用的扩展出现了 Bug:%r"
#: sphinx/transforms/__init__.py:303
@@ -3177,27 +3130,19 @@ msgid "Footnote [#] is not referenced."
msgstr "脚注 [#] 没有被引用过。"
#: sphinx/transforms/i18n.py:293 sphinx/transforms/i18n.py:363
-msgid ""
-"inconsistent footnote references in translated message. original: {0}, "
-"translated: {1}"
+msgid "inconsistent footnote references in translated message. original: {0}, translated: {1}"
msgstr "译文中的脚注引用与原文不一致。原始为:{0},翻译后为:{1}"
#: sphinx/transforms/i18n.py:335
-msgid ""
-"inconsistent references in translated message. original: {0}, translated: "
-"{1}"
+msgid "inconsistent references in translated message. original: {0}, translated: {1}"
msgstr "译文中的引用与原文不一致。原始为:{0},翻译后为:{1}"
#: sphinx/transforms/i18n.py:382
-msgid ""
-"inconsistent citation references in translated message. original: {0}, "
-"translated: {1}"
+msgid "inconsistent citation references in translated message. original: {0}, translated: {1}"
msgstr "译文中的引文引用与原文不一致。原始为:{0},翻译后为:{1}"
#: sphinx/transforms/i18n.py:402
-msgid ""
-"inconsistent term references in translated message. original: {0}, "
-"translated: {1}"
+msgid "inconsistent term references in translated message. original: {0}, translated: {1}"
msgstr "译文中的术语引用与原文不一致。原始为:{0},翻译后为:{1}"
#: sphinx/transforms/post_transforms/__init__.py:110
@@ -3259,9 +3204,7 @@ msgstr "写入时发生错误:%s,%s"
#: sphinx/util/i18n.py:215
#, python-format
-msgid ""
-"Invalid date format. Quote the string by single quote if you want to output "
-"it directly: %s"
+msgid "Invalid date format. Quote the string by single quote if you want to output it directly: %s"
msgstr "无效的日期格式。如果你想直接输出日期字符串,请用单引号:%s"
#: sphinx/util/nodes.py:428
@@ -3330,12 +3273,10 @@ msgid "document title is not a single Text node"
msgstr "文档标题不是一个单纯文本节点"
#: sphinx/writers/latex.py:926 sphinx/writers/texinfo.py:658
-msgid ""
-"encountered title node not in section, topic, table, admonition or sidebar"
+msgid "encountered title node not in section, topic, table, admonition or sidebar"
msgstr "在节、话题、表格、警示或边栏以外的位置发现标题节点"
-#: sphinx/writers/latex.py:1102 sphinx/writers/manpage.py:277
-#: sphinx/writers/texinfo.py:675
+#: sphinx/writers/latex.py:1102 sphinx/writers/manpage.py:277 sphinx/writers/texinfo.py:675
msgid "Footnotes"
msgstr "脚注"
diff --git a/sphinx/make_mode.py b/sphinx/make_mode.py
deleted file mode 100644
index 792f4ab85..000000000
--- a/sphinx/make_mode.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""
- sphinx.make_mode
- ~~~~~~~~~~~~~~~~
-
- sphinx-build -M command-line handling.
-
- This replaces the old, platform-dependent and once-generated content
- of Makefile / make.bat.
-
- This is in its own module so that importing it is fast. It should not
- import the main Sphinx modules (like sphinx.applications, sphinx.builders).
-
- :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-"""
-
-import warnings
-
-from sphinx.cmd import make_mode
-from sphinx.deprecation import RemovedInSphinx30Warning
-
-
-BUILDERS = make_mode.BUILDERS
-
-
-class Make(make_mode.Make):
- def __init__(self, *args):
- warnings.warn('sphinx.make_mode.Make is deprecated. '
- 'Please use sphinx.cmd.make_mode.Make instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- super().__init__(*args)
-
-
-def run_make_mode(args):
- warnings.warn('sphinx.make_mode.run_make_mode() is deprecated. '
- 'Please use sphinx.cmd.make_mode.run_make_mode() instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- return make_mode.run_make_mode(args)
diff --git a/sphinx/parsers.py b/sphinx/parsers.py
index 2a61971f3..3974d1c66 100644
--- a/sphinx/parsers.py
+++ b/sphinx/parsers.py
@@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details.
"""
+import warnings
from typing import Any, Dict, List, Union
import docutils.parsers
@@ -17,6 +18,7 @@ from docutils.parsers.rst import states
from docutils.statemachine import StringList
from docutils.transforms.universal import SmartQuotes
+from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.util.rst import append_epilog, prepend_prolog
if False:
@@ -47,6 +49,8 @@ class Parser(docutils.parsers.Parser):
.. deprecated:: 1.6
``warn()`` and ``info()`` is deprecated. Use :mod:`sphinx.util.logging` instead.
+ .. deprecated:: 3.0
+ parser.app is deprecated.
"""
def set_application(self, app: "Sphinx") -> None:
@@ -54,10 +58,15 @@ class Parser(docutils.parsers.Parser):
:param sphinx.application.Sphinx app: Sphinx application object
"""
- self.app = app
+ self._app = app
self.config = app.config
self.env = app.env
+ @property
+ def app(self) -> "Sphinx":
+ warnings.warn('parser.app is deprecated.', RemovedInSphinx50Warning)
+ return self._app
+
class RSTParser(docutils.parsers.rst.Parser, Parser):
"""A reST parser for Sphinx."""
diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py
index 22207b715..52617e3bc 100644
--- a/sphinx/pycode/ast.py
+++ b/sphinx/pycode/ast.py
@@ -9,6 +9,7 @@
"""
import sys
+from typing import List
if sys.version_info > (3, 8):
import ast
@@ -40,6 +41,13 @@ def unparse(node: ast.AST) -> str:
return None
elif isinstance(node, str):
return node
+ elif isinstance(node, ast.arg):
+ if node.annotation:
+ return "%s: %s" % (node.arg, unparse(node.annotation))
+ else:
+ return node.arg
+ elif isinstance(node, ast.arguments):
+ return unparse_arguments(node)
elif isinstance(node, ast.Attribute):
return "%s.%s" % (unparse(node.value), node.attr)
elif isinstance(node, ast.Bytes):
@@ -58,7 +66,7 @@ def unparse(node: ast.AST) -> str:
elif isinstance(node, ast.Index):
return unparse(node.value)
elif isinstance(node, ast.Lambda):
- return "<function <lambda>>" # TODO
+ return "lambda %s: ..." % unparse(node.args)
elif isinstance(node, ast.List):
return "[" + ", ".join(unparse(e) for e in node.elts) + "]"
elif isinstance(node, ast.Name):
@@ -80,3 +88,61 @@ def unparse(node: ast.AST) -> str:
return repr(node.value)
else:
raise NotImplementedError('Unable to parse %s object' % type(node).__name__)
+
+
+def unparse_arguments(node: ast.arguments) -> str:
+ """Unparse an arguments to string."""
+ defaults = list(node.defaults)
+ positionals = len(node.args)
+ posonlyargs = 0
+ if hasattr(node, "posonlyargs"): # for py38+
+ posonlyargs += len(node.posonlyargs) # type:ignore
+ positionals += posonlyargs
+ for _ in range(len(defaults), positionals):
+ defaults.insert(0, None)
+
+ kw_defaults = list(node.kw_defaults)
+ for _ in range(len(kw_defaults), len(node.kwonlyargs)):
+ kw_defaults.insert(0, None)
+
+ args = [] # type: List[str]
+ if hasattr(node, "posonlyargs"): # for py38+
+ for i, arg in enumerate(node.posonlyargs): # type: ignore
+ name = unparse(arg)
+ if defaults[i]:
+ if arg.annotation:
+ name += " = %s" % unparse(defaults[i])
+ else:
+ name += "=%s" % unparse(defaults[i])
+ args.append(name)
+
+ if node.posonlyargs: # type: ignore
+ args.append('/')
+
+ for i, arg in enumerate(node.args):
+ name = unparse(arg)
+ if defaults[i + posonlyargs]:
+ if arg.annotation:
+ name += " = %s" % unparse(defaults[i + posonlyargs])
+ else:
+ name += "=%s" % unparse(defaults[i + posonlyargs])
+ args.append(name)
+
+ if node.vararg:
+ args.append("*" + unparse(node.vararg))
+
+ if node.kwonlyargs and not node.vararg:
+ args.append('*')
+ for i, arg in enumerate(node.kwonlyargs):
+ name = unparse(arg)
+ if kw_defaults[i]:
+ if arg.annotation:
+ name += " = %s" % unparse(kw_defaults[i])
+ else:
+ name += "=%s" % unparse(kw_defaults[i])
+ args.append(name)
+
+ if node.kwarg:
+ args.append("**" + unparse(node.kwarg))
+
+ return ", ".join(args)
diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py
index 1f803e950..cb3cf0cc1 100644
--- a/sphinx/pycode/parser.py
+++ b/sphinx/pycode/parser.py
@@ -142,7 +142,7 @@ class TokenProcessor:
def fetch_token(self) -> Token:
"""Fetch a next token from source code.
- Returns ``False`` if sequence finished.
+ Returns ``None`` if sequence finished.
"""
try:
self.previous = self.current
diff --git a/sphinx/registry.py b/sphinx/registry.py
index d7bffa2ce..93a7ebc25 100644
--- a/sphinx/registry.py
+++ b/sphinx/registry.py
@@ -9,9 +9,7 @@
"""
import traceback
-import warnings
from importlib import import_module
-from inspect import isclass
from types import MethodType
from typing import Any, Callable, Dict, Iterator, List, Tuple, Union
@@ -25,18 +23,15 @@ from pkg_resources import iter_entry_points
from sphinx.builders import Builder
from sphinx.config import Config
-from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.domains import Domain, Index, ObjType
from sphinx.domains.std import GenericObject, Target
from sphinx.environment import BuildEnvironment
from sphinx.errors import ExtensionError, SphinxError, VersionRequirementError
from sphinx.extension import Extension
-from sphinx.io import SphinxFileInput
from sphinx.locale import __
from sphinx.parsers import Parser as SphinxParser
from sphinx.roles import XRefRole
from sphinx.util import logging
-from sphinx.util.docutils import directive_helper
from sphinx.util.logging import prefixed_warnings
from sphinx.util.typing import RoleFunction, TitleGetter
@@ -176,17 +171,9 @@ class SphinxComponentRegistry:
yield domain
- def override_domain(self, domain: "Type[Domain]") -> None:
- warnings.warn('registry.override_domain() is deprecated. '
- 'Use app.add_domain(domain, override=True) instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- self.add_domain(domain, override=True)
-
- def add_directive_to_domain(self, domain: str, name: str, obj: Any,
- has_content: bool = None, argument_spec: Any = None,
- override: bool = False, **option_spec: Any) -> None:
- logger.debug('[app] adding directive to domain: %r',
- (domain, name, obj, has_content, argument_spec, option_spec))
+ def add_directive_to_domain(self, domain: str, name: str,
+ 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)
@@ -194,10 +181,7 @@ class SphinxComponentRegistry:
if name in directives and not override:
raise ExtensionError(__('The %r directive is already registered to domain %s') %
(name, domain))
- if not isclass(obj) or not issubclass(obj, Directive):
- directives[name] = directive_helper(obj, has_content, argument_spec, **option_spec)
- else:
- directives[name] = obj
+ directives[name] = cls
def add_role_to_domain(self, domain: str, name: str,
role: Union[RoleFunction, XRefRole], override: bool = False
@@ -273,27 +257,8 @@ class SphinxComponentRegistry:
else:
self.source_suffix[suffix] = filetype
- def add_source_parser(self, *args: Any, **kwargs: Any) -> None:
- logger.debug('[app] adding search source_parser: %r', args)
- if len(args) == 1:
- # new sytle arguments: (source_parser)
- suffix = None # type: str
- parser = args[0] # type: Type[Parser]
- else:
- # old style arguments: (suffix, source_parser)
- warnings.warn('app.add_source_parser() does not support suffix argument. '
- 'Use app.add_source_suffix() instead.',
- RemovedInSphinx30Warning, stacklevel=3)
- suffix = args[0]
- parser = args[1]
-
- if suffix:
- self.add_source_suffix(suffix, suffix, override=True)
-
- if len(parser.supported) == 0:
- warnings.warn('Old source_parser has been detected. Please fill Parser.supported '
- 'attribute: %s' % parser.__name__,
- RemovedInSphinx30Warning, stacklevel=3)
+ def add_source_parser(self, parser: "Type[Parser]", **kwargs: Any) -> None:
+ logger.debug('[app] adding search source_parser: %r', parser)
# create a map from filetype to parser
for filetype in parser.supported:
@@ -303,12 +268,6 @@ class SphinxComponentRegistry:
else:
self.source_parsers[filetype] = parser
- # also maps suffix to parser
- #
- # This rescues old styled parsers which does not have ``supported`` filetypes.
- if suffix:
- self.source_parsers[suffix] = parser
-
def get_source_parser(self, filetype: str) -> "Type[Parser]":
try:
return self.source_parsers[filetype]
@@ -325,16 +284,6 @@ class SphinxComponentRegistry:
parser.set_application(app)
return parser
- def add_source_input(self, input_class: "Type[SphinxFileInput]", override: bool = False
- ) -> None:
- warnings.warn('registry.source_input() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- for filetype in input_class.supported:
- if filetype in self.source_inputs and not override:
- raise ExtensionError(__('source_input for %r is already registered') %
- filetype)
- self.source_inputs[filetype] = input_class
-
def get_source_input(self, filetype: str) -> "Type[Input]":
try:
return self.source_inputs[filetype]
@@ -347,7 +296,7 @@ class SphinxComponentRegistry:
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)
+ 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
@@ -407,7 +356,7 @@ class SphinxComponentRegistry:
attrgetter: Callable[[Any, str, Any], Any]) -> None:
self.autodoc_attrgettrs[typ] = attrgetter
- def add_css_files(self, filename, **attributes):
+ def add_css_files(self, filename: str, **attributes: str) -> None:
self.css_files.append((filename, attributes))
def add_js_file(self, filename: str, **attributes: str) -> None:
diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py
index 668248b6b..d1f444be1 100644
--- a/sphinx/search/ja.py
+++ b/sphinx/search/ja.py
@@ -19,7 +19,6 @@
import os
import re
import sys
-import warnings
from typing import Any, Dict, List
try:
@@ -34,7 +33,6 @@ try:
except ImportError:
janome_module = False
-from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.errors import SphinxError, ExtensionError
from sphinx.search import SearchLanguage
from sphinx.util import import_object
@@ -525,21 +523,9 @@ class SearchJapanese(SearchLanguage):
"""
lang = 'ja'
language_name = 'Japanese'
- splitters = {
- 'default': 'sphinx.search.ja.DefaultSplitter',
- 'mecab': 'sphinx.search.ja.MecabSplitter',
- 'janome': 'sphinx.search.ja.JanomeSplitter',
- }
def init(self, options: Dict) -> None:
- type = options.get('type', 'sphinx.search.ja.DefaultSplitter')
- if type in self.splitters:
- dotted_path = self.splitters[type]
- warnings.warn('html_search_options["type"]: %s is deprecated. '
- 'Please give "%s" instead.' % (type, dotted_path),
- RemovedInSphinx30Warning, stacklevel=2)
- else:
- dotted_path = type
+ dotted_path = options.get('type', 'sphinx.search.ja.DefaultSplitter')
try:
self.splitter = import_object(dotted_path)(options)
except ExtensionError:
diff --git a/sphinx/templates/latex/longtable.tex_t b/sphinx/templates/latex/longtable.tex_t
index f9e8802e4..8d4cd748c 100644
--- a/sphinx/templates/latex/longtable.tex_t
+++ b/sphinx/templates/latex/longtable.tex_t
@@ -26,7 +26,7 @@
\endhead
\hline
-\multicolumn{<%= table.colcount %>}{r}{\makebox[0pt][r]{\sphinxtablecontinued{<%= _('Continued on next page') %>}}}\\
+\multicolumn{<%= table.colcount %>}{r}{\makebox[0pt][r]{\sphinxtablecontinued{<%= _('continues on next page') %>}}}\\
\endfoot
\endlastfoot
diff --git a/sphinx/testing/comparer.py b/sphinx/testing/comparer.py
index 0bcd194a8..ddd818394 100644
--- a/sphinx/testing/comparer.py
+++ b/sphinx/testing/comparer.py
@@ -9,7 +9,7 @@
"""
import difflib
import pathlib
-from typing import List, Union
+from typing import Any, List, Union
class PathComparer:
@@ -38,13 +38,13 @@ class PathComparer:
"""
self.path = pathlib.Path(path)
- def __str__(self):
+ def __str__(self) -> str:
return self.path.as_posix()
- def __repr__(self):
+ def __repr__(self) -> str:
return "<{0.__class__.__name__}: '{0}'>".format(self)
- def __eq__(self, other):
+ def __eq__(self, other: Union[str, pathlib.Path]) -> bool: # type: ignore
return not bool(self.ldiff(other))
def diff(self, other: Union[str, pathlib.Path]) -> List[str]:
@@ -94,8 +94,10 @@ class PathComparer:
return [line.strip() for line in difflib.Differ().compare([s_path], [o_path])]
-def pytest_assertrepr_compare(op, left, right):
+def pytest_assertrepr_compare(op: str, left: Any, right: Any) -> List[str]:
if isinstance(left, PathComparer) and op == "==":
return ['Comparing path:'] + left.ldiff(right)
- if isinstance(right, PathComparer) and op == "==":
+ elif isinstance(right, PathComparer) and op == "==":
return ['Comparing path:'] + right.rdiff(left)
+ else:
+ return []
diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py
index c6dd7ecdf..eec3b4208 100644
--- a/sphinx/testing/fixtures.py
+++ b/sphinx/testing/fixtures.py
@@ -14,20 +14,44 @@ import sys
from collections import namedtuple
from io import StringIO
from subprocess import PIPE
-from typing import Any, Dict
+from typing import Any, Callable, Dict, Generator, Tuple
import pytest
-from . import util
+from sphinx.testing import util
+from sphinx.testing.util import SphinxTestApp, SphinxTestAppWrapperForSkipBuilding
@pytest.fixture(scope='session')
-def rootdir() -> None:
+def rootdir() -> str:
return None
+class SharedResult:
+ cache = {} # type: Dict[str, Dict[str, str]]
+
+ def store(self, key: str, app_: SphinxTestApp) -> Any:
+ if key in self.cache:
+ return
+ data = {
+ 'status': app_._status.getvalue(),
+ 'warning': app_._warning.getvalue(),
+ }
+ self.cache[key] = data
+
+ def restore(self, key: str) -> Dict[str, StringIO]:
+ if key not in self.cache:
+ return {}
+ data = self.cache[key]
+ return {
+ 'status': StringIO(data['status']),
+ 'warning': StringIO(data['warning']),
+ }
+
+
@pytest.fixture
-def app_params(request, test_params, shared_result, sphinx_test_tempdir, rootdir):
+def app_params(request: Any, test_params: Dict, shared_result: SharedResult,
+ sphinx_test_tempdir: str, rootdir: str) -> Tuple[Dict, Dict]:
"""
parameters that is specified by 'pytest.mark.sphinx' for
sphinx.application.Sphinx initialization
@@ -40,7 +64,7 @@ def app_params(request, test_params, shared_result, sphinx_test_tempdir, rootdir
else:
markers = request.node.get_marker("sphinx")
pargs = {}
- kwargs = {} # type: Dict[str, str]
+ kwargs = {} # type: Dict[str, Any]
if markers is not None:
# to avoid stacking positional args
@@ -75,7 +99,7 @@ def app_params(request, test_params, shared_result, sphinx_test_tempdir, rootdir
@pytest.fixture
-def test_params(request):
+def test_params(request: Any) -> Dict:
"""
test parameters that is specified by 'pytest.mark.test_params'
@@ -102,7 +126,8 @@ def test_params(request):
@pytest.fixture(scope='function')
-def app(test_params, app_params, make_app, shared_result):
+def app(test_params: Dict, app_params: Tuple[Dict, Dict], make_app: Callable,
+ shared_result: SharedResult) -> Generator[SphinxTestApp, None, None]:
"""
provides sphinx.application.Sphinx object
"""
@@ -122,7 +147,7 @@ def app(test_params, app_params, make_app, shared_result):
@pytest.fixture(scope='function')
-def status(app):
+def status(app: SphinxTestApp) -> StringIO:
"""
compat for testing with previous @with_app decorator
"""
@@ -130,7 +155,7 @@ def status(app):
@pytest.fixture(scope='function')
-def warning(app):
+def warning(app: SphinxTestApp) -> StringIO:
"""
compat for testing with previous @with_app decorator
"""
@@ -138,7 +163,7 @@ def warning(app):
@pytest.fixture()
-def make_app(test_params, monkeypatch):
+def make_app(test_params: Dict, monkeypatch: Any) -> Generator[Callable, None, None]:
"""
provides make_app function to initialize SphinxTestApp instance.
if you want to initialize 'app' in your test function. please use this
@@ -153,10 +178,10 @@ def make_app(test_params, monkeypatch):
status, warning = StringIO(), StringIO()
kwargs.setdefault('status', status)
kwargs.setdefault('warning', warning)
- app_ = util.SphinxTestApp(*args, **kwargs) # type: Any
+ app_ = SphinxTestApp(*args, **kwargs) # type: Any
apps.append(app_)
if test_params['shared_result']:
- app_ = util.SphinxTestAppWrapperForSkipBuilding(app_)
+ app_ = SphinxTestAppWrapperForSkipBuilding(app_)
return app_
yield make
@@ -165,40 +190,18 @@ def make_app(test_params, monkeypatch):
app_.cleanup()
-class SharedResult:
- cache = {} # type: Dict[str, Dict[str, str]]
-
- def store(self, key, app_):
- if key in self.cache:
- return
- data = {
- 'status': app_._status.getvalue(),
- 'warning': app_._warning.getvalue(),
- }
- self.cache[key] = data
-
- def restore(self, key):
- if key not in self.cache:
- return {}
- data = self.cache[key]
- return {
- 'status': StringIO(data['status']),
- 'warning': StringIO(data['warning']),
- }
-
-
@pytest.fixture
-def shared_result():
+def shared_result() -> SharedResult:
return SharedResult()
@pytest.fixture(scope='module', autouse=True)
-def _shared_result_cache():
+def _shared_result_cache() -> None:
SharedResult.cache.clear()
@pytest.fixture
-def if_graphviz_found(app):
+def if_graphviz_found(app: SphinxTestApp) -> None:
"""
The test will be skipped when using 'if_graphviz_found' fixture and graphviz
dot command is not found.
@@ -215,7 +218,7 @@ def if_graphviz_found(app):
@pytest.fixture(scope='session')
-def sphinx_test_tempdir(tmpdir_factory):
+def sphinx_test_tempdir(tmpdir_factory: Any) -> "util.path":
"""
temporary directory that wrapped with `path` class.
"""
@@ -227,7 +230,7 @@ def sphinx_test_tempdir(tmpdir_factory):
@pytest.fixture
-def tempdir(tmpdir):
+def tempdir(tmpdir: str) -> "util.path":
"""
temporary directory that wrapped with `path` class.
this fixture is for compat with old test implementation.
diff --git a/sphinx/testing/path.py b/sphinx/testing/path.py
index 1c883af2f..4c3702e3d 100644
--- a/sphinx/testing/path.py
+++ b/sphinx/testing/path.py
@@ -10,8 +10,11 @@ import builtins
import os
import shutil
import sys
+import warnings
from typing import Any, Callable, IO, List
+from sphinx.deprecation import RemovedInSphinx50Warning
+
FILESYSTEMENCODING = sys.getfilesystemencoding() or sys.getdefaultencoding()
@@ -138,6 +141,14 @@ class path(str):
"""
Returns the text in the file.
"""
+ warnings.warn('Path.text() is deprecated. Please use read_text() instead.',
+ RemovedInSphinx50Warning, stacklevel=2)
+ return self.read_text(encoding, **kwargs)
+
+ def read_text(self, encoding: str = 'utf-8', **kwargs: Any) -> str:
+ """
+ Returns the text in the file.
+ """
with open(self, encoding=encoding, **kwargs) as f:
return f.read()
@@ -145,6 +156,14 @@ class path(str):
"""
Returns the bytes in the file.
"""
+ warnings.warn('Path.bytes() is deprecated. Please use read_bytes() instead.',
+ RemovedInSphinx50Warning, stacklevel=2)
+ return self.read_bytes()
+
+ def read_bytes(self) -> builtins.bytes:
+ """
+ Returns the bytes in the file.
+ """
with open(self, mode='rb') as f:
return f.read()
diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py
index f4ef35d61..75cd0f411 100644
--- a/sphinx/testing/util.py
+++ b/sphinx/testing/util.py
@@ -11,10 +11,12 @@ import os
import re
import sys
import warnings
+from io import StringIO
from typing import Any, Dict, Generator, IO, List, Pattern
from xml.etree import ElementTree
from docutils import nodes
+from docutils.nodes import Node
from docutils.parsers.rst import directives, roles
from sphinx import application, locale
@@ -47,7 +49,7 @@ def assert_startswith(thing: str, prefix: str) -> None:
assert False, '%r does not start with %r' % (thing, prefix)
-def assert_node(node: nodes.Node, cls: Any = None, xpath: str = "", **kwargs: Any) -> None:
+def assert_node(node: Node, cls: Any = None, xpath: str = "", **kwargs: Any) -> None:
if cls:
if isinstance(cls, list):
assert_node(node, cls[0], xpath=xpath, **kwargs)
@@ -101,6 +103,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
def __init__(self, buildername: str = 'html', srcdir: path = None, freshenv: bool = False,
confoverrides: Dict = None, status: IO = None, warning: IO = None,
diff --git a/sphinx/texinputs/Makefile_t b/sphinx/texinputs/Makefile_t
index 08ddd2f0a..96bb0fed1 100644
--- a/sphinx/texinputs/Makefile_t
+++ b/sphinx/texinputs/Makefile_t
@@ -10,7 +10,6 @@ ALLDVI = $(addsuffix .dvi,$(ALLDOCS))
ALLXDV =
{% endif -%}
ALLPS = $(addsuffix .ps,$(ALLDOCS))
-ALLIMGS = $(wildcard *.png *.gif *.jpg *.jpeg)
# Prefix for archive names
ARCHIVEPREFIX =
@@ -46,15 +45,7 @@ LATEX = latexmk -dvi
PDFLATEX = latexmk -pdf -dvi- -ps-
{% endif %}
-%.png %.gif %.jpg %.jpeg: FORCE_MAKE
- extractbb '$@'
-
-{% if latex_engine in ('platex', 'uplatex') -%}
-%.dvi: %.tex $(ALLIMGS) FORCE_MAKE
- for f in *.pdf; do extractbb "$$f"; done
- $(LATEX) $(LATEXMKOPTS) '$<'
-
-{% elif latex_engine != 'xelatex' -%}
+{% if latex_engine != 'xelatex' -%}
%.dvi: %.tex FORCE_MAKE
$(LATEX) $(LATEXMKOPTS) '$<'
@@ -62,12 +53,7 @@ PDFLATEX = latexmk -pdf -dvi- -ps-
%.ps: %.dvi
dvips '$<'
-{% if latex_engine in ('platex', 'uplatex') -%}
-%.pdf: %.tex $(ALLIMGS) FORCE_MAKE
- for f in *.pdf; do extractbb "$$f"; done
-{%- else -%}
%.pdf: %.tex FORCE_MAKE
-{%- endif %}
$(PDFLATEX) $(LATEXMKOPTS) '$<'
all: $(ALLPDF)
diff --git a/sphinx/texinputs/make.bat_t b/sphinx/texinputs/make.bat_t
index 83dd449c2..9dfa38c13 100644
--- a/sphinx/texinputs/make.bat_t
+++ b/sphinx/texinputs/make.bat_t
@@ -31,11 +31,6 @@ if "%1" == "" goto all-pdf
if "%1" == "all-pdf" (
:all-pdf
-{%- if latex_engine == 'platex' %}
- for %%i in (*.png *.gif *.jpg *.jpeg *.pdf) do (
- extractbb %%i
- )
-{%- endif %}
for %%i in (*.tex) do (
%PDFLATEX% %LATEXMKOPTS% %%i
)
diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty
index 3e67b5610..714d98e05 100644
--- a/sphinx/texinputs/sphinx.sty
+++ b/sphinx/texinputs/sphinx.sty
@@ -1407,7 +1407,8 @@
% \sphinxcode). Sphinx uses \#, \%, \& ... always inside \sphinxhref.
\protected\def\sphinxhref#1#2{{%
\sphinxunactivateextrasandspace % never do \scantokens with active space!
- \endlinechar\m@ne\everyeof{{#2}}% keep catcode regime for #2
+% 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.
diff --git a/sphinx/themes/basic/search.html b/sphinx/themes/basic/search.html
index 02ee85abc..2673369f2 100644
--- a/sphinx/themes/basic/search.html
+++ b/sphinx/themes/basic/search.html
@@ -27,10 +27,8 @@
</p>
</div>
<p>
- {% trans %}From here you can search these documents. Enter your search
- words into the box below and click "search". Note that the search
- function will automatically search for all of the words. Pages
- containing fewer words won't appear in the result list.{% endtrans %}
+ {% trans %}Searching for multiple words only shows matches that contain
+ all words.{% endtrans %}
</p>
<form action="" method="get">
<input type="text" name="q" aria-labelledby="search-documentation" value="" />
diff --git a/sphinx/themes/basic/searchresults.html b/sphinx/themes/basic/searchresults.html
deleted file mode 100644
index 50700c158..000000000
--- a/sphinx/themes/basic/searchresults.html
+++ /dev/null
@@ -1,36 +0,0 @@
-{#
- basic/searchresults.html
- ~~~~~~~~~~~~~~~~~~~~~~~~
-
- Template for the body of the search results page.
-
- :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-#}
-<h1 id="search-documentation">{{ _('Search') }}</h1>
-<p>
- From here you can search these documents. Enter your search
- words into the box below and click "search".
-</p>
-<form action="" method="get">
- <input type="text" name="q" value="" />
- <input type="submit" value="{{ _('search') }}" />
- <span id="search-progress" style="padding-left: 10px"></span>
-</form>
-{%- if search_performed %}
- <h2>{{ _('Search Results') }}</h2>
- {%- if not search_results %}
- <p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
- {%- endif %}
-{%- endif %}
-<div id="search-results">
- {%- if search_results %}
- <ul class="search">
- {% for href, caption, context in search_results %}
- <li><a href="{{ docroot }}{{ href }}/?highlight={{ q }}">{{ caption }}</a>
- <div class="context">{{ context|e }}</div>
- </li>
- {% endfor %}
- </ul>
- {%- endif %}
-</div>
diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py
index ee459cc56..6d7c3b0eb 100644
--- a/sphinx/transforms/post_transforms/__init__.py
+++ b/sphinx/transforms/post_transforms/__init__.py
@@ -131,7 +131,7 @@ class ReferencesResolver(SphinxPostTransform):
if not results:
return None
if len(results) > 1:
- def stringify(name, node):
+ def stringify(name: str, node: Element) -> str:
reftitle = node.get('reftitle', node.astext())
return ':%s:`%s`' % (name, reftitle)
candidates = ' or '.join(stringify(name, role) for name, role in results)
diff --git a/sphinx/transforms/post_transforms/compat.py b/sphinx/transforms/post_transforms/compat.py
deleted file mode 100644
index c71191dec..000000000
--- a/sphinx/transforms/post_transforms/compat.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""
- sphinx.transforms.post_transforms.compat
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Post transforms for compatibility
-
- :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-"""
-
-import warnings
-from typing import Any, Dict
-
-from docutils import nodes
-from docutils.writers.docutils_xml import XMLTranslator
-
-from sphinx.addnodes import math_block, displaymath
-from sphinx.application import Sphinx
-from sphinx.deprecation import RemovedInSphinx30Warning
-from sphinx.transforms import SphinxTransform
-from sphinx.util import logging
-
-
-logger = logging.getLogger(__name__)
-
-
-class MathNodeMigrator(SphinxTransform):
- """Migrate a math node to docutils'.
-
- For a long time, Sphinx uses an original node for math. Since 1.8,
- Sphinx starts to use a math node of docutils'. This transform converts
- old and new nodes to keep compatibility.
- """
- default_priority = 999
-
- def apply(self, **kwargs: Any) -> None:
- for math_node in self.document.traverse(nodes.math):
- # case: old styled ``math`` node generated by old extensions
- if len(math_node) == 0:
- warnings.warn("math node for Sphinx was replaced by docutils'. "
- "Please use ``docutils.nodes.math`` instead.",
- RemovedInSphinx30Warning)
- equation = math_node['latex']
- math_node += nodes.Text(equation, equation)
-
- translator = self.app.builder.get_translator_class()
- if hasattr(translator, 'visit_displaymath') and translator != XMLTranslator:
- # case: old translators which does not support ``math_block`` node
- warnings.warn("Translator for %s does not support math_block node'. "
- "Please update your extension." % translator,
- RemovedInSphinx30Warning)
- for old_math_block_node in self.document.traverse(math_block):
- alt = displaymath(latex=old_math_block_node.astext(),
- number=old_math_block_node.get('number'),
- label=old_math_block_node.get('label'),
- nowrap=old_math_block_node.get('nowrap'),
- docname=old_math_block_node.get('docname'))
- old_math_block_node.replace_self(alt)
- elif getattr(self.app.builder, 'math_renderer_name', None) == 'unknown':
- # case: math extension provides old styled math renderer
- for math_block_node in self.document.traverse(nodes.math_block):
- math_block_node['latex'] = math_block_node.astext()
-
- # case: old styled ``displaymath`` node generated by old extensions
- for math_block_node in self.document.traverse(math_block):
- if len(math_block_node) == 0:
- warnings.warn("math node for Sphinx was replaced by docutils'. "
- "Please use ``docutils.nodes.math_block`` instead.",
- RemovedInSphinx30Warning)
- if isinstance(math_block_node, displaymath):
- newnode = nodes.math_block('', math_block_node['latex'],
- **math_block_node.attributes)
- math_block_node.replace_self(newnode)
- else:
- latex = math_block_node['latex']
- math_block_node += nodes.Text(latex, latex)
-
-
-def setup(app: Sphinx) -> Dict[str, Any]:
- app.add_post_transform(MathNodeMigrator)
-
- return {
- 'version': 'builtin',
- 'parallel_read_safe': True,
- 'parallel_write_safe': True,
- }
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py
index 262837b50..12ae051f1 100644
--- a/sphinx/util/__init__.py
+++ b/sphinx/util/__init__.py
@@ -28,16 +28,13 @@ from time import mktime, strptime
from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, Set, Tuple
from urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode
-from docutils.utils import relative_path
-
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.errors import (
PycodeError, SphinxParallelError, ExtensionError, FiletypeNotFoundError
)
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.console import strip_colors, colorize, bold, term_width_line # type: ignore
-from sphinx.util.fileutil import copy_asset_file
from sphinx.util.typing import PathMatcher
from sphinx.util import smartypants # noqa
@@ -45,7 +42,7 @@ from sphinx.util import smartypants # noqa
# prune unused ones indiscriminately
from sphinx.util.osutil import ( # noqa
SEP, os_path, relative_uri, ensuredir, walk, mtimes_of_files, movefile,
- copyfile, copytimes, make_filename, ustrftime)
+ copyfile, copytimes, make_filename)
from sphinx.util.nodes import ( # noqa
nested_parse_with_titles, split_explicit_title, explicit_title_re,
caption_ref_re)
@@ -56,7 +53,7 @@ if False:
# For type annotation
from typing import Type # for python3.5.1
from sphinx.application import Sphinx
- from sphinx.builders import Builder
+
logger = logging.getLogger(__name__)
@@ -201,35 +198,6 @@ class DownloadFiles(dict):
self.add_file(docname, filename)
-def copy_static_entry(source: str, targetdir: str, builder: "Builder", context: Dict = {},
- exclude_matchers: Tuple[PathMatcher, ...] = (), level: int = 0) -> None:
- """[DEPRECATED] Copy a HTML builder static_path entry from source to targetdir.
-
- Handles all possible cases of files, directories and subdirectories.
- """
- warnings.warn('sphinx.util.copy_static_entry is deprecated for removal',
- RemovedInSphinx30Warning, stacklevel=2)
-
- if exclude_matchers:
- relpath = relative_path(path.join(builder.srcdir, 'dummy'), source)
- for matcher in exclude_matchers:
- if matcher(relpath):
- return
- if path.isfile(source):
- copy_asset_file(source, targetdir, context, builder.templates)
- elif path.isdir(source):
- ensuredir(targetdir)
- for entry in os.listdir(source):
- if entry.startswith('.'):
- continue
- newtarget = targetdir
- if path.isdir(path.join(source, entry)):
- newtarget = path.join(targetdir, entry)
- copy_static_entry(path.join(source, entry), newtarget,
- builder, context, level=level + 1,
- exclude_matchers=exclude_matchers)
-
-
_DEBUG_HEADER = '''\
# Sphinx version: %s
# Python version: %s (%s)
@@ -681,7 +649,7 @@ class progress_message:
def __call__(self, f: Callable) -> Callable:
@functools.wraps(f)
- def wrapper(*args, **kwargs):
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
with self:
return f(*args, **kwargs)
diff --git a/sphinx/util/compat.py b/sphinx/util/compat.py
index ba3c4d27c..0135899eb 100644
--- a/sphinx/util/compat.py
+++ b/sphinx/util/compat.py
@@ -15,27 +15,14 @@ from typing import Any, Dict
from docutils.utils import get_source_line
from sphinx import addnodes
-from sphinx.config import Config
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.transforms import SphinxTransform
-from sphinx.util import import_object
if False:
# For type annotation
from sphinx.application import Sphinx
-def deprecate_source_parsers(app: "Sphinx", config: Config) -> None:
- if config.source_parsers:
- warnings.warn('The config variable "source_parsers" is deprecated. '
- 'Please update your extension for the parser and remove the setting.',
- RemovedInSphinx30Warning)
- for suffix, parser in config.source_parsers.items():
- if isinstance(parser, str):
- parser = import_object(parser, 'source parser')
- app.add_source_parser(suffix, parser)
-
-
def register_application_for_autosummary(app: "Sphinx") -> None:
"""Register application object to autosummary module.
@@ -65,7 +52,6 @@ class IndexEntriesMigrator(SphinxTransform):
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_transform(IndexEntriesMigrator)
- app.connect('config-inited', deprecate_source_parsers)
app.connect('builder-inited', register_application_for_autosummary)
return {
diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py
index 8854a1f98..7b3f011d1 100644
--- a/sphinx/util/docstrings.py
+++ b/sphinx/util/docstrings.py
@@ -8,8 +8,38 @@
:license: BSD, see LICENSE for details.
"""
+import re
import sys
-from typing import List
+from typing import Dict, List
+
+from docutils.parsers.rst.states import Body
+
+
+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]
+
+ if not s:
+ return metadata
+
+ for line in prepare_docstring(s):
+ if line.strip() == '':
+ in_other_element = False
+ else:
+ matched = field_list_item_re.match(line)
+ if matched and not in_other_element:
+ field_name = matched.group()[1:].split(':', 1)[0]
+ if field_name.startswith('meta '):
+ name = field_name[5:].strip()
+ metadata[name] = line[matched.end():].strip()
+ else:
+ in_other_element = True
+
+ return metadata
def prepare_docstring(s: str, ignore: int = 1, tabsize: int = 8) -> List[str]:
diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py
index 23f2c888b..3b7b60201 100644
--- a/sphinx/util/docutils.py
+++ b/sphinx/util/docutils.py
@@ -10,8 +10,6 @@
import os
import re
-import types
-import warnings
from contextlib import contextmanager
from copy import copy
from distutils.version import LooseVersion
@@ -24,14 +22,12 @@ import docutils
from docutils import nodes
from docutils.io import FileOutput
from docutils.nodes import Element, Node, system_message
-from docutils.parsers.rst import Directive, directives, roles, convert_directive_function
+from docutils.parsers.rst import Directive, directives, roles
from docutils.parsers.rst.states import Inliner
from docutils.statemachine import StateMachine, State, StringList
from docutils.utils import Reporter, unescape
-from sphinx.deprecation import RemovedInSphinx30Warning
-from sphinx.errors import ExtensionError, SphinxError
-from sphinx.locale import __
+from sphinx.errors import SphinxError
from sphinx.util import logging
from sphinx.util.typing import RoleFunction
@@ -279,25 +275,6 @@ def is_html5_writer_available() -> bool:
return __version_info__ > (0, 13, 0)
-def directive_helper(obj: Any, has_content: bool = None,
- argument_spec: Tuple[int, int, bool] = None, **option_spec: Any
- ) -> Any:
- warnings.warn('function based directive support is now deprecated. '
- 'Use class based directive instead.',
- RemovedInSphinx30Warning)
-
- if isinstance(obj, (types.FunctionType, types.MethodType)):
- obj.content = has_content # type: ignore
- obj.arguments = argument_spec or (0, 0, False) # type: ignore
- obj.options = option_spec # type: ignore
- return convert_directive_function(obj)
- else:
- if has_content or argument_spec or option_spec:
- raise ExtensionError(__('when adding directive classes, no '
- 'additional arguments may be given'))
- return obj
-
-
@contextmanager
def switch_source_input(state: State, content: StringList) -> Generator[None, None, None]:
"""Switch current source input of state temporarily."""
@@ -467,7 +444,7 @@ class SphinxTranslator(nodes.NodeVisitor):
self.config = builder.config
self.settings = document.settings
- def dispatch_visit(self, node):
+ def dispatch_visit(self, node: Node) -> None:
"""
Dispatch node to appropriate visitor method.
The priority of visitor method is:
@@ -479,13 +456,14 @@ class SphinxTranslator(nodes.NodeVisitor):
for node_class in node.__class__.__mro__:
method = getattr(self, 'visit_%s' % (node_class.__name__), None)
if method:
- logger.debug('SphinxTranslator.dispatch_visit calling %s for %s' %
- (method.__name__, node))
- return method(node)
+ logger.debug('SphinxTranslator.dispatch_visit calling %s for %s',
+ method.__name__, node)
+ method(node)
+ break
else:
super().dispatch_visit(node)
- def dispatch_departure(self, node):
+ def dispatch_departure(self, node: Node) -> None:
"""
Dispatch node to appropriate departure method.
The priority of departure method is:
@@ -497,9 +475,10 @@ class SphinxTranslator(nodes.NodeVisitor):
for node_class in node.__class__.__mro__:
method = getattr(self, 'depart_%s' % (node_class.__name__), None)
if method:
- logger.debug('SphinxTranslator.dispatch_departure calling %s for %s' %
- (method.__name__, node))
- return method(node)
+ logger.debug('SphinxTranslator.dispatch_departure calling %s for %s',
+ method.__name__, node)
+ method(node)
+ break
else:
super().dispatch_departure(node)
diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py
index 096a200bf..1cb75637c 100644
--- a/sphinx/util/i18n.py
+++ b/sphinx/util/i18n.py
@@ -20,7 +20,7 @@ import babel.dates
from babel.messages.mofile import write_mo
from babel.messages.pofile import read_po
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.errors import SphinxError
from sphinx.locale import __
from sphinx.util import logging
@@ -151,9 +151,8 @@ def find_catalog_files(docname: str, srcdir: str, locale_dirs: List[str],
def find_catalog_source_files(locale_dirs: List[str], locale: str, domains: List[str] = None,
- gettext_compact: bool = None, charset: str = 'utf-8',
- force_all: bool = False, excluded: Matcher = Matcher([])
- ) -> Set[CatalogInfo]:
+ 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
@@ -169,9 +168,6 @@ def find_catalog_source_files(locale_dirs: List[str], locale: str, domains: List
"""
warnings.warn('find_catalog_source_files() is deprecated.',
RemovedInSphinx40Warning, stacklevel=2)
- if gettext_compact is not None:
- warnings.warn('gettext_compact argument for find_catalog_source_files() '
- 'is deprecated.', RemovedInSphinx30Warning, stacklevel=2)
catalogs = set() # type: Set[CatalogInfo]
diff --git a/sphinx/util/images.py b/sphinx/util/images.py
index 506e4a931..568682b37 100644
--- a/sphinx/util/images.py
+++ b/sphinx/util/images.py
@@ -10,16 +10,12 @@
import base64
import imghdr
-import warnings
from collections import OrderedDict
-from io import BytesIO
from os import path
from typing import IO, NamedTuple, Tuple
import imagesize
-from sphinx.deprecation import RemovedInSphinx30Warning
-
try:
from PIL import Image
except ImportError:
@@ -49,12 +45,8 @@ def get_image_size(filename: str) -> Tuple[int, int]:
size = (int(size[0]), int(size[1]))
if size is None and Image: # fallback to Pillow
- im = Image.open(filename)
- size = im.size
- try:
- im.fp.close()
- except Exception:
- pass
+ with Image.open(filename) as im:
+ size = im.size
return size
except Exception:
@@ -69,16 +61,10 @@ def guess_mimetype_for_stream(stream: IO, default: str = None) -> str:
return default
-def guess_mimetype(filename: str = '', content: bytes = None, default: str = None) -> str:
+def guess_mimetype(filename: str = '', default: str = None) -> str:
_, ext = path.splitext(filename.lower())
if ext in mime_suffixes:
return mime_suffixes[ext]
- elif content:
- # TODO: When the content argument is removed, make filename a required
- # argument.
- warnings.warn('The content argument of guess_mimetype() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return guess_mimetype_for_stream(BytesIO(content), default=default)
elif path.exists(filename):
with open(filename, 'rb') as f:
return guess_mimetype_for_stream(f, default=default)
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 2bcccdd60..281ef4493 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -17,12 +17,15 @@ import typing
import warnings
from functools import partial, partialmethod
from inspect import ( # NOQA
- isclass, ismethod, ismethoddescriptor, isroutine
+ Parameter, isclass, ismethod, ismethoddescriptor, isroutine
)
from io import StringIO
from typing import Any, Callable, Mapping, List, Tuple
+from typing import cast
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
+from sphinx.pycode.ast import ast # for py35-37
+from sphinx.pycode.ast import unparse as ast_unparse
from sphinx.util import logging
from sphinx.util.typing import stringify as stringify_annotation
@@ -51,9 +54,11 @@ memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
# 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software
# Foundation; All Rights Reserved
-def getargspec(func):
+def getargspec(func: Callable) -> Any:
"""Like inspect.getfullargspec but supports bound methods, and wrapped
methods."""
+ warnings.warn('sphinx.ext.inspect.getargspec() is deprecated',
+ RemovedInSphinx50Warning)
# On 3.5+, signature(int) or similar raises ValueError. On 3.4, it
# succeeds with a bogus signature. We want a TypeError uniformly, to
# match historical behavior.
@@ -81,19 +86,19 @@ def getargspec(func):
kind = param.kind
name = param.name
- if kind is inspect.Parameter.POSITIONAL_ONLY:
+ if kind is Parameter.POSITIONAL_ONLY:
args.append(name)
- elif kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
+ elif kind is Parameter.POSITIONAL_OR_KEYWORD:
args.append(name)
if param.default is not param.empty:
defaults += (param.default,) # type: ignore
- elif kind is inspect.Parameter.VAR_POSITIONAL:
+ elif kind is Parameter.VAR_POSITIONAL:
varargs = name
- elif kind is inspect.Parameter.KEYWORD_ONLY:
+ elif kind is Parameter.KEYWORD_ONLY:
kwonlyargs.append(name)
if param.default is not param.empty:
kwdefaults[name] = param.default
- elif kind is inspect.Parameter.VAR_KEYWORD:
+ elif kind is Parameter.VAR_KEYWORD:
varkw = name
if param.annotation is not param.empty:
@@ -359,7 +364,7 @@ def signature(subject: Callable, bound_method: bool = False) -> inspect.Signatur
# https://bugs.python.org/issue33009
if hasattr(subject, '_partialmethod'):
parameters = []
- return_annotation = inspect.Parameter.empty
+ return_annotation = Parameter.empty
else:
raise
@@ -427,11 +432,11 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
args.append(arg.getvalue())
last_kind = param.kind
- if last_kind == inspect.Parameter.POSITIONAL_ONLY:
+ if last_kind == Parameter.POSITIONAL_ONLY:
# PEP-570: Separator for Positional Only Parameter: /
args.append('/')
- if (sig.return_annotation is inspect.Parameter.empty or
+ if (sig.return_annotation is Parameter.empty or
show_annotation is False or
show_return_annotation is False):
return '(%s)' % ', '.join(args)
@@ -440,24 +445,50 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
return '(%s) -> %s' % (', '.join(args), annotation)
-class Parameter:
- """Fake parameter class for python2."""
- POSITIONAL_ONLY = 0
- POSITIONAL_OR_KEYWORD = 1
- VAR_POSITIONAL = 2
- KEYWORD_ONLY = 3
- VAR_KEYWORD = 4
- empty = object()
+def signature_from_str(signature: str) -> inspect.Signature:
+ """Create a Signature object from string."""
+ module = ast.parse('def func' + signature + ': pass')
+ definition = cast(ast.FunctionDef, module.body[0]) # type: ignore
- def __init__(self, name: str, kind: int = POSITIONAL_OR_KEYWORD,
- default: Any = empty) -> None:
- self.name = name
- self.kind = kind
- self.default = default
- self.annotation = self.empty
+ # parameters
+ args = definition.args
+ params = []
- warnings.warn('sphinx.util.inspect.Parameter is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
+ if hasattr(args, "posonlyargs"):
+ for arg in args.posonlyargs: # type: ignore
+ annotation = ast_unparse(arg.annotation) or Parameter.empty
+ params.append(Parameter(arg.arg, Parameter.POSITIONAL_ONLY,
+ annotation=annotation))
+
+ for i, arg in enumerate(args.args):
+ if len(args.args) - i <= len(args.defaults):
+ default = ast_unparse(args.defaults[-len(args.args) + i])
+ else:
+ default = Parameter.empty
+
+ annotation = ast_unparse(arg.annotation) or Parameter.empty
+ params.append(Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD,
+ default=default, annotation=annotation))
+
+ if args.vararg:
+ annotation = ast_unparse(args.vararg.annotation) or Parameter.empty
+ params.append(Parameter(args.vararg.arg, Parameter.VAR_POSITIONAL,
+ annotation=annotation))
+
+ for i, arg in enumerate(args.kwonlyargs):
+ default = ast_unparse(args.kw_defaults[i])
+ annotation = ast_unparse(arg.annotation) or Parameter.empty
+ params.append(Parameter(arg.arg, Parameter.KEYWORD_ONLY, default=default,
+ annotation=annotation))
+
+ if args.kwarg:
+ annotation = ast_unparse(args.kwarg.annotation) or Parameter.empty
+ params.append(Parameter(args.kwarg.arg, Parameter.VAR_KEYWORD,
+ annotation=annotation))
+
+ return_annotation = ast_unparse(definition.returns) or Parameter.empty
+
+ return inspect.Signature(params, return_annotation=return_annotation)
class Signature:
@@ -528,12 +559,12 @@ class Signature:
if self.has_retval:
return self.signature.return_annotation
else:
- return inspect.Parameter.empty
+ return Parameter.empty
else:
return None
def format_args(self, show_annotation: bool = True) -> str:
- def get_annotation(param: inspect.Parameter) -> Any:
+ def get_annotation(param: Parameter) -> Any:
if isinstance(param.annotation, str) and param.name in self.annotations:
return self.annotations[param.name]
else:
@@ -585,7 +616,7 @@ class Signature:
args.append(arg.getvalue())
last_kind = param.kind
- if self.return_annotation is inspect.Parameter.empty or show_annotation is False:
+ if self.return_annotation is Parameter.empty or show_annotation is False:
return '(%s)' % ', '.join(args)
else:
if 'return' in self.annotations:
diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py
index 7c7300c60..474a704a1 100644
--- a/sphinx/util/nodes.py
+++ b/sphinx/util/nodes.py
@@ -443,7 +443,7 @@ def make_id(env: "BuildEnvironment", document: nodes.document,
if prefix:
idformat = prefix + "-%s"
else:
- idformat = document.settings.id_prefix + "%s"
+ idformat = (document.settings.id_prefix or "id") + "%s"
# try to generate node_id by *term*
if prefix and term:
@@ -451,6 +451,10 @@ def make_id(env: "BuildEnvironment", document: nodes.document,
if node_id == prefix:
# *term* is not good to generate a node_id.
node_id = None
+ elif term:
+ node_id = nodes.make_id(term)
+ if node_id == '':
+ node_id = None # fallback to None
while node_id is None or node_id in document.ids:
node_id = idformat % env.new_serialno(prefix)
diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py
index 23fa208d7..e44211cf9 100644
--- a/sphinx/util/osutil.py
+++ b/sphinx/util/osutil.py
@@ -15,13 +15,12 @@ import os
import re
import shutil
import sys
-import time
import warnings
from io import StringIO
from os import path
from typing import Any, Generator, Iterator, List, Tuple
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning
try:
# for ALT Linux (#6712)
@@ -144,27 +143,6 @@ def make_filename_from_project(project: str) -> str:
return make_filename(project_suffix_re.sub('', project)).lower()
-def ustrftime(format: str, *args: Any) -> str:
- """[DEPRECATED] strftime for unicode strings."""
- warnings.warn('sphinx.util.osutil.ustrtime is deprecated for removal',
- RemovedInSphinx30Warning, stacklevel=2)
-
- if not args:
- # If time is not specified, try to use $SOURCE_DATE_EPOCH variable
- # See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal
- source_date_epoch = os.getenv('SOURCE_DATE_EPOCH')
- if source_date_epoch is not None:
- time_struct = time.gmtime(float(source_date_epoch))
- args = [time_struct] # type: ignore
- # On Windows, time.strftime() and Unicode characters will raise UnicodeEncodeError.
- # https://bugs.python.org/issue8304
- try:
- return time.strftime(format, *args)
- except UnicodeEncodeError:
- r = time.strftime(format.encode('unicode-escape').decode(), *args)
- return r.encode().decode('unicode-escape')
-
-
def relpath(path: str, start: str = os.curdir) -> str:
"""Return a relative filepath to *path* either from the current directory or
from an optional *start* directory.
diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py
index ba4f83a71..061bbcb6d 100644
--- a/sphinx/util/pycompat.py
+++ b/sphinx/util/pycompat.py
@@ -52,7 +52,7 @@ class UnicodeMixin:
.. deprecated:: 2.0
"""
- def __str__(self):
+ def __str__(self) -> str:
warnings.warn('UnicodeMixin is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
return self.__unicode__() # type: ignore
diff --git a/sphinx/util/requests.py b/sphinx/util/requests.py
index 884e7c997..d9d1d1b2c 100644
--- a/sphinx/util/requests.py
+++ b/sphinx/util/requests.py
@@ -14,7 +14,6 @@ from contextlib import contextmanager
from typing import Any, Generator, Union
from urllib.parse import urlsplit
-import pkg_resources
import requests
import sphinx
@@ -37,28 +36,6 @@ except ImportError:
# for requests < 2.4.0
InsecureRequestWarning = None # type: ignore
-try:
- from requests.packages.urllib3.exceptions import InsecurePlatformWarning
-except ImportError:
- try:
- # for Debian-jessie
- from urllib3.exceptions import InsecurePlatformWarning # type: ignore
- except ImportError:
- # for requests < 2.4.0
- InsecurePlatformWarning = None # type: ignore
-
-# try to load requests[security] (but only if SSL is available)
-try:
- import ssl # NOQA
-except ImportError:
- pass
-else:
- try:
- pkg_resources.require(['requests[security]'])
- except (pkg_resources.DistributionNotFound,
- pkg_resources.VersionConflict):
- pass # ignored
-
useragent_header = [('User-Agent',
'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0')]
diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py
index ccceefed6..eb38d232c 100644
--- a/sphinx/util/typing.py
+++ b/sphinx/util/typing.py
@@ -49,7 +49,8 @@ def stringify(annotation: Any) -> str:
return repr(annotation)
elif annotation is NoneType: # type: ignore
return 'None'
- elif getattr(annotation, '__module__', None) == 'builtins':
+ elif (getattr(annotation, '__module__', None) == 'builtins' and
+ hasattr(annotation, '__qualname__')):
return annotation.__qualname__
elif annotation is Ellipsis:
return '...'
@@ -88,6 +89,8 @@ def _stringify_py37(annotation: Any) -> str:
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
returns = stringify(annotation.__args__[-1])
return '%s[[%s], %s]' % (qualname, args, returns)
+ elif str(annotation).startswith('typing.Annotated'): # for py39+
+ return stringify(annotation.__args__[0])
elif annotation._special:
return qualname
else:
diff --git a/sphinx/versioning.py b/sphinx/versioning.py
index e5fa3b9b3..502bf361c 100644
--- a/sphinx/versioning.py
+++ b/sphinx/versioning.py
@@ -9,17 +9,14 @@
:license: BSD, see LICENSE for details.
"""
import pickle
-import warnings
from itertools import product, zip_longest
from operator import itemgetter
from os import path
from typing import Any, Dict, Iterator
from uuid import uuid4
-from docutils import nodes
from docutils.nodes import Node
-from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.transforms import SphinxTransform
if False:
@@ -177,14 +174,6 @@ class UIDTransform(SphinxTransform):
list(merge_doctrees(old_doctree, self.document, env.versioning_condition))
-def prepare(document: nodes.document) -> None:
- """Simple wrapper for UIDTransform."""
- warnings.warn('versioning.prepare() is deprecated. Use UIDTransform instead.',
- RemovedInSphinx30Warning, stacklevel=2)
- transform = UIDTransform(document)
- transform.apply()
-
-
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_transform(UIDTransform)
diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py
index ab5712410..e74c0334f 100644
--- a/sphinx/writers/html.py
+++ b/sphinx/writers/html.py
@@ -11,7 +11,6 @@
import copy
import os
import posixpath
-import sys
import warnings
from typing import Any, Iterable, Tuple
from typing import cast
@@ -22,7 +21,7 @@ from docutils.writers.html4css1 import Writer, HTMLTranslator as BaseTranslator
from sphinx import addnodes
from sphinx.builders import Builder
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.locale import admonitionlabels, _, __
from sphinx.util import logging
from sphinx.util.docutils import SphinxTranslator
@@ -116,10 +115,6 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
def visit_desc_signature(self, node: Element) -> None:
# the id is set automatically
self.body.append(self.starttag(node, 'dt'))
- # anchor for per-desc interactive data
- if node.parent['objtype'] != 'describe' \
- and node['ids'] and node['first']:
- self.body.append('<!--[%s]-->' % node['ids'][0])
def depart_desc_signature(self, node: Element) -> None:
if not node.get('is_multiline'):
@@ -831,29 +826,3 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
def unknown_visit(self, node: Node) -> None:
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
-
- # --------- METHODS FOR COMPATIBILITY --------------------------------------
-
- @property
- def highlightlang(self) -> str:
- warnings.warn('HTMLTranslator.highlightlang is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.builder.config.highlight_language
-
- @property
- def highlightlang_base(self) -> str:
- warnings.warn('HTMLTranslator.highlightlang_base is deprecated.',
- RemovedInSphinx30Warning)
- return self.builder.config.highlight_language
-
- @property
- def highlightopts(self) -> str:
- warnings.warn('HTMLTranslator.highlightopts is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.builder.config.highlight_options
-
- @property
- def highlightlinenothreshold(self) -> int:
- warnings.warn('HTMLTranslator.highlightlinenothreshold is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return sys.maxsize
diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
index 9f38bf4f3..0c00a1fa4 100644
--- a/sphinx/writers/html5.py
+++ b/sphinx/writers/html5.py
@@ -10,7 +10,6 @@
import os
import posixpath
-import sys
import warnings
from typing import Any, Iterable, Tuple
from typing import cast
@@ -21,7 +20,7 @@ from docutils.writers.html5_polyglot import HTMLTranslator as BaseTranslator
from sphinx import addnodes
from sphinx.builders import Builder
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.locale import admonitionlabels, _, __
from sphinx.util import logging
from sphinx.util.docutils import SphinxTranslator
@@ -88,10 +87,6 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def visit_desc_signature(self, node: Element) -> None:
# the id is set automatically
self.body.append(self.starttag(node, 'dt'))
- # anchor for per-desc interactive data
- if node.parent['objtype'] != 'describe' \
- and node['ids'] and node['first']:
- self.body.append('<!--[%s]-->' % node['ids'][0])
def depart_desc_signature(self, node: Element) -> None:
if not node.get('is_multiline'):
@@ -777,29 +772,3 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def unknown_visit(self, node: Node) -> None:
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
-
- # --------- METHODS FOR COMPATIBILITY --------------------------------------
-
- @property
- def highlightlang(self) -> str:
- warnings.warn('HTMLTranslator.highlightlang is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.builder.config.highlight_language
-
- @property
- def highlightlang_base(self) -> str:
- warnings.warn('HTMLTranslator.highlightlang_base is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.builder.config.highlight_language
-
- @property
- def highlightopts(self) -> str:
- warnings.warn('HTMLTranslator.highlightopts is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return self.builder.config.highlight_options
-
- @property
- def highlightlinenothreshold(self) -> int:
- warnings.warn('HTMLTranslator.highlightlinenothreshold is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return sys.maxsize
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 85579a65a..7a8822585 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -12,11 +12,10 @@
"""
import re
-import sys
import warnings
from collections import defaultdict
from os import path
-from typing import Any, Callable, Dict, Iterable, Iterator, List, Pattern, Tuple, Set, Union
+from typing import Any, Dict, Iterable, Iterator, List, Tuple, Set, Union
from typing import cast
from docutils import nodes, writers
@@ -24,9 +23,7 @@ from docutils.nodes import Element, Node, Text
from sphinx import addnodes
from sphinx import highlighting
-from sphinx.deprecation import (
- RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
-)
+from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.domains import IndexEntry
from sphinx.domains.std import StandardDomain
from sphinx.errors import SphinxError
@@ -132,18 +129,6 @@ class Table:
# (cell = rectangular area)
self.cell_id = 0 # last assigned cell_id
- @property
- def caption_footnotetexts(self) -> List[str]:
- warnings.warn('table.caption_footnotetexts is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return []
-
- @property
- def header_footnotetexts(self) -> List[str]:
- warnings.warn('table.header_footnotetexts is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return []
-
def is_longtable(self) -> bool:
"""True if and only if table uses longtable environment."""
return self.row > 30 or 'longtable' in self.classes
@@ -484,25 +469,6 @@ class LaTeXTranslator(SphinxTranslator):
self.body = self.bodystack.pop()
return body
- def restrict_footnote(self, node: Element) -> None:
- warnings.warn('LaTeXWriter.restrict_footnote() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
-
- if self.footnote_restricted is None:
- self.footnote_restricted = node
- self.pending_footnotes = []
-
- def unrestrict_footnote(self, node: Element) -> None:
- warnings.warn('LaTeXWriter.unrestrict_footnote() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
-
- if self.footnote_restricted == node:
- self.footnote_restricted = None
- for footnode in self.pending_footnotes:
- footnode['footnotetext'] = True
- footnode.walkabout(self)
- self.pending_footnotes = []
-
def format_docclass(self, docclass: str) -> str:
""" prepends prefix to sphinx document classes
"""
@@ -1501,8 +1467,8 @@ class LaTeXTranslator(SphinxTranslator):
def depart_attribution(self, node: Element) -> None:
self.body.append('\n\\end{flushright}\n')
- def visit_index(self, node: Element, scre: Pattern = None) -> None:
- def escape(value):
+ def visit_index(self, node: Element) -> None:
+ def escape(value: str) -> str:
value = self.encode(value)
value = value.replace(r'\{', r'\sphinxleftcurlybrace{}')
value = value.replace(r'\}', r'\sphinxrightcurlybrace{}')
@@ -1512,17 +1478,13 @@ class LaTeXTranslator(SphinxTranslator):
value = value.replace('|', r'\textbar{}')
return value
- def style(string):
+ def style(string: str) -> str:
match = EXTRA_RE.match(string)
if match:
return match.expand(r'\\spxentry{\1}\\spxextra{\2}')
else:
return '\\spxentry{%s}' % string
- if scre:
- warnings.warn(('LaTeXTranslator.visit_index() optional argument '
- '"scre" is deprecated. It is ignored.'),
- RemovedInSphinx30Warning, stacklevel=2)
if not node.get('inline', True):
self.body.append('\n')
entries = node['entries']
@@ -2122,61 +2084,6 @@ class LaTeXTranslator(SphinxTranslator):
RemovedInSphinx40Warning, stacklevel=2)
return 0
- @property
- def footnotestack(self) -> List[Dict[str, List[Union[collected_footnote, bool]]]]:
- warnings.warn('LaTeXWriter.footnotestack is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return []
-
- @property
- def bibitems(self) -> List[List[str]]:
- warnings.warn('LaTeXTranslator.bibitems() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return []
-
- @property
- def in_container_literal_block(self) -> int:
- warnings.warn('LaTeXTranslator.in_container_literal_block is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return 0
-
- @property
- def next_section_ids(self) -> Set[str]:
- warnings.warn('LaTeXTranslator.next_section_ids is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return set()
-
- @property
- def next_hyperlink_ids(self) -> Dict:
- warnings.warn('LaTeXTranslator.next_hyperlink_ids is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return {}
-
- def push_hyperlink_ids(self, figtype: str, ids: Set[str]) -> None:
- warnings.warn('LaTeXTranslator.push_hyperlink_ids() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- pass
-
- def pop_hyperlink_ids(self, figtype: str) -> Set[str]:
- warnings.warn('LaTeXTranslator.pop_hyperlink_ids() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return set()
-
- @property
- def hlsettingstack(self) -> List[List[Union[str, int]]]:
- warnings.warn('LaTeXTranslator.hlsettingstack is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
- return [[self.builder.config.highlight_language, sys.maxsize]]
-
- def check_latex_elements(self) -> None:
- warnings.warn('check_latex_elements() is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
-
- for key in self.builder.config.latex_elements:
- if key not in self.elements:
- msg = __("Unknown configure key: latex_elements[%r] is ignored.")
- logger.warning(msg % key)
-
def babel_defmacro(self, name: str, definition: str) -> str:
warnings.warn('babel_defmacro() is deprecated.',
RemovedInSphinx40Warning)
@@ -2190,15 +2097,6 @@ class LaTeXTranslator(SphinxTranslator):
return ('%s\\def%s{%s}%s\n' % (prefix, name, definition, suffix))
- def _make_visit_admonition(name: str) -> Callable[["LaTeXTranslator", Element], None]: # type: ignore # NOQA
- warnings.warn('LaTeXTranslator._make_visit_admonition() is deprecated.',
- RemovedInSphinx30Warning)
-
- def visit_admonition(self: "LaTeXTranslator", node: Element) -> None:
- self.body.append('\n\\begin{sphinxadmonition}{%s}{%s:}' %
- (name, admonitionlabels[name]))
- return visit_admonition
-
def generate_numfig_format(self, builder: "LaTeXBuilder") -> str:
warnings.warn('generate_numfig_format() is deprecated.',
RemovedInSphinx40Warning)
@@ -2239,18 +2137,11 @@ class LaTeXTranslator(SphinxTranslator):
# Import old modules here for compatibility
from sphinx.builders.latex import constants # NOQA
-from sphinx.builders.latex.transforms import URI_SCHEMES, ShowUrlsTransform # NOQA
from sphinx.builders.latex.util import ExtBabel # NOQA
deprecated_alias('sphinx.writers.latex',
{
- 'ShowUrlsTransform': ShowUrlsTransform,
- 'URI_SCHEMES': URI_SCHEMES,
- },
- RemovedInSphinx30Warning)
-deprecated_alias('sphinx.writers.latex',
- {
'ADDITIONAL_SETTINGS': constants.ADDITIONAL_SETTINGS,
'DEFAULT_SETTINGS': constants.DEFAULT_SETTINGS,
'LUALATEX_DEFAULT_FONTPKG': constants.LUALATEX_DEFAULT_FONTPKG,
diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py
index 65b4f1a0e..9c30244e9 100644
--- a/sphinx/writers/texinfo.py
+++ b/sphinx/writers/texinfo.py
@@ -10,16 +10,14 @@
import re
import textwrap
-import warnings
from os import path
-from typing import Any, Callable, Dict, Iterable, Iterator, List, Pattern, Set, Tuple, Union
+from typing import Any, Dict, Iterable, Iterator, List, Pattern, Set, Tuple, Union
from typing import cast
from docutils import nodes, writers
from docutils.nodes import Element, Node, Text
from sphinx import addnodes, __display_version__
-from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.domains import IndexEntry
from sphinx.domains.index import IndexDomain
from sphinx.errors import ExtensionError
@@ -1528,11 +1526,3 @@ class TexinfoTranslator(SphinxTranslator):
self.body.append('\n\n@example\n%s\n@end example\n\n' %
self.escape_arg(node.astext()))
raise nodes.SkipNode
-
- def _make_visit_admonition(name: str) -> Callable[["TexinfoTranslator", Element], None]: # type: ignore # NOQA
- warnings.warn('TexinfoTranslator._make_visit_admonition() is deprecated.',
- RemovedInSphinx30Warning)
-
- def visit(self: "TexinfoTranslator", node: Element) -> None:
- self.visit_admonition(node, admonitionlabels[name])
- return visit
diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py
index 882007d7e..b2ccd7b89 100644
--- a/sphinx/writers/text.py
+++ b/sphinx/writers/text.py
@@ -11,9 +11,8 @@ import math
import os
import re
import textwrap
-import warnings
from itertools import groupby, chain
-from typing import Any, Callable, Dict, List, Iterable, Optional, Set, Tuple, Union
+from typing import Any, Dict, Generator, List, Iterable, Optional, Set, Tuple, Union
from typing import cast
from docutils import nodes, writers
@@ -21,7 +20,6 @@ from docutils.nodes import Element, Node, Text
from docutils.utils import column_width
from sphinx import addnodes
-from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.locale import admonitionlabels, _
from sphinx.util.docutils import SphinxTranslator
@@ -34,23 +32,23 @@ class Cell:
"""Represents a cell in a table.
It can span on multiple columns or on multiple lines.
"""
- def __init__(self, text="", rowspan=1, colspan=1):
+ def __init__(self, text: str = "", rowspan: int = 1, colspan: int = 1) -> None:
self.text = text
self.wrapped = [] # type: List[str]
self.rowspan = rowspan
self.colspan = colspan
- self.col = None
- self.row = None
+ self.col = None # type: Optional[int]
+ self.row = None # type: Optional[int]
- def __repr__(self):
+ def __repr__(self) -> str:
return "<Cell {!r} {}v{}/{}>{}>".format(
self.text, self.row, self.rowspan, self.col, self.colspan
)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash((self.col, self.row))
- def wrap(self, width):
+ def wrap(self, width: int) -> None:
self.wrapped = my_wrap(self.text, width)
@@ -100,7 +98,7 @@ class Table:
+--------+--------+
"""
- def __init__(self, colwidth=None):
+ def __init__(self, colwidth: List[int] = None) -> None:
self.lines = [] # type: List[List[Cell]]
self.separator = 0
self.colwidth = (colwidth if colwidth is not None
@@ -108,19 +106,19 @@ class Table:
self.current_line = 0
self.current_col = 0
- def add_row(self):
+ def add_row(self) -> None:
"""Add a row to the table, to use with ``add_cell()``. It is not needed
to call ``add_row()`` before the first ``add_cell()``.
"""
self.current_line += 1
self.current_col = 0
- def set_separator(self):
+ def set_separator(self) -> None:
"""Sets the separator below the current line.
"""
self.separator = len(self.lines)
- def add_cell(self, cell):
+ def add_cell(self, cell: Cell) -> None:
"""Add a cell to the current line, to use with ``add_row()``. To add
a cell spanning on multiple lines or rows, simply set the
``cell.colspan`` or ``cell.rowspan`` BEFORE inserting it to
@@ -131,13 +129,13 @@ class Table:
self[self.current_line, self.current_col] = cell
self.current_col += cell.colspan
- def __getitem__(self, pos):
+ def __getitem__(self, pos: Tuple[int, int]) -> Cell:
line, col = pos
self._ensure_has_line(line + 1)
self._ensure_has_column(col + 1)
return self.lines[line][col]
- def __setitem__(self, pos, cell):
+ def __setitem__(self, pos: Tuple[int, int], cell: Cell) -> None:
line, col = pos
self._ensure_has_line(line + cell.rowspan)
self._ensure_has_column(col + cell.colspan)
@@ -147,19 +145,19 @@ class Table:
cell.row = line
cell.col = col
- def _ensure_has_line(self, line):
+ def _ensure_has_line(self, line: int) -> None:
while len(self.lines) < line:
self.lines.append([])
- def _ensure_has_column(self, col):
+ def _ensure_has_column(self, col: int) -> None:
for line in self.lines:
while len(line) < col:
line.append(None)
- def __repr__(self):
+ def __repr__(self) -> str:
return "\n".join(repr(line) for line in self.lines)
- def cell_width(self, cell, source):
+ def cell_width(self, cell: Cell, source: List[int]) -> int:
"""Give the cell width, according to the given source (either
``self.colwidth`` or ``self.measured_widths``).
This take into account cells spanning on multiple columns.
@@ -170,7 +168,7 @@ class Table:
return width + (cell.colspan - 1) * 3
@property
- def cells(self):
+ def cells(self) -> Generator[Cell, None, None]:
seen = set() # type: Set[Cell]
for lineno, line in enumerate(self.lines):
for colno, cell in enumerate(line):
@@ -178,7 +176,7 @@ class Table:
yield cell
seen.add(cell)
- def rewrap(self):
+ def rewrap(self) -> None:
"""Call ``cell.wrap()`` on all cells, and measure each column width
after wrapping (result written in ``self.measured_widths``).
"""
@@ -191,7 +189,7 @@ class Table:
for col in range(cell.col, cell.col + cell.colspan):
self.measured_widths[col] = max(self.measured_widths[col], width)
- def physical_lines_for_line(self, line):
+ def physical_lines_for_line(self, line: List[Cell]) -> int:
"""From a given line, compute the number of physical lines it spans
due to text wrapping.
"""
@@ -200,7 +198,7 @@ class Table:
physical_lines = max(physical_lines, len(cell.wrapped))
return physical_lines
- def __str__(self):
+ def __str__(self) -> str:
out = []
self.rewrap()
@@ -1176,11 +1174,3 @@ class TextTranslator(SphinxTranslator):
def unknown_visit(self, node: Node) -> None:
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
-
- def _make_depart_admonition(name: str) -> Callable[["TextTranslator", Element], None]: # type: ignore # NOQA
- warnings.warn('TextTranslator._make_depart_admonition() is deprecated.',
- RemovedInSphinx30Warning)
-
- def depart_admonition(self: "TextTranslator", node: Element) -> None:
- self.end_state(first=admonitionlabels[name] + ': ')
- return depart_admonition
diff --git a/tests/roots/test-add_source_parser/conf.py b/tests/roots/test-add_source_parser/conf.py
index 9ff50b21e..2acd4d24c 100644
--- a/tests/roots/test-add_source_parser/conf.py
+++ b/tests/roots/test-add_source_parser/conf.py
@@ -1,17 +1,8 @@
import os
import sys
-from docutils.parsers import Parser
-
sys.path.insert(0, os.path.abspath('.'))
-class DummyMarkdownParser(Parser):
- supported = ('markdown',)
-
-
extensions = ['source_parser']
-source_suffix = ['.rst', '.md']
-source_parsers = {
- '.md': DummyMarkdownParser
-}
+source_suffix = ['.rst']
diff --git a/tests/roots/test-domain-cpp/lookup-key-overload.rst b/tests/roots/test-domain-cpp/lookup-key-overload.rst
new file mode 100644
index 000000000..2011e26d6
--- /dev/null
+++ b/tests/roots/test-domain-cpp/lookup-key-overload.rst
@@ -0,0 +1,8 @@
+.. default-domain:: cpp
+
+.. namespace:: lookup_key_overload
+
+.. function:: void g(int a)
+.. function:: void g(double b)
+
+ :var:`b`
diff --git a/tests/roots/test-domain-cpp/multi-decl-lookup.rst b/tests/roots/test-domain-cpp/multi-decl-lookup.rst
new file mode 100644
index 000000000..9706d1822
--- /dev/null
+++ b/tests/roots/test-domain-cpp/multi-decl-lookup.rst
@@ -0,0 +1,24 @@
+.. default-domain:: cpp
+
+.. namespace:: multi_decl_lookup
+
+.. function:: void f1(int a)
+ void f1(double b)
+
+ - a: :var:`a`
+ - b: :var:`b`
+
+.. function:: template<typename T> void f2(int a)
+ template<typename U> void f2(double b)
+
+ - T: :type:`T`
+ - U: :type:`U`
+
+
+.. class:: template<typename T> A
+ template<typename U> B
+
+ .. function:: void f3()
+
+ - T: :type:`T`
+ - U: :type:`U`
diff --git a/tests/roots/test-domain-cpp/warn-template-param-qualified-name.rst b/tests/roots/test-domain-cpp/warn-template-param-qualified-name.rst
new file mode 100644
index 000000000..49a650de0
--- /dev/null
+++ b/tests/roots/test-domain-cpp/warn-template-param-qualified-name.rst
@@ -0,0 +1,11 @@
+.. default-domain:: cpp
+
+.. class:: template<typename T> A
+
+ .. type:: N1 = T::typeOk
+
+ - Not ok, warn: :type:`T::typeWarn`
+
+ .. type:: N2 = T::U::typeOk
+
+ - Not ok, warn: :type:`T::U::typeWarn`
diff --git a/tests/roots/test-ext-autodoc/target/__init__.py b/tests/roots/test-ext-autodoc/target/__init__.py
index f6970a36c..e28eeef8a 100644
--- a/tests/roots/test-ext-autodoc/target/__init__.py
+++ b/tests/roots/test-ext-autodoc/target/__init__.py
@@ -110,6 +110,10 @@ class Outer(object):
factory = dict
+class InnerChild(Outer.Inner):
+ """InnerChild docstring"""
+
+
class DocstringSig(object):
def meth(self):
"""meth(FOO, BAR=1) -> BAZ
diff --git a/tests/roots/test-ext-autodoc/target/annotated.py b/tests/roots/test-ext-autodoc/target/annotated.py
new file mode 100644
index 000000000..427188256
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/annotated.py
@@ -0,0 +1,6 @@
+from typing import Annotated
+
+
+def hello(name: Annotated[str, "attribute"]) -> None:
+ """docstring"""
+ pass
diff --git a/tests/roots/test-ext-autodoc/target/private.py b/tests/roots/test-ext-autodoc/target/private.py
new file mode 100644
index 000000000..38f276663
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/private.py
@@ -0,0 +1,5 @@
+def private_function(name):
+ """private_function is a docstring().
+
+ :meta private:
+ """
diff --git a/tests/roots/test-ext-math-compat/conf.py b/tests/roots/test-ext-math-compat/conf.py
index 30d26f97c..85e3950a5 100644
--- a/tests/roots/test-ext-math-compat/conf.py
+++ b/tests/roots/test-ext-math-compat/conf.py
@@ -1,17 +1,18 @@
+from docutils import nodes
from docutils.parsers.rst import Directive
-from sphinx.ext.mathbase import math, displaymath
-
extensions = ['sphinx.ext.mathjax']
def my_math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
- return [math(latex='E = mc^2')], []
+ text = 'E = mc^2'
+ return [nodes.math(text, text)], []
class MyMathDirective(Directive):
def run(self):
- return [displaymath(latex='E = mc^2')]
+ text = 'E = mc^2'
+ return [nodes.math_block(text, text)]
def setup(app):
diff --git a/tests/roots/test-html_scaled_image_link/conf.py b/tests/roots/test-html_scaled_image_link/conf.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/roots/test-html_scaled_image_link/conf.py
diff --git a/tests/roots/test-html_scaled_image_link/img.png b/tests/roots/test-html_scaled_image_link/img.png
new file mode 100644
index 000000000..a97e86d66
--- /dev/null
+++ b/tests/roots/test-html_scaled_image_link/img.png
Binary files differ
diff --git a/tests/roots/test-html_scaled_image_link/index.rst b/tests/roots/test-html_scaled_image_link/index.rst
new file mode 100644
index 000000000..0e4794058
--- /dev/null
+++ b/tests/roots/test-html_scaled_image_link/index.rst
@@ -0,0 +1,11 @@
+test-html_scaled_image_link
+===========================
+
+.. image:: img.png
+
+.. image:: img.png
+ :scale: 50%
+
+.. image:: img.png
+ :scale: 50%
+ :class: no-scaled-link
diff --git a/tests/roots/test-inheritance/diagram_w_nested_classes.rst b/tests/roots/test-inheritance/diagram_w_nested_classes.rst
new file mode 100644
index 000000000..7fa02175f
--- /dev/null
+++ b/tests/roots/test-inheritance/diagram_w_nested_classes.rst
@@ -0,0 +1,5 @@
+Diagram with Nested Classes
+===========================
+
+.. inheritance-diagram::
+ dummy.test_nested
diff --git a/tests/roots/test-inheritance/dummy/test_nested.py b/tests/roots/test-inheritance/dummy/test_nested.py
new file mode 100644
index 000000000..1e732aab5
--- /dev/null
+++ b/tests/roots/test-inheritance/dummy/test_nested.py
@@ -0,0 +1,12 @@
+"""
+ Test with nested classes.
+"""
+
+
+class A(object):
+ class B(object):
+ pass
+
+
+class C(A.B):
+ pass
diff --git a/tests/roots/test-latex-table/expects/longtable.tex b/tests/roots/test-latex-table/expects/longtable.tex
index 9ba11269e..9febfcef5 100644
--- a/tests/roots/test-latex-table/expects/longtable.tex
+++ b/tests/roots/test-latex-table/expects/longtable.tex
@@ -22,7 +22,7 @@ header2
\endhead
\hline
-\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{Continued on next page}}}\\
+\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{continues on next page}}}\\
\endfoot
\endlastfoot
diff --git a/tests/roots/test-latex-table/expects/longtable_having_align.tex b/tests/roots/test-latex-table/expects/longtable_having_align.tex
index 0165a3829..1969e19d2 100644
--- a/tests/roots/test-latex-table/expects/longtable_having_align.tex
+++ b/tests/roots/test-latex-table/expects/longtable_having_align.tex
@@ -22,7 +22,7 @@ header2
\endhead
\hline
-\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{Continued on next page}}}\\
+\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{continues on next page}}}\\
\endfoot
\endlastfoot
diff --git a/tests/roots/test-latex-table/expects/longtable_having_caption.tex b/tests/roots/test-latex-table/expects/longtable_having_caption.tex
index c5acb1be8..f0041e9ec 100644
--- a/tests/roots/test-latex-table/expects/longtable_having_caption.tex
+++ b/tests/roots/test-latex-table/expects/longtable_having_caption.tex
@@ -24,7 +24,7 @@ header2
\endhead
\hline
-\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{Continued on next page}}}\\
+\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{continues on next page}}}\\
\endfoot
\endlastfoot
diff --git a/tests/roots/test-latex-table/expects/longtable_having_problematic_cell.tex b/tests/roots/test-latex-table/expects/longtable_having_problematic_cell.tex
index 23bce882c..050527b69 100644
--- a/tests/roots/test-latex-table/expects/longtable_having_problematic_cell.tex
+++ b/tests/roots/test-latex-table/expects/longtable_having_problematic_cell.tex
@@ -22,7 +22,7 @@ header2
\endhead
\hline
-\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{Continued on next page}}}\\
+\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{continues on next page}}}\\
\endfoot
\endlastfoot
diff --git a/tests/roots/test-latex-table/expects/longtable_having_stub_columns_and_problematic_cell.tex b/tests/roots/test-latex-table/expects/longtable_having_stub_columns_and_problematic_cell.tex
index d46bd1539..68e74c5f4 100644
--- a/tests/roots/test-latex-table/expects/longtable_having_stub_columns_and_problematic_cell.tex
+++ b/tests/roots/test-latex-table/expects/longtable_having_stub_columns_and_problematic_cell.tex
@@ -26,7 +26,7 @@ header3
\endhead
\hline
-\multicolumn{3}{r}{\makebox[0pt][r]{\sphinxtablecontinued{Continued on next page}}}\\
+\multicolumn{3}{r}{\makebox[0pt][r]{\sphinxtablecontinued{continues on next page}}}\\
\endfoot
\endlastfoot
diff --git a/tests/roots/test-latex-table/expects/longtable_having_verbatim.tex b/tests/roots/test-latex-table/expects/longtable_having_verbatim.tex
index 0d67bbe35..c7213b906 100644
--- a/tests/roots/test-latex-table/expects/longtable_having_verbatim.tex
+++ b/tests/roots/test-latex-table/expects/longtable_having_verbatim.tex
@@ -22,7 +22,7 @@ header2
\endhead
\hline
-\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{Continued on next page}}}\\
+\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{continues on next page}}}\\
\endfoot
\endlastfoot
diff --git a/tests/roots/test-latex-table/expects/longtable_having_widths.tex b/tests/roots/test-latex-table/expects/longtable_having_widths.tex
index 134b4465c..884fd9f8a 100644
--- a/tests/roots/test-latex-table/expects/longtable_having_widths.tex
+++ b/tests/roots/test-latex-table/expects/longtable_having_widths.tex
@@ -22,7 +22,7 @@ header2
\endhead
\hline
-\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{Continued on next page}}}\\
+\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{continues on next page}}}\\
\endfoot
\endlastfoot
diff --git a/tests/roots/test-latex-table/expects/longtable_having_widths_and_problematic_cell.tex b/tests/roots/test-latex-table/expects/longtable_having_widths_and_problematic_cell.tex
index 98a8ec915..17c5ec4cc 100644
--- a/tests/roots/test-latex-table/expects/longtable_having_widths_and_problematic_cell.tex
+++ b/tests/roots/test-latex-table/expects/longtable_having_widths_and_problematic_cell.tex
@@ -22,7 +22,7 @@ header2
\endhead
\hline
-\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{Continued on next page}}}\\
+\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{continues on next page}}}\\
\endfoot
\endlastfoot
diff --git a/tests/roots/test-latex-table/expects/longtable_with_tabularcolumn.tex b/tests/roots/test-latex-table/expects/longtable_with_tabularcolumn.tex
index 642ae596a..2fbbbc4ef 100644
--- a/tests/roots/test-latex-table/expects/longtable_with_tabularcolumn.tex
+++ b/tests/roots/test-latex-table/expects/longtable_with_tabularcolumn.tex
@@ -22,7 +22,7 @@ header2
\endhead
\hline
-\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{Continued on next page}}}\\
+\multicolumn{2}{r}{\makebox[0pt][r]{\sphinxtablecontinued{continues on next page}}}\\
\endfoot
\endlastfoot
diff --git a/tests/roots/test-productionlist/Bare.rst b/tests/roots/test-productionlist/Bare.rst
new file mode 100644
index 000000000..8ea9213f1
--- /dev/null
+++ b/tests/roots/test-productionlist/Bare.rst
@@ -0,0 +1,6 @@
+Bare
+====
+
+.. productionlist::
+ A: `A` | somethingA
+ B: `B` | somethingB
diff --git a/tests/roots/test-productionlist/Dup1.rst b/tests/roots/test-productionlist/Dup1.rst
new file mode 100644
index 000000000..5cd09cb54
--- /dev/null
+++ b/tests/roots/test-productionlist/Dup1.rst
@@ -0,0 +1,5 @@
+Dup1
+====
+
+.. productionlist::
+ Dup: `Dup` | somethingDup
diff --git a/tests/roots/test-productionlist/Dup2.rst b/tests/roots/test-productionlist/Dup2.rst
new file mode 100644
index 000000000..1d663757f
--- /dev/null
+++ b/tests/roots/test-productionlist/Dup2.rst
@@ -0,0 +1,5 @@
+Dup2
+====
+
+.. productionlist::
+ Dup: `Dup` | somethingDup
diff --git a/tests/roots/test-productionlist/LineContinuation.rst b/tests/roots/test-productionlist/LineContinuation.rst
new file mode 100644
index 000000000..4943e8bd2
--- /dev/null
+++ b/tests/roots/test-productionlist/LineContinuation.rst
@@ -0,0 +1,6 @@
+LineContinuation
+================
+
+.. productionlist:: lineContinuation
+ A: B C D \
+ E F G
diff --git a/tests/roots/test-productionlist/P1.rst b/tests/roots/test-productionlist/P1.rst
new file mode 100644
index 000000000..6f9a863a7
--- /dev/null
+++ b/tests/roots/test-productionlist/P1.rst
@@ -0,0 +1,6 @@
+P1
+==
+
+.. productionlist:: P1
+ A: `A` | somethingA
+ B: `B` | somethingB
diff --git a/tests/roots/test-productionlist/P2.rst b/tests/roots/test-productionlist/P2.rst
new file mode 100644
index 000000000..e6c3bc144
--- /dev/null
+++ b/tests/roots/test-productionlist/P2.rst
@@ -0,0 +1,6 @@
+P2
+==
+
+.. productionlist:: P2
+ A: `A` | somethingA
+ B: `B` | somethingB
diff --git a/tests/roots/test-productionlist/conf.py b/tests/roots/test-productionlist/conf.py
new file mode 100644
index 000000000..a45d22e28
--- /dev/null
+++ b/tests/roots/test-productionlist/conf.py
@@ -0,0 +1 @@
+exclude_patterns = ['_build']
diff --git a/tests/roots/test-productionlist/firstLineRule.rst b/tests/roots/test-productionlist/firstLineRule.rst
new file mode 100644
index 000000000..30ea6e005
--- /dev/null
+++ b/tests/roots/test-productionlist/firstLineRule.rst
@@ -0,0 +1,5 @@
+FirstLineRule
+=============
+
+.. productionlist:: FirstLine: something
+ SecondLine: somethingElse
diff --git a/tests/roots/test-productionlist/index.rst b/tests/roots/test-productionlist/index.rst
new file mode 100644
index 000000000..4a0b9789c
--- /dev/null
+++ b/tests/roots/test-productionlist/index.rst
@@ -0,0 +1,27 @@
+.. toctree::
+
+ P1
+ P2
+ Bare
+ Dup1
+ Dup2
+ firstLineRule
+ LineContinuation
+
+- A: :token:`A`
+- B: :token:`B`
+- P1:A: :token:`P1:A`
+- P1:B: :token:`P1:B`
+- P2:A: :token:`P1:A`
+- P2:B: :token:`P2:B`
+- Explicit title A, plain: :token:`MyTitle <A>`
+- Explicit title A, colon: :token:`My:Title <A>`
+- Explicit title P1:A, plain: :token:`MyTitle <P1:A>`
+- Explicit title P1:A, colon: :token:`My:Title <P1:A>`
+- Tilde A: :token:`~A`.
+- Tilde P1:A: :token:`~P1:A`.
+- Tilde explicit title P1:A: :token:`~MyTitle <P1:A>`
+- Tilde, explicit title P1:A: :token:`MyTitle <~P1:A>`
+- Dup: :token:`Dup`
+- FirstLine: :token:`FirstLine`
+- SecondLine: :token:`SecondLine`
diff --git a/tests/roots/test-search/nosearch.rst b/tests/roots/test-search/nosearch.rst
new file mode 100644
index 000000000..9aa48b374
--- /dev/null
+++ b/tests/roots/test-search/nosearch.rst
@@ -0,0 +1,7 @@
+:nosearch:
+
+nosearch
+========
+
+zfs
+latex
diff --git a/tests/test_application.py b/tests/test_application.py
index 86fff9d57..a089a4bc0 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -61,20 +61,13 @@ def test_extension_in_blacklist(app, status, warning):
@pytest.mark.sphinx(testroot='add_source_parser')
-@pytest.mark.filterwarnings('ignore:The config variable "source_parsers"')
-@pytest.mark.filterwarnings('ignore:app.add_source_parser\\(\\) does not support suffix')
def test_add_source_parser(app, status, warning):
- assert set(app.config.source_suffix) == {'.rst', '.md', '.test'}
+ assert set(app.config.source_suffix) == {'.rst', '.test'}
# .rst; only in :confval:`source_suffix`
assert '.rst' not in app.registry.get_source_parsers()
assert app.registry.source_suffix['.rst'] is None
- # .md; configured by :confval:`source_suffix` and :confval:`source_parsers`
- assert '.md' in app.registry.get_source_parsers()
- assert app.registry.source_suffix['.md'] == '.md'
- assert app.registry.get_source_parsers()['.md'].__name__ == 'DummyMarkdownParser'
-
# .test; configured by API
assert app.registry.source_suffix['.test'] == 'test'
assert 'test' in app.registry.get_source_parsers()
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index a86211f18..b001de804 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -582,6 +582,30 @@ def test_autodoc_inherited_members(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodoc_inherited_members_Base(app):
+ options = {"members": None,
+ "inherited-members": "Base",
+ "special-members": None}
+
+ # check methods for object class are shown
+ actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
+ assert ' .. py:method:: Derived.inheritedmeth()' in actual
+ assert ' .. py:method:: Derived.inheritedclassmeth' not in actual
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodoc_inherited_members_None(app):
+ options = {"members": None,
+ "inherited-members": "None",
+ "special-members": None}
+
+ # check methods for object class are shown
+ actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
+ assert ' .. py:method:: Derived.__init__' in actual
+ assert ' .. py:method:: Derived.__str__' in actual
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_imported_members(app):
options = {"members": None,
"imported-members": None,
@@ -661,6 +685,7 @@ def test_autodoc_ignore_module_all(app):
assert list(filter(lambda l: 'class::' in l, actual)) == [
'.. py:class:: Class(arg)',
'.. py:class:: CustomDict',
+ '.. py:class:: InnerChild',
'.. py:class:: InstAttCls()',
'.. py:class:: Outer',
' .. py:class:: Outer.Inner',
@@ -751,6 +776,18 @@ def test_autodoc_inner_class(app):
' ',
]
+ options['show-inheritance'] = True
+ actual = do_autodoc(app, 'class', 'target.InnerChild', options)
+ assert list(actual) == [
+ '',
+ '.. py:class:: InnerChild',
+ ' :module: target', '',
+ ' Bases: :class:`target.Outer.Inner`',
+ '',
+ ' InnerChild docstring',
+ ' '
+ ]
+
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_classmethod(app):
@@ -1487,6 +1524,24 @@ def test_autodoc_typed_instance_variables(app):
]
+@pytest.mark.skipif(sys.version_info < (3, 9), reason='py39+ is required.')
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodoc_Annotated(app):
+ options = {"members": None}
+ actual = do_autodoc(app, 'module', 'target.annotated', options)
+ assert list(actual) == [
+ '',
+ '.. py:module:: target.annotated',
+ '',
+ '',
+ '.. py:function:: hello(name: str) -> None',
+ ' :module: target.annotated',
+ '',
+ ' docstring',
+ ' '
+ ]
+
+
@pytest.mark.sphinx('html', testroot='pycode-egg')
def test_autodoc_for_egged_code(app):
options = {"members": None,
diff --git a/tests/test_build.py b/tests/test_build.py
index de73cb6d9..9dcf78165 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -45,7 +45,7 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir):
"""))
master_doc = srcdir / 'index.txt'
- master_doc.write_text(master_doc.text() + dedent("""
+ master_doc.write_text(master_doc.read_text() + dedent("""
.. toctree::
%(test_name)s/%(test_name)s
diff --git a/tests/test_build_changes.py b/tests/test_build_changes.py
index 5adaa5777..2e87fe0bf 100644
--- a/tests/test_build_changes.py
+++ b/tests/test_build_changes.py
@@ -17,7 +17,7 @@ def test_build(app):
app.build()
# TODO: Use better checking of html content
- htmltext = (app.outdir / 'changes.html').text()
+ htmltext = (app.outdir / 'changes.html').read_text()
assert 'New in version 0.6: Some funny stuff.' in htmltext
assert 'Changed in version 0.6: Even more funny stuff.' in htmltext
assert 'Deprecated since version 0.6: Boring stuff.' in htmltext
diff --git a/tests/test_build_dirhtml.py b/tests/test_build_dirhtml.py
index 715db1146..e89e6888d 100644
--- a/tests/test_build_dirhtml.py
+++ b/tests/test_build_dirhtml.py
@@ -25,15 +25,16 @@ def test_dirhtml(app, status, warning):
assert (app.outdir / 'foo/foo_2/index.html').exists()
assert (app.outdir / 'bar/index.html').exists()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert 'href="foo/"' in content
assert 'href="foo/foo_1/"' in content
assert 'href="foo/foo_2/"' in content
assert 'href="bar/"' in content
# objects.inv (refs: #7095)
- f = (app.outdir / 'objects.inv').open('rb')
- invdata = InventoryFile.load(f, 'path/to', posixpath.join)
+ with (app.outdir / 'objects.inv').open('rb') as f:
+ invdata = InventoryFile.load(f, 'path/to', posixpath.join)
+
assert 'index' in invdata.get('std:doc')
assert ('Python', '', 'path/to/', '-') == invdata['std:doc']['index']
diff --git a/tests/test_build_epub.py b/tests/test_build_epub.py
index 9436ac020..a61c3cbbb 100644
--- a/tests/test_build_epub.py
+++ b/tests/test_build_epub.py
@@ -67,11 +67,11 @@ class EPUBElementTree:
@pytest.mark.sphinx('epub', testroot='basic')
def test_build_epub(app):
app.build()
- assert (app.outdir / 'mimetype').text() == 'application/epub+zip'
+ assert (app.outdir / 'mimetype').read_text() == 'application/epub+zip'
assert (app.outdir / 'META-INF' / 'container.xml').exists()
# toc.ncx
- toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').text())
+ toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').read_text())
assert toc.find("./ncx:docTitle/ncx:text").text == 'Python'
# toc.ncx / head
@@ -91,7 +91,7 @@ def test_build_epub(app):
assert navlabel.text == 'The basic Sphinx documentation for testing'
# content.opf
- opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').text())
+ opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text())
# content.opf / metadata
metadata = opf.find("./idpf:metadata")
@@ -143,7 +143,7 @@ def test_build_epub(app):
assert reference.get('href') == 'index.xhtml'
# nav.xhtml
- nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').text())
+ nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').read_text())
assert nav.attrib == {'lang': 'en',
'{http://www.w3.org/XML/1998/namespace}lang': 'en'}
assert nav.find("./xhtml:head/xhtml:title").text == 'Table of Contents'
@@ -163,7 +163,7 @@ def test_epub_cover(app):
app.build()
# content.opf / metadata
- opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').text())
+ opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text())
cover_image = opf.find("./idpf:manifest/idpf:item[@href='%s']" % app.config.epub_cover[0])
cover = opf.find("./idpf:metadata/idpf:meta[@name='cover']")
assert cover
@@ -175,7 +175,7 @@ def test_nested_toc(app):
app.build()
# toc.ncx
- toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').bytes())
+ toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').read_bytes())
assert toc.find("./ncx:docTitle/ncx:text").text == 'Python'
# toc.ncx / navPoint
@@ -205,7 +205,7 @@ def test_nested_toc(app):
anchor = elem.find("./xhtml:a")
return (anchor.get('href'), anchor.text)
- nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').bytes())
+ nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').read_bytes())
toc = nav.findall("./xhtml:body/xhtml:nav/xhtml:ol/xhtml:li")
assert len(toc) == 4
assert navinfo(toc[0]) == ('index.xhtml',
@@ -230,7 +230,7 @@ def test_escaped_toc(app):
app.build()
# toc.ncx
- toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').bytes())
+ toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').read_bytes())
assert toc.find("./ncx:docTitle/ncx:text").text == 'need <b>"escaped"</b> project'
# toc.ncx / navPoint
@@ -260,7 +260,7 @@ def test_escaped_toc(app):
anchor = elem.find("./xhtml:a")
return (anchor.get('href'), anchor.text)
- nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').bytes())
+ nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').read_bytes())
toc = nav.findall("./xhtml:body/xhtml:nav/xhtml:ol/xhtml:li")
assert len(toc) == 4
assert navinfo(toc[0]) == ('index.xhtml',
@@ -286,7 +286,7 @@ def test_epub_writing_mode(app):
app.build()
# horizontal / page-progression-direction
- opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').text())
+ opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text())
assert opf.find("./idpf:spine").get('page-progression-direction') == 'ltr'
# horizontal / ibooks:scroll-axis
@@ -294,7 +294,7 @@ def test_epub_writing_mode(app):
assert metadata.find("./idpf:meta[@property='ibooks:scroll-axis']").text == 'vertical'
# horizontal / writing-mode (CSS)
- css = (app.outdir / '_static' / 'epub.css').text()
+ css = (app.outdir / '_static' / 'epub.css').read_text()
assert 'writing-mode: horizontal-tb;' in css
# vertical
@@ -303,7 +303,7 @@ def test_epub_writing_mode(app):
app.build()
# vertical / page-progression-direction
- opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').text())
+ opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text())
assert opf.find("./idpf:spine").get('page-progression-direction') == 'rtl'
# vertical / ibooks:scroll-axis
@@ -311,7 +311,7 @@ def test_epub_writing_mode(app):
assert metadata.find("./idpf:meta[@property='ibooks:scroll-axis']").text == 'horizontal'
# vertical / writing-mode (CSS)
- css = (app.outdir / '_static' / 'epub.css').text()
+ css = (app.outdir / '_static' / 'epub.css').read_text()
assert 'writing-mode: vertical-rl;' in css
@@ -319,10 +319,14 @@ def test_epub_writing_mode(app):
def test_epub_anchor_id(app):
app.build()
- html = (app.outdir / 'index.xhtml').text()
- assert '<p id="std-setting-STATICFILES_FINDERS">blah blah blah</p>' in html
- assert '<span id="std-setting-STATICFILES_SECTION"></span><h1>blah blah blah</h1>' in html
- assert 'see <a class="reference internal" href="#std-setting-STATICFILES_FINDERS">' in html
+ html = (app.outdir / 'index.xhtml').read_text()
+ assert ('<p id="std-setting-staticfiles-finders">'
+ '<span id="std-setting-STATICFILES_FINDERS"></span>'
+ 'blah blah blah</p>' in html)
+ assert ('<span id="std-setting-staticfiles-section"></span>'
+ '<span id="std-setting-STATICFILES_SECTION"></span>'
+ '<h1>blah blah blah</h1>' in html)
+ assert 'see <a class="reference internal" href="#std-setting-staticfiles-finders">' in html
@pytest.mark.sphinx('epub', testroot='html_assets')
@@ -330,7 +334,7 @@ def test_epub_assets(app):
app.builder.build_all()
# epub_sytlesheets (same as html_css_files)
- content = (app.outdir / 'index.xhtml').text()
+ content = (app.outdir / 'index.xhtml').read_text()
assert ('<link rel="stylesheet" type="text/css" href="_static/css/style.css" />'
in content)
assert ('<link media="print" rel="stylesheet" title="title" type="text/css" '
@@ -343,7 +347,7 @@ def test_epub_css_files(app):
app.builder.build_all()
# epub_css_files
- content = (app.outdir / 'index.xhtml').text()
+ content = (app.outdir / 'index.xhtml').read_text()
assert '<link rel="stylesheet" type="text/css" href="_static/css/epub.css" />' in content
# files in html_css_files are not outputed
@@ -360,7 +364,7 @@ def test_html_download_role(app, status, warning):
app.build()
assert not (app.outdir / '_downloads' / 'dummy.dat').exists()
- content = (app.outdir / 'index.xhtml').text()
+ content = (app.outdir / 'index.xhtml').read_text()
assert ('<li><p><code class="xref download docutils literal notranslate">'
'<span class="pre">dummy.dat</span></code></p></li>' in content)
assert ('<li><p><code class="xref download docutils literal notranslate">'
diff --git a/tests/test_build_gettext.py b/tests/test_build_gettext.py
index a6107167a..1c86b8daa 100644
--- a/tests/test_build_gettext.py
+++ b/tests/test_build_gettext.py
@@ -31,7 +31,7 @@ def test_build_gettext(app):
assert (app.outdir / 'subdir.pot').isfile()
# regression test for issue #960
- catalog = (app.outdir / 'markup.pot').text()
+ catalog = (app.outdir / 'markup.pot').read_text()
assert 'msgid "something, something else, something more"' in catalog
@@ -84,7 +84,7 @@ def test_gettext_index_entries(app):
return m.groups()[0]
return None
- pot = (app.outdir / 'index_entries.pot').text()
+ pot = (app.outdir / 'index_entries.pot').read_text()
msgids = [_f for _f in map(msgid_getter, pot.splitlines()) if _f]
expected_msgids = [
@@ -133,7 +133,7 @@ def test_gettext_disable_index_entries(app):
return m.groups()[0]
return None
- pot = (app.outdir / 'index_entries.pot').text()
+ pot = (app.outdir / 'index_entries.pot').read_text()
msgids = [_f for _f in map(msgid_getter, pot.splitlines()) if _f]
expected_msgids = [
@@ -156,7 +156,7 @@ def test_gettext_template(app):
app.builder.build_all()
assert (app.outdir / 'sphinx.pot').isfile()
- result = (app.outdir / 'sphinx.pot').text()
+ result = (app.outdir / 'sphinx.pot').read_text()
assert "Welcome" in result
assert "Sphinx %(version)s" in result
@@ -166,7 +166,7 @@ def test_gettext_template_msgid_order_in_sphinxpot(app):
app.builder.build_all()
assert (app.outdir / 'sphinx.pot').isfile()
- result = (app.outdir / 'sphinx.pot').text()
+ result = (app.outdir / 'sphinx.pot').read_text()
assert re.search(
('msgid "Template 1".*'
'msgid "This is Template 1\\.".*'
diff --git a/tests/test_build_html.py b/tests/test_build_html.py
index 63a5948af..4c2618af9 100644
--- a/tests/test_build_html.py
+++ b/tests/test_build_html.py
@@ -176,8 +176,8 @@ def test_html4_output(app, status, warning):
r'-| |-'),
],
'autodoc.html': [
- (".//dt[@id='autodoc_target.Class']", ''),
- (".//dt[@id='autodoc_target.function']/em", r'\*\*kwds'),
+ (".//dl[@class='py class']/dt[@id='autodoc_target.Class']", ''),
+ (".//dl[@class='py function']/dt[@id='autodoc_target.function']/em", r'\*\*kwds'),
(".//dd/p", r'Return spam\.'),
],
'extapi.html': [
@@ -218,11 +218,11 @@ def test_html4_output(app, status, warning):
"[@class='rfc reference external']/strong", 'RFC 1'),
(".//a[@href='https://tools.ietf.org/html/rfc1.html']"
"[@class='rfc reference external']/strong", 'Request for Comments #1'),
- (".//a[@href='objects.html#envvar-HOME']"
+ (".//a[@href='objects.html#envvar-home']"
"[@class='reference internal']/code/span[@class='pre']", 'HOME'),
(".//a[@href='#with']"
"[@class='reference internal']/code/span[@class='pre']", '^with$'),
- (".//a[@href='#grammar-token-try-stmt']"
+ (".//a[@href='#grammar-token-_try-stmt']"
"[@class='reference internal']/code/span", '^statement$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^here$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^there$'),
@@ -254,7 +254,7 @@ def test_html4_output(app, status, warning):
(".//dl/dt[@id='term-boson']", 'boson'),
# a production list
(".//pre/strong", 'try_stmt'),
- (".//pre/a[@href='#grammar-token-try1-stmt']/code/span", 'try1_stmt'),
+ (".//pre/a[@href='#grammar-token-_try1-stmt']/code/span", 'try1_stmt'),
# tests for ``only`` directive
(".//p", 'A global substitution.'),
(".//p", 'In HTML.'),
@@ -279,7 +279,7 @@ def test_html4_output(app, status, warning):
(".//dt/code", r'long\(parameter,\s* list\)'),
(".//dt/code", 'another one'),
(".//a[@href='#mod.Cls'][@class='reference internal']", ''),
- (".//dl[@class='userdesc']", ''),
+ (".//dl[@class='std userdesc']", ''),
(".//dt[@id='userdesc-myobj']", ''),
(".//a[@href='#userdesc-myobj'][@class='reference internal']", ''),
# docfields
@@ -426,7 +426,7 @@ def test_html_download(app):
app.build()
# subdir/includes.html
- result = (app.outdir / 'subdir' / 'includes.html').text()
+ result = (app.outdir / 'subdir' / 'includes.html').read_text()
pattern = ('<a class="reference download internal" download="" '
'href="../(_downloads/.*/img.png)">')
matched = re.search(pattern, result)
@@ -435,7 +435,7 @@ def test_html_download(app):
filename = matched.group(1)
# includes.html
- result = (app.outdir / 'includes.html').text()
+ result = (app.outdir / 'includes.html').read_text()
pattern = ('<a class="reference download internal" download="" '
'href="(_downloads/.*/img.png)">')
matched = re.search(pattern, result)
@@ -454,7 +454,7 @@ def test_html_download_role(app, status, warning):
digest_another = md5(b'another/dummy.dat').hexdigest()
assert (app.outdir / '_downloads' / digest_another / 'dummy.dat').exists()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert (('<li><p><a class="reference download internal" download="" '
'href="_downloads/%s/dummy.dat">'
'<code class="xref download docutils literal notranslate">'
@@ -646,7 +646,7 @@ def test_numfig_disabled(app, cached_etree_parse, fname, expect):
def test_numfig_without_numbered_toctree_warn(app, warning):
app.build()
# remove :numbered: option
- index = (app.srcdir / 'index.rst').text()
+ index = (app.srcdir / 'index.rst').read_text()
index = re.sub(':numbered:.*', '', index)
(app.srcdir / 'index.rst').write_text(index)
app.builder.build_all()
@@ -746,7 +746,7 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
confoverrides={'numfig': True})
def test_numfig_without_numbered_toctree(app, cached_etree_parse, fname, expect):
# remove :numbered: option
- index = (app.srcdir / 'index.rst').text()
+ index = (app.srcdir / 'index.rst').read_text()
index = re.sub(':numbered:.*', '', index)
(app.srcdir / 'index.rst').write_text(index)
@@ -1189,7 +1189,7 @@ def test_html_assets(app):
assert not (app.outdir / '_static' / '.htaccess').exists()
assert not (app.outdir / '_static' / '.htpasswd').exists()
assert (app.outdir / '_static' / 'API.html').exists()
- assert (app.outdir / '_static' / 'API.html').text() == 'Sphinx-1.4.4'
+ assert (app.outdir / '_static' / 'API.html').read_text() == 'Sphinx-1.4.4'
assert (app.outdir / '_static' / 'css' / 'style.css').exists()
assert (app.outdir / '_static' / 'js' / 'custom.js').exists()
assert (app.outdir / '_static' / 'rimg.png').exists()
@@ -1210,7 +1210,7 @@ def test_html_assets(app):
assert not (app.outdir / 'subdir' / '.htpasswd').exists()
# html_css_files
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert '<link rel="stylesheet" type="text/css" href="_static/css/style.css" />' in content
assert ('<link media="print" rel="stylesheet" title="title" type="text/css" '
'href="https://example.com/custom.css" />' in content)
@@ -1249,7 +1249,7 @@ def test_html_sourcelink_suffix_empty(app):
def test_html_entity(app):
app.builder.build_all()
valid_entities = {'amp', 'lt', 'gt', 'quot', 'apos'}
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
for entity in re.findall(r'&([a-z]+);', content, re.M):
assert entity not in valid_entities
@@ -1261,11 +1261,18 @@ def test_html_inventory(app):
with open(app.outdir / 'objects.inv', 'rb') as f:
invdata = InventoryFile.load(f, 'https://www.google.com', os.path.join)
assert set(invdata.keys()) == {'std:label', 'std:doc'}
- assert set(invdata['std:label'].keys()) == {'modindex', 'genindex', 'search'}
+ assert set(invdata['std:label'].keys()) == {'modindex',
+ 'py-modindex',
+ 'genindex',
+ 'search'}
assert invdata['std:label']['modindex'] == ('Python',
'',
'https://www.google.com/py-modindex.html',
'Module Index')
+ assert invdata['std:label']['py-modindex'] == ('Python',
+ '',
+ 'https://www.google.com/py-modindex.html',
+ 'Python Module Index')
assert invdata['std:label']['genindex'] == ('Python',
'',
'https://www.google.com/genindex.html',
@@ -1284,7 +1291,7 @@ def test_html_inventory(app):
@pytest.mark.sphinx('html', testroot='images', confoverrides={'html_sourcelink_suffix': ''})
def test_html_anchor_for_figure(app):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert ('<p class="caption"><span class="caption-text">The caption of pic</span>'
'<a class="headerlink" href="#id1" title="Permalink to this image">¶</a></p>'
in content)
@@ -1293,7 +1300,7 @@ def test_html_anchor_for_figure(app):
@pytest.mark.sphinx('html', testroot='directives-raw')
def test_html_raw_directive(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'index.html').text(encoding='utf8')
+ result = (app.outdir / 'index.html').read_text()
# standard case
assert 'standalone raw directive (HTML)' in result
@@ -1337,7 +1344,7 @@ def test_alternate_stylesheets(app, cached_etree_parse, fname, expect):
@pytest.mark.sphinx('html', testroot='html_style')
def test_html_style(app, status, warning):
app.build()
- result = (app.outdir / 'index.html').text()
+ 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" />'
not in result)
@@ -1347,7 +1354,7 @@ def test_html_style(app, status, warning):
def test_html_remote_images(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'index.html').text(encoding='utf8')
+ result = (app.outdir / 'index.html').read_text()
assert ('<img alt="https://www.python.org/static/img/python-logo.png" '
'src="https://www.python.org/static/img/python-logo.png" />' in result)
assert not (app.outdir / 'python-logo.png').exists()
@@ -1359,7 +1366,7 @@ def test_html_sidebar(app, status, warning):
# default for alabaster
app.builder.build_all()
- result = (app.outdir / 'index.html').text(encoding='utf8')
+ result = (app.outdir / 'index.html').read_text()
assert ('<div class="sphinxsidebar" role="navigation" '
'aria-label="main navigation">' in result)
assert '<h1 class="logo"><a href="#">Python</a></h1>' in result
@@ -1374,7 +1381,7 @@ def test_html_sidebar(app, status, warning):
# only relations.html
app.config.html_sidebars = {'**': ['relations.html']}
app.builder.build_all()
- result = (app.outdir / 'index.html').text(encoding='utf8')
+ result = (app.outdir / 'index.html').read_text()
assert ('<div class="sphinxsidebar" role="navigation" '
'aria-label="main navigation">' in result)
assert '<h1 class="logo"><a href="#">Python</a></h1>' not in result
@@ -1388,7 +1395,7 @@ def test_html_sidebar(app, status, warning):
# no sidebars
app.config.html_sidebars = {'**': []}
app.builder.build_all()
- result = (app.outdir / 'index.html').text(encoding='utf8')
+ result = (app.outdir / 'index.html').read_text()
assert ('<div class="sphinxsidebar" role="navigation" '
'aria-label="main navigation">' not in result)
assert '<h1 class="logo"><a href="#">Python</a></h1>' not in result
@@ -1419,10 +1426,10 @@ def test_html_manpage(app, cached_etree_parse, fname, expect):
def test_html_baseurl(app, status, warning):
app.build()
- result = (app.outdir / 'index.html').text(encoding='utf8')
+ result = (app.outdir / 'index.html').read_text()
assert '<link rel="canonical" href="https://example.com/index.html" />' in result
- result = (app.outdir / 'qux' / 'index.html').text(encoding='utf8')
+ result = (app.outdir / 'qux' / 'index.html').read_text()
assert '<link rel="canonical" href="https://example.com/qux/index.html" />' in result
@@ -1432,10 +1439,10 @@ def test_html_baseurl(app, status, warning):
def test_html_baseurl_and_html_file_suffix(app, status, warning):
app.build()
- result = (app.outdir / 'index.htm').text(encoding='utf8')
+ result = (app.outdir / 'index.htm').read_text()
assert '<link rel="canonical" href="https://example.com/subdir/index.htm" />' in result
- result = (app.outdir / 'qux' / 'index.htm').text(encoding='utf8')
+ result = (app.outdir / 'qux' / 'index.htm').read_text()
assert '<link rel="canonical" href="https://example.com/subdir/qux/index.htm" />' in result
@@ -1542,3 +1549,22 @@ def test_validate_html_static_path(app):
]
validate_html_static_path(app, app.config)
assert app.config.html_static_path == ['_static']
+
+
+@pytest.mark.sphinx(testroot='html_scaled_image_link')
+def test_html_scaled_image_link(app):
+ app.build()
+ context = (app.outdir / 'index.html').read_text()
+
+ # no scaled parameters
+ assert re.search('\n<img alt="_images/img.png" src="_images/img.png" />', context)
+
+ # scaled_image_link
+ assert re.search('\n<a class="reference internal image-reference" href="_images/img.png">'
+ '<img alt="_images/img.png" src="_images/img.png" style="[^"]+" /></a>',
+ context)
+
+ # no-scaled-link class disables the feature
+ assert re.search('\n<img alt="_images/img.png" class="no-scaled-link"'
+ ' src="_images/img.png" style="[^"]+" />',
+ context)
diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py
index 7a1a32507..bd4089fe7 100644
--- a/tests/test_build_latex.py
+++ b/tests/test_build_latex.py
@@ -113,7 +113,7 @@ def test_build_latex_doc(app, status, warning, engine, docclass):
@pytest.mark.sphinx('latex')
def test_writer(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'sphinxtests.tex').text(encoding='utf8')
+ result = (app.outdir / 'sphinxtests.tex').read_text()
assert ('\\begin{sphinxfigure-in-table}\n\\centering\n\\capstart\n'
'\\noindent\\sphinxincludegraphics{{img}.png}\n'
@@ -156,7 +156,7 @@ def test_latex_warnings(app, status, warning):
@pytest.mark.sphinx('latex', testroot='basic')
def test_latex_basic(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'test.tex').text(encoding='utf8')
+ result = (app.outdir / 'test.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -168,7 +168,7 @@ def test_latex_basic(app, status, warning):
@pytest.mark.sphinx('latex', testroot='basic', confoverrides={'language': 'zh'})
def test_latex_additional_settings_for_language_code(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'test.tex').text(encoding='utf8')
+ result = (app.outdir / 'test.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -178,7 +178,7 @@ def test_latex_additional_settings_for_language_code(app, status, warning):
@pytest.mark.sphinx('latex', testroot='basic', confoverrides={'language': 'el'})
def test_latex_additional_settings_for_greek(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'test.tex').text(encoding='utf8')
+ result = (app.outdir / 'test.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -189,7 +189,7 @@ def test_latex_additional_settings_for_greek(app, status, warning):
@pytest.mark.sphinx('latex', testroot='latex-title')
def test_latex_title_after_admonitions(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'test.tex').text(encoding='utf8')
+ result = (app.outdir / 'test.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -200,7 +200,7 @@ def test_latex_title_after_admonitions(app, status, warning):
confoverrides={'release': '1.0'})
def test_latex_release(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'test.tex').text(encoding='utf8')
+ result = (app.outdir / 'test.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -212,7 +212,7 @@ def test_latex_release(app, status, warning):
confoverrides={'numfig': True})
def test_numref(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -238,7 +238,7 @@ def test_numref(app, status, warning):
'\\nameref{\\detokenize{foo:foo}}}') in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\addto\captionsenglish{\renewcommand{\figurename}{Fig.\@{} }}' in result
assert r'\addto\captionsenglish{\renewcommand{\tablename}{Table }}' in result
@@ -254,7 +254,7 @@ def test_numref(app, status, warning):
'section': 'SECTION-%s'}})
def test_numref_with_prefix1(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -286,7 +286,7 @@ def test_numref_with_prefix1(app, status, warning):
'\\nameref{\\detokenize{foo:foo}}}') in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\addto\captionsenglish{\renewcommand{\figurename}{Figure:}}' in result
assert r'\addto\captionsenglish{\renewcommand{\tablename}{Tab\_}}' in result
@@ -302,7 +302,7 @@ def test_numref_with_prefix1(app, status, warning):
'section': 'SECTION_%s_'}})
def test_numref_with_prefix2(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -328,7 +328,7 @@ def test_numref_with_prefix2(app, status, warning):
'\\nameref{\\detokenize{foo:foo}}}') in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\addto\captionsenglish{\renewcommand{\figurename}{Figure:}}' in result
assert r'\def\fnum@figure{\figurename\thefigure{}.}' in result
@@ -342,7 +342,7 @@ def test_numref_with_prefix2(app, status, warning):
confoverrides={'numfig': True, 'language': 'ja'})
def test_numref_with_language_ja(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -368,7 +368,7 @@ def test_numref_with_language_ja(app, status, warning):
'\\nameref{\\detokenize{foo:foo}}}') in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert '\\@iden{\\renewcommand{\\figurename}{図 }}' in result
assert '\\@iden{\\renewcommand{\\tablename}{表 }}' in result
@@ -379,10 +379,10 @@ def test_numref_with_language_ja(app, status, warning):
def test_latex_obey_numfig_is_false(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'SphinxManual.tex').text(encoding='utf8')
+ result = (app.outdir / 'SphinxManual.tex').read_text()
assert '\\usepackage{sphinx}' in result
- result = (app.outdir / 'SphinxHowTo.tex').text(encoding='utf8')
+ result = (app.outdir / 'SphinxHowTo.tex').read_text()
assert '\\usepackage{sphinx}' in result
@@ -392,10 +392,10 @@ def test_latex_obey_numfig_is_false(app, status, warning):
def test_latex_obey_numfig_secnum_depth_is_zero(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'SphinxManual.tex').text(encoding='utf8')
+ result = (app.outdir / 'SphinxManual.tex').read_text()
assert '\\usepackage[,nonumfigreset,mathnumfig]{sphinx}' in result
- result = (app.outdir / 'SphinxHowTo.tex').text(encoding='utf8')
+ result = (app.outdir / 'SphinxHowTo.tex').read_text()
assert '\\usepackage[,nonumfigreset,mathnumfig]{sphinx}' in result
@@ -405,10 +405,10 @@ def test_latex_obey_numfig_secnum_depth_is_zero(app, status, warning):
def test_latex_obey_numfig_secnum_depth_is_two(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'SphinxManual.tex').text(encoding='utf8')
+ result = (app.outdir / 'SphinxManual.tex').read_text()
assert '\\usepackage[,numfigreset=2,mathnumfig]{sphinx}' in result
- result = (app.outdir / 'SphinxHowTo.tex').text(encoding='utf8')
+ result = (app.outdir / 'SphinxHowTo.tex').read_text()
assert '\\usepackage[,numfigreset=3,mathnumfig]{sphinx}' in result
@@ -418,10 +418,10 @@ def test_latex_obey_numfig_secnum_depth_is_two(app, status, warning):
def test_latex_obey_numfig_but_math_numfig_false(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'SphinxManual.tex').text(encoding='utf8')
+ result = (app.outdir / 'SphinxManual.tex').read_text()
assert '\\usepackage[,numfigreset=1]{sphinx}' in result
- result = (app.outdir / 'SphinxHowTo.tex').text(encoding='utf8')
+ result = (app.outdir / 'SphinxHowTo.tex').read_text()
assert '\\usepackage[,numfigreset=2]{sphinx}' in result
@@ -430,7 +430,7 @@ def test_latex_add_latex_package(app, status, warning):
app.add_latex_package('foo')
app.add_latex_package('bar', 'baz')
app.builder.build_all()
- result = (app.outdir / 'test.tex').text(encoding='utf8')
+ result = (app.outdir / 'test.tex').read_text()
assert '\\usepackage{foo}' in result
assert '\\usepackage[baz]{bar}' in result
@@ -438,7 +438,7 @@ def test_latex_add_latex_package(app, status, warning):
@pytest.mark.sphinx('latex', testroot='latex-babel')
def test_babel_with_no_language_settings(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -451,7 +451,7 @@ def test_babel_with_no_language_settings(app, status, warning):
assert '\\shorthandoff' not in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\def\pageautorefname{page}' in result
assert r'\addto\captionsenglish{\renewcommand{\figurename}{Fig.\@{} }}' in result
@@ -463,7 +463,7 @@ def test_babel_with_no_language_settings(app, status, warning):
confoverrides={'language': 'de'})
def test_babel_with_language_de(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -476,7 +476,7 @@ def test_babel_with_language_de(app, status, warning):
assert '\\shorthandoff{"}' in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\def\pageautorefname{Seite}' in result
assert r'\addto\captionsngerman{\renewcommand{\figurename}{Fig.\@{} }}' in result
@@ -488,7 +488,7 @@ def test_babel_with_language_de(app, status, warning):
confoverrides={'language': 'ru'})
def test_babel_with_language_ru(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -501,7 +501,7 @@ def test_babel_with_language_ru(app, status, warning):
assert '\\shorthandoff{"}' in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\def\pageautorefname{страница}' in result
assert r'\addto\captionsrussian{\renewcommand{\figurename}{Fig.\@{} }}' in result
@@ -513,7 +513,7 @@ def test_babel_with_language_ru(app, status, warning):
confoverrides={'language': 'tr'})
def test_babel_with_language_tr(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -526,7 +526,7 @@ def test_babel_with_language_tr(app, status, warning):
assert '\\shorthandoff{=}' in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\def\pageautorefname{sayfa}' in result
assert r'\addto\captionsturkish{\renewcommand{\figurename}{Fig.\@{} }}' in result
@@ -538,7 +538,7 @@ def test_babel_with_language_tr(app, status, warning):
confoverrides={'language': 'ja'})
def test_babel_with_language_ja(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -550,7 +550,7 @@ def test_babel_with_language_ja(app, status, warning):
assert '\\shorthandoff' not in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\def\pageautorefname{ページ}' in result
assert '\\@iden{\\renewcommand{\\figurename}{Fig.\\@{} }}' in result
@@ -562,7 +562,7 @@ def test_babel_with_language_ja(app, status, warning):
confoverrides={'language': 'unknown'})
def test_babel_with_unknown_language(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -577,7 +577,7 @@ def test_babel_with_unknown_language(app, status, warning):
assert "WARNING: no Babel option known for language 'unknown'" in warning.getvalue()
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\def\pageautorefname{page}' in result
assert r'\addto\captionsenglish{\renewcommand{\figurename}{Fig.\@{} }}' in result
@@ -589,7 +589,7 @@ def test_babel_with_unknown_language(app, status, warning):
confoverrides={'language': 'de', 'latex_engine': 'lualatex'})
def test_polyglossia_with_language_de(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -603,7 +603,7 @@ def test_polyglossia_with_language_de(app, status, warning):
assert '\\shorthandoff' not in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\def\pageautorefname{Seite}' in result
assert r'\addto\captionsgerman{\renewcommand{\figurename}{Fig.\@{} }}' in result
@@ -615,7 +615,7 @@ def test_polyglossia_with_language_de(app, status, warning):
confoverrides={'language': 'de-1901', 'latex_engine': 'lualatex'})
def test_polyglossia_with_language_de_1901(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -629,7 +629,7 @@ def test_polyglossia_with_language_de_1901(app, status, warning):
assert '\\shorthandoff' not in result
# sphinxmessages.sty
- result = (app.outdir / 'sphinxmessages.sty').text(encoding='utf8')
+ result = (app.outdir / 'sphinxmessages.sty').read_text()
print(result)
assert r'\def\pageautorefname{page}' in result
assert r'\addto\captionsgerman{\renewcommand{\figurename}{Fig.\@{} }}' in result
@@ -639,7 +639,7 @@ def test_polyglossia_with_language_de_1901(app, status, warning):
@pytest.mark.sphinx('latex')
def test_footnote(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'sphinxtests.tex').text(encoding='utf8')
+ result = (app.outdir / 'sphinxtests.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -666,7 +666,7 @@ def test_footnote(app, status, warning):
@pytest.mark.sphinx('latex', testroot='footnotes')
def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -707,7 +707,7 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning):
confoverrides={'latex_show_urls': 'inline'})
def test_latex_show_urls_is_inline(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -752,7 +752,7 @@ def test_latex_show_urls_is_inline(app, status, warning):
confoverrides={'latex_show_urls': 'footnote'})
def test_latex_show_urls_is_footnote(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -806,7 +806,7 @@ def test_latex_show_urls_is_footnote(app, status, warning):
confoverrides={'latex_show_urls': 'no'})
def test_latex_show_urls_is_no(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -856,7 +856,7 @@ def test_latex_show_urls_footnote_and_substitutions(app, status, warning):
@pytest.mark.sphinx('latex', testroot='image-in-section')
def test_image_in_section(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -882,7 +882,7 @@ def test_latex_logo_if_not_found(app, status, warning):
@pytest.mark.sphinx('latex', testroot='toctree-maxdepth')
def test_toctree_maxdepth_manual(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -899,7 +899,7 @@ def test_toctree_maxdepth_manual(app, status, warning):
]})
def test_toctree_maxdepth_howto(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -913,7 +913,7 @@ def test_toctree_maxdepth_howto(app, status, warning):
confoverrides={'master_doc': 'foo'})
def test_toctree_not_found(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -927,7 +927,7 @@ def test_toctree_not_found(app, status, warning):
confoverrides={'master_doc': 'bar'})
def test_toctree_without_maxdepth(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -940,7 +940,7 @@ def test_toctree_without_maxdepth(app, status, warning):
confoverrides={'master_doc': 'qux'})
def test_toctree_with_deeper_maxdepth(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -953,7 +953,7 @@ def test_toctree_with_deeper_maxdepth(app, status, warning):
confoverrides={'latex_toplevel_sectioning': None})
def test_latex_toplevel_sectioning_is_None(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -965,7 +965,7 @@ def test_latex_toplevel_sectioning_is_None(app, status, warning):
confoverrides={'latex_toplevel_sectioning': 'part'})
def test_latex_toplevel_sectioning_is_part(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -983,7 +983,7 @@ def test_latex_toplevel_sectioning_is_part(app, status, warning):
]})
def test_latex_toplevel_sectioning_is_part_with_howto(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -997,7 +997,7 @@ def test_latex_toplevel_sectioning_is_part_with_howto(app, status, warning):
confoverrides={'latex_toplevel_sectioning': 'chapter'})
def test_latex_toplevel_sectioning_is_chapter(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -1013,7 +1013,7 @@ def test_latex_toplevel_sectioning_is_chapter(app, status, warning):
]})
def test_latex_toplevel_sectioning_is_chapter_with_howto(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -1025,7 +1025,7 @@ def test_latex_toplevel_sectioning_is_chapter_with_howto(app, status, warning):
confoverrides={'latex_toplevel_sectioning': 'section'})
def test_latex_toplevel_sectioning_is_section(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -1036,7 +1036,7 @@ def test_latex_toplevel_sectioning_is_section(app, status, warning):
@pytest.mark.sphinx('latex', testroot='maxlistdepth')
def test_maxlistdepth_at_ten(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
print(status.getvalue())
print(warning.getvalue())
@@ -1049,14 +1049,14 @@ def test_maxlistdepth_at_ten(app, status, warning):
@pytest.mark.test_params(shared_result='latex-table')
def test_latex_table_tabulars(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
tables = {}
for chap in re.split(r'\\(?:section|chapter){', result)[1:]:
sectname, content = chap.split('}', 1)
tables[sectname] = content.strip()
def get_expected(name):
- return (app.srcdir / 'expects' / (name + '.tex')).text().strip()
+ return (app.srcdir / 'expects' / (name + '.tex')).read_text().strip()
# simple_table
actual = tables['simple table']
@@ -1120,14 +1120,14 @@ def test_latex_table_tabulars(app, status, warning):
@pytest.mark.test_params(shared_result='latex-table')
def test_latex_table_longtable(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
tables = {}
for chap in re.split(r'\\(?:section|chapter){', result)[1:]:
sectname, content = chap.split('}', 1)
tables[sectname] = content.strip()
def get_expected(name):
- return (app.srcdir / 'expects' / (name + '.tex')).text().strip()
+ return (app.srcdir / 'expects' / (name + '.tex')).read_text().strip()
# longtable
actual = tables['longtable']
@@ -1181,14 +1181,14 @@ def test_latex_table_longtable(app, status, warning):
@pytest.mark.test_params(shared_result='latex-table')
def test_latex_table_complex_tables(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
tables = {}
for chap in re.split(r'\\(?:section|renewcommand){', result)[1:]:
sectname, content = chap.split('}', 1)
tables[sectname] = content.strip()
def get_expected(name):
- return (app.srcdir / 'expects' / (name + '.tex')).text().strip()
+ return (app.srcdir / 'expects' / (name + '.tex')).read_text().strip()
# grid table
actual = tables['grid table']
@@ -1205,7 +1205,7 @@ def test_latex_table_complex_tables(app, status, warning):
confoverrides={'templates_path': ['_mytemplates/latex']})
def test_latex_table_custom_template_caseA(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert 'SALUT LES COPAINS' in result
@@ -1213,7 +1213,7 @@ def test_latex_table_custom_template_caseA(app, status, warning):
confoverrides={'templates_path': ['_mytemplates']})
def test_latex_table_custom_template_caseB(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert 'SALUT LES COPAINS' not in result
@@ -1221,14 +1221,14 @@ def test_latex_table_custom_template_caseB(app, status, warning):
@pytest.mark.test_params(shared_result='latex-table')
def test_latex_table_custom_template_caseC(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert 'SALUT LES COPAINS' not in result
@pytest.mark.sphinx('latex', testroot='directives-raw')
def test_latex_raw_directive(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
# standard case
assert 'standalone raw directive (HTML)' not in result
@@ -1244,7 +1244,7 @@ def test_latex_raw_directive(app, status, warning):
def test_latex_images(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
# images are copied
assert '\\sphinxincludegraphics{{python-logo}.png}' in result
@@ -1268,7 +1268,7 @@ def test_latex_images(app, status, warning):
def test_latex_index(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert ('A \\index{famous@\\spxentry{famous}}famous '
'\\index{equation@\\spxentry{equation}}equation:\n' in result)
assert ('\n\\index{Einstein@\\spxentry{Einstein}}'
@@ -1282,8 +1282,8 @@ def test_latex_index(app, status, warning):
def test_latex_equations(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
- expected = (app.srcdir / 'expects' / 'latex-equations.tex').text().strip()
+ result = (app.outdir / 'python.tex').read_text()
+ expected = (app.srcdir / 'expects' / 'latex-equations.tex').read_text().strip()
assert expected in result
@@ -1292,7 +1292,7 @@ def test_latex_equations(app, status, warning):
def test_latex_image_in_parsed_literal(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert ('{\\sphinxunactivateextrasandspace \\raisebox{-0.5\\height}'
'{\\sphinxincludegraphics[height=2.00000cm]{{pic}.png}}'
'}AFTER') in result
@@ -1302,7 +1302,7 @@ def test_latex_image_in_parsed_literal(app, status, warning):
def test_latex_nested_enumerated_list(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert ('\\sphinxsetlistlabels{\\arabic}{enumi}{enumii}{}{.}%\n'
'\\setcounter{enumi}{4}\n' in result)
assert ('\\sphinxsetlistlabels{\\alph}{enumii}{enumiii}{}{.}%\n'
@@ -1319,7 +1319,7 @@ def test_latex_nested_enumerated_list(app, status, warning):
def test_latex_thebibliography(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(result)
assert ('\\begin{sphinxthebibliography}{AuthorYe}\n'
'\\bibitem[AuthorYear]{index:authoryear}\n'
@@ -1332,7 +1332,7 @@ def test_latex_thebibliography(app, status, warning):
def test_latex_glossary(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert ('\\item[{änhlich\\index{änhlich@\\spxentry{änhlich}|spxpagem}'
r'\phantomsection'
r'\label{\detokenize{index:term-anhlich}}}] \leavevmode' in result)
@@ -1356,7 +1356,7 @@ def test_latex_glossary(app, status, warning):
def test_latex_labels(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
# figures
assert (r'\caption{labeled figure}'
@@ -1403,7 +1403,7 @@ def test_latex_labels(app, status, warning):
@pytest.mark.sphinx('latex', testroot='latex-figure-in-admonition')
def test_latex_figure_in_admonition(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert(r'\begin{figure}[H]' in result)
@@ -1433,7 +1433,7 @@ def test_includegraphics_oversized(app, status, warning):
@pytest.mark.sphinx('latex', testroot='index_on_title')
def test_index_on_title(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert ('\\chapter{Test for index in top level title}\n'
'\\label{\\detokenize{contents:test-for-index-in-top-level-title}}'
'\\index{index@\\spxentry{index}}\n'
@@ -1444,7 +1444,7 @@ def test_index_on_title(app, status, warning):
confoverrides={'latex_engine': 'pdflatex'})
def test_texescape_for_non_unicode_supported_engine(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text()
+ result = (app.outdir / 'python.tex').read_text()
print(result)
assert 'script small e: e' in result
assert 'double struck italic small i: i' in result
@@ -1456,7 +1456,7 @@ def test_texescape_for_non_unicode_supported_engine(app, status, warning):
confoverrides={'latex_engine': 'xelatex'})
def test_texescape_for_unicode_supported_engine(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text()
+ result = (app.outdir / 'python.tex').read_text()
print(result)
assert 'script small e: e' in result
assert 'double struck italic small i: i' in result
@@ -1468,7 +1468,7 @@ def test_texescape_for_unicode_supported_engine(app, status, warning):
confoverrides={'latex_elements': {'extrapackages': r'\usepackage{foo}'}})
def test_latex_elements_extrapackages(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'test.tex').text()
+ result = (app.outdir / 'test.tex').read_text()
assert r'\usepackage{foo}' in result
diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py
index 22ed86e3e..54bde6b68 100644
--- a/tests/test_build_linkcheck.py
+++ b/tests/test_build_linkcheck.py
@@ -8,6 +8,8 @@
:license: BSD, see LICENSE for details.
"""
+import json
+import re
from unittest import mock
import pytest
@@ -17,10 +19,10 @@ def test_defaults(app, status, warning):
app.builder.build_all()
assert (app.outdir / 'output.txt').exists()
- content = (app.outdir / 'output.txt').text()
+ content = (app.outdir / 'output.txt').read_text()
print(content)
- # looking for '#top' and 'does-not-exist' not found should fail
+ # looking for '#top' and '#does-not-exist' not found should fail
assert "Anchor 'top' not found" in content
assert "Anchor 'does-not-exist' not found" in content
# looking for non-existent URL should fail
@@ -31,6 +33,58 @@ def test_defaults(app, status, warning):
assert len(content.splitlines()) == 5
+@pytest.mark.sphinx('linkcheck', testroot='linkcheck', freshenv=True)
+def test_defaults_json(app, status, warning):
+ app.builder.build_all()
+
+ assert (app.outdir / 'output.json').exists()
+ content = (app.outdir / 'output.json').read_text()
+ print(content)
+
+ rows = [json.loads(x) for x in content.splitlines()]
+ row = rows[0]
+ for attr in ["filename", "lineno", "status", "code", "uri",
+ "info"]:
+ assert attr in row
+
+ assert len(content.splitlines()) == 8
+ assert len(rows) == 8
+ # the output order of the rows is not stable
+ # due to possible variance in network latency
+ rowsby = {row["uri"]:row for row in rows}
+ assert rowsby["https://www.google.com#!bar"] == {
+ 'filename': 'links.txt',
+ 'lineno': 10,
+ 'status': 'working',
+ 'code': 0,
+ 'uri': 'https://www.google.com#!bar',
+ 'info': ''
+ }
+ # looking for non-existent URL should fail
+ dnerow = rowsby['https://localhost:7777/doesnotexist']
+ assert dnerow['filename'] == 'links.txt'
+ assert dnerow['lineno'] == 13
+ assert dnerow['status'] == 'broken'
+ assert dnerow['code'] == 0
+ assert dnerow['uri'] == 'https://localhost:7777/doesnotexist'
+ assert rowsby['https://www.google.com/image2.png'] == {
+ 'filename': 'links.txt',
+ 'lineno': 16,
+ 'status': 'broken',
+ 'code': 0,
+ 'uri': 'https://www.google.com/image2.png',
+ 'info': '404 Client Error: Not Found for url: https://www.google.com/image2.png'
+ }
+ # looking for '#top' and '#does-not-exist' not found should fail
+ assert "Anchor 'top' not found" == \
+ rowsby["https://www.google.com/#top"]["info"]
+ assert "Anchor 'does-not-exist' not found" == \
+ rowsby["http://www.sphinx-doc.org/en/1.7/intro.html#does-not-exist"]["info"]
+ # images should fail
+ assert "Not Found for url: https://www.google.com/image.png" in \
+ rowsby["https://www.google.com/image.png"]["info"]
+
+
@pytest.mark.sphinx(
'linkcheck', testroot='linkcheck', freshenv=True,
confoverrides={'linkcheck_anchors_ignore': ["^!", "^top$"],
@@ -44,7 +98,7 @@ def test_anchors_ignored(app, status, warning):
app.builder.build_all()
assert (app.outdir / 'output.txt').exists()
- content = (app.outdir / 'output.txt').text()
+ content = (app.outdir / 'output.txt').read_text()
# expect all ok when excluding #top
assert not content
diff --git a/tests/test_build_manpage.py b/tests/test_build_manpage.py
index 902c58131..c3c41a2ae 100644
--- a/tests/test_build_manpage.py
+++ b/tests/test_build_manpage.py
@@ -19,7 +19,7 @@ def test_all(app, status, warning):
app.builder.build_all()
assert (app.outdir / 'sphinxtests.1').exists()
- content = (app.outdir / 'sphinxtests.1').text()
+ content = (app.outdir / 'sphinxtests.1').read_text()
assert r'\fBprint \fP\fIi\fP\fB\en\fP' in content
assert r'\fBmanpage\en\fP' in content
@@ -33,7 +33,7 @@ def test_all(app, status, warning):
@pytest.mark.sphinx('man', testroot='directive-code')
def test_captioned_code_block(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'python.1').text()
+ content = (app.outdir / 'python.1').read_text()
assert ('.sp\n'
'caption \\fItest\\fP rb\n'
@@ -64,5 +64,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').text()
+ content = (app.outdir / 'python.1').read_text()
assert 'This is a rubric\n' in content
diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py
index 247863bdb..378eaa192 100644
--- a/tests/test_build_texinfo.py
+++ b/tests/test_build_texinfo.py
@@ -49,7 +49,7 @@ def test_texinfo_warnings(app, status, warning):
def test_texinfo(app, status, warning):
TexinfoTranslator.ignore_missing_images = True
app.builder.build_all()
- result = (app.outdir / 'sphinxtests.texi').text(encoding='utf8')
+ result = (app.outdir / 'sphinxtests.texi').read_text()
assert ('@anchor{markup doc}@anchor{11}'
'@anchor{markup id1}@anchor{12}'
'@anchor{markup testing-various-markup}@anchor{13}' in result)
@@ -70,7 +70,7 @@ def test_texinfo(app, status, warning):
def test_texinfo_rubric(app, status, warning):
app.build()
- output = (app.outdir / 'python.texi').text()
+ output = (app.outdir / 'python.texi').read_text()
assert '@heading This is a rubric' in output
assert '@heading This is a multiline rubric' in output
@@ -79,7 +79,7 @@ def test_texinfo_rubric(app, status, warning):
def test_texinfo_citation(app, status, warning):
app.builder.build_all()
- output = (app.outdir / 'python.texi').text()
+ output = (app.outdir / 'python.texi').read_text()
assert 'This is a citation ref; @ref{1,,[CITE1]} and @ref{2,,[CITE2]}.' in output
assert ('@anchor{index cite1}@anchor{1}@w{(CITE1)} \n'
'This is a citation\n') in output
diff --git a/tests/test_build_text.py b/tests/test_build_text.py
index 6dc155f80..8c00f5550 100644
--- a/tests/test_build_text.py
+++ b/tests/test_build_text.py
@@ -26,7 +26,7 @@ def with_text_app(*args, **kw):
@with_text_app()
def test_maxwitdh_with_prefix(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'maxwidth.txt').text()
+ result = (app.outdir / 'maxwidth.txt').read_text()
lines = result.splitlines()
line_widths = [column_width(line) for line in lines]
@@ -49,7 +49,7 @@ def test_maxwitdh_with_prefix(app, status, warning):
def test_lineblock(app, status, warning):
# regression test for #1109: need empty line after line block
app.builder.build_update()
- result = (app.outdir / 'lineblock.txt').text()
+ result = (app.outdir / 'lineblock.txt').read_text()
expect = (
"* one\n"
"\n"
@@ -64,7 +64,7 @@ def test_lineblock(app, status, warning):
@with_text_app()
def test_nonascii_title_line(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'nonascii_title.txt').text()
+ result = (app.outdir / 'nonascii_title.txt').read_text()
expect_underline = '*********'
result_underline = result.splitlines()[1].strip()
assert expect_underline == result_underline
@@ -73,7 +73,7 @@ def test_nonascii_title_line(app, status, warning):
@with_text_app()
def test_nonascii_table(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'nonascii_table.txt').text()
+ result = (app.outdir / 'nonascii_table.txt').read_text()
lines = [line.strip() for line in result.splitlines() if line.strip()]
line_widths = [column_width(line) for line in lines]
assert len(set(line_widths)) == 1 # same widths
@@ -82,7 +82,7 @@ def test_nonascii_table(app, status, warning):
@with_text_app()
def test_nonascii_maxwidth(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'nonascii_maxwidth.txt').text()
+ result = (app.outdir / 'nonascii_maxwidth.txt').read_text()
lines = [line.strip() for line in result.splitlines() if line.strip()]
line_widths = [column_width(line) for line in lines]
assert max(line_widths) < MAXWIDTH
@@ -126,7 +126,7 @@ def test_table_cell():
@with_text_app()
def test_table_with_empty_cell(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'table.txt').text()
+ result = (app.outdir / 'table.txt').read_text()
lines = [line.strip() for line in result.splitlines() if line.strip()]
assert lines[0] == "+-------+-------+"
assert lines[1] == "| XXX | XXX |"
@@ -140,7 +140,7 @@ def test_table_with_empty_cell(app, status, warning):
@with_text_app()
def test_table_with_rowspan(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'table_rowspan.txt').text()
+ result = (app.outdir / 'table_rowspan.txt').read_text()
lines = [line.strip() for line in result.splitlines() if line.strip()]
assert lines[0] == "+-------+-------+"
assert lines[1] == "| XXXXXXXXX |"
@@ -154,7 +154,7 @@ def test_table_with_rowspan(app, status, warning):
@with_text_app()
def test_table_with_colspan(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'table_colspan.txt').text()
+ result = (app.outdir / 'table_colspan.txt').read_text()
lines = [line.strip() for line in result.splitlines() if line.strip()]
assert lines[0] == "+-------+-------+"
assert lines[1] == "| XXX | XXX |"
@@ -168,7 +168,7 @@ def test_table_with_colspan(app, status, warning):
@with_text_app()
def test_table_with_colspan_left(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'table_colspan_left.txt').text()
+ result = (app.outdir / 'table_colspan_left.txt').read_text()
lines = [line.strip() for line in result.splitlines() if line.strip()]
assert lines[0] == "+-------+-------+"
assert lines[1] == "| XXX | XXX |"
@@ -182,7 +182,7 @@ def test_table_with_colspan_left(app, status, warning):
@with_text_app()
def test_table_with_colspan_and_rowspan(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'table_colspan_and_rowspan.txt').text()
+ result = (app.outdir / 'table_colspan_and_rowspan.txt').read_text()
lines = [line.strip() for line in result.splitlines() if line.strip()]
assert result
assert lines[0] == "+-------+-------+-------+"
@@ -197,7 +197,7 @@ def test_table_with_colspan_and_rowspan(app, status, warning):
@with_text_app()
def test_list_items_in_admonition(app, status, warning):
app.builder.build_update()
- result = (app.outdir / 'listitems.txt').text()
+ result = (app.outdir / 'listitems.txt').read_text()
lines = [line.rstrip() for line in result.splitlines()]
assert lines[0] == "See also:"
assert lines[1] == ""
@@ -209,7 +209,7 @@ def test_list_items_in_admonition(app, status, warning):
@with_text_app()
def test_secnums(app, status, warning):
app.builder.build_all()
- index = (app.outdir / 'index.txt').text(encoding='utf8')
+ index = (app.outdir / 'index.txt').read_text()
lines = index.splitlines()
assert lines[0] == "* 1. Section A"
assert lines[1] == ""
@@ -218,7 +218,7 @@ def test_secnums(app, status, warning):
assert lines[4] == " * 2.1. Sub Ba"
assert lines[5] == ""
assert lines[6] == " * 2.2. Sub Bb"
- doc2 = (app.outdir / 'doc2.txt').text(encoding='utf8')
+ doc2 = (app.outdir / 'doc2.txt').read_text()
expect = (
"2. Section B\n"
"************\n"
@@ -235,7 +235,7 @@ def test_secnums(app, status, warning):
app.config.text_secnumber_suffix = " "
app.builder.build_all()
- index = (app.outdir / 'index.txt').text(encoding='utf8')
+ index = (app.outdir / 'index.txt').read_text()
lines = index.splitlines()
assert lines[0] == "* 1 Section A"
assert lines[1] == ""
@@ -244,7 +244,7 @@ def test_secnums(app, status, warning):
assert lines[4] == " * 2.1 Sub Ba"
assert lines[5] == ""
assert lines[6] == " * 2.2 Sub Bb"
- doc2 = (app.outdir / 'doc2.txt').text(encoding='utf8')
+ doc2 = (app.outdir / 'doc2.txt').read_text()
expect = (
"2 Section B\n"
"***********\n"
@@ -261,7 +261,7 @@ def test_secnums(app, status, warning):
app.config.text_add_secnumbers = False
app.builder.build_all()
- index = (app.outdir / 'index.txt').text(encoding='utf8')
+ index = (app.outdir / 'index.txt').read_text()
lines = index.splitlines()
assert lines[0] == "* Section A"
assert lines[1] == ""
@@ -270,7 +270,7 @@ def test_secnums(app, status, warning):
assert lines[4] == " * Sub Ba"
assert lines[5] == ""
assert lines[6] == " * Sub Bb"
- doc2 = (app.outdir / 'doc2.txt').text(encoding='utf8')
+ doc2 = (app.outdir / 'doc2.txt').read_text()
expect = (
"Section B\n"
"*********\n"
diff --git a/tests/test_correct_year.py b/tests/test_correct_year.py
index cbba75946..7dc3ea89d 100644
--- a/tests/test_correct_year.py
+++ b/tests/test_correct_year.py
@@ -32,5 +32,5 @@ def expect_date(request, monkeypatch):
@pytest.mark.sphinx('html', testroot='correct-year')
def test_correct_year(expect_date, app):
app.build()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert expect_date in content
diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py
index 7aeef81ec..eda331645 100644
--- a/tests/test_directive_code.py
+++ b/tests/test_directive_code.py
@@ -36,7 +36,7 @@ def test_LiteralIncludeReader(literal_inc_path):
options = {'lineno-match': True}
reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG)
content, lines = reader.read()
- assert content == literal_inc_path.text()
+ assert content == literal_inc_path.read_text()
assert lines == 13
assert reader.lineno_start == 1
@@ -46,7 +46,7 @@ def test_LiteralIncludeReader_lineno_start(literal_inc_path):
options = {'lineno-start': 4}
reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG)
content, lines = reader.read()
- assert content == literal_inc_path.text()
+ assert content == literal_inc_path.read_text()
assert lines == 13
assert reader.lineno_start == 4
@@ -324,7 +324,7 @@ def test_force_option(app, status, warning):
@pytest.mark.sphinx('html', testroot='directive-code')
def test_code_block_caption_html(app, status, warning):
app.builder.build(['caption'])
- html = (app.outdir / 'caption.html').text()
+ html = (app.outdir / 'caption.html').read_text()
caption = ('<div class="code-block-caption">'
'<span class="caption-number">Listing 1 </span>'
'<span class="caption-text">caption <em>test</em> rb'
@@ -336,7 +336,7 @@ def test_code_block_caption_html(app, status, warning):
@pytest.mark.sphinx('latex', testroot='directive-code')
def test_code_block_caption_latex(app, status, warning):
app.builder.build_all()
- latex = (app.outdir / 'python.tex').text()
+ latex = (app.outdir / 'python.tex').read_text()
caption = '\\sphinxSetupCaptionForVerbatim{caption \\sphinxstyleemphasis{test} rb}'
label = '\\def\\sphinxLiteralBlockLabel{\\label{\\detokenize{caption:id1}}}'
link = '\\hyperref[\\detokenize{caption:name-test-rb}]' \
@@ -349,7 +349,7 @@ def test_code_block_caption_latex(app, status, warning):
@pytest.mark.sphinx('latex', testroot='directive-code')
def test_code_block_namedlink_latex(app, status, warning):
app.builder.build_all()
- latex = (app.outdir / 'python.tex').text()
+ latex = (app.outdir / 'python.tex').read_text()
label1 = '\\def\\sphinxLiteralBlockLabel{\\label{\\detokenize{caption:name-test-rb}}}'
link1 = '\\hyperref[\\detokenize{caption:name-test-rb}]'\
'{\\sphinxcrossref{\\DUrole{std,std-ref}{Ruby}}'
@@ -366,7 +366,7 @@ def test_code_block_namedlink_latex(app, status, warning):
@pytest.mark.sphinx('latex', testroot='directive-code')
def test_code_block_emphasize_latex(app, status, warning):
app.builder.build(['emphasize'])
- latex = (app.outdir / 'python.tex').text().replace('\r\n', '\n')
+ latex = (app.outdir / 'python.tex').read_text().replace('\r\n', '\n')
includes = '\\fvset{hllines={, 5, 6, 13, 14, 15, 24, 25, 26,}}%\n'
assert includes in latex
includes = '\\end{sphinxVerbatim}\n\\sphinxresetverbatimhllines\n'
@@ -379,7 +379,7 @@ def test_literal_include(app, status, warning):
et = etree_parse(app.outdir / 'index.xml')
secs = et.findall('./section/section')
literal_include = secs[1].findall('literal_block')
- literal_src = (app.srcdir / 'literal.inc').text()
+ literal_src = (app.srcdir / 'literal.inc').read_text()
assert len(literal_include) > 0
actual = literal_include[0].text
assert actual == literal_src
@@ -412,7 +412,7 @@ def test_literal_include_block_start_with_comment_or_brank(app, status, warning)
@pytest.mark.sphinx('html', testroot='directive-code')
def test_literal_include_linenos(app, status, warning):
app.builder.build(['linenos'])
- html = (app.outdir / 'linenos.html').text()
+ html = (app.outdir / 'linenos.html').read_text()
# :linenos:
assert ('<td class="linenos"><div class="linenodiv"><pre>'
@@ -458,7 +458,7 @@ def test_literal_include_linenos(app, status, warning):
@pytest.mark.sphinx('latex', testroot='directive-code')
def test_literalinclude_file_whole_of_emptyline(app, status, warning):
app.builder.build_all()
- latex = (app.outdir / 'python.tex').text().replace('\r\n', '\n')
+ latex = (app.outdir / 'python.tex').read_text().replace('\r\n', '\n')
includes = (
'\\begin{sphinxVerbatim}'
'[commandchars=\\\\\\{\\},numbers=left,firstnumber=1,stepnumber=1]\n'
@@ -472,7 +472,7 @@ def test_literalinclude_file_whole_of_emptyline(app, status, warning):
@pytest.mark.sphinx('html', testroot='directive-code')
def test_literalinclude_caption_html(app, status, warning):
app.builder.build('index')
- html = (app.outdir / 'caption.html').text()
+ html = (app.outdir / 'caption.html').read_text()
caption = ('<div class="code-block-caption">'
'<span class="caption-number">Listing 2 </span>'
'<span class="caption-text">caption <strong>test</strong> py'
@@ -484,7 +484,7 @@ def test_literalinclude_caption_html(app, status, warning):
@pytest.mark.sphinx('latex', testroot='directive-code')
def test_literalinclude_caption_latex(app, status, warning):
app.builder.build('index')
- latex = (app.outdir / 'python.tex').text()
+ latex = (app.outdir / 'python.tex').read_text()
caption = '\\sphinxSetupCaptionForVerbatim{caption \\sphinxstylestrong{test} py}'
label = '\\def\\sphinxLiteralBlockLabel{\\label{\\detokenize{caption:id2}}}'
link = '\\hyperref[\\detokenize{caption:name-test-py}]' \
@@ -497,7 +497,7 @@ def test_literalinclude_caption_latex(app, status, warning):
@pytest.mark.sphinx('latex', testroot='directive-code')
def test_literalinclude_namedlink_latex(app, status, warning):
app.builder.build('index')
- latex = (app.outdir / 'python.tex').text()
+ latex = (app.outdir / 'python.tex').read_text()
label1 = '\\def\\sphinxLiteralBlockLabel{\\label{\\detokenize{caption:name-test-py}}}'
link1 = '\\hyperref[\\detokenize{caption:name-test-py}]'\
'{\\sphinxcrossref{\\DUrole{std,std-ref}{Python}}'
@@ -584,7 +584,7 @@ def test_code_block_highlighted(app, status, warning):
@pytest.mark.sphinx('html', testroot='directive-code')
def test_linenothreshold(app, status, warning):
app.builder.build(['linenothreshold'])
- html = (app.outdir / 'linenothreshold.html').text()
+ html = (app.outdir / 'linenothreshold.html').read_text()
lineos_head = '<td class="linenos"><div class="linenodiv"><pre>'
lineos_tail = '</pre></div></td>'
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index b2cbf3035..fba5fdb42 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -796,6 +796,25 @@ def filter_warnings(warning, file):
return res
+@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True})
+def test_build_domain_cpp_multi_decl_lookup(app, status, warning):
+ app.builder.build_all()
+ ws = filter_warnings(warning, "lookup-key-overload")
+ assert len(ws) == 0
+
+ ws = filter_warnings(warning, "multi-decl-lookup")
+ assert len(ws) == 0
+
+
+@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True})
+def test_build_domain_cpp_warn_template_param_qualified_name(app, status, warning):
+ app.builder.build_all()
+ ws = filter_warnings(warning, "warn-template-param-qualified-name")
+ assert len(ws) == 2
+ assert "WARNING: cpp:type reference target not found: T::typeWarn" in ws[0]
+ assert "WARNING: cpp:type reference target not found: T::U::typeWarn" in ws[1]
+
+
@pytest.mark.sphinx(testroot='domain-cpp')
def test_build_domain_cpp_misuse_of_roles(app, status, warning):
app.builder.build_all()
@@ -872,14 +891,14 @@ def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, war
]
f = 'roles.html'
- t = (app.outdir / f).text()
+ t = (app.outdir / f).read_text()
for s in rolePatterns:
check(s, t, f)
for s in parenPatterns:
check(s, t, f)
f = 'any-role.html'
- t = (app.outdir / f).text()
+ t = (app.outdir / f).read_text()
for s in parenPatterns:
check(s, t, f)
@@ -915,14 +934,14 @@ def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, wa
]
f = 'roles.html'
- t = (app.outdir / f).text()
+ t = (app.outdir / f).read_text()
for s in rolePatterns:
check(s, t, f)
for s in parenPatterns:
check(s, t, f)
f = 'any-role.html'
- t = (app.outdir / f).text()
+ t = (app.outdir / f).read_text()
for s in parenPatterns:
check(s, t, f)
@@ -932,7 +951,7 @@ def test_xref_consistency(app, status, warning):
app.builder.build_all()
test = 'xref_consistency.html'
- output = (app.outdir / test).text()
+ output = (app.outdir / test).read_text()
def classes(role, tag):
pattern = (r'{role}-role:.*?'
diff --git a/tests/test_domain_js.py b/tests/test_domain_js.py
index 581856502..2c2e2b7cc 100644
--- a/tests/test_domain_js.py
+++ b/tests/test_domain_js.py
@@ -14,7 +14,12 @@ import pytest
from docutils import nodes
from sphinx import addnodes
+from sphinx.addnodes import (
+ desc, desc_annotation, desc_content, desc_name,
+ desc_parameter, desc_parameterlist, desc_signature
+)
from sphinx.domains.javascript import JavaScriptDomain
+from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
@@ -86,22 +91,22 @@ def test_domain_js_objects(app, status, warning):
assert 'module_b.submodule' in modules
assert 'module_b.submodule' in objects
- assert objects['module_a.submodule.ModTopLevel'] == ('module', 'class')
- assert objects['module_a.submodule.ModTopLevel.mod_child_1'] == ('module', 'method')
- assert objects['module_a.submodule.ModTopLevel.mod_child_2'] == ('module', 'method')
- assert objects['module_b.submodule.ModTopLevel'] == ('module', 'class')
+ assert objects['module_a.submodule.ModTopLevel'][2] == 'class'
+ assert objects['module_a.submodule.ModTopLevel.mod_child_1'][2] == 'method'
+ assert objects['module_a.submodule.ModTopLevel.mod_child_2'][2] == 'method'
+ assert objects['module_b.submodule.ModTopLevel'][2] == 'class'
- assert objects['TopLevel'] == ('roles', 'class')
- assert objects['top_level'] == ('roles', 'function')
- assert objects['NestedParentA'] == ('roles', 'class')
- assert objects['NestedParentA.child_1'] == ('roles', 'function')
- assert objects['NestedParentA.any_child'] == ('roles', 'function')
- assert objects['NestedParentA.NestedChildA'] == ('roles', 'class')
- assert objects['NestedParentA.NestedChildA.subchild_1'] == ('roles', 'function')
- assert objects['NestedParentA.NestedChildA.subchild_2'] == ('roles', 'function')
- assert objects['NestedParentA.child_2'] == ('roles', 'function')
- assert objects['NestedParentB'] == ('roles', 'class')
- assert objects['NestedParentB.child_1'] == ('roles', 'function')
+ assert objects['TopLevel'][2] == 'class'
+ assert objects['top_level'][2] == 'function'
+ assert objects['NestedParentA'][2] == 'class'
+ assert objects['NestedParentA.child_1'][2] == 'function'
+ assert objects['NestedParentA.any_child'][2] == 'function'
+ assert objects['NestedParentA.NestedChildA'][2] == 'class'
+ assert objects['NestedParentA.NestedChildA.subchild_1'][2] == 'function'
+ assert objects['NestedParentA.NestedChildA.subchild_2'][2] == 'function'
+ assert objects['NestedParentA.child_2'][2] == 'function'
+ assert objects['NestedParentB'][2] == 'class'
+ assert objects['NestedParentB.child_1'][2] == 'function'
@pytest.mark.sphinx('dummy', testroot='domain-js')
@@ -115,21 +120,28 @@ def test_domain_js_find_obj(app, status, warning):
assert (find_obj(None, None, 'NONEXISTANT', 'class') == (None, None))
assert (find_obj(None, None, 'NestedParentA', 'class') ==
- ('NestedParentA', ('roles', 'class')))
+ ('NestedParentA', ('roles', 'nestedparenta', 'class')))
assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') ==
- ('NestedParentA.NestedChildA', ('roles', 'class')))
+ ('NestedParentA.NestedChildA',
+ ('roles', 'nestedparenta-nestedchilda', 'class')))
assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') ==
- ('NestedParentA.NestedChildA', ('roles', 'class')))
+ ('NestedParentA.NestedChildA',
+ ('roles', 'nestedparenta-nestedchilda', 'class')))
assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'func') ==
- ('NestedParentA.NestedChildA.subchild_1', ('roles', 'function')))
+ ('NestedParentA.NestedChildA.subchild_1',
+ ('roles', 'nestedparenta-nestedchilda-subchild-1', 'function')))
assert (find_obj(None, 'NestedParentA', 'NestedChildA.subchild_1', 'func') ==
- ('NestedParentA.NestedChildA.subchild_1', ('roles', 'function')))
+ ('NestedParentA.NestedChildA.subchild_1',
+ ('roles', 'nestedparenta-nestedchilda-subchild-1', 'function')))
assert (find_obj(None, 'NestedParentA.NestedChildA', 'subchild_1', 'func') ==
- ('NestedParentA.NestedChildA.subchild_1', ('roles', 'function')))
+ ('NestedParentA.NestedChildA.subchild_1',
+ ('roles', 'nestedparenta-nestedchilda-subchild-1', 'function')))
assert (find_obj('module_a.submodule', 'ModTopLevel', 'mod_child_2', 'meth') ==
- ('module_a.submodule.ModTopLevel.mod_child_2', ('module', 'method')))
+ ('module_a.submodule.ModTopLevel.mod_child_2',
+ ('module', 'module-a-submodule-modtoplevel-mod-child-2', 'method')))
assert (find_obj('module_b.submodule', 'ModTopLevel', 'module_a.submodule', 'mod') ==
- ('module_a.submodule', ('module', 'module')))
+ ('module_a.submodule',
+ ('module', 'module-module-a-submodule', 'module')))
def test_get_full_qualified_name():
@@ -158,3 +170,51 @@ def test_get_full_qualified_name():
kwargs = {'js:module': 'module1', 'js:object': 'Class'}
node = nodes.reference(reftarget='func', **kwargs)
assert domain.get_full_qualified_name(node) == 'module1.Class.func'
+
+
+def test_js_module(app):
+ text = ".. js:module:: sphinx"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (nodes.target,
+ addnodes.index))
+ assert_node(doctree[0], nodes.target, ids=["module-sphinx"])
+ assert_node(doctree[1], addnodes.index,
+ entries=[("single", "sphinx (module)", "module-sphinx", "", None)])
+
+
+def test_js_function(app):
+ text = ".. js:function:: sum(a, b)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (addnodes.index,
+ [desc, ([desc_signature, ([desc_name, "sum"],
+ desc_parameterlist)],
+ [desc_content, ()])]))
+ assert_node(doctree[1][0][1], [desc_parameterlist, ([desc_parameter, "a"],
+ [desc_parameter, "b"])])
+ assert_node(doctree[0], addnodes.index,
+ entries=[("single", "sum() (built-in function)", "sum", "", None)])
+ assert_node(doctree[1], addnodes.desc, domain="js", objtype="function", noindex=False)
+
+
+def test_js_class(app):
+ text = ".. js:class:: Application"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (addnodes.index,
+ [desc, ([desc_signature, ([desc_annotation, "class "],
+ [desc_name, "Application"],
+ [desc_parameterlist, ()])],
+ [desc_content, ()])]))
+ assert_node(doctree[0], addnodes.index,
+ entries=[("single", "Application() (class)", "application", "", None)])
+ assert_node(doctree[1], addnodes.desc, domain="js", objtype="class", noindex=False)
+
+
+def test_js_data(app):
+ text = ".. js:data:: name"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (addnodes.index,
+ [desc, ([desc_signature, desc_name, "name"],
+ [desc_content, ()])]))
+ assert_node(doctree[0], addnodes.index,
+ entries=[("single", "name (global variable or constant)", "name", "", None)])
+ assert_node(doctree[1], addnodes.desc, domain="js", objtype="data", noindex=False)
diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py
index f78c1e9d8..178fe18cf 100644
--- a/tests/test_domain_py.py
+++ b/tests/test_domain_py.py
@@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details.
"""
+import sys
from unittest.mock import Mock
import pytest
@@ -168,7 +169,7 @@ def test_domain_py_objects(app, status, warning):
def test_resolve_xref_for_properties(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'module.html').text()
+ content = (app.outdir / 'module.html').read_text()
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">'
@@ -241,7 +242,73 @@ def test_pyfunction_signature(app):
desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="function",
domain="py", objtype="function", noindex=False)
- assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, "name: str"])
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, desc_parameter, ("name",
+ ": str")])
+
+
+def test_pyfunction_signature_full(app):
+ text = (".. py:function:: hello(a: str, b = 1, *args: str, "
+ "c: bool = True, **kwargs: str) -> str")
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (addnodes.index,
+ [desc, ([desc_signature, ([desc_name, "hello"],
+ desc_parameterlist,
+ [desc_returns, "str"])],
+ desc_content)]))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="py", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, ([desc_parameter, ("a",
+ ": str")],
+ [desc_parameter, ("b",
+ "=1")],
+ [desc_parameter, ("*args",
+ ": str")],
+ [desc_parameter, ("c",
+ ": bool",
+ " = True")],
+ [desc_parameter, ("**kwargs",
+ ": str")])])
+
+
+@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
+def test_pyfunction_signature_full_py38(app):
+ # case: separator at head
+ text = ".. py:function:: hello(*, a)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, ([desc_parameter, "*"],
+ [desc_parameter, ("a",
+ "=None")])])
+
+ # case: separator in the middle
+ text = ".. py:function:: hello(a, /, b, *, c)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, ([desc_parameter, "a"],
+ [desc_parameter, "/"],
+ [desc_parameter, "b"],
+ [desc_parameter, "*"],
+ [desc_parameter, ("c",
+ "=None")])])
+
+ # case: separator in the middle (2)
+ text = ".. py:function:: hello(a, /, *, b)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, ([desc_parameter, "a"],
+ [desc_parameter, "/"],
+ [desc_parameter, "*"],
+ [desc_parameter, ("b",
+ "=None")])])
+
+ # case: separator at tail
+ text = ".. py:function:: hello(a, /)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, ([desc_parameter, "a"],
+ [desc_parameter, "/"])])
def test_optional_pyfunction_signature(app):
@@ -559,3 +626,26 @@ def test_module_index_not_collapsed(app):
('s', [IndexEntry('sphinx', 0, 'index', 'module-sphinx', '', '', '')])],
True
)
+
+
+@pytest.mark.sphinx(freshenv=True, confoverrides={'modindex_common_prefix': ['sphinx.']})
+def test_modindex_common_prefix(app):
+ text = (".. py:module:: docutils\n"
+ ".. py:module:: sphinx\n"
+ ".. py:module:: sphinx.config\n"
+ ".. py:module:: sphinx.builders\n"
+ ".. py:module:: sphinx.builders.html\n"
+ ".. py:module:: sphinx_intl\n")
+ restructuredtext.parse(app, text)
+ index = PythonModuleIndex(app.env.get_domain('py'))
+ assert index.generate() == (
+ [('b', [IndexEntry('sphinx.builders', 1, 'index', 'module-sphinx.builders', '', '', ''), # NOQA
+ IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx.builders.html', '', '', '')]), # NOQA
+ ('c', [IndexEntry('sphinx.config', 0, 'index', 'module-sphinx.config', '', '', '')]),
+ ('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]),
+ ('s', [IndexEntry('sphinx', 0, 'index', 'module-sphinx', '', '', ''),
+ IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx_intl', '', '', '')])],
+ True
+ )
+
+
diff --git a/tests/test_domain_rst.py b/tests/test_domain_rst.py
index 50d9d154f..86fe7ef3f 100644
--- a/tests/test_domain_rst.py
+++ b/tests/test_domain_rst.py
@@ -76,7 +76,7 @@ def test_rst_directive_option(app):
[desc_content, ()])]))
assert_node(doctree[0],
entries=[("single", ":foo: (directive option)",
- "directive:option--foo", "", "F")])
+ "directive-option-foo", "", "F")])
assert_node(doctree[1], addnodes.desc, desctype="directive:option",
domain="rst", objtype="directive:option", noindex=False)
@@ -90,7 +90,7 @@ def test_rst_directive_option_with_argument(app):
[desc_content, ()])]))
assert_node(doctree[0],
entries=[("single", ":foo: (directive option)",
- "directive:option--foo", "", "F")])
+ "directive-option-foo", "", "F")])
assert_node(doctree[1], addnodes.desc, desctype="directive:option",
domain="rst", objtype="directive:option", noindex=False)
@@ -105,7 +105,7 @@ def test_rst_directive_option_type(app):
[desc_content, ()])]))
assert_node(doctree[0],
entries=[("single", ":foo: (directive option)",
- "directive:option--foo", "", "F")])
+ "directive-option-foo", "", "F")])
assert_node(doctree[1], addnodes.desc, desctype="directive:option",
domain="rst", objtype="directive:option", noindex=False)
@@ -121,7 +121,7 @@ def test_rst_directive_and_directive_option(app):
desc)])]))
assert_node(doctree[1][1][0],
entries=[("pair", "foo (directive); :bar: (directive option)",
- "directive:option-foo-bar", "", "B")])
+ "directive-option-foo-bar", "", "B")])
assert_node(doctree[1][1][1], ([desc_signature, desc_name, ":bar:"],
[desc_content, ()]))
assert_node(doctree[1][1][1], addnodes.desc, desctype="directive:option",
diff --git a/tests/test_domain_std.py b/tests/test_domain_std.py
index 56c2d968c..973208298 100644
--- a/tests/test_domain_std.py
+++ b/tests/test_domain_std.py
@@ -8,11 +8,15 @@
:license: BSD, see LICENSE for details.
"""
+import pytest
+
from unittest import mock
from docutils import nodes
from docutils.nodes import definition, definition_list, definition_list_item, term
+from html5lib import HTMLParser
+
from sphinx import addnodes
from sphinx.addnodes import (
desc, desc_addname, desc_content, desc_name, desc_signature, glossary, index
@@ -20,6 +24,7 @@ from sphinx.addnodes import (
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():
@@ -172,6 +177,15 @@ def test_glossary_warning(app, status, warning):
assert ("case3.rst:4: WARNING: glossary term must be preceded by empty line"
in warning.getvalue())
+ # duplicated terms
+ text = (".. glossary::\n"
+ "\n"
+ " term-case4\n"
+ " term-case4\n")
+ restructuredtext.parse(app, text, "case4")
+ assert ("case4.rst:3: WARNING: duplicate term description of term-case4, "
+ "other instance in case4" in warning.getvalue())
+
def test_glossary_comment(app):
text = (".. glossary::\n"
@@ -303,3 +317,59 @@ def test_multiple_cmdoptions(app):
assert ('cmd', '--output') in domain.progoptions
assert domain.progoptions[('cmd', '-o')] == ('index', 'cmdoption-cmd-o')
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()
+
+ warnings = warning.getvalue().split("\n");
+ assert len(warnings) == 2
+ assert warnings[-1] == ''
+ assert "Dup2.rst:4: WARNING: duplicate token description of Dup, other instance in Dup1" in warnings[0]
+
+ with (app.outdir / 'index.html').open('rb') as f:
+ etree = HTMLParser(namespaceHTMLElements=False).parse(f)
+ ul = list(etree.iter('ul'))[1]
+ cases = []
+ for li in list(ul):
+ assert len(list(li)) == 1
+ p = list(li)[0]
+ assert p.tag == 'p'
+ text = str(p.text).strip(' :')
+ assert len(list(p)) == 1
+ a = list(p)[0]
+ assert a.tag == 'a'
+ link = a.get('href')
+ assert len(list(a)) == 1
+ code = list(a)[0]
+ assert code.tag == 'code'
+ assert len(list(code)) == 1
+ span = list(code)[0]
+ assert span.tag == 'span'
+ linkText = span.text.strip()
+ cases.append((text, link, linkText))
+ assert cases == [
+ ('A', 'Bare.html#grammar-token-_a', 'A'),
+ ('B', 'Bare.html#grammar-token-_b', 'B'),
+ ('P1:A', 'P1.html#grammar-token-p1_a', 'P1:A'),
+ ('P1:B', 'P1.html#grammar-token-p1_b', 'P1:B'),
+ ('P2:A', 'P1.html#grammar-token-p1_a', 'P1:A'),
+ ('P2:B', 'P2.html#grammar-token-p2_b', 'P2:B'),
+ ('Explicit title A, plain', 'Bare.html#grammar-token-_a', 'MyTitle'),
+ ('Explicit title A, colon', 'Bare.html#grammar-token-_a', 'My:Title'),
+ ('Explicit title P1:A, plain', 'P1.html#grammar-token-p1_a', 'MyTitle'),
+ ('Explicit title P1:A, colon', 'P1.html#grammar-token-p1_a', 'My:Title'),
+ ('Tilde A', 'Bare.html#grammar-token-_a', 'A'),
+ ('Tilde P1:A', 'P1.html#grammar-token-p1_a', 'A'),
+ ('Tilde explicit title P1:A', 'P1.html#grammar-token-p1_a', '~MyTitle'),
+ ('Tilde, explicit title P1:A', 'P1.html#grammar-token-p1_a', 'MyTitle'),
+ ('Dup', 'Dup2.html#grammar-token-_dup', 'Dup'),
+ ('FirstLine', 'firstLineRule.html#grammar-token-_firstline', 'FirstLine'),
+ ('SecondLine', 'firstLineRule.html#grammar-token-_secondline', 'SecondLine'),
+ ]
+
+ text = (app.outdir / 'LineContinuation.html').read_text()
+ assert "A</strong> ::= B C D E F G" in text
diff --git a/tests/test_environment_indexentries.py b/tests/test_environment_indexentries.py
index ad7559ff2..97882d44b 100644
--- a/tests/test_environment_indexentries.py
+++ b/tests/test_environment_indexentries.py
@@ -113,6 +113,43 @@ def test_create_seealso_index(app):
@pytest.mark.sphinx('dummy', freshenv=True)
+def test_create_main_index(app):
+ text = (".. index:: !docutils\n"
+ ".. index:: docutils\n"
+ ".. index:: pip; install\n"
+ ".. index:: !pip; install\n")
+ restructuredtext.parse(app, text)
+ index = IndexEntries(app.env).create_index(app.builder)
+ assert len(index) == 2
+ assert index[0] == ('D', [('docutils', [[('main', '#index-0'),
+ ('', '#index-1')], [], None])])
+ assert index[1] == ('P', [('pip', [[], [('install', [('main', '#index-3'),
+ ('', '#index-2')])], None])])
+
+
+@pytest.mark.sphinx('dummy', freshenv=True)
+def test_create_index_with_name(app):
+ text = (".. index:: single: docutils\n"
+ " :name: ref1\n"
+ ".. index:: single: Python\n"
+ " :name: ref2\n"
+ ".. index:: Sphinx\n")
+ restructuredtext.parse(app, text)
+ index = IndexEntries(app.env).create_index(app.builder)
+
+ # check index is created correctly
+ assert len(index) == 3
+ assert index[0] == ('D', [('docutils', [[('', '#ref1')], [], None])])
+ assert index[1] == ('P', [('Python', [[('', '#ref2')], [], None])])
+ assert index[2] == ('S', [('Sphinx', [[('', '#index-0')], [], None])])
+
+ # check the reference labels are created correctly
+ std = app.env.get_domain('std')
+ assert std.anonlabels['ref1'] == ('index', 'ref1')
+ assert std.anonlabels['ref2'] == ('index', 'ref2')
+
+
+@pytest.mark.sphinx('dummy', freshenv=True)
def test_create_index_by_key(app):
# At present, only glossary directive is able to create index key
text = (".. glossary::\n"
diff --git a/tests/test_events.py b/tests/test_events.py
new file mode 100644
index 000000000..4881588a4
--- /dev/null
+++ b/tests/test_events.py
@@ -0,0 +1,24 @@
+"""
+ test_events
+ ~~~~~~~~~~~
+
+ Test the EventManager class.
+
+ :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from sphinx.events import EventManager
+
+
+def test_event_priority():
+ result = []
+ events = EventManager(object()) # pass an dummy object as an app
+ events.connect('builder-inited', lambda app: result.append(1), priority = 500)
+ events.connect('builder-inited', lambda app: result.append(2), priority = 500)
+ events.connect('builder-inited', lambda app: result.append(3), priority = 200) # eariler
+ events.connect('builder-inited', lambda app: result.append(4), priority = 700) # later
+ events.connect('builder-inited', lambda app: result.append(5), priority = 500)
+
+ events.emit('builder-inited')
+ assert result == [3, 1, 2, 5, 4]
diff --git a/tests/test_ext_apidoc.py b/tests/test_ext_apidoc.py
index 3033cb450..a37527a02 100644
--- a/tests/test_ext_apidoc.py
+++ b/tests/test_ext_apidoc.py
@@ -275,7 +275,7 @@ def test_multibyte_parameters(make_app, apidoc):
assert (outdir / 'conf.py').isfile()
assert (outdir / 'index.rst').isfile()
- conf_py = (outdir / 'conf.py').text()
+ conf_py = (outdir / 'conf.py').read_text()
assert "project = 'プロジェクト名'" in conf_py
assert "author = '著者名'" in conf_py
assert "version = 'バージョン'" in conf_py
@@ -408,13 +408,13 @@ def test_private(tempdir):
# without --private option
apidoc_main(['-o', tempdir, tempdir])
assert (tempdir / 'hello.rst').exists()
- assert ':private-members:' not in (tempdir / 'hello.rst').text()
+ assert ':private-members:' not in (tempdir / 'hello.rst').read_text()
assert not (tempdir / '_world.rst').exists()
# with --private option
apidoc_main(['--private', '-f', '-o', tempdir, tempdir])
assert (tempdir / 'hello.rst').exists()
- assert ':private-members:' in (tempdir / 'hello.rst').text()
+ assert ':private-members:' in (tempdir / 'hello.rst').read_text()
assert (tempdir / '_world.rst').exists()
@@ -426,7 +426,7 @@ def test_toc_file(tempdir):
apidoc_main(['-o', tempdir, tempdir])
assert (outdir / 'modules.rst').exists()
- content = (outdir / 'modules.rst').text()
+ content = (outdir / 'modules.rst').read_text()
assert content == ("test_toc_file0\n"
"==============\n"
"\n"
@@ -442,7 +442,7 @@ def test_module_file(tempdir):
apidoc_main(['-o', tempdir, tempdir])
assert (outdir / 'example.rst').exists()
- content = (outdir / 'example.rst').text()
+ content = (outdir / 'example.rst').read_text()
assert content == ("example module\n"
"==============\n"
"\n"
@@ -458,7 +458,7 @@ def test_module_file_noheadings(tempdir):
apidoc_main(['--no-headings', '-o', tempdir, tempdir])
assert (outdir / 'example.rst').exists()
- content = (outdir / 'example.rst').text()
+ content = (outdir / 'example.rst').read_text()
assert content == (".. automodule:: example\n"
" :members:\n"
" :undoc-members:\n"
@@ -477,7 +477,7 @@ def test_package_file(tempdir):
assert (outdir / 'testpkg.rst').exists()
assert (outdir / 'testpkg.subpkg.rst').exists()
- content = (outdir / 'testpkg.rst').text()
+ content = (outdir / 'testpkg.rst').read_text()
assert content == ("testpkg package\n"
"===============\n"
"\n"
@@ -516,7 +516,7 @@ def test_package_file(tempdir):
" :undoc-members:\n"
" :show-inheritance:\n")
- content = (outdir / 'testpkg.subpkg.rst').text()
+ content = (outdir / 'testpkg.subpkg.rst').read_text()
assert content == ("testpkg.subpkg package\n"
"======================\n"
"\n"
@@ -538,7 +538,7 @@ def test_package_file_separate(tempdir):
assert (outdir / 'testpkg.rst').exists()
assert (outdir / 'testpkg.example.rst').exists()
- content = (outdir / 'testpkg.rst').text()
+ content = (outdir / 'testpkg.rst').read_text()
assert content == ("testpkg package\n"
"===============\n"
"\n"
@@ -557,7 +557,7 @@ def test_package_file_separate(tempdir):
" :undoc-members:\n"
" :show-inheritance:\n")
- content = (outdir / 'testpkg.example.rst').text()
+ content = (outdir / 'testpkg.example.rst').read_text()
assert content == ("testpkg.example module\n"
"======================\n"
"\n"
@@ -574,7 +574,7 @@ def test_package_file_module_first(tempdir):
(outdir / 'testpkg' / 'example.py').write_text('')
apidoc_main(['--module-first', '-o', tempdir, tempdir])
- content = (outdir / 'testpkg.rst').text()
+ content = (outdir / 'testpkg.rst').read_text()
assert content == ("testpkg package\n"
"===============\n"
"\n"
@@ -603,7 +603,7 @@ def test_package_file_without_submodules(tempdir):
apidoc_main(['-o', tempdir, tempdir / 'testpkg'])
assert (outdir / 'testpkg.rst').exists()
- content = (outdir / 'testpkg.rst').text()
+ content = (outdir / 'testpkg.rst').read_text()
assert content == ("testpkg package\n"
"===============\n"
"\n"
@@ -623,7 +623,7 @@ def test_namespace_package_file(tempdir):
apidoc_main(['--implicit-namespace', '-o', tempdir, tempdir / 'testpkg'])
assert (outdir / 'testpkg.rst').exists()
- content = (outdir / 'testpkg.rst').text()
+ content = (outdir / 'testpkg.rst').read_text()
assert content == ("testpkg namespace\n"
"=================\n"
"\n"
diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py
index f21431238..b90772f6e 100644
--- a/tests/test_ext_autodoc_configs.py
+++ b/tests/test_ext_autodoc_configs.py
@@ -12,7 +12,6 @@ import platform
import pytest
-from sphinx.ext.autodoc import merge_autodoc_default_flags
from test_autodoc import do_autodoc
IS_PYPY = platform.python_implementation() == 'PyPy'
@@ -569,7 +568,7 @@ def test_autodoc_typehints_none(app):
'autodoc_typehints': 'description'})
def test_autodoc_typehints_description(app):
app.build()
- context = (app.outdir / 'index.txt').text()
+ context = (app.outdir / 'index.txt').read_text()
assert ('target.typehints.incr(a, b=1)\n'
'\n'
' Parameters:\n'
@@ -583,27 +582,6 @@ def test_autodoc_typehints_description(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc')
-@pytest.mark.filterwarnings('ignore:autodoc_default_flags is now deprecated.')
-def test_merge_autodoc_default_flags1(app):
- app.config.autodoc_default_flags = ['members', 'undoc-members']
- merge_autodoc_default_flags(app, app.config)
- assert app.config.autodoc_default_options == {'members': None,
- 'undoc-members': None}
-
-
-@pytest.mark.sphinx('html', testroot='ext-autodoc')
-@pytest.mark.filterwarnings('ignore:autodoc_default_flags is now deprecated.')
-def test_merge_autodoc_default_flags2(app):
- app.config.autodoc_default_flags = ['members', 'undoc-members']
- app.config.autodoc_default_options = {'members': 'this,that,order',
- 'inherited-members': 'this'}
- merge_autodoc_default_flags(app, app.config)
- assert app.config.autodoc_default_options == {'members': None,
- 'undoc-members': None,
- 'inherited-members': 'this'}
-
-
-@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_options(app):
# no settings
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
@@ -612,7 +590,7 @@ def test_autodoc_default_options(app):
actual = do_autodoc(app, 'class', 'target.CustomIter')
assert ' .. py:method:: target.CustomIter' not in actual
actual = do_autodoc(app, 'module', 'target')
- assert '.. py:function:: save_traceback(app: Sphinx) -> str' not in actual
+ assert '.. py:function:: save_traceback(app)' not in actual
# with :members:
app.config.autodoc_default_options = {'members': None}
@@ -676,16 +654,6 @@ def test_autodoc_default_options(app):
assert ' .. py:method:: CustomIter.snafucate()' in actual
assert ' Makes this snafucated.' in actual
- # with :imported-members:
- app.config.autodoc_default_options = {
- 'members': None,
- 'imported-members': None,
- 'ignore-module-all': None,
- }
- actual = do_autodoc(app, 'module', 'target')
- print('\n'.join(actual))
- assert '.. py:function:: save_traceback(app: Sphinx) -> str' in actual
-
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_options_with_values(app):
diff --git a/tests/test_ext_autodoc_mock.py b/tests/test_ext_autodoc_mock.py
index 35084c23e..4760493cf 100644
--- a/tests/test_ext_autodoc_mock.py
+++ b/tests/test_ext_autodoc_mock.py
@@ -18,7 +18,7 @@ from sphinx.ext.autodoc.mock import _MockModule, _MockObject, mock
def test_MockModule():
- mock = _MockModule('mocked_module', None)
+ mock = _MockModule('mocked_module')
assert isinstance(mock.some_attr, _MockObject)
assert isinstance(mock.some_method, _MockObject)
assert isinstance(mock.attr1.attr2, _MockObject)
diff --git a/tests/test_ext_autodoc_private_members.py b/tests/test_ext_autodoc_private_members.py
new file mode 100644
index 000000000..e8f3e53ef
--- /dev/null
+++ b/tests/test_ext_autodoc_private_members.py
@@ -0,0 +1,46 @@
+"""
+ test_ext_autodoc_private_members
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Test the autodoc extension. This tests mainly for private-members option.
+
+ :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import pytest
+
+from test_autodoc import do_autodoc
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_private_field(app):
+ app.config.autoclass_content = 'class'
+ options = {"members": None}
+ actual = do_autodoc(app, 'module', 'target.private', options)
+ assert list(actual) == [
+ '',
+ '.. py:module:: target.private',
+ '',
+ ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_private_field_and_private_members(app):
+ app.config.autoclass_content = 'class'
+ options = {"members": None,
+ "private-members": None}
+ actual = do_autodoc(app, 'module', 'target.private', options)
+ assert list(actual) == [
+ '',
+ '.. py:module:: target.private',
+ '',
+ '',
+ '.. py:function:: private_function(name)',
+ ' :module: target.private',
+ '',
+ ' private_function is a docstring().',
+ ' ',
+ ' :meta private:',
+ ' '
+ ]
diff --git a/tests/test_ext_autosectionlabel.py b/tests/test_ext_autosectionlabel.py
index 947be0e11..310435d8e 100644
--- a/tests/test_ext_autosectionlabel.py
+++ b/tests/test_ext_autosectionlabel.py
@@ -21,7 +21,7 @@ from sphinx.util import docutils
def test_autosectionlabel_html(app, status, warning, skipped_labels=False):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
html = ('<li><p><a class="reference internal" href="#introduce-of-sphinx">'
'<span class=".*?">Introduce of Sphinx</span></a></p></li>')
assert re.search(html, content, re.S)
@@ -69,7 +69,7 @@ def test_autosectionlabel_prefix_document_html(app, status, warning):
def test_autosectionlabel_maxdepth(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
# depth: 1
html = ('<li><p><a class="reference internal" href="#test-ext-autosectionlabel">'
diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py
index 628c2524b..3e15ef244 100644
--- a/tests/test_ext_autosummary.py
+++ b/tests/test_ext_autosummary.py
@@ -31,6 +31,7 @@ default_kw = {
'confoverrides': {
'extensions': ['sphinx.ext.autosummary'],
'autosummary_generate': True,
+ 'autosummary_generate_overwrite': False,
'source_suffix': '.rst'
}
}
@@ -209,13 +210,13 @@ def test_autosummary_generate(app, status, warning):
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
assert doctree[3][0][0][2][3].astext() == 'autosummary_importfail\n\n'
- module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').text()
+ module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
assert (' .. autosummary::\n'
' \n'
' Foo\n'
' \n' in module)
- Foo = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.rst').text()
+ Foo = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.rst').read_text()
assert '.. automethod:: __init__' in Foo
assert (' .. autosummary::\n'
' \n'
@@ -228,10 +229,40 @@ def test_autosummary_generate(app, status, warning):
' \n' in Foo)
+@pytest.mark.sphinx('dummy', testroot='ext-autosummary',
+ confoverrides={'autosummary_generate_overwrite': False})
+def test_autosummary_generate_overwrite1(app_params, make_app):
+ args, kwargs = app_params
+ srcdir = kwargs.get('srcdir')
+
+ (srcdir / 'generated').makedirs(exist_ok=True)
+ (srcdir / 'generated' / 'autosummary_dummy_module.rst').write_text('')
+
+ app = make_app(*args, **kwargs)
+ content = (srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
+ assert content == ''
+ assert 'autosummary_dummy_module.rst' not in app._warning.getvalue()
+
+
+@pytest.mark.sphinx('dummy', testroot='ext-autosummary',
+ confoverrides={'autosummary_generate_overwrite': True})
+def test_autosummary_generate_overwrite2(app_params, make_app):
+ args, kwargs = app_params
+ srcdir = kwargs.get('srcdir')
+
+ (srcdir / 'generated').makedirs(exist_ok=True)
+ (srcdir / 'generated' / 'autosummary_dummy_module.rst').write_text('')
+
+ app = make_app(*args, **kwargs)
+ content = (srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
+ assert content != ''
+ assert 'autosummary_dummy_module.rst' not in app._warning.getvalue()
+
+
@pytest.mark.sphinx('latex', **default_kw)
def test_autosummary_latex_table_colspec(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
print(status.getvalue())
print(warning.getvalue())
assert r'\begin{longtable}[c]{\X{1}{2}\X{1}{2}}' in result
@@ -281,7 +312,7 @@ def test_autosummary_imported_members(app, status, warning):
# generated/foo is generated successfully
assert app.env.get_doctree('generated/autosummary_dummy_package')
- module = (app.srcdir / 'generated' / 'autosummary_dummy_package.rst').text()
+ module = (app.srcdir / 'generated' / 'autosummary_dummy_package.rst').read_text()
assert (' .. autosummary::\n'
' \n'
' Bar\n'
@@ -300,7 +331,7 @@ def test_generate_autosummary_docs_property(app):
mock.return_value = [('target.methods.Base.prop', 'prop', None)]
generate_autosummary_docs([], output_dir=app.srcdir, builder=app.builder, app=app)
- content = (app.srcdir / 'target.methods.Base.prop.rst').text()
+ content = (app.srcdir / 'target.methods.Base.prop.rst').read_text()
assert content == ("target.methods.Base.prop\n"
"========================\n"
"\n"
@@ -313,7 +344,7 @@ def test_generate_autosummary_docs_property(app):
def test_autosummary_skip_member(app):
app.build()
- content = (app.srcdir / 'generate' / 'target.Foo.rst').text()
+ content = (app.srcdir / 'generate' / 'target.Foo.rst').read_text()
assert 'Foo.skipmeth' not in content
assert 'Foo._privatemeth' in content
diff --git a/tests/test_ext_coverage.py b/tests/test_ext_coverage.py
index 87e5e4931..16f53112b 100644
--- a/tests/test_ext_coverage.py
+++ b/tests/test_ext_coverage.py
@@ -17,7 +17,7 @@ import pytest
def test_build(app, status, warning):
app.builder.build_all()
- py_undoc = (app.outdir / 'python.txt').text()
+ py_undoc = (app.outdir / 'python.txt').read_text()
assert py_undoc.startswith('Undocumented Python objects\n'
'===========================\n')
assert 'autodoc_target\n--------------\n' in py_undoc
@@ -28,13 +28,13 @@ def test_build(app, status, warning):
assert ' * mod -- No module named mod' # in the "failed import" section
- c_undoc = (app.outdir / 'c.txt').text()
+ c_undoc = (app.outdir / 'c.txt').read_text()
assert c_undoc.startswith('Undocumented C API elements\n'
'===========================\n')
assert 'api.h' in c_undoc
assert ' * Py_SphinxTest' in c_undoc
- undoc_py, undoc_c = pickle.loads((app.outdir / 'undoc.pickle').bytes())
+ undoc_py, undoc_c = pickle.loads((app.outdir / 'undoc.pickle').read_bytes())
assert len(undoc_c) == 1
# the key is the full path to the header file, which isn't testable
assert list(undoc_c.values())[0] == {('function', 'Py_SphinxTest')}
@@ -50,7 +50,7 @@ def test_build(app, status, warning):
@pytest.mark.sphinx('coverage', testroot='ext-coverage')
def test_coverage_ignore_pyobjects(app, status, warning):
app.builder.build_all()
- actual = (app.outdir / 'python.txt').text()
+ actual = (app.outdir / 'python.txt').read_text()
expected = '''Undocumented Python objects
===========================
coverage_not_ignored
diff --git a/tests/test_ext_githubpages.py b/tests/test_ext_githubpages.py
index 8a8004347..41dbe4f60 100644
--- a/tests/test_ext_githubpages.py
+++ b/tests/test_ext_githubpages.py
@@ -31,4 +31,4 @@ def test_no_cname_for_github_io_domain(app, status, warning):
def test_cname_for_custom_domain(app, status, warning):
app.builder.build_all()
assert (app.outdir / '.nojekyll').exists()
- assert (app.outdir / 'CNAME').text() == 'sphinx-doc.org'
+ assert (app.outdir / 'CNAME').read_text() == 'sphinx-doc.org'
diff --git a/tests/test_ext_graphviz.py b/tests/test_ext_graphviz.py
index f89a96860..a8de950ce 100644
--- a/tests/test_ext_graphviz.py
+++ b/tests/test_ext_graphviz.py
@@ -20,7 +20,7 @@ from sphinx.ext.graphviz import ClickableMapDefinition
def test_graphviz_png_html(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
html = (r'<div class="figure align-default" .*?>\s*'
r'<div class="graphviz"><img .*?/></div>\s*<p class="caption">'
r'<span class="caption-text">caption of graph</span>.*</p>\s*</div>')
@@ -51,7 +51,7 @@ def test_graphviz_png_html(app, status, warning):
def test_graphviz_svg_html(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
html = (r'<div class=\"figure align-default\" .*?>\n'
r'<div class="graphviz"><object data=\".*\.svg\".*>\n'
@@ -91,7 +91,7 @@ def test_graphviz_svg_html(app, status, warning):
def test_graphviz_latex(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'python.tex').text()
+ content = (app.outdir / 'python.tex').read_text()
macro = ('\\\\begin{figure}\\[htbp\\]\n\\\\centering\n\\\\capstart\n\n'
'\\\\sphinxincludegraphics\\[\\]{graphviz-\\w+.pdf}\n'
'\\\\caption{caption of graph}\\\\label{.*}\\\\end{figure}')
@@ -117,7 +117,7 @@ def test_graphviz_latex(app, status, warning):
def test_graphviz_i18n(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
html = '<img src=".*?" alt="digraph {\n BAR -&gt; BAZ\n}" class="graphviz" />'
assert re.search(html, content, re.M)
diff --git a/tests/test_ext_ifconfig.py b/tests/test_ext_ifconfig.py
index abce1d025..232ddf0d8 100644
--- a/tests/test_ext_ifconfig.py
+++ b/tests/test_ext_ifconfig.py
@@ -14,6 +14,6 @@ import pytest
@pytest.mark.sphinx('text', testroot='ext-ifconfig')
def test_ifconfig(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'index.txt').text()
+ result = (app.outdir / 'index.txt').read_text()
assert 'spam' in result
assert 'ham' not in result
diff --git a/tests/test_ext_imgconverter.py b/tests/test_ext_imgconverter.py
index 232808270..075446b0a 100644
--- a/tests/test_ext_imgconverter.py
+++ b/tests/test_ext_imgconverter.py
@@ -18,7 +18,7 @@ import pytest
def test_ext_imgconverter(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'python.tex').text()
+ content = (app.outdir / 'python.tex').read_text()
assert '\\sphinxincludegraphics{{svgimg}.png}' in content
assert not (app.outdir / 'svgimg.svg').exists()
assert (app.outdir / 'svgimg.png').exists()
diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py
index c1cf4524d..3125f2c6e 100644
--- a/tests/test_ext_inheritance_diagram.py
+++ b/tests/test_ext_inheritance_diagram.py
@@ -132,13 +132,21 @@ def test_inheritance_diagram(app, status, warning):
('dummy.test.A', 'dummy.test.A', [], None),
]
+ # inheritance diagram involving a base class nested within another class
+ for cls in graphs['diagram_w_nested_classes'].class_info:
+ assert cls in [
+ ('dummy.test_nested.A', 'dummy.test_nested.A', [], None),
+ ('dummy.test_nested.C', 'dummy.test_nested.C', ['dummy.test_nested.A.B'], None),
+ ('dummy.test_nested.A.B', 'dummy.test_nested.A.B', [], None)
+ ]
+
@pytest.mark.sphinx('html', testroot='ext-inheritance_diagram')
@pytest.mark.usefixtures('if_graphviz_found')
def test_inheritance_diagram_png_html(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
@@ -155,7 +163,7 @@ def test_inheritance_diagram_png_html(app, status, warning):
def test_inheritance_diagram_svg_html(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
@@ -173,7 +181,7 @@ def test_inheritance_diagram_svg_html(app, status, warning):
def test_inheritance_diagram_latex(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'python.tex').text()
+ content = (app.outdir / 'python.tex').read_text()
pattern = ('\\\\begin{figure}\\[htbp]\n\\\\centering\n\\\\capstart\n\n'
'\\\\sphinxincludegraphics\\[\\]{inheritance-\\w+.pdf}\n'
@@ -195,7 +203,7 @@ def test_inheritance_diagram_latex_alias(app, status, warning):
assert ('test.Bar', 'test.Bar', ['alias.Foo'], None) in aliased_graph
assert ('alias.Foo', 'alias.Foo', [], None) in aliased_graph
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py
index eac2394c8..53faa7a37 100644
--- a/tests/test_ext_intersphinx.py
+++ b/tests/test_ext_intersphinx.py
@@ -242,7 +242,7 @@ def test_missing_reference_cppdomain(tempdir, app, status, warning):
load_mappings(app)
app.build()
- html = (app.outdir / 'index.html').text()
+ html = (app.outdir / 'index.html').read_text()
assert ('<a class="reference external"'
' href="https://docs.python.org/index.html#cpp_foo_bar"'
' title="(in foo v2.0)">'
diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py
index 256236489..b5de6cc1c 100644
--- a/tests/test_ext_math.py
+++ b/tests/test_ext_math.py
@@ -39,7 +39,7 @@ def test_imgmath_png(app, status, warning):
if "dvipng command 'dvipng' cannot be run" in warning.getvalue():
raise pytest.skip.Exception('dvipng command "dvipng" is not available')
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
html = (r'<div class="math">\s*<p>\s*<img src="_images/math/\w+.png"'
r'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
assert re.search(html, content, re.S)
@@ -57,7 +57,7 @@ def test_imgmath_svg(app, status, warning):
if "dvisvgm command 'dvisvgm' cannot be run" in warning.getvalue():
raise pytest.skip.Exception('dvisvgm command "dvisvgm" is not available')
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
html = (r'<div class="math">\s*<p>\s*<img src="_images/math/\w+.svg"'
r'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
assert re.search(html, content, re.S)
@@ -69,7 +69,7 @@ def test_imgmath_svg(app, status, warning):
def test_mathjax_options(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert ('<script async="async" integrity="sha384-0123456789" '
'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?'
'config=TeX-AMS-MML_HTMLorMML"></script>' in content)
@@ -80,7 +80,7 @@ def test_mathjax_options(app, status, warning):
def test_mathjax_align(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
html = (r'<div class="math notranslate nohighlight">\s*'
r'\\\[ \\begin\{align\}\\begin\{aligned\}S \&amp;= \\pi r\^2\\\\'
r'V \&amp;= \\frac\{4\}\{3\} \\pi r\^3\\end\{aligned\}\\end\{align\} \\\]</div>')
@@ -93,7 +93,7 @@ def test_mathjax_align(app, status, warning):
def test_math_number_all_mathjax(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
html = (r'<div class="math notranslate nohighlight" id="equation-index-0">\s*'
r'<span class="eqno">\(1\)<a .*>\xb6</a></span>\\\[a\^2\+b\^2=c\^2\\\]</div>')
assert re.search(html, content, re.S)
@@ -104,7 +104,7 @@ def test_math_number_all_mathjax(app, status, warning):
def test_math_number_all_latex(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'python.tex').text()
+ content = (app.outdir / 'python.tex').read_text()
macro = (r'\\begin{equation\*}\s*'
r'\\begin{split}a\^2\+b\^2=c\^2\\end{split}\s*'
r'\\end{equation\*}')
@@ -134,7 +134,7 @@ def test_math_number_all_latex(app, status, warning):
def test_math_eqref_format_html(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'math.html').text()
+ content = (app.outdir / 'math.html').read_text()
html = ('<p>Referencing equation <a class="reference internal" '
'href="#equation-foo">Eq.1</a> and <a class="reference internal" '
'href="#equation-foo">Eq.1</a>.</p>')
@@ -147,7 +147,7 @@ def test_math_eqref_format_html(app, status, warning):
def test_math_eqref_format_latex(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'python.tex').text()
+ content = (app.outdir / 'python.tex').read_text()
macro = (r'Referencing equation Eq.\\ref{equation:math:foo} and '
r'Eq.\\ref{equation:math:foo}.')
assert re.search(macro, content, re.S)
@@ -160,7 +160,7 @@ def test_math_eqref_format_latex(app, status, warning):
def test_mathjax_numfig_html(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'math.html').text()
+ content = (app.outdir / 'math.html').read_text()
html = ('<div class="math notranslate nohighlight" id="equation-math-0">\n'
'<span class="eqno">(1.2)')
assert html in content
@@ -178,7 +178,7 @@ def test_mathjax_numfig_html(app, status, warning):
def test_imgmath_numfig_html(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'page.html').text()
+ content = (app.outdir / 'page.html').read_text()
html = '<span class="eqno">(3)<a class="headerlink" href="#equation-bar"'
assert html in content
html = ('<p>Referencing equations <a class="reference internal" '
@@ -218,7 +218,7 @@ def test_math_compat(app, status, warning):
def test_mathjax_config(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert ('<script type="text/x-mathjax-config">'
'MathJax.Hub.Config({"extensions": ["tex2jax.js"]})'
'</script>' in content)
@@ -229,5 +229,5 @@ def test_mathjax_config(app, status, warning):
def test_mathjax_is_not_installed_if_no_equations(app, status, warning):
app.builder.build_all()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert 'MathJax.js' not in content
diff --git a/tests/test_ext_todo.py b/tests/test_ext_todo.py
index 8c00ef088..7b4fdeabe 100644
--- a/tests/test_ext_todo.py
+++ b/tests/test_ext_todo.py
@@ -29,7 +29,7 @@ def test_todo(app, status, warning):
app.builder.build_all()
# check todolist
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert ('<p class="admonition-title">Todo</p>\n'
'<p>todo in foo</p>') in content
@@ -37,7 +37,7 @@ def test_todo(app, status, warning):
'<p>todo in bar</p>') in content
# check todo
- content = (app.outdir / 'foo.html').text()
+ content = (app.outdir / 'foo.html').read_text()
assert ('<p class="admonition-title">Todo</p>\n'
'<p>todo in foo</p>') in content
@@ -67,7 +67,7 @@ def test_todo_not_included(app, status, warning):
app.builder.build_all()
# check todolist
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert ('<p class="admonition-title">Todo</p>\n'
'<p>todo in foo</p>') not in content
@@ -75,7 +75,7 @@ def test_todo_not_included(app, status, warning):
'<p>todo in bar</p>') not in content
# check todo
- content = (app.outdir / 'foo.html').text()
+ content = (app.outdir / 'foo.html').read_text()
assert ('<p class="admonition-title">Todo</p>\n'
'<p>todo in foo</p>') not in content
@@ -102,7 +102,7 @@ def test_todo_valid_link(app, status, warning):
# Ensure the LaTeX output is built.
app.builder.build_all()
- content = (app.outdir / 'python.tex').text()
+ content = (app.outdir / 'python.tex').read_text()
# Look for the link to foo. Note that there are two of them because the
# source document uses todolist twice. We could equally well look for links
diff --git a/tests/test_ext_viewcode.py b/tests/test_ext_viewcode.py
index 2540d09ea..3d9ea27d7 100644
--- a/tests/test_ext_viewcode.py
+++ b/tests/test_ext_viewcode.py
@@ -24,7 +24,7 @@ def test_viewcode(app, status, warning):
warnings
)
- result = (app.outdir / 'index.html').text()
+ result = (app.outdir / 'index.html').read_text()
assert result.count('href="_modules/spam/mod1.html#func1"') == 2
assert result.count('href="_modules/spam/mod2.html#func2"') == 2
assert result.count('href="_modules/spam/mod1.html#Class1"') == 2
@@ -37,7 +37,7 @@ def test_viewcode(app, status, warning):
# the next assert fails, until the autodoc bug gets fixed
assert result.count('this is the class attribute class_attr') == 2
- result = (app.outdir / '_modules/spam/mod1.html').text()
+ result = (app.outdir / '_modules/spam/mod1.html').read_text()
result = re.sub('<span class=".*?">', '<span>', result) # filter pygments classes
assert ('<div class="viewcode-block" id="Class1"><a class="viewcode-back" '
'href="../../index.html#spam.Class1">[docs]</a>'
@@ -53,7 +53,7 @@ def test_viewcode(app, status, warning):
def test_linkcode(app, status, warning):
app.builder.build(['objects'])
- stuff = (app.outdir / 'objects.html').text()
+ stuff = (app.outdir / 'objects.html').read_text()
assert 'http://foobar/source/foolib.py' in stuff
assert 'http://foobar/js/' in stuff
@@ -65,7 +65,7 @@ def test_linkcode(app, status, warning):
def test_local_source_files(app, status, warning):
def find_source(app, modname):
if modname == 'not_a_package':
- source = (app.srcdir / 'not_a_package/__init__.py').text()
+ source = (app.srcdir / 'not_a_package/__init__.py').read_text()
tags = {
'func1': ('def', 1, 1),
'Class1': ('class', 1, 1),
@@ -73,7 +73,7 @@ def test_local_source_files(app, status, warning):
'not_a_package.submodule.Class1': ('class', 1, 1),
}
else:
- source = (app.srcdir / 'not_a_package/submodule.py').text()
+ source = (app.srcdir / 'not_a_package/submodule.py').read_text()
tags = {
'not_a_package.submodule.func1': ('def', 11, 15),
'Class1': ('class', 19, 22),
@@ -93,7 +93,7 @@ def test_local_source_files(app, status, warning):
warnings
)
- result = (app.outdir / 'index.html').text()
+ result = (app.outdir / 'index.html').read_text()
assert result.count('href="_modules/not_a_package.html#func1"') == 1
assert result.count('href="_modules/not_a_package.html#not_a_package.submodule.func1"') == 1
assert result.count('href="_modules/not_a_package/submodule.html#Class1"') == 1
diff --git a/tests/test_highlighting.py b/tests/test_highlighting.py
index 525af2547..c2b470a6b 100644
--- a/tests/test_highlighting.py
+++ b/tests/test_highlighting.py
@@ -40,7 +40,7 @@ class ComplainOnUnhighlighted(PygmentsBridge):
def test_add_lexer(app, status, warning):
- app.add_lexer('test', MyLexer())
+ app.add_lexer('test', MyLexer)
bridge = PygmentsBridge('html')
ret = bridge.highlight_block('ab', 'test')
diff --git a/tests/test_intl.py b/tests/test_intl.py
index f038c2b28..0e7dd4f62 100644
--- a/tests/test_intl.py
+++ b/tests/test_intl.py
@@ -95,7 +95,7 @@ def assert_count(expected_expr, result, count):
@pytest.mark.test_params(shared_result='test_intl_basic')
def test_text_toctree(app):
app.build()
- result = (app.outdir / 'index.txt').text()
+ result = (app.outdir / 'index.txt').read_text()
assert_startswith(result, "CONTENTS\n********\n\nTABLE OF CONTENTS\n")
@@ -117,7 +117,7 @@ def test_text_emit_warnings(app, warning):
def test_text_warning_node(app):
app.build()
# test warnings in translation
- result = (app.outdir / 'warnings.txt').text()
+ result = (app.outdir / 'warnings.txt').read_text()
expect = ("3. I18N WITH REST WARNINGS"
"\n**************************\n"
"\nLINE OF >>``<<BROKEN LITERAL MARKUP.\n")
@@ -131,7 +131,7 @@ def test_text_warning_node(app):
def test_text_title_underline(app):
app.build()
# --- simple translation; check title underlines
- result = (app.outdir / 'bom.txt').text()
+ result = (app.outdir / 'bom.txt').read_text()
expect = ("2. Datei mit UTF-8"
"\n******************\n" # underline matches new translation
"\nThis file has umlauts: äöü.\n")
@@ -144,7 +144,7 @@ def test_text_title_underline(app):
def test_text_subdirs(app):
app.build()
# --- check translation in subdirs
- result = (app.outdir / 'subdir' / 'index.txt').text()
+ result = (app.outdir / 'subdir' / 'index.txt').read_text()
assert_startswith(result, "1. subdir contents\n******************\n")
@@ -154,7 +154,7 @@ def test_text_subdirs(app):
def test_text_inconsistency_warnings(app, warning):
app.build()
# --- check warnings for inconsistency in number of references
- result = (app.outdir / 'refs_inconsistency.txt').text()
+ result = (app.outdir / 'refs_inconsistency.txt').read_text()
expect = ("8. I18N WITH REFS INCONSISTENCY"
"\n*******************************\n"
"\n* FOR CITATION [ref3].\n"
@@ -204,7 +204,7 @@ def test_text_inconsistency_warnings(app, warning):
def test_text_literalblock_warnings(app, warning):
app.build()
# --- check warning for literal block
- result = (app.outdir / 'literalblock.txt').text()
+ result = (app.outdir / 'literalblock.txt').read_text()
expect = ("9. I18N WITH LITERAL BLOCK"
"\n**************************\n"
"\nCORRECT LITERAL BLOCK:\n"
@@ -226,7 +226,7 @@ def test_text_literalblock_warnings(app, warning):
def test_text_definition_terms(app):
app.build()
# --- definition terms: regression test for #975, #2198, #2205
- result = (app.outdir / 'definition_terms.txt').text()
+ result = (app.outdir / 'definition_terms.txt').read_text()
expect = ("13. I18N WITH DEFINITION TERMS"
"\n******************************\n"
"\nSOME TERM"
@@ -246,7 +246,7 @@ def test_text_definition_terms(app):
def test_text_glossary_term(app, warning):
app.build()
# --- glossary terms: regression test for #1090
- result = (app.outdir / 'glossary_terms.txt').text()
+ result = (app.outdir / 'glossary_terms.txt').read_text()
expect = ("18. I18N WITH GLOSSARY TERMS"
"\n****************************\n"
"\nSOME NEW TERM"
@@ -265,7 +265,7 @@ def test_text_glossary_term(app, warning):
def test_text_glossary_term_inconsistencies(app, warning):
app.build()
# --- glossary term inconsistencies: regression test for #1090
- result = (app.outdir / 'glossary_terms_inconsistency.txt').text()
+ result = (app.outdir / 'glossary_terms_inconsistency.txt').read_text()
expect = ("19. I18N WITH GLOSSARY TERMS INCONSISTENCY"
"\n******************************************\n"
"\n1. LINK TO *SOME NEW TERM*.\n")
@@ -298,7 +298,7 @@ def test_gettext_section(app):
def test_text_section(app):
app.build()
# --- section
- result = (app.outdir / 'section.txt').text()
+ result = (app.outdir / 'section.txt').read_text()
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'section.po')
for expect_msg in [m for m in expect if m.id]:
assert expect_msg.string in result
@@ -310,7 +310,7 @@ def test_text_section(app):
def test_text_seealso(app):
app.build()
# --- seealso
- result = (app.outdir / 'seealso.txt').text()
+ result = (app.outdir / 'seealso.txt').read_text()
expect = ("12. I18N WITH SEEALSO"
"\n*********************\n"
"\nSee also: SHORT TEXT 1\n"
@@ -327,7 +327,7 @@ def test_text_seealso(app):
def test_text_figure_captions(app):
app.build()
# --- figure captions: regression test for #940
- result = (app.outdir / 'figure.txt').text()
+ result = (app.outdir / 'figure.txt').read_text()
expect = ("14. I18N WITH FIGURE CAPTION"
"\n****************************\n"
"\n [image]MY CAPTION OF THE FIGURE\n"
@@ -371,7 +371,7 @@ def test_text_figure_captions(app):
def test_text_rubric(app):
app.build()
# --- rubric: regression test for pull request #190
- result = (app.outdir / 'rubric.txt').text()
+ result = (app.outdir / 'rubric.txt').read_text()
expect = ("I18N WITH RUBRIC"
"\n****************\n"
"\n-[ RUBRIC TITLE ]-\n"
@@ -389,7 +389,7 @@ def test_text_rubric(app):
def test_text_docfields(app):
app.build()
# --- docfields
- result = (app.outdir / 'docfields.txt').text()
+ result = (app.outdir / 'docfields.txt').read_text()
expect = ("21. I18N WITH DOCFIELDS"
"\n***********************\n"
"\nclass Cls1\n"
@@ -420,7 +420,7 @@ def test_text_admonitions(app):
# --- admonitions
# #1206: gettext did not translate admonition directive's title
# seealso: http://docutils.sourceforge.net/docs/ref/rst/directives.html#admonitions
- result = (app.outdir / 'admonitions.txt').text()
+ result = (app.outdir / 'admonitions.txt').read_text()
directives = (
"attention", "caution", "danger", "error", "hint",
"important", "note", "tip", "warning", "admonition")
@@ -462,7 +462,7 @@ def test_gettext_table(app):
def test_text_table(app):
app.build()
# --- toctree
- result = (app.outdir / 'table.txt').text()
+ result = (app.outdir / 'table.txt').read_text()
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'table.po')
for expect_msg in [m for m in expect if m.id]:
assert expect_msg.string in result
@@ -486,7 +486,7 @@ def test_gettext_toctree(app):
def test_text_toctree(app):
app.build()
# --- toctree
- result = (app.outdir / 'toctree.txt').text()
+ result = (app.outdir / 'toctree.txt').read_text()
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
for expect_msg in [m for m in expect if m.id]:
assert expect_msg.string in result
@@ -510,7 +510,7 @@ def test_gettext_topic(app):
def test_text_topic(app):
app.build()
# --- topic
- result = (app.outdir / 'topic.txt').text()
+ result = (app.outdir / 'topic.txt').read_text()
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'topic.po')
for expect_msg in [m for m in expect if m.id]:
assert expect_msg.string in result
@@ -628,7 +628,7 @@ def test_gettext_dont_rebuild_mo(make_app, app_params):
def test_html_meta(app):
app.build()
# --- test for meta
- result = (app.outdir / 'index.html').text()
+ result = (app.outdir / 'index.html').read_text()
expected_expr = '<meta content="TESTDATA FOR I18N" name="description" />'
assert expected_expr in result
expected_expr = '<meta content="I18N, SPHINX, MARKUP" name="keywords" />'
@@ -644,7 +644,7 @@ def test_html_footnotes(app):
app.build()
# --- test for #955 cant-build-html-with-footnotes-when-using
# expect no error by build
- (app.outdir / 'footnote.html').text()
+ (app.outdir / 'footnote.html').read_text()
@sphinx_intl
@@ -653,7 +653,7 @@ def test_html_footnotes(app):
def test_html_undefined_refs(app):
app.build()
# --- links to undefined reference
- result = (app.outdir / 'refs_inconsistency.html').text()
+ result = (app.outdir / 'refs_inconsistency.html').read_text()
expected_expr = ('<a class="reference external" '
'href="http://www.example.com">reference</a>')
@@ -675,7 +675,7 @@ def test_html_undefined_refs(app):
def test_html_index_entries(app):
app.build()
# --- index entries: regression test for #976
- result = (app.outdir / 'genindex.html').text()
+ result = (app.outdir / 'genindex.html').read_text()
def wrap(tag, keyword):
start_tag = "<%s[^>]*>" % tag
@@ -713,7 +713,7 @@ def test_html_index_entries(app):
def test_html_versionchanges(app):
app.build()
# --- versionchanges
- result = (app.outdir / 'versionchange.html').text()
+ result = (app.outdir / 'versionchange.html').read_text()
def get_content(result, name):
matched = re.search(r'<div class="%s">\n*(.*?)</div>' % name,
@@ -750,7 +750,7 @@ def test_html_docfields(app):
app.build()
# --- docfields
# expect no error by build
- (app.outdir / 'docfields.html').text()
+ (app.outdir / 'docfields.html').read_text()
@sphinx_intl
@@ -759,7 +759,7 @@ def test_html_docfields(app):
def test_html_template(app):
app.build()
# --- gettext template
- result = (app.outdir / 'contents.html').text()
+ result = (app.outdir / 'contents.html').read_text()
assert "WELCOME" in result
assert "SPHINX 2013.120" in result
@@ -1056,7 +1056,7 @@ def test_xml_label_targets(app):
def test_additional_targets_should_not_be_translated(app):
app.build()
# [literalblock.txt]
- result = (app.outdir / 'literalblock.html').text()
+ result = (app.outdir / 'literalblock.html').read_text()
# title should be translated
expected_expr = 'CODE-BLOCKS'
@@ -1092,7 +1092,7 @@ def test_additional_targets_should_not_be_translated(app):
# [raw.txt]
- result = (app.outdir / 'raw.html').text()
+ result = (app.outdir / 'raw.html').read_text()
# raw block should not be translated
expected_expr = """<iframe src="http://sphinx-doc.org"></iframe></div>"""
@@ -1100,7 +1100,7 @@ def test_additional_targets_should_not_be_translated(app):
# [figure.txt]
- result = (app.outdir / 'figure.html').text()
+ 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" />"""
@@ -1130,7 +1130,7 @@ def test_additional_targets_should_not_be_translated(app):
def test_additional_targets_should_be_translated(app):
app.build()
# [literalblock.txt]
- result = (app.outdir / 'literalblock.html').text()
+ result = (app.outdir / 'literalblock.html').read_text()
# title should be translated
expected_expr = 'CODE-BLOCKS'
@@ -1166,7 +1166,7 @@ def test_additional_targets_should_be_translated(app):
# [raw.txt]
- result = (app.outdir / 'raw.html').text()
+ result = (app.outdir / 'raw.html').read_text()
# raw block should be translated
expected_expr = """<iframe src="HTTP://SPHINX-DOC.ORG"></iframe></div>"""
@@ -1174,7 +1174,7 @@ def test_additional_targets_should_be_translated(app):
# [figure.txt]
- result = (app.outdir / 'figure.html').text()
+ result = (app.outdir / 'figure.html').read_text()
# alt and src for image block should be translated
expected_expr = """<img alt="I18N -&gt; IMG" src="_images/img.png" />"""
diff --git a/tests/test_pycode_ast.py b/tests/test_pycode_ast.py
index af7e34a86..d195e5c6f 100644
--- a/tests/test_pycode_ast.py
+++ b/tests/test_pycode_ast.py
@@ -8,6 +8,8 @@
:license: BSD, see LICENSE for details.
"""
+import sys
+
import pytest
from sphinx.pycode import ast
@@ -23,7 +25,7 @@ from sphinx.pycode import ast
("...", "..."), # Ellipsis
("Tuple[int, int]", "Tuple[int, int]"), # Index, Subscript
("lambda x, y: x + y",
- "<function <lambda>>"), # Lambda
+ "lambda x, y: ..."), # Lambda
("[1, 2, 3]", "[1, 2, 3]"), # List
("sys", "sys"), # Name, NameConstant
("1234", "1234"), # Num
@@ -38,3 +40,11 @@ def test_unparse(source, expected):
def test_unparse_None():
assert ast.unparse(None) is None
+
+
+@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
+def test_unparse_py38():
+ source = "lambda x=0, /, y=1, *args, z, **kwargs: x + y + z"
+ expected = "lambda x=0, /, y=1, *args, z, **kwargs: ..."
+ module = ast.parse(source)
+ assert ast.unparse(module.body[0].value) == expected
diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py
index 70a6f030e..bdd7073d1 100644
--- a/tests/test_quickstart.py
+++ b/tests/test_quickstart.py
@@ -193,7 +193,7 @@ def test_generated_files_eol(tempdir):
qs.generate(d)
def assert_eol(filename, eol):
- content = filename.bytes().decode()
+ content = filename.read_bytes().decode()
assert all([l[-len(eol):] == eol for l in content.splitlines(True)])
assert_eol(tempdir / 'make.bat', '\r\n')
diff --git a/tests/test_search.py b/tests/test_search.py
index 802602d5f..a4cefbc67 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -41,7 +41,7 @@ def setup_module():
def jsload(path):
- searchindex = path.text()
+ searchindex = path.read_text()
assert searchindex.startswith('Search.setIndex(')
assert searchindex.endswith(')')
@@ -98,7 +98,7 @@ def test_meta_keys_are_handled_for_language_de(app, status, warning):
@pytest.mark.sphinx(testroot='search')
def test_stemmer_does_not_remove_short_words(app, status, warning):
app.builder.build_all()
- searchindex = (app.outdir / 'searchindex.js').text()
+ searchindex = (app.outdir / 'searchindex.js').read_text()
assert 'zfs' in searchindex
@@ -112,11 +112,11 @@ def test_stemmer(app, status, warning):
@pytest.mark.sphinx(testroot='search')
def test_term_in_heading_and_section(app, status, warning):
- searchindex = (app.outdir / 'searchindex.js').text()
+ searchindex = (app.outdir / 'searchindex.js').read_text()
# if search term is in the title of one doc and in the text of another
# both documents should be a hit in the search index as a title,
# respectively text hit
- assert 'textinhead:1' in searchindex
+ assert 'textinhead:2' in searchindex
assert 'textinhead:0' in searchindex
@@ -247,8 +247,18 @@ def test_IndexBuilder_lookup():
def test_search_index_gen_zh(app, status, warning):
app.builder.build_all()
# jsdump fails if search language is 'zh'; hence we just get the text:
- searchindex = (app.outdir / 'searchindex.js').text()
+ searchindex = (app.outdir / 'searchindex.js').read_text()
assert 'chinesetest ' not in searchindex
assert 'chinesetest' in searchindex
assert 'chinesetesttwo' in searchindex
assert 'cas' in searchindex
+
+
+@pytest.mark.sphinx(testroot='search')
+def test_nosearch(app):
+ app.build()
+ index = jsload(app.outdir / 'searchindex.js')
+ 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
diff --git a/tests/test_setup_command.py b/tests/test_setup_command.py
index 9bfc08d06..14a687ada 100644
--- a/tests/test_setup_command.py
+++ b/tests/test_setup_command.py
@@ -94,7 +94,7 @@ def nonascii_srcdir(request, setup_command):
"""))
master_doc = srcdir / 'index.txt'
- master_doc.write_bytes((master_doc.text() + dedent("""
+ master_doc.write_bytes((master_doc.read_text() + dedent("""
.. toctree::
%(mb_name)s/%(mb_name)s
diff --git a/tests/test_smartquotes.py b/tests/test_smartquotes.py
index 50a1cf52e..6276e6a74 100644
--- a/tests/test_smartquotes.py
+++ b/tests/test_smartquotes.py
@@ -17,7 +17,7 @@ from sphinx.util import docutils
def test_basic(app, status, warning):
app.build()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert '<p>– “Sphinx” is a tool that makes it easy …</p>' in content
@@ -25,7 +25,7 @@ def test_basic(app, status, warning):
def test_text_builder(app, status, warning):
app.build()
- content = (app.outdir / 'index.txt').text()
+ content = (app.outdir / 'index.txt').read_text()
assert '-- "Sphinx" is a tool that makes it easy ...' in content
@@ -33,7 +33,7 @@ def test_text_builder(app, status, warning):
def test_man_builder(app, status, warning):
app.build()
- content = (app.outdir / 'python.1').text()
+ content = (app.outdir / 'python.1').read_text()
assert '\\-\\- "Sphinx" is a tool that makes it easy ...' in content
@@ -41,7 +41,7 @@ def test_man_builder(app, status, warning):
def test_latex_builder(app, status, warning):
app.build()
- content = (app.outdir / 'python.tex').text()
+ content = (app.outdir / 'python.tex').read_text()
assert '\\textendash{} “Sphinx” is a tool that makes it easy …' in content
@@ -50,7 +50,7 @@ def test_latex_builder(app, status, warning):
def test_ja_html_builder(app, status, warning):
app.build()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert '<p>-- &quot;Sphinx&quot; is a tool that makes it easy ...</p>' in content
@@ -59,7 +59,7 @@ def test_ja_html_builder(app, status, warning):
def test_smartquotes_disabled(app, status, warning):
app.build()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert '<p>-- &quot;Sphinx&quot; is a tool that makes it easy ...</p>' in content
@@ -70,7 +70,7 @@ def test_smartquotes_disabled(app, status, warning):
def test_smartquotes_action(app, status, warning):
app.build()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert '<p>-- “Sphinx” is a tool that makes it easy ...</p>' in content
@@ -79,7 +79,7 @@ def test_smartquotes_action(app, status, warning):
def test_smartquotes_excludes_language(app, status, warning):
app.build()
- content = (app.outdir / 'index.html').text()
+ content = (app.outdir / 'index.html').read_text()
assert '<p>– 「Sphinx」 is a tool that makes it easy …</p>' in content
@@ -88,5 +88,5 @@ def test_smartquotes_excludes_language(app, status, warning):
def test_smartquotes_excludes_builders(app, status, warning):
app.build()
- content = (app.outdir / 'python.1').text()
+ content = (app.outdir / 'python.1').read_text()
assert '– “Sphinx” is a tool that makes it easy …' in content
diff --git a/tests/test_templating.py b/tests/test_templating.py
index 0be960b88..becacda0d 100644
--- a/tests/test_templating.py
+++ b/tests/test_templating.py
@@ -20,7 +20,7 @@ def test_layout_overloading(make_app, app_params):
setup_documenters(app)
app.builder.build_update()
- result = (app.outdir / 'index.html').text()
+ result = (app.outdir / 'index.html').read_text()
assert '<!-- layout overloading -->' in result
@@ -31,5 +31,5 @@ def test_autosummary_class_template_overloading(make_app, app_params):
setup_documenters(app)
app.builder.build_update()
- result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').text()
+ result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').read_text()
assert 'autosummary/class.rst method block overloading' in result
diff --git a/tests/test_theming.py b/tests/test_theming.py
index d154d8a4c..7854cbd00 100644
--- a/tests/test_theming.py
+++ b/tests/test_theming.py
@@ -76,16 +76,16 @@ def test_js_source(app, status, warning):
v = '3.4.1'
msg = 'jquery.js version does not match to {v}'.format(v=v)
- jquery_min = (app.outdir / '_static' / 'jquery.js').text()
+ jquery_min = (app.outdir / '_static' / 'jquery.js').read_text()
assert 'jQuery v{v}'.format(v=v) in jquery_min, msg
- jquery_src = (app.outdir / '_static' / 'jquery-{v}.js'.format(v=v)).text()
+ jquery_src = (app.outdir / '_static' / 'jquery-{v}.js'.format(v=v)).read_text()
assert 'jQuery JavaScript Library v{v}'.format(v=v) in jquery_src, msg
v = '1.3.1'
msg = 'underscore.js version does not match to {v}'.format(v=v)
- underscore_min = (app.outdir / '_static' / 'underscore.js').text()
+ underscore_min = (app.outdir / '_static' / 'underscore.js').read_text()
assert 'Underscore.js {v}'.format(v=v) in underscore_min, msg
- underscore_src = (app.outdir / '_static' / 'underscore-{v}.js'.format(v=v)).text()
+ underscore_src = (app.outdir / '_static' / 'underscore-{v}.js'.format(v=v)).read_text()
assert 'Underscore.js {v}'.format(v=v) in underscore_src, msg
@@ -108,12 +108,12 @@ def test_staticfiles(app, status, warning):
app.build()
assert (app.outdir / '_static' / 'staticimg.png').exists()
assert (app.outdir / '_static' / 'statictmpl.html').exists()
- assert (app.outdir / '_static' / 'statictmpl.html').text() == (
+ assert (app.outdir / '_static' / 'statictmpl.html').read_text() == (
'<!-- testing static templates -->\n'
'<html><project>Python</project></html>'
)
- result = (app.outdir / 'index.html').text()
+ result = (app.outdir / 'index.html').read_text()
assert '<meta name="testopt" content="optdefault" />' in result
@@ -122,7 +122,7 @@ def test_theme_sidebars(app, status, warning):
app.build()
# test-theme specifies globaltoc and searchbox as default sidebars
- result = (app.outdir / 'index.html').text(encoding='utf8')
+ result = (app.outdir / 'index.html').read_text()
assert '<h3><a href="#">Table of Contents</a></h3>' in result
assert '<h3>Related Topics</h3>' not in result
assert '<h3>This Page</h3>' not in result
diff --git a/tests/test_toctree.py b/tests/test_toctree.py
index c4d0c9b7b..38886769a 100644
--- a/tests/test_toctree.py
+++ b/tests/test_toctree.py
@@ -41,7 +41,7 @@ def test_singlehtml_toctree(app, status, warning):
@pytest.mark.sphinx(testroot='toctree', srcdir="numbered-toctree")
def test_numbered_toctree(app, status, warning):
# give argument to :numbered: option
- index = (app.srcdir / 'index.rst').text()
+ index = (app.srcdir / 'index.rst').read_text()
index = re.sub(':numbered:.*', ':numbered: 1', index)
(app.srcdir / 'index.rst').write_text(index)
app.builder.build_all()
diff --git a/tests/test_transforms_post_transforms_code.py b/tests/test_transforms_post_transforms_code.py
index bd66a4c91..880f4711b 100644
--- a/tests/test_transforms_post_transforms_code.py
+++ b/tests/test_transforms_post_transforms_code.py
@@ -13,7 +13,7 @@ import pytest
def test_trim_doctest_flags_html(app, status, warning):
app.build()
- result = (app.outdir / 'index.html').text(encoding='utf8')
+ result = (app.outdir / 'index.html').read_text()
assert 'FOO' not in result
assert 'BAR' in result
assert 'BAZ' not in result
@@ -25,7 +25,7 @@ def test_trim_doctest_flags_html(app, status, warning):
def test_trim_doctest_flags_latex(app, status, warning):
app.build()
- result = (app.outdir / 'python.tex').text(encoding='utf8')
+ result = (app.outdir / 'python.tex').read_text()
assert 'FOO' not in result
assert 'BAR' in result
assert 'BAZ' not in result
diff --git a/tests/test_util.py b/tests/test_util.py
index f794c4f74..434d96d3a 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -18,7 +18,7 @@ import sphinx
from sphinx.errors import ExtensionError, PycodeError
from sphinx.testing.util import strip_escseq
from sphinx.util import (
- SkipProgressMessage, display_chunk, encode_uri, ensuredir, get_module_source,
+ SkipProgressMessage, display_chunk, encode_uri, ensuredir,
import_object, parselinenos, progress_message, status_iterator, xmlname_checker
)
from sphinx.util import logging
@@ -61,16 +61,6 @@ def test_display_chunk():
assert display_chunk(('hello', 'sphinx', 'world')) == 'hello .. world'
-def test_get_module_source():
- assert get_module_source('sphinx') == ('file', sphinx.__file__)
-
- # failed to obtain source information from builtin modules
- with pytest.raises(PycodeError):
- get_module_source('builtins')
- with pytest.raises(PycodeError):
- get_module_source('itertools')
-
-
def test_import_object():
module = import_object('sphinx')
assert module.__name__ == 'sphinx'
diff --git a/tests/test_util_docstrings.py b/tests/test_util_docstrings.py
index bfd5b58b4..2f0901d06 100644
--- a/tests/test_util_docstrings.py
+++ b/tests/test_util_docstrings.py
@@ -8,7 +8,34 @@
:license: BSD, see LICENSE for details.
"""
-from sphinx.util.docstrings import prepare_docstring, prepare_commentdoc
+from sphinx.util.docstrings import (
+ extract_metadata, prepare_docstring, prepare_commentdoc
+)
+
+
+def test_extract_metadata():
+ metadata = extract_metadata(":meta foo: bar\n"
+ ":meta baz:\n")
+ assert metadata == {'foo': 'bar', 'baz': ''}
+
+ # field_list like text following just after paragaph is not a field_list
+ metadata = extract_metadata("blah blah blah\n"
+ ":meta foo: bar\n"
+ ":meta baz:\n")
+ assert metadata == {}
+
+ # field_list like text following after blank line is a field_list
+ metadata = extract_metadata("blah blah blah\n"
+ "\n"
+ ":meta foo: bar\n"
+ ":meta baz:\n")
+ assert metadata == {'foo': 'bar', 'baz': ''}
+
+ # non field_list item breaks field_list
+ metadata = extract_metadata(":meta foo: bar\n"
+ "blah blah blah\n"
+ ":meta baz:\n")
+ assert metadata == {'foo': 'bar'}
def test_prepare_docstring():
diff --git a/tests/test_util_fileutil.py b/tests/test_util_fileutil.py
index 02d1acada..e9c80d5b6 100644
--- a/tests/test_util_fileutil.py
+++ b/tests/test_util_fileutil.py
@@ -33,7 +33,7 @@ def test_copy_asset_file(tempdir):
copy_asset_file(src, dest)
assert dest.exists()
- assert src.text() == dest.text()
+ assert src.read_text() == dest.read_text()
# copy template file
src = (tempdir / 'asset.txt_t')
@@ -43,7 +43,7 @@ def test_copy_asset_file(tempdir):
copy_asset_file(src, dest, {'var1': 'template'}, renderer)
assert not dest.exists()
assert (tempdir / 'output.txt').exists()
- assert (tempdir / 'output.txt').text() == '# template data'
+ assert (tempdir / 'output.txt').read_text() == '# template data'
# copy template file to subdir
src = (tempdir / 'asset.txt_t')
@@ -53,7 +53,7 @@ def test_copy_asset_file(tempdir):
copy_asset_file(src, subdir1, {'var1': 'template'}, renderer)
assert (subdir1 / 'asset.txt').exists()
- assert (subdir1 / 'asset.txt').text() == '# template data'
+ assert (subdir1 / 'asset.txt').read_text() == '# template data'
# copy template file without context
src = (tempdir / 'asset.txt_t')
@@ -63,7 +63,7 @@ def test_copy_asset_file(tempdir):
copy_asset_file(src, subdir2)
assert not (subdir2 / 'asset.txt').exists()
assert (subdir2 / 'asset.txt_t').exists()
- assert (subdir2 / 'asset.txt_t').text() == '# {{var1}} data'
+ assert (subdir2 / 'asset.txt_t').read_text() == '# {{var1}} data'
def test_copy_asset(tempdir):
@@ -91,11 +91,11 @@ def test_copy_asset(tempdir):
copy_asset(source, destdir, context=dict(var1='bar', var2='baz'), renderer=renderer)
assert (destdir / 'index.rst').exists()
assert (destdir / 'foo.rst').exists()
- assert (destdir / 'foo.rst').text() == 'bar.rst'
+ assert (destdir / 'foo.rst').read_text() == 'bar.rst'
assert (destdir / '_static' / 'basic.css').exists()
assert (destdir / '_templates' / 'layout.html').exists()
assert (destdir / '_templates' / 'sidebar.html').exists()
- assert (destdir / '_templates' / 'sidebar.html').text() == 'sidebar: baz'
+ assert (destdir / '_templates' / 'sidebar.html').read_text() == 'sidebar: baz'
# copy with exclusion
def excluded(path):
diff --git a/tests/test_util_images.py b/tests/test_util_images.py
index da492fd05..2a256577f 100644
--- a/tests/test_util_images.py
+++ b/tests/test_util_images.py
@@ -20,20 +20,15 @@ PDF_FILENAME = 'img.pdf'
TXT_FILENAME = 'index.txt'
-@pytest.fixture(scope='module')
-def testroot(rootdir):
- return rootdir / 'test-root'
-
-
-def test_get_image_size(testroot):
- assert get_image_size(testroot / GIF_FILENAME) == (200, 181)
- assert get_image_size(testroot / PNG_FILENAME) == (200, 181)
- assert get_image_size(testroot / PDF_FILENAME) is None
- assert get_image_size(testroot / TXT_FILENAME) is None
+def test_get_image_size(rootdir):
+ assert get_image_size(rootdir / 'test-root' / GIF_FILENAME) == (200, 181)
+ assert get_image_size(rootdir / 'test-root' / PNG_FILENAME) == (200, 181)
+ assert get_image_size(rootdir / 'test-root' / PDF_FILENAME) is None
+ assert get_image_size(rootdir / 'test-root' / TXT_FILENAME) is None
@pytest.mark.filterwarnings('ignore:The content argument')
-def test_guess_mimetype(testroot):
+def test_guess_mimetype():
# guess by filename
assert guess_mimetype('img.png') == 'image/png'
assert guess_mimetype('img.jpg') == 'image/jpeg'
@@ -42,24 +37,9 @@ def test_guess_mimetype(testroot):
assert guess_mimetype('no_extension') is None
assert guess_mimetype('IMG.PNG') == 'image/png'
- # guess by content
- assert guess_mimetype(content=(testroot / GIF_FILENAME).bytes()) == 'image/gif'
- assert guess_mimetype(content=(testroot / PNG_FILENAME).bytes()) == 'image/png'
- assert guess_mimetype(content=(testroot / PDF_FILENAME).bytes()) is None
- assert guess_mimetype(content=(testroot / TXT_FILENAME).bytes()) is None
- assert guess_mimetype(content=(testroot / TXT_FILENAME).bytes(),
- default='text/plain') == 'text/plain'
-
- # the priority of params: filename > content > default
- assert guess_mimetype('img.png',
- content=(testroot / GIF_FILENAME).bytes(),
- default='text/plain') == 'image/png'
- assert guess_mimetype('no_extension',
- content=(testroot / GIF_FILENAME).bytes(),
- default='text/plain') == 'image/gif'
- assert guess_mimetype('no_extension',
- content=(testroot / TXT_FILENAME).bytes(),
- default='text/plain') == 'text/plain'
+ # default parameter is used when no extension
+ assert guess_mimetype('img.png', 'text/plain') == 'image/png'
+ assert guess_mimetype('no_extension', 'text/plain') == 'text/plain'
def test_get_image_extension():
diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py
index a8019a9c3..627d20f54 100644
--- a/tests/test_util_inspect.py
+++ b/tests/test_util_inspect.py
@@ -13,6 +13,7 @@ import datetime
import functools
import sys
import types
+from inspect import Parameter
import pytest
@@ -20,76 +21,6 @@ from sphinx.util import inspect
from sphinx.util.inspect import stringify_signature
-def test_getargspec():
- def func(a, b, c=1, d=2, *e, **f):
- pass
-
- spec = inspect.getargspec(func)
- assert spec.args == ['a', 'b', 'c', 'd']
- assert spec.varargs == 'e'
- assert spec.varkw == 'f'
- assert spec.defaults == (1, 2)
- assert spec.kwonlyargs == []
- assert spec.kwonlydefaults is None
- assert spec.annotations == {}
-
-
-def test_getargspec_partial():
- def func1(a, b, c=1, d=2, *e, **f):
- pass
-
- partial = functools.partial(func1, 10, c=11)
- spec = inspect.getargspec(partial)
- assert spec.args == ['b']
- assert spec.varargs is None
- assert spec.varkw == 'f'
- assert spec.defaults is None
- assert spec.kwonlyargs == ['c', 'd']
- assert spec.kwonlydefaults == {'c': 11, 'd': 2}
- assert spec.annotations == {}
-
-
-def test_getargspec_partial2():
- def fun(a, b, c=1, d=2):
- pass
- p = functools.partial(fun, 10, c=11)
-
- def f_expected(b, *, c=11, d=2):
- pass
- expected = inspect.getargspec(f_expected)
-
- assert expected == inspect.getargspec(p)
-
-
-def test_getargspec_builtin_type():
- with pytest.raises(TypeError):
- inspect.getargspec(int)
-
-
-def test_getargspec_bound_methods():
- def f_expected_unbound(self, arg1, **kwargs):
- pass
- expected_unbound = inspect.getargspec(f_expected_unbound)
-
- def f_expected_bound(arg1, **kwargs):
- pass
- expected_bound = inspect.getargspec(f_expected_bound)
-
- class Foo:
- def method(self, arg1, **kwargs):
- pass
-
- bound_method = Foo().method
-
- @functools.wraps(bound_method)
- def wrapped_bound_method(*args, **kwargs):
- pass
-
- assert expected_unbound == inspect.getargspec(Foo.method)
- assert expected_bound == inspect.getargspec(bound_method)
- assert expected_bound == inspect.getargspec(wrapped_bound_method)
-
-
def test_signature():
# literals
with pytest.raises(TypeError):
@@ -316,6 +247,90 @@ def test_signature_annotations_py38(app):
assert stringify_signature(sig) == '(a, b, /)'
+def test_signature_from_str_basic():
+ signature = '(a, b, *args, c=0, d="blah", **kwargs)'
+ sig = inspect.signature_from_str(signature)
+ assert list(sig.parameters.keys()) == ['a', 'b', 'args', 'c', 'd', 'kwargs']
+ assert sig.parameters['a'].name == 'a'
+ assert sig.parameters['a'].kind == Parameter.POSITIONAL_OR_KEYWORD
+ assert sig.parameters['a'].default == Parameter.empty
+ assert sig.parameters['a'].annotation == Parameter.empty
+ assert sig.parameters['b'].name == 'b'
+ assert sig.parameters['b'].kind == Parameter.POSITIONAL_OR_KEYWORD
+ assert sig.parameters['b'].default == Parameter.empty
+ assert sig.parameters['b'].annotation == Parameter.empty
+ assert sig.parameters['args'].name == 'args'
+ assert sig.parameters['args'].kind == Parameter.VAR_POSITIONAL
+ assert sig.parameters['args'].default == Parameter.empty
+ assert sig.parameters['args'].annotation == Parameter.empty
+ assert sig.parameters['c'].name == 'c'
+ assert sig.parameters['c'].kind == Parameter.KEYWORD_ONLY
+ assert sig.parameters['c'].default == '0'
+ assert sig.parameters['c'].annotation == Parameter.empty
+ assert sig.parameters['d'].name == 'd'
+ assert sig.parameters['d'].kind == Parameter.KEYWORD_ONLY
+ assert sig.parameters['d'].default == "'blah'"
+ assert sig.parameters['d'].annotation == Parameter.empty
+ assert sig.parameters['kwargs'].name == 'kwargs'
+ assert sig.parameters['kwargs'].kind == Parameter.VAR_KEYWORD
+ assert sig.parameters['kwargs'].default == Parameter.empty
+ assert sig.parameters['kwargs'].annotation == Parameter.empty
+ assert sig.return_annotation == Parameter.empty
+
+
+def test_signature_from_str_default_values():
+ signature = ('(a=0, b=0.0, c="str", d=b"bytes", e=..., f=True, '
+ 'g=[1, 2, 3], h={"a": 1}, i={1, 2, 3}, '
+ 'j=lambda x, y: None, k=None, l=object(), m=foo.bar.CONSTANT)')
+ sig = inspect.signature_from_str(signature)
+ assert sig.parameters['a'].default == '0'
+ assert sig.parameters['b'].default == '0.0'
+ assert sig.parameters['c'].default == "'str'"
+ assert sig.parameters['d'].default == "b'bytes'"
+ assert sig.parameters['e'].default == '...'
+ assert sig.parameters['f'].default == 'True'
+ assert sig.parameters['g'].default == '[1, 2, 3]'
+ assert sig.parameters['h'].default == "{'a': 1}"
+ assert sig.parameters['i'].default == '{1, 2, 3}'
+ assert sig.parameters['j'].default == 'lambda x, y: ...'
+ assert sig.parameters['k'].default == 'None'
+ assert sig.parameters['l'].default == 'object()'
+ assert sig.parameters['m'].default == 'foo.bar.CONSTANT'
+
+
+def test_signature_from_str_annotations():
+ signature = '(a: int, *args: bytes, b: str = "blah", **kwargs: float) -> None'
+ sig = inspect.signature_from_str(signature)
+ assert list(sig.parameters.keys()) == ['a', 'args', 'b', 'kwargs']
+ assert sig.parameters['a'].annotation == "int"
+ assert sig.parameters['args'].annotation == "bytes"
+ assert sig.parameters['b'].annotation == "str"
+ assert sig.parameters['kwargs'].annotation == "float"
+ assert sig.return_annotation == 'None'
+
+
+def test_signature_from_str_complex_annotations():
+ sig = inspect.signature_from_str('() -> Tuple[str, int, ...]')
+ assert sig.return_annotation == 'Tuple[str, int, ...]'
+
+ sig = inspect.signature_from_str('() -> Callable[[int, int], int]')
+ assert sig.return_annotation == 'Callable[[int, int], int]'
+
+
+@pytest.mark.skipif(sys.version_info < (3, 8),
+ reason='python-3.8 or above is required')
+def test_signature_from_str_positionaly_only_args():
+ sig = inspect.signature_from_str('(a, /, b)')
+ assert list(sig.parameters.keys()) == ['a', 'b']
+ assert sig.parameters['a'].kind == Parameter.POSITIONAL_ONLY
+ assert sig.parameters['b'].kind == Parameter.POSITIONAL_OR_KEYWORD
+
+
+def test_signature_from_str_invalid():
+ with pytest.raises(SyntaxError):
+ inspect.signature_from_str('')
+
+
def test_safe_getattr_with_default():
class Foo:
def __getattr__(self, item):
diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py
index 9a225f0f1..f6fd35fb0 100644
--- a/tests/test_util_typing.py
+++ b/tests/test_util_typing.py
@@ -12,6 +12,8 @@ import sys
from numbers import Integral
from typing import Any, Dict, List, TypeVar, Union, Callable, Tuple, Optional
+import pytest
+
from sphinx.util.typing import stringify
@@ -42,6 +44,12 @@ def test_stringify_type_hints_containers():
assert stringify(List[Dict[str, Tuple]]) == "List[Dict[str, Tuple]]"
+@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.')
+def test_stringify_Annotated():
+ from typing import Annotated
+ assert stringify(Annotated[str, "foo", "bar"]) == "str"
+
+
def test_stringify_type_hints_string():
assert stringify("int") == "int"
assert stringify("str") == "str"
diff --git a/tox.ini b/tox.ini
index fea21e1a9..54af28366 100644
--- a/tox.ini
+++ b/tox.ini
@@ -18,7 +18,7 @@ description =
du{12,13,14}: Run unit tests with the given version of docutils.
deps =
coverage < 5.0 # refs: https://github.com/sphinx-doc/sphinx/pull/6924
- git+https://github.com/html5lib/html5lib-python ; python_version >= "3.9" # refs: https://github.com/html5lib/html5lib-python/issues/419
+ git+https://github.com/html5lib/html5lib-python # refs: https://github.com/html5lib/html5lib-python/issues/419
du12: docutils==0.12
du13: docutils==0.13.1
du14: docutils==0.14
@@ -27,7 +27,7 @@ deps =
extras =
test
setenv =
- PYTHONWARNINGS = all,ignore::ImportWarning:pkgutil,ignore::ImportWarning:importlib._bootstrap,ignore::ImportWarning:importlib._bootstrap_external,ignore::ImportWarning:pytest_cov.plugin,ignore::DeprecationWarning:site,ignore::DeprecationWarning:_pytest.assertion.rewrite,ignore::DeprecationWarning:_pytest.fixtures,ignore::DeprecationWarning:distutils
+ PYTHONWARNINGS = all,ignore::ImportWarning:importlib._bootstrap_external,ignore::DeprecationWarning:site,ignore::DeprecationWarning:distutils
commands=
pytest --durations 25 {posargs}
@@ -35,8 +35,12 @@ commands=
basepython = python3
description =
Run style checks.
-commands =
+whitelist_externals =
flake8
+extras =
+ lint
+commands =
+ flake8 {posargs}
[testenv:pylint]
basepython = python3
@@ -62,9 +66,8 @@ commands =
basepython = python3
description =
Run type checks.
-deps =
- mypy
- docutils-stubs
+extras =
+ lint
commands=
mypy sphinx/
diff --git a/utils/bump_docker.sh b/utils/bump_docker.sh
new file mode 100755
index 000000000..436aaef10
--- /dev/null
+++ b/utils/bump_docker.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+set -ex
+
+if [ -z "$1" ]; then
+ echo "bump_docker.sh [VERSION]"
+ exit
+fi
+
+cd docker
+git checkout .
+sed -i "" -e "s/==[0-9.]\{1,\}/==$1/" base/Dockerfile
+sed -i "" -e "s/==[0-9.]\{1,\}/==$1/" latexpdf/Dockerfile
+git commit -am "Bump to $1"
+git tag $1
+git push origin master --tags
diff --git a/utils/bump_version.py b/utils/bump_version.py
index 6491a38e3..b829877b6 100755
--- a/utils/bump_version.py
+++ b/utils/bump_version.py
@@ -114,7 +114,7 @@ class Changes:
f.truncate(0)
f.write(heading + '\n')
f.write('=' * len(heading) + '\n')
- f.write(body)
+ f.write(self.filter_empty_sections(body))
def add_release(self, version_info):
if version_info[-2:] in (('beta', 0), ('final', 0)):
@@ -142,6 +142,9 @@ class Changes:
f.write('\n')
f.write(body)
+ def filter_empty_sections(self, body):
+ return re.sub('^\n.+\n-{3,}\n+(?=\n.+\n[-=]{3,}\n)', '', body, flags=re.M)
+
def parse_options(argv):
parser = argparse.ArgumentParser()
diff --git a/utils/jssplitter_generator.py b/utils/jssplitter_generator.py
index 360ce7d15..7806d64d2 100644
--- a/utils/jssplitter_generator.py
+++ b/utils/jssplitter_generator.py
@@ -133,7 +133,7 @@ with open('../sphinx/search/jssplitter.py', 'w') as f:
f.write(python_src)
with open('./regression_test.js', 'w') as f:
- f.write(js_test_src.encode())
+ f.write(js_test_src)
print("starting test...")
result = subprocess.call(['node', './regression_test.js'])
diff --git a/utils/release-checklist b/utils/release-checklist
index 12cbe6381..5a60e59c8 100644
--- a/utils/release-checklist
+++ b/utils/release-checklist
@@ -8,13 +8,13 @@ for stable releases
* Run ``git fetch; git status`` and check nothing changed
* ``python utils/bump_version.py X.Y.Z``
* Check diff by ``git diff``
-* Edit CHANGES if empty section exists
* ``git commit -am 'Bump to X.Y.Z final'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
-* ``twine check dist/Sphinx-X.Y.Z*``
-* ``twine upload dist/Sphinx-X.Y.Z* --sign --identity [your GPG key]``
+* ``twine check dist/Sphinx-*``
+* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
+* ``sh utils/bump_docker.sh X.Y.Z``
* ``git tag vX.Y.Z``
* ``python utils/bump_version.py --in-develop X.Y.Zb0`` (ex. 1.5.3b0)
* Check diff by ``git diff``
@@ -23,7 +23,6 @@ for stable releases
* ``git checkout master``
* ``git merge X.Y``
* ``git push origin master``
-* Update `sphinx-doc-translations <https://github.com/sphinx-doc/sphinx-doc-translations>`_
* Add new version/milestone to tracker categories
* Write announcement and send to sphinx-dev, sphinx-users and python-announce
@@ -36,12 +35,11 @@ for first beta releases
* Run ``(cd sphinx/locale; tx push -s)``
* ``python utils/bump_version.py X.Y.0b1``
* Check diff by ``git diff``
-* Edit CHANGES if empty section exists
* ``git commit -am 'Bump to X.Y.0 beta1'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
-* ``twine check dist/Sphinx-X.Y.Z*``
-* ``twine upload dist/Sphinx-X.Y.Z* --sign --identity [your GPG key]``
+* ``twine check dist/Sphinx-*``
+* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``git tag vX.Y.0b1``
* ``python utils/bump_version.py --in-develop X.Y.0b2`` (ex. 1.6.0b2)
@@ -56,7 +54,6 @@ for first beta releases
* ``git commit -am 'Bump version'``
* ``git push origin master``
* open https://github.com/sphinx-doc/sphinx/settings/branches and make ``X.Y`` branch protected
-* Update `sphinx-doc-translations <https://github.com/sphinx-doc/sphinx-doc-translations>`_
* Add new version/milestone to tracker categories
* Write announcement and send to sphinx-dev, sphinx-users and python-announce
@@ -67,12 +64,11 @@ for other beta releases
* Run ``git fetch; git status`` and check nothing changed
* ``python utils/bump_version.py X.Y.0bN``
* Check diff by ``git diff``
-* Edit CHANGES if empty section exists
* ``git commit -am 'Bump to X.Y.0 betaN'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
-* ``twine check dist/Sphinx-X.Y.Z*``
-* ``twine upload dist/Sphinx-X.Y.Z* --sign --identity [your GPG key]``
+* ``twine check dist/Sphinx-*``
+* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``git tag vX.Y.0bN``
* ``python utils/bump_version.py --in-develop X.Y.0bM`` (ex. 1.6.0b3)
@@ -82,7 +78,6 @@ for other beta releases
* ``git checkout master``
* ``git merge X.Y``
* ``git push origin master``
-* Update `sphinx-doc-translations <https://github.com/sphinx-doc/sphinx-doc-translations>`_
* Add new version/milestone to tracker categories
* Write announcement and send to sphinx-dev, sphinx-users and python-announce
@@ -97,13 +92,13 @@ for major releases
* Run ``git commit -am 'Update message catalogs'``
* ``python utils/bump_version.py X.Y.0``
* Check diff by ``git diff``
-* Edit CHANGES if empty section exists
* ``git commit -am 'Bump to X.Y.0 final'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
-* ``twine check dist/Sphinx-X.Y.Z*``
-* ``twine upload dist/Sphinx-X.Y.Z* --sign --identity [your GPG key]``
+* ``twine check dist/Sphinx-*``
+* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
+* ``sh utils/bump_docker.sh X.Y.Z``
* ``git tag vX.Y.0``
* ``python utils/bump_version.py --in-develop X.Y.1b0`` (ex. 1.6.1b0)
* Check diff by ``git diff``
@@ -117,6 +112,5 @@ for major releases
* Run ``git tag A.B`` to paste a tag instead branch
* Run ``git push origin :A.B --tags`` to remove old stable branch
* open https://readthedocs.org/dashboard/sphinx/versions/ and enable the released version
-* Update `sphinx-doc-translations <https://github.com/sphinx-doc/sphinx-doc-translations>`_
* Add new version/milestone to tracker categories
* Write announcement and send to sphinx-dev, sphinx-users and python-announce