summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/builddoc.yml6
-rw-r--r--.github/workflows/coverage.yml4
-rw-r--r--.github/workflows/create-release.yml2
-rw-r--r--.github/workflows/docutils-latest.yml6
-rw-r--r--.github/workflows/lint.yml6
-rw-r--r--.github/workflows/main.yml4
-rw-r--r--.github/workflows/nodejs.yml4
-rw-r--r--.github/workflows/transifex.yml8
-rw-r--r--AUTHORS4
-rw-r--r--CHANGES16
-rw-r--r--Makefile2
-rw-r--r--doc/development/index.rst6
-rw-r--r--doc/development/tutorials/autodoc_ext.rst4
-rw-r--r--doc/extdev/deprecated.rst7
-rw-r--r--doc/latex.rst6
-rw-r--r--doc/templating.rst2
-rw-r--r--doc/tutorial/deploying.rst4
-rw-r--r--doc/tutorial/describing-code.rst9
-rw-r--r--doc/usage/configuration.rst9
-rw-r--r--doc/usage/extensions/intersphinx.rst2
-rw-r--r--doc/usage/extensions/napoleon.rst8
-rw-r--r--doc/usage/installation.rst2
-rw-r--r--doc/usage/restructuredtext/domains.rst3
-rw-r--r--doc/usage/restructuredtext/roles.rst2
-rw-r--r--setup.cfg2
-rw-r--r--setup.py1
-rw-r--r--sphinx/application.py64
-rw-r--r--sphinx/builders/__init__.py16
-rw-r--r--sphinx/builders/html/__init__.py18
-rw-r--r--sphinx/cmd/quickstart.py5
-rw-r--r--sphinx/config.py1
-rw-r--r--sphinx/domains/std.py50
-rw-r--r--sphinx/ext/autodoc/importer.py7
-rw-r--r--sphinx/pycode/ast.py9
-rw-r--r--sphinx/registry.py18
-rw-r--r--sphinx/search/en.py7
-rw-r--r--sphinx/search/zh.py9
-rw-r--r--sphinx/themes/agogo/layout.html2
-rw-r--r--sphinx/themes/agogo/static/agogo.css_t6
-rw-r--r--sphinx/util/console.py5
-rw-r--r--sphinx/util/inspect.py4
-rw-r--r--sphinx/util/logging.py7
-rw-r--r--sphinx/util/parallel.py8
-rw-r--r--sphinx/util/stemmer/__init__.py63
-rw-r--r--sphinx/util/stemmer/porter.py406
-rw-r--r--sphinx/util/typing.py5
-rw-r--r--tests/roots/test-root/objects.txt9
-rw-r--r--tests/test_application.py33
-rw-r--r--tests/test_build_html.py24
-rw-r--r--tests/test_build_latex.py2
-rw-r--r--tests/test_build_texinfo.py2
-rw-r--r--tests/test_domain_py.py33
-rw-r--r--tests/test_environment.py6
-rw-r--r--tests/test_environment_toctree.py2
-rw-r--r--tests/test_ext_autosectionlabel.py2
-rw-r--r--tests/test_markup.py3
-rw-r--r--tests/test_pycode_ast.py10
-rw-r--r--tests/test_pycode_parser.py24
-rw-r--r--tests/test_util_logging.py18
-rw-r--r--tox.ini4
-rw-r--r--utils/doclinter.py77
61 files changed, 426 insertions, 662 deletions
diff --git a/.github/workflows/builddoc.yml b/.github/workflows/builddoc.yml
index 88082b31e..b045fcfc2 100644
--- a/.github/workflows/builddoc.yml
+++ b/.github/workflows/builddoc.yml
@@ -7,11 +7,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v1
+ uses: actions/setup-python@v4
with:
- python-version: 3.8
+ python-version: 3
- name: Install dependencies
run: |
sudo apt update
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 12945f665..091abd5c2 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -9,8 +9,8 @@ jobs:
steps:
- uses: actions/checkout@v3
- - name: Set up Python 3
- uses: actions/setup-python@v3
+ - name: Set up Python
+ uses: actions/setup-python@v4
with:
python-version: 3
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index cd86ec2fd..2da2ad3ea 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
diff --git a/.github/workflows/docutils-latest.yml b/.github/workflows/docutils-latest.yml
index b082ad193..7a730d657 100644
--- a/.github/workflows/docutils-latest.yml
+++ b/.github/workflows/docutils-latest.yml
@@ -11,9 +11,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3
- name: Check Python version
run: python --version
- name: Unpin docutils
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index fb85629e5..48c21510a 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -11,11 +11,11 @@ jobs:
tool: [docslint, flake8, isort, mypy, twine]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v1
+ uses: actions/setup-python@v4
with:
- python-version: 3.8
+ python-version: 3
- name: Install dependencies
run: pip install -U tox
- name: Run Tox
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 4dfae1781..6f14eac32 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -24,7 +24,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python }}
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
if: "!endsWith(matrix.python, '-dev')"
with:
python-version: ${{ matrix.python }}
@@ -47,7 +47,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: 3
- name: Install dependencies
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index be8d7f027..ac92ac7f3 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -9,9 +9,9 @@ jobs:
node-version: 16
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Use Node.js ${{ env.node-version }}
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
with:
node-version: ${{ env.node-version }}
cache: "npm"
diff --git a/.github/workflows/transifex.yml b/.github/workflows/transifex.yml
index ba8b6a734..d986293f8 100644
--- a/.github/workflows/transifex.yml
+++ b/.github/workflows/transifex.yml
@@ -11,11 +11,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
ref: 5.x
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: 3.9 # https://github.com/transifex/transifex-client/pull/330
- name: Install dependencies
@@ -34,11 +34,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
ref: 5.x
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: 3.9 # https://github.com/transifex/transifex-client/pull/330
- name: Install dependencies
diff --git a/AUTHORS b/AUTHORS
index 52d0ee8e5..c3f306672 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -15,10 +15,10 @@ Other co-maintainers:
* Jean-François Burnol <@jfbu>
* Yoshiki Shibukawa <@shibu_jp>
* Timotheus Kampik - <@TimKam>
+* Adam Turner - <@AA-Turner>
Other contributors, listed alphabetically, are:
-* Adam Turner -- JavaScript improvements
* Alastair Houghton -- Apple Help builder
* Alexander Todorov -- inheritance_diagram tests and improvements
* Andi Albrecht -- agogo theme
@@ -96,5 +96,3 @@ authors and projects:
* sphinx.util.jsdump uses the basestring encoding from simplejson,
written by Bob Ippolito, released under the MIT license
-* sphinx.util.stemmer was written by Vivake Gupta, placed in the
- Public Domain
diff --git a/CHANGES b/CHANGES
index f34b8a7b0..a727f483f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -33,12 +33,26 @@ Incompatible changes
Deprecated
----------
+* #10467: Deprecated ``sphinx.util.stemmer`` in favour of ``snowballstemmer``.
+ Patch by Adam Turner.
+
Features added
--------------
+* #10366: std domain: Add support for emphasising placeholders in :rst:dir`option`
+ directives through a new ``option_emphasise_placeholders`` configuration option.
+* #10439: std domain: Use the repr of some variables when displaying warnings,
+ making whitespace issues easier to identify.
+
Bugs fixed
----------
+* #10031: py domain: Fix spurious whitespace in unparsing various operators (``+``,
+ ``-``, ``~``, and ``**``). Patch by Adam Turner.
+* #10460: logging: Always show node source locations as absolute paths.
+* #10520: HTML Theme: Fix use of sidebar classes in ``agogo.css_t``.
+* #6679: HTML Theme: Fix inclusion of hidden toctrees in the agogo theme.
+
Testing
--------
@@ -159,6 +173,8 @@ Deprecated
<script src="{{ pathto('_static/underscore.js', resource=True) }}"></script>
{{ super() }}
{%- endblock %}
+
+ Patch by Adam Turner.
* setuptools integration. The ``build_sphinx`` sub-command for setup.py is
marked as deprecated to follow the policy of setuptools team.
* The ``locale`` argument of ``sphinx.util.i18n:babel_format_date()`` becomes
diff --git a/Makefile b/Makefile
index b430bdd1b..9213820b7 100644
--- a/Makefile
+++ b/Makefile
@@ -62,7 +62,7 @@ type-check:
.PHONY: doclinter
doclinter:
- python utils/doclinter.py CHANGES *.rst doc/
+ sphinx-lint --enable line-too-long --max-line-length 85 CHANGES *.rst doc/
.PHONY: test
test:
diff --git a/doc/development/index.rst b/doc/development/index.rst
index b4a7920ba..8ae71e76f 100644
--- a/doc/development/index.rst
+++ b/doc/development/index.rst
@@ -2,10 +2,10 @@
Extending Sphinx
================
-This guide is aimed at giving a quick introduction for those wishing to
-develop their own extensions for Sphinx. Sphinx possesses significant
+This guide is aimed at giving a quick introduction for those wishing to
+develop their own extensions for Sphinx. Sphinx possesses significant
extensibility capabilities including the ability to hook into almost every
-point of the build process. If you simply wish to use Sphinx with existing
+point of the build process. If you simply wish to use Sphinx with existing
extensions, refer to :doc:`/usage/index`. For a more detailed discussion of
the extension interface see :doc:`/extdev/index`.
diff --git a/doc/development/tutorials/autodoc_ext.rst b/doc/development/tutorials/autodoc_ext.rst
index d8905710c..8de2e4d4a 100644
--- a/doc/development/tutorials/autodoc_ext.rst
+++ b/doc/development/tutorials/autodoc_ext.rst
@@ -123,7 +123,7 @@ For example, you have the following ``IntEnum``:
.. code-block:: python
:caption: my_enums.py
-
+
class Colors(IntEnum):
"""Colors enumerator"""
NONE = 0
@@ -138,5 +138,3 @@ This will be the documentation file with auto-documentation directive:
:caption: index.rst
.. autointenum:: my_enums.Colors
-
-
diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst
index 98bd463a9..d88eb27b0 100644
--- a/doc/extdev/deprecated.rst
+++ b/doc/extdev/deprecated.rst
@@ -22,6 +22,11 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- Alternatives
+ * - ``sphinx.util.stemmer``
+ - 5.1
+ - 7.0
+ - ``snowballstemmer``
+
* - ``sphinx.util.jsdump``
- 5.0
- 7.0
@@ -824,7 +829,7 @@ The following is a list of deprecated interfaces.
- ``sphinx.domains.std.StandardDomain.process_doc()``
* - ``sphinx.domains.js.JSObject.display_prefix``
- -
+ -
- 4.3
- ``sphinx.domains.js.JSObject.get_display_prefix()``
diff --git a/doc/latex.rst b/doc/latex.rst
index b49714e88..294de5599 100644
--- a/doc/latex.rst
+++ b/doc/latex.rst
@@ -330,7 +330,7 @@ Keys that don't need to be overridden unless in special cases are:
.. attention::
- - Do not use this key for a :confval:`latex_engine` other than
+ - Do not use this key for a :confval:`latex_engine` other than
``'pdflatex'``.
- If Greek is main language, do not use this key. Since Sphinx 2.2.1,
@@ -528,7 +528,7 @@ Keys that don't need to be overridden unless in special cases are:
is adapted to the relative widths of the FreeFont family.
.. versionchanged:: 4.0.0
- Changed default for ``'pdflatex'``. Previously it was using
+ Changed default for ``'pdflatex'``. Previously it was using
``'\\fvset{fontsize=\\small}'``.
.. versionchanged:: 4.1.0
@@ -915,7 +915,7 @@ Do not use quotes to enclose values, whether numerical or strings.
``attentionBorderColor``, ``dangerBorderColor``,
``errorBorderColor``
-.. |wgbdcolorslatex| replace:: ``warningBorderColor``, and
+.. |wgbdcolorslatex| replace:: ``warningBorderColor``, and
``(caution|attention|danger|error)BorderColor``
.. else latex goes into right margin, as it does not hyphenate the names
diff --git a/doc/templating.rst b/doc/templating.rst
index d9755a836..f2f4022ad 100644
--- a/doc/templating.rst
+++ b/doc/templating.rst
@@ -377,7 +377,7 @@ in the future.
.. data:: sphinx_version_tuple
The version of Sphinx used to build represented as a tuple of five elements.
- For Sphinx version 3.5.1 beta 3 this would be `(3, 5, 1, 'beta', 3)``.
+ For Sphinx version 3.5.1 beta 3 this would be ``(3, 5, 1, 'beta', 3)``.
The fourth element can be one of: ``alpha``, ``beta``, ``rc``, ``final``.
``final`` always has 0 as the last element.
diff --git a/doc/tutorial/deploying.rst b/doc/tutorial/deploying.rst
index 85fc6643a..cc62fd428 100644
--- a/doc/tutorial/deploying.rst
+++ b/doc/tutorial/deploying.rst
@@ -190,11 +190,11 @@ contents:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Build HTML
uses: ammaraskar/sphinx-action@0.4
- name: Upload artifacts
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v3
with:
name: html-docs
path: docs/build/html/
diff --git a/doc/tutorial/describing-code.rst b/doc/tutorial/describing-code.rst
index 57f1b2008..24fea38a6 100644
--- a/doc/tutorial/describing-code.rst
+++ b/doc/tutorial/describing-code.rst
@@ -85,10 +85,11 @@ you can use :rst:role:`py:func` for that, as follows:
or ``"veggies"``. Otherwise, :py:func:`lumache.get_random_ingredients`
will raise an exception.
-When generating code documentation, Sphinx will generate a cross-reference automatically just
-by using the name of the object, without you having to explicitly use a role
-for that. For example, you can describe the custom exception raised by the
-function using the :rst:dir:`py:exception` directive:
+When generating code documentation, Sphinx will generate a
+cross-reference automatically just by using the name of the object,
+without you having to explicitly use a role for that. For example, you
+can describe the custom exception raised by the function using the
+:rst:dir:`py:exception` directive:
.. code-block:: rst
:caption: docs/source/usage.rst
diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst
index d4f482e4e..182f09e49 100644
--- a/doc/usage/configuration.rst
+++ b/doc/usage/configuration.rst
@@ -706,6 +706,15 @@ General configuration
.. versionadded:: 3.0
+.. confval:: option_emphasise_placeholders
+
+ Default is ``False``.
+ When enabled, emphasise placeholders in ``.. option:`` directives.
+ To display literal braces, escape with a backslash (``\{``). For example,
+ ``option_emphasise_placeholders=True`` and ``.. option:: -foption={TYPE}`` would
+ render with ``TYPE`` emphasised.
+
+ .. versionadded:: 5.1
.. _intl-options:
diff --git a/doc/usage/extensions/intersphinx.rst b/doc/usage/extensions/intersphinx.rst
index 6b7b1e1bd..a70c7c531 100644
--- a/doc/usage/extensions/intersphinx.rst
+++ b/doc/usage/extensions/intersphinx.rst
@@ -209,7 +209,7 @@ The Intersphinx extension provides the following role.
If you would like to constrain the lookup to a specific external project,
then the key of the project, as specified in :confval:`intersphinx_mapping`,
- is added as well to get the two forms
+ is added as well to get the two forms
- ``:external+invname:domain:reftype:`target```,
e.g., ``:external+python:py:class:`zipfile.ZipFile```, or
diff --git a/doc/usage/extensions/napoleon.rst b/doc/usage/extensions/napoleon.rst
index ad5af65c6..59ecad890 100644
--- a/doc/usage/extensions/napoleon.rst
+++ b/doc/usage/extensions/napoleon.rst
@@ -136,7 +136,7 @@ separate sections, whereas NumPy uses underlines.
Google style:
-.. code-block:: python3
+.. code-block:: python
def func(arg1, arg2):
"""Summary line.
@@ -155,7 +155,7 @@ Google style:
NumPy style:
-.. code-block:: python3
+.. code-block:: python
def func(arg1, arg2):
"""Summary line.
@@ -221,7 +221,7 @@ Google style with Python 3 type annotations::
"""
return True
-
+
class Class:
"""Summary line.
@@ -251,7 +251,7 @@ Google style with types in docstrings::
"""
return True
-
+
class Class:
"""Summary line.
diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst
index 997bd40ca..8a597ce01 100644
--- a/doc/usage/installation.rst
+++ b/doc/usage/installation.rst
@@ -118,7 +118,7 @@ Chocolatey
::
$ choco install sphinx
-
+
You would need to `install Chocolatey
<https://chocolatey.org/install>`_
before running this.
diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst
index 30bde8ea1..54e325400 100644
--- a/doc/usage/restructuredtext/domains.rst
+++ b/doc/usage/restructuredtext/domains.rst
@@ -1750,6 +1750,9 @@ There is a set of directives allowing documenting command-line programs:
referenceable by :rst:role:`option` (in the example case, you'd use something
like ``:option:`dest_dir```, ``:option:`-m```, or ``:option:`--module```).
+ Use :confval:`option_emphasise_placeholders` for parsing of
+ "variable part" of a literal text (similarly to the ``samp`` role).
+
``cmdoption`` directive is a deprecated alias for the ``option`` directive.
.. rst:directive:: .. envvar:: name
diff --git a/doc/usage/restructuredtext/roles.rst b/doc/usage/restructuredtext/roles.rst
index 9d790b30e..e2755dd4e 100644
--- a/doc/usage/restructuredtext/roles.rst
+++ b/doc/usage/restructuredtext/roles.rst
@@ -349,7 +349,7 @@ different style:
The name of a file or directory. Within the contents, you can use curly
braces to indicate a "variable" part, for example::
- ... is installed in :file:`/usr/lib/python2.{x}/site-packages` ...
+ ... is installed in :file:`/usr/lib/python3.{x}/site-packages` ...
In the built documentation, the ``x`` will be displayed differently to
indicate that it is to be replaced by the Python minor version.
diff --git a/setup.cfg b/setup.cfg
index bc8f14998..b0bfa28d3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,11 +26,13 @@ python_version = 3.6
disallow_incomplete_defs = True
show_column_numbers = True
show_error_context = True
+show_error_codes = true
ignore_missing_imports = True
follow_imports = skip
check_untyped_defs = True
warn_unused_ignores = True
strict_optional = False
+no_implicit_optional = True
[tool:pytest]
filterwarnings =
diff --git a/setup.py b/setup.py
index 94451f531..b9e048d07 100644
--- a/setup.py
+++ b/setup.py
@@ -42,6 +42,7 @@ extras_require = {
'flake8>=3.5.0',
'isort',
'mypy>=0.950',
+ 'sphinx-lint',
'docutils-stubs',
"types-typed-ast",
"types-requests",
diff --git a/sphinx/application.py b/sphinx/application.py
index 0aceff56b..218801322 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -133,9 +133,6 @@ class Sphinx:
self.phase = BuildPhase.INITIALIZATION
self.verbosity = verbosity
self.extensions: Dict[str, Extension] = {}
- self.builder: Optional[Builder] = None
- self.env: Optional[BuildEnvironment] = None
- self.project: Optional[Project] = None
self.registry = SphinxComponentRegistry()
# validate provided directories
@@ -246,10 +243,16 @@ class Sphinx:
# create the project
self.project = Project(self.srcdir, self.config.source_suffix)
+
+ # set up the build environment
+ self.env = self._init_env(freshenv)
+
# create the builder
self.builder = self.create_builder(buildername)
- # set up the build environment
- self._init_env(freshenv)
+
+ # build environment post-initialisation, after creating the builder
+ self._post_init_env()
+
# set up the builder
self._init_builder()
@@ -281,20 +284,34 @@ class Sphinx:
else:
logger.info(__('not available for built-in messages'))
- def _init_env(self, freshenv: bool) -> None:
+ def _init_env(self, freshenv: bool) -> BuildEnvironment:
filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
if freshenv or not os.path.exists(filename):
- self.env = BuildEnvironment(self)
- self.env.find_files(self.config, self.builder)
+ return self._create_fresh_env()
else:
- try:
- with progress_message(__('loading pickled environment')):
- with open(filename, 'rb') as f:
- self.env = pickle.load(f)
- self.env.setup(self)
- except Exception as err:
- logger.info(__('failed: %s'), err)
- self._init_env(freshenv=True)
+ return self._load_existing_env(filename)
+
+ def _create_fresh_env(self) -> BuildEnvironment:
+ env = BuildEnvironment(self)
+ self._fresh_env_used = True
+ return env
+
+ def _load_existing_env(self, filename: str) -> BuildEnvironment:
+ try:
+ with progress_message(__('loading pickled environment')):
+ with open(filename, 'rb') as f:
+ env = pickle.load(f)
+ env.setup(self)
+ self._fresh_env_used = False
+ except Exception as err:
+ logger.info(__('failed: %s'), err)
+ env = self._create_fresh_env()
+ return env
+
+ def _post_init_env(self) -> None:
+ if self._fresh_env_used:
+ self.env.find_files(self.config, self.builder)
+ del self._fresh_env_used
def preload_builder(self, name: str) -> None:
self.registry.preload_builder(self, name)
@@ -304,10 +321,11 @@ class Sphinx:
logger.info(__('No builder selected, using default: html'))
name = 'html'
- return self.registry.create_builder(self, name)
+ return self.registry.create_builder(self, name, self.env)
def _init_builder(self) -> None:
- self.builder.set_environment(self.env)
+ if not hasattr(self.builder, "env"):
+ self.builder.set_environment(self.env)
self.builder.init()
self.events.emit('builder-inited')
@@ -984,8 +1002,9 @@ class Sphinx:
kwargs['defer'] = 'defer'
self.registry.add_js_file(filename, priority=priority, **kwargs)
- if hasattr(self.builder, 'add_js_file'):
- self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore
+ if hasattr(self, 'builder') and hasattr(self.builder, 'add_js_file'):
+ self.builder.add_js_file(filename, # type: ignore[attr-defined]
+ priority=priority, **kwargs)
def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None:
"""Register a stylesheet to include in the HTML output.
@@ -1045,8 +1064,9 @@ class Sphinx:
"""
logger.debug('[app] adding stylesheet: %r', filename)
self.registry.add_css_files(filename, priority=priority, **kwargs)
- if hasattr(self.builder, 'add_css_file'):
- self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore
+ if hasattr(self, 'builder') and hasattr(self.builder, 'add_css_file'):
+ self.builder.add_css_file(filename, # type: ignore[attr-defined]
+ priority=priority, **kwargs)
def add_latex_package(self, packagename: str, options: str = None,
after_hyperref: bool = False) -> None:
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index d8500e11b..9705ba894 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -3,6 +3,7 @@
import codecs
import pickle
import time
+import warnings
from os import path
from typing import (TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple,
Type, Union)
@@ -11,6 +12,7 @@ from docutils import nodes
from docutils.nodes import Node
from sphinx.config import Config
+from sphinx.deprecation import RemovedInSphinx70Warning
from sphinx.environment import CONFIG_CHANGED_REASON, CONFIG_OK, BuildEnvironment
from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.errors import SphinxError
@@ -75,7 +77,7 @@ class Builder:
#: The builder supports data URIs or not.
supported_data_uri_images = False
- def __init__(self, app: "Sphinx") -> None:
+ def __init__(self, app: "Sphinx", env: BuildEnvironment = None) -> None:
self.srcdir = app.srcdir
self.confdir = app.confdir
self.outdir = app.outdir
@@ -83,7 +85,14 @@ class Builder:
ensuredir(self.doctreedir)
self.app: Sphinx = app
- self.env: Optional[BuildEnvironment] = None
+ if env is not None:
+ self.env: BuildEnvironment = env
+ self.env.set_versioning_method(self.versioning_method,
+ self.versioning_compare)
+ elif env is not Ellipsis:
+ # ... is passed by SphinxComponentRegistry.create_builder to not show two warnings.
+ warnings.warn("The 'env' argument to Builder will be required from Sphinx 7.",
+ RemovedInSphinx70Warning, stacklevel=2)
self.events: EventManager = app.events
self.config: Config = app.config
self.tags: Tags = app.tags
@@ -105,6 +114,9 @@ class Builder:
def set_environment(self, env: BuildEnvironment) -> None:
"""Store BuildEnvironment object."""
+ warnings.warn("Builder.set_environment is deprecated, pass env to "
+ "'Builder.__init__()' instead.",
+ RemovedInSphinx70Warning, stacklevel=2)
self.env = env
self.env.set_versioning_method(self.versioning_method,
self.versioning_compare)
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index b76739523..7737a1d38 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -26,6 +26,7 @@ from sphinx.builders import Builder
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx70Warning, deprecated_alias
from sphinx.domains import Domain, Index, IndexEntry
+from sphinx.environment import BuildEnvironment
from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.environment.adapters.indexentries import IndexEntries
from sphinx.environment.adapters.toctree import TocTree
@@ -51,6 +52,17 @@ INVENTORY_FILENAME = 'objects.inv'
logger = logging.getLogger(__name__)
return_codes_re = re.compile('[\r\n]+')
+DOMAIN_INDEX_TYPE = Tuple[
+ # Index name (e.g. py-modindex)
+ str,
+ # Index class
+ Type[Index],
+ # list of (heading string, list of index entries) pairs.
+ List[Tuple[str, List[IndexEntry]]],
+ # whether sub-entries should start collapsed
+ bool
+]
+
def get_stable_hash(obj: Any) -> str:
"""
@@ -197,10 +209,10 @@ class StandaloneHTMLBuilder(Builder):
download_support = True # enable download role
imgpath: str = None
- domain_indices: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] = [] # NOQA
+ domain_indices: List[DOMAIN_INDEX_TYPE] = []
- def __init__(self, app: Sphinx) -> None:
- super().__init__(app)
+ def __init__(self, app: Sphinx, env: BuildEnvironment = None) -> None:
+ super().__init__(app, env)
# CSS files
self.css_files: List[Stylesheet] = []
diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py
index 47853c90d..610052ea9 100644
--- a/sphinx/cmd/quickstart.py
+++ b/sphinx/cmd/quickstart.py
@@ -7,11 +7,14 @@ import sys
import time
from collections import OrderedDict
from os import path
-from typing import Any, Callable, Dict, List, Union
+from typing import TYPE_CHECKING, Any, Callable, Dict, List, Union
# try to import readline, unix specific enhancement
try:
import readline
+ if TYPE_CHECKING and sys.platform == "win32": # always false, for type checking
+ raise ImportError
+
if readline.__doc__ and 'libedit' in readline.__doc__:
readline.parse_and_bind("bind ^I rl_complete")
USE_LIBEDIT = True
diff --git a/sphinx/config.py b/sphinx/config.py
index 8c6dcfe32..318173f27 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -140,6 +140,7 @@ class Config:
'smartquotes_excludes': ({'languages': ['ja'],
'builders': ['man', 'text']},
'env', []),
+ 'option_emphasise_placeholders': (False, 'env', []),
}
def __init__(self, config: Dict[str, Any] = {}, overrides: Dict[str, Any] = {}) -> None:
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index d5c962dc8..88a4d28cb 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -15,7 +15,7 @@ from sphinx.addnodes import desc_signature, pending_xref
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.locale import _, __
-from sphinx.roles import XRefRole
+from sphinx.roles import EmphasizedLiteral, XRefRole
from sphinx.util import docname_join, logging, ws_re
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import clean_astext, make_id, make_refnode
@@ -34,6 +34,8 @@ option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)')
# RE for grammar tokens
token_re = re.compile(r'`((~?\w*:)?\w+)`', re.U)
+samp_role = EmphasizedLiteral()
+
class GenericObject(ObjectDescription[str]):
"""
@@ -170,15 +172,41 @@ class Cmdoption(ObjectDescription[str]):
location=signode)
continue
optname, args = m.groups()
- if optname.endswith('[') and args.endswith(']'):
+ if optname[-1] == '[' and args[-1] == ']':
# optional value surrounded by brackets (ex. foo[=bar])
optname = optname[:-1]
args = '[' + args
if count:
- signode += addnodes.desc_addname(', ', ', ')
+ if self.env.config.option_emphasise_placeholders:
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_space()
+ else:
+ signode += addnodes.desc_addname(', ', ', ')
signode += addnodes.desc_name(optname, optname)
- signode += addnodes.desc_addname(args, args)
+ if self.env.config.option_emphasise_placeholders:
+ add_end_bracket = False
+ if not args:
+ continue
+ if args[0] == '[' and args[-1] == ']':
+ add_end_bracket = True
+ signode += addnodes.desc_sig_punctuation('[', '[')
+ args = args[1:-1]
+ if args[0] == ' ':
+ signode += addnodes.desc_sig_space()
+ args = args.strip()
+ if args[0] == '=':
+ signode += addnodes.desc_sig_punctuation('=', '=')
+ args = args[1:]
+ for part in samp_role.parse(args):
+ if isinstance(part, nodes.Text):
+ signode += nodes.Text(part.astext())
+ else:
+ signode += part
+ if add_end_bracket:
+ signode += addnodes.desc_sig_punctuation(']', ']')
+ else:
+ signode += addnodes.desc_addname(args, args)
if not count:
firstname = optname
signode['allnames'] = [optname]
@@ -573,11 +601,11 @@ class StandardDomain(Domain):
}
dangling_warnings = {
- 'term': 'term not in glossary: %(target)s',
- 'numref': 'undefined label: %(target)s',
- 'keyword': 'unknown keyword: %(target)s',
- 'doc': 'unknown document: %(target)s',
- 'option': 'unknown option: %(target)s',
+ 'term': 'term not in glossary: %(target)r',
+ 'numref': 'undefined label: %(target)r',
+ 'keyword': 'unknown keyword: %(target)r',
+ 'doc': 'unknown document: %(target)r',
+ 'option': 'unknown option: %(target)r',
}
# node_class -> (figtype, title_getter)
@@ -1072,9 +1100,9 @@ def warn_missing_reference(app: "Sphinx", domain: Domain, node: pending_xref
else:
target = node['reftarget']
if target not in domain.anonlabels: # type: ignore
- msg = __('undefined label: %s')
+ msg = __('undefined label: %r')
else:
- msg = __('Failed to create a cross reference. A title or caption not found: %s')
+ msg = __('Failed to create a cross reference. A title or caption not found: %r')
logger.warning(msg % target, location=node, type='ref', subtype=node['reftype'])
return True
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index d392ae75d..977cfbba4 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -3,7 +3,7 @@
import importlib
import traceback
import warnings
-from typing import Any, Callable, Dict, List, NamedTuple, Optional
+from typing import TYPE_CHECKING, Any, Callable, Dict, List, NamedTuple, Optional
from sphinx.ext.autodoc.mock import ismock, undecorate
from sphinx.pycode import ModuleAnalyzer, PycodeError
@@ -11,10 +11,7 @@ from sphinx.util import logging
from sphinx.util.inspect import (getannotations, getmro, getslots, isclass, isenumclass,
safe_getattr)
-if False:
- # For type annotation
- from typing import Type # NOQA
-
+if TYPE_CHECKING:
from sphinx.ext.autodoc import ObjectMember
logger = logging.getLogger(__name__)
diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py
index 755116475..d4646f0b7 100644
--- a/sphinx/pycode/ast.py
+++ b/sphinx/pycode/ast.py
@@ -141,6 +141,9 @@ class _UnparseVisitor(ast.NodeVisitor):
return "%s.%s" % (self.visit(node.value), node.attr)
def visit_BinOp(self, node: ast.BinOp) -> str:
+ # Special case ``**`` to not have surrounding spaces.
+ if isinstance(node.op, ast.Pow):
+ return "".join(map(self.visit, (node.left, node.op, node.right)))
return " ".join(self.visit(e) for e in [node.left, node.op, node.right])
def visit_BoolOp(self, node: ast.BoolOp) -> str:
@@ -202,7 +205,11 @@ class _UnparseVisitor(ast.NodeVisitor):
return "%s[%s]" % (self.visit(node.value), self.visit(node.slice))
def visit_UnaryOp(self, node: ast.UnaryOp) -> str:
- return "%s %s" % (self.visit(node.op), self.visit(node.operand))
+ # UnaryOp is one of {UAdd, USub, Invert, Not}, which refer to ``+x``,
+ # ``-x``, ``~x``, and ``not x``. Only Not needs a space.
+ if isinstance(node.op, ast.Not):
+ return "%s %s" % (self.visit(node.op), self.visit(node.operand))
+ return "%s%s" % (self.visit(node.op), self.visit(node.operand))
def visit_Tuple(self, node: ast.Tuple) -> str:
if len(node.elts) == 0:
diff --git a/sphinx/registry.py b/sphinx/registry.py
index 87864b311..d08ba71a7 100644
--- a/sphinx/registry.py
+++ b/sphinx/registry.py
@@ -1,6 +1,7 @@
"""Sphinx component registry."""
import traceback
+import warnings
from importlib import import_module
from types import MethodType
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Tuple, Type, Union
@@ -19,6 +20,7 @@ except ImportError:
from sphinx.builders import Builder
from sphinx.config import Config
+from sphinx.deprecation import RemovedInSphinx70Warning
from sphinx.domains import Domain, Index, ObjType
from sphinx.domains.std import GenericObject, Target
from sphinx.environment import BuildEnvironment
@@ -146,11 +148,23 @@ class SphinxComponentRegistry:
self.load_extension(app, entry_point.module)
- def create_builder(self, app: "Sphinx", name: str) -> Builder:
+ def create_builder(self, app: "Sphinx", name: str,
+ env: BuildEnvironment = None) -> Builder:
if name not in self.builders:
raise SphinxError(__('Builder name %s not registered') % name)
- return self.builders[name](app)
+ try:
+ return self.builders[name](app, env)
+ except TypeError:
+ warnings.warn(
+ f"The custom builder {name} defines a custom __init__ method without the "
+ f"'env'argument. Report this bug to the developers of your custom builder, "
+ f"this is likely not a issue with Sphinx. The 'env' argument will be required "
+ f"from Sphinx 7.", RemovedInSphinx70Warning, stacklevel=2)
+ builder = self.builders[name](app, env=...) # type: ignore[arg-type]
+ if env is not None:
+ builder.set_environment(env)
+ return builder
def add_domain(self, domain: Type[Domain], override: bool = False) -> None:
logger.debug('[app] adding domain: %r', domain)
diff --git a/sphinx/search/en.py b/sphinx/search/en.py
index 53cd917dc..19bd9f019 100644
--- a/sphinx/search/en.py
+++ b/sphinx/search/en.py
@@ -2,8 +2,9 @@
from typing import Dict
+import snowballstemmer
+
from sphinx.search import SearchLanguage
-from sphinx.util.stemmer import get_stemmer
english_stopwords = set("""
a and are as at
@@ -211,7 +212,7 @@ class SearchEnglish(SearchLanguage):
stopwords = english_stopwords
def init(self, options: Dict) -> None:
- self.stemmer = get_stemmer()
+ self.stemmer = snowballstemmer.stemmer('porter')
def stem(self, word: str) -> str:
- return self.stemmer.stem(word.lower())
+ return self.stemmer.stemWord(word.lower())
diff --git a/sphinx/search/zh.py b/sphinx/search/zh.py
index 700c2683f..86f612d5d 100644
--- a/sphinx/search/zh.py
+++ b/sphinx/search/zh.py
@@ -4,8 +4,9 @@ import os
import re
from typing import Dict, List
+import snowballstemmer
+
from sphinx.search import SearchLanguage
-from sphinx.util.stemmer import get_stemmer
try:
import jieba
@@ -230,7 +231,7 @@ class SearchChinese(SearchLanguage):
if dict_path and os.path.isfile(dict_path):
jieba.load_userdict(dict_path)
- self.stemmer = get_stemmer()
+ self.stemmer = snowballstemmer.stemmer('english')
def split(self, input: str) -> List[str]:
chinese: List[str] = []
@@ -252,8 +253,8 @@ class SearchChinese(SearchLanguage):
should_not_be_stemmed = (
word in self.latin_terms and
len(word) >= 3 and
- len(self.stemmer.stem(word.lower())) < 3
+ len(self.stemmer.stemWord(word.lower())) < 3
)
if should_not_be_stemmed:
return word.lower()
- return self.stemmer.stem(word.lower())
+ return self.stemmer.stemWord(word.lower())
diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html
index e89657ba1..d76050c9b 100644
--- a/sphinx/themes/agogo/layout.html
+++ b/sphinx/themes/agogo/layout.html
@@ -36,7 +36,7 @@
{%- macro agogo_sidebar() %}
{%- block sidebartoc %}
<h3>{{ _('Table of Contents') }}</h3>
- {{ toctree() }}
+ {{ toctree(includehidden=True) }}
{%- endblock %}
{%- block sidebarsearch %}
<div role="search">
diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t
index 14c5e52ce..53c4c3848 100644
--- a/sphinx/themes/agogo/static/agogo.css_t
+++ b/sphinx/themes/agogo/static/agogo.css_t
@@ -273,12 +273,6 @@ div.document ol {
div.sidebar,
aside.sidebar {
- width: {{ theme_sidebarwidth|todim }};
- {%- if theme_rightsidebar|tobool %}
- float: right;
- {%- else %}
- float: left;
- {%- endif %}
font-size: .9em;
}
diff --git a/sphinx/util/console.py b/sphinx/util/console.py
index abdbf4219..88b208470 100644
--- a/sphinx/util/console.py
+++ b/sphinx/util/console.py
@@ -23,6 +23,9 @@ def terminal_safe(s: str) -> str:
def get_terminal_width() -> int:
"""Borrowed from the py lib."""
+ if sys.platform == "win32":
+ # For static typing, as fcntl & termios never exist on Windows.
+ return int(os.environ.get('COLUMNS', 80)) - 1
try:
import fcntl
import struct
@@ -32,7 +35,7 @@ def get_terminal_width() -> int:
terminal_width = width
except Exception:
# FALLBACK
- terminal_width = int(os.environ.get('COLUMNS', "80")) - 1
+ terminal_width = int(os.environ.get('COLUMNS', 80)) - 1
return terminal_width
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index a807ceb83..3d89a4f6e 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -28,10 +28,6 @@ else:
MethodDescriptorType = type(str.join)
WrapperDescriptorType = type(dict.__dict__['fromkeys'])
-if False:
- # For type annotation
- from typing import Type # NOQA
-
logger = logging.getLogger(__name__)
memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py
index 37fa672af..d43116f87 100644
--- a/sphinx/util/logging.py
+++ b/sphinx/util/logging.py
@@ -12,6 +12,7 @@ from docutils.utils import get_source_line
from sphinx.errors import SphinxWarning
from sphinx.util.console import colorize
+from sphinx.util.osutil import abspath
if TYPE_CHECKING:
from sphinx.application import Sphinx
@@ -381,8 +382,8 @@ class WarningSuppressor(logging.Filter):
super().__init__()
def filter(self, record: logging.LogRecord) -> bool:
- type = getattr(record, 'type', None)
- subtype = getattr(record, 'subtype', None)
+ type = getattr(record, 'type', '')
+ subtype = getattr(record, 'subtype', '')
try:
suppress_warnings = self.app.config.suppress_warnings
@@ -514,6 +515,8 @@ class WarningLogRecordTranslator(SphinxLogRecordTranslator):
def get_node_location(node: Node) -> Optional[str]:
(source, line) = get_source_line(node)
+ if source:
+ source = abspath(source)
if source and line:
return "%s:%s" % (source, line)
elif source:
diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py
index e4bd852b0..193d2a80d 100644
--- a/sphinx/util/parallel.py
+++ b/sphinx/util/parallel.py
@@ -1,6 +1,7 @@
"""Parallel building utilities."""
import os
+import sys
import time
import traceback
from math import sqrt
@@ -16,6 +17,11 @@ from sphinx.util import logging
logger = logging.getLogger(__name__)
+if sys.platform != "win32":
+ ForkProcess = multiprocessing.context.ForkProcess
+else:
+ # For static typing, as ForkProcess doesn't exist on Windows
+ ForkProcess = multiprocessing.process.BaseProcess
# our parallel functionality only works for the forking Process
parallel_available = multiprocessing and os.name == 'posix'
@@ -49,7 +55,7 @@ class ParallelTasks:
# task arguments
self._args: Dict[int, Optional[List[Any]]] = {}
# list of subprocesses (both started and waiting)
- self._procs: Dict[int, multiprocessing.context.ForkProcess] = {}
+ self._procs: Dict[int, ForkProcess] = {}
# list of receiving pipe connections of running subprocesses
self._precvs: Dict[int, Any] = {}
# list of receiving pipe connections of waiting subprocesses
diff --git a/sphinx/util/stemmer/__init__.py b/sphinx/util/stemmer/__init__.py
index ff6c365c7..6d27592d8 100644
--- a/sphinx/util/stemmer/__init__.py
+++ b/sphinx/util/stemmer/__init__.py
@@ -1,37 +1,62 @@
"""Word stemming utilities for Sphinx."""
-from sphinx.util.stemmer.porter import PorterStemmer
+import warnings
-try:
- from Stemmer import Stemmer as _PyStemmer
- PYSTEMMER = True
-except ImportError:
- PYSTEMMER = False
+import snowballstemmer
+
+from sphinx.deprecation import RemovedInSphinx70Warning
+
+
+class PorterStemmer:
+ def __init__(self):
+ warnings.warn(f"{self.__class__.__name__} is deprecated, use "
+ "snowballstemmer.stemmer('porter') instead.",
+ RemovedInSphinx70Warning, stacklevel=2)
+ self.stemmer = snowballstemmer.stemmer('porter')
+
+ def stem(self, p: str, i: int, j: int) -> str:
+ warnings.warn(f"{self.__class__.__name__}.stem() is deprecated, use "
+ "snowballstemmer.stemmer('porter').stemWord() instead.",
+ RemovedInSphinx70Warning, stacklevel=2)
+ return self.stemmer.stemWord(p)
class BaseStemmer:
+ def __init__(self):
+ warnings.warn(f"{self.__class__.__name__} is deprecated, use "
+ "snowballstemmer.stemmer('porter') instead.",
+ RemovedInSphinx70Warning, stacklevel=3)
+
def stem(self, word: str) -> str:
- raise NotImplementedError()
+ raise NotImplementedError
class PyStemmer(BaseStemmer):
- def __init__(self) -> None:
- self.stemmer = _PyStemmer('porter')
+ def __init__(self): # NoQA
+ super().__init__()
+ self.stemmer = snowballstemmer.stemmer('porter')
def stem(self, word: str) -> str:
+ warnings.warn(f"{self.__class__.__name__}.stem() is deprecated, use "
+ "snowballstemmer.stemmer('porter').stemWord() instead.",
+ RemovedInSphinx70Warning, stacklevel=2)
return self.stemmer.stemWord(word)
-class StandardStemmer(PorterStemmer, BaseStemmer):
- """All those porter stemmer implementations look hideous;
- make at least the stem method nicer.
- """
- def stem(self, word: str) -> str: # type: ignore
- return super().stem(word, 0, len(word) - 1)
+class StandardStemmer(BaseStemmer):
+ def __init__(self): # NoQA
+ super().__init__()
+ self.stemmer = snowballstemmer.stemmer('porter')
+
+ def stem(self, word: str) -> str:
+ warnings.warn(f"{self.__class__.__name__}.stem() is deprecated, use "
+ "snowballstemmer.stemmer('porter').stemWord() instead.",
+ RemovedInSphinx70Warning, stacklevel=2)
+ return self.stemmer.stemWord(word)
def get_stemmer() -> BaseStemmer:
- if PYSTEMMER:
- return PyStemmer()
- else:
- return StandardStemmer()
+ warnings.warn("get_stemmer() is deprecated, use "
+ "snowballstemmer.stemmer('porter') instead.",
+ RemovedInSphinx70Warning, stacklevel=2)
+ return PyStemmer()
diff --git a/sphinx/util/stemmer/porter.py b/sphinx/util/stemmer/porter.py
deleted file mode 100644
index c4f89eb95..000000000
--- a/sphinx/util/stemmer/porter.py
+++ /dev/null
@@ -1,406 +0,0 @@
-"""Porter Stemming Algorithm
-
-This is the Porter stemming algorithm, ported to Python from the
-version coded up in ANSI C by the author. It may be be regarded
-as canonical, in that it follows the algorithm presented in
-
-Porter, 1980, An algorithm for suffix stripping, Program, Vol. 14,
-no. 3, pp 130-137,
-
-only differing from it at the points made --DEPARTURE-- below.
-
-See also https://tartarus.org/martin/PorterStemmer/
-
-The algorithm as described in the paper could be exactly replicated
-by adjusting the points of DEPARTURE, but this is barely necessary,
-because (a) the points of DEPARTURE are definitely improvements, and
-(b) no encoding of the Porter stemmer I have seen is anything like
-as exact as this version, even with the points of DEPARTURE!
-
-Release 1: January 2001
-
-:author: Vivake Gupta <v@nano.com>.
-:license: Public Domain ("can be used free of charge for any purpose").
-"""
-
-
-class PorterStemmer:
-
- def __init__(self) -> None:
- """The main part of the stemming algorithm starts here.
- b is a buffer holding a word to be stemmed. The letters are in b[k0],
- b[k0+1] ... ending at b[k]. In fact k0 = 0 in this demo program. k is
- readjusted downwards as the stemming progresses. Zero termination is
- not in fact used in the algorithm.
-
- Note that only lower case sequences are stemmed. Forcing to lower case
- should be done before stem(...) is called.
- """
-
- self.b = "" # buffer for word to be stemmed
- self.k = 0
- self.k0 = 0
- self.j = 0 # j is a general offset into the string
-
- def cons(self, i: int) -> int:
- """cons(i) is TRUE <=> b[i] is a consonant."""
- if self.b[i] == 'a' or self.b[i] == 'e' or self.b[i] == 'i' \
- or self.b[i] == 'o' or self.b[i] == 'u':
- return 0
- if self.b[i] == 'y':
- if i == self.k0:
- return 1
- else:
- return (not self.cons(i - 1))
- return 1
-
- def m(self) -> int:
- """m() measures the number of consonant sequences between k0 and j.
- if c is a consonant sequence and v a vowel sequence, and <..>
- indicates arbitrary presence,
-
- <c><v> gives 0
- <c>vc<v> gives 1
- <c>vcvc<v> gives 2
- <c>vcvcvc<v> gives 3
- ....
- """
- n = 0
- i = self.k0
- while 1:
- if i > self.j:
- return n
- if not self.cons(i):
- break
- i = i + 1
- i = i + 1
- while 1:
- while 1:
- if i > self.j:
- return n
- if self.cons(i):
- break
- i = i + 1
- i = i + 1
- n = n + 1
- while 1:
- if i > self.j:
- return n
- if not self.cons(i):
- break
- i = i + 1
- i = i + 1
-
- def vowelinstem(self) -> int:
- """vowelinstem() is TRUE <=> k0,...j contains a vowel"""
- for i in range(self.k0, self.j + 1):
- if not self.cons(i):
- return 1
- return 0
-
- def doublec(self, j: int) -> int:
- """doublec(j) is TRUE <=> j,(j-1) contain a double consonant."""
- if j < (self.k0 + 1):
- return 0
- if (self.b[j] != self.b[j - 1]):
- return 0
- return self.cons(j)
-
- def cvc(self, i: int) -> int:
- """cvc(i) is TRUE <=> i-2,i-1,i has the form
- consonant - vowel - consonant
- and also if the second c is not w,x or y. this is used when trying to
- restore an e at the end of a short e.g.
-
- cav(e), lov(e), hop(e), crim(e), but
- snow, box, tray.
- """
- if i < (self.k0 + 2) or not self.cons(i) or self.cons(i - 1) \
- or not self.cons(i - 2):
- return 0
- ch = self.b[i]
- if ch in ('w', 'x', 'y'):
- return 0
- return 1
-
- def ends(self, s: str) -> int:
- """ends(s) is TRUE <=> k0,...k ends with the string s."""
- length = len(s)
- if s[length - 1] != self.b[self.k]: # tiny speed-up
- return 0
- if length > (self.k - self.k0 + 1):
- return 0
- if self.b[self.k - length + 1:self.k + 1] != s:
- return 0
- self.j = self.k - length
- return 1
-
- def setto(self, s: str) -> None:
- """setto(s) sets (j+1),...k to the characters in the string s,
- readjusting k."""
- length = len(s)
- self.b = self.b[:self.j + 1] + s + self.b[self.j + length + 1:]
- self.k = self.j + length
-
- def r(self, s: str) -> None:
- """r(s) is used further down."""
- if self.m() > 0:
- self.setto(s)
-
- def step1ab(self) -> None:
- """step1ab() gets rid of plurals and -ed or -ing. e.g.
-
- caresses -> caress
- ponies -> poni
- ties -> ti
- caress -> caress
- cats -> cat
-
- feed -> feed
- agreed -> agree
- disabled -> disable
-
- matting -> mat
- mating -> mate
- meeting -> meet
- milling -> mill
- messing -> mess
-
- meetings -> meet
- """
- if self.b[self.k] == 's':
- if self.ends("sses"):
- self.k = self.k - 2
- elif self.ends("ies"):
- self.setto("i")
- elif self.b[self.k - 1] != 's':
- self.k = self.k - 1
- if self.ends("eed"):
- if self.m() > 0:
- self.k = self.k - 1
- elif (self.ends("ed") or self.ends("ing")) and self.vowelinstem():
- self.k = self.j
- if self.ends("at"):
- self.setto("ate")
- elif self.ends("bl"):
- self.setto("ble")
- elif self.ends("iz"):
- self.setto("ize")
- elif self.doublec(self.k):
- self.k = self.k - 1
- ch = self.b[self.k]
- if ch in ('l', 's', 'z'):
- self.k = self.k + 1
- elif (self.m() == 1 and self.cvc(self.k)):
- self.setto("e")
-
- def step1c(self) -> None:
- """step1c() turns terminal y to i when there is another vowel in
- the stem."""
- if (self.ends("y") and self.vowelinstem()):
- self.b = self.b[:self.k] + 'i' + self.b[self.k + 1:]
-
- def step2(self) -> None:
- """step2() maps double suffices to single ones.
- so -ization ( = -ize plus -ation) maps to -ize etc. note that the
- string before the suffix must give m() > 0.
- """
- if self.b[self.k - 1] == 'a':
- if self.ends("ational"):
- self.r("ate")
- elif self.ends("tional"):
- self.r("tion")
- elif self.b[self.k - 1] == 'c':
- if self.ends("enci"):
- self.r("ence")
- elif self.ends("anci"):
- self.r("ance")
- elif self.b[self.k - 1] == 'e':
- if self.ends("izer"):
- self.r("ize")
- elif self.b[self.k - 1] == 'l':
- if self.ends("bli"):
- self.r("ble") # --DEPARTURE--
- # To match the published algorithm, replace this phrase with
- # if self.ends("abli"): self.r("able")
- elif self.ends("alli"):
- self.r("al")
- elif self.ends("entli"):
- self.r("ent")
- elif self.ends("eli"):
- self.r("e")
- elif self.ends("ousli"):
- self.r("ous")
- elif self.b[self.k - 1] == 'o':
- if self.ends("ization"):
- self.r("ize")
- elif self.ends("ation"):
- self.r("ate")
- elif self.ends("ator"):
- self.r("ate")
- elif self.b[self.k - 1] == 's':
- if self.ends("alism"):
- self.r("al")
- elif self.ends("iveness"):
- self.r("ive")
- elif self.ends("fulness"):
- self.r("ful")
- elif self.ends("ousness"):
- self.r("ous")
- elif self.b[self.k - 1] == 't':
- if self.ends("aliti"):
- self.r("al")
- elif self.ends("iviti"):
- self.r("ive")
- elif self.ends("biliti"):
- self.r("ble")
- elif self.b[self.k - 1] == 'g': # --DEPARTURE--
- if self.ends("logi"):
- self.r("log")
- # To match the published algorithm, delete this phrase
-
- def step3(self) -> None:
- """step3() dels with -ic-, -full, -ness etc. similar strategy
- to step2."""
- if self.b[self.k] == 'e':
- if self.ends("icate"):
- self.r("ic")
- elif self.ends("ative"):
- self.r("")
- elif self.ends("alize"):
- self.r("al")
- elif self.b[self.k] == 'i':
- if self.ends("iciti"):
- self.r("ic")
- elif self.b[self.k] == 'l':
- if self.ends("ical"):
- self.r("ic")
- elif self.ends("ful"):
- self.r("")
- elif self.b[self.k] == 's':
- if self.ends("ness"):
- self.r("")
-
- def step4(self) -> None:
- """step4() takes off -ant, -ence etc., in context <c>vcvc<v>."""
- if self.b[self.k - 1] == 'a':
- if self.ends("al"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 'c':
- if self.ends("ance"):
- pass
- elif self.ends("ence"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 'e':
- if self.ends("er"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 'i':
- if self.ends("ic"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 'l':
- if self.ends("able"):
- pass
- elif self.ends("ible"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 'n':
- if self.ends("ant"):
- pass
- elif self.ends("ement"):
- pass
- elif self.ends("ment"):
- pass
- elif self.ends("ent"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 'o':
- if self.ends("ion") and (self.b[self.j] == 's' or
- self.b[self.j] == 't'):
- pass
- elif self.ends("ou"):
- pass
- # takes care of -ous
- else:
- return
- elif self.b[self.k - 1] == 's':
- if self.ends("ism"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 't':
- if self.ends("ate"):
- pass
- elif self.ends("iti"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 'u':
- if self.ends("ous"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 'v':
- if self.ends("ive"):
- pass
- else:
- return
- elif self.b[self.k - 1] == 'z':
- if self.ends("ize"):
- pass
- else:
- return
- else:
- return
- if self.m() > 1:
- self.k = self.j
-
- def step5(self) -> None:
- """step5() removes a final -e if m() > 1, and changes -ll to -l if
- m() > 1.
- """
- self.j = self.k
- if self.b[self.k] == 'e':
- a = self.m()
- if a > 1 or (a == 1 and not self.cvc(self.k - 1)):
- self.k = self.k - 1
- if self.b[self.k] == 'l' and self.doublec(self.k) and self.m() > 1:
- self.k = self.k - 1
-
- def stem(self, p: str, i: int, j: int) -> str:
- """In stem(p,i,j), p is a char pointer, and the string to be stemmed
- is from p[i] to p[j] inclusive. Typically i is zero and j is the
- offset to the last character of a string, (p[j+1] == '\0'). The
- stemmer adjusts the characters p[i] ... p[j] and returns the new
- end-point of the string, k. Stemming never increases word length, so
- i <= k <= j. To turn the stemmer into a module, declare 'stem' as
- extern, and delete the remainder of this file.
- """
- # copy the parameters into statics
- self.b = p
- self.k = j
- self.k0 = i
- if self.k <= self.k0 + 1:
- return self.b # --DEPARTURE--
-
- # With this line, strings of length 1 or 2 don't go through the
- # stemming process, although no mention is made of this in the
- # published algorithm. Remove the line to match the published
- # algorithm.
-
- self.step1ab()
- self.step1c()
- self.step2()
- self.step3()
- self.step4()
- self.step5()
- return self.b[self.k0:self.k + 1]
diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py
index 62bd462b8..430d22d16 100644
--- a/sphinx/util/typing.py
+++ b/sphinx/util/typing.py
@@ -31,11 +31,6 @@ try:
except ImportError:
UnionType = None
-if False:
- # For type annotation
- from typing import Type # NOQA # for python3.5.1
-
-
# builtin classes that have incorrect __module__
INVALID_BUILTIN_CLASSES = {
Struct: 'struct.Struct', # Before Python 3.9
diff --git a/tests/roots/test-root/objects.txt b/tests/roots/test-root/objects.txt
index 7e2db1bb8..a4a5c667c 100644
--- a/tests/roots/test-root/objects.txt
+++ b/tests/roots/test-root/objects.txt
@@ -194,6 +194,15 @@ Link to :option:`perl +p`, :option:`--ObjC++`, :option:`--plugin.option`, :optio
Link to :option:`hg commit` and :option:`git commit -p`.
+.. option:: --abi={TYPE}
+
+.. option:: --test={WHERE}-{COUNT}
+
+.. option:: --wrap=\{\{value\}\}
+
+.. option:: -allowable_client {client_name}
+
+Foo bar.
User markup
===========
diff --git a/tests/test_application.py b/tests/test_application.py
index 365fff8ea..90758a939 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -1,15 +1,46 @@
"""Test the Sphinx class."""
+import shutil
+import sys
+from io import StringIO
+from pathlib import Path
from unittest.mock import Mock
import pytest
from docutils import nodes
+import sphinx.application
from sphinx.errors import ExtensionError
-from sphinx.testing.util import strip_escseq
+from sphinx.testing.path import path
+from sphinx.testing.util import SphinxTestApp, strip_escseq
from sphinx.util import logging
+def test_instantiation(tmp_path_factory, rootdir: str, monkeypatch):
+ # Given
+ src_dir = tmp_path_factory.getbasetemp() / 'root'
+
+ # special support for sphinx/tests
+ if rootdir and not src_dir.exists():
+ shutil.copytree(Path(str(rootdir)) / 'test-root', src_dir)
+
+ monkeypatch.setattr('sphinx.application.abspath', lambda x: x)
+
+ syspath = sys.path[:]
+
+ # When
+ app_ = SphinxTestApp(
+ srcdir=path(src_dir),
+ status=StringIO(),
+ warning=StringIO()
+ )
+ sys.path[:] = syspath
+ app_.cleanup()
+
+ # Then
+ assert isinstance(app_, sphinx.application.Sphinx)
+
+
def test_events(app, status, warning):
def empty():
pass
diff --git a/tests/test_build_html.py b/tests/test_build_html.py
index 6a653664e..cb036a1af 100644
--- a/tests/test_build_html.py
+++ b/tests/test_build_html.py
@@ -39,7 +39,7 @@ with "\\?": b?'here: >>>(\\\\|/)xbb<<<((\\\\|/)r)?'
"""
HTML_WARNINGS = ENV_WARNINGS + """\
-%(root)s/index.rst:\\d+: WARNING: unknown option: &option
+%(root)s/index.rst:\\d+: WARNING: unknown option: '&option'
%(root)s/index.rst:\\d+: WARNING: citation not found: missing
%(root)s/index.rst:\\d+: WARNING: a suitable image for html builder not found: foo.\\*
%(root)s/index.rst:\\d+: WARNING: Could not lex literal_block as "c". Highlighting skipped.
@@ -1719,3 +1719,25 @@ def test_html_code_role(app):
assert ('<div class="highlight-python notranslate">' +
'<div class="highlight"><pre><span></span>' +
common_content) in content
+
+
+@pytest.mark.sphinx('html', testroot='root',
+ confoverrides={'option_emphasise_placeholders': True})
+def test_option_emphasise_placeholders(app, status, warning):
+ app.build()
+ content = (app.outdir / 'objects.html').read_text()
+ assert '<em><span class="pre">TYPE</span></em>' in content
+ assert '{TYPE}' not in content
+ assert ('<em><span class="pre">WHERE</span></em>'
+ '<span class="pre">-</span>'
+ '<em><span class="pre">COUNT</span></em>' in content)
+ assert '<span class="pre">{{value}}</span>' in content
+
+
+@pytest.mark.sphinx('html', testroot='root')
+def test_option_emphasise_placeholders_default(app, status, warning):
+ app.build()
+ content = (app.outdir / 'objects.html').read_text()
+ assert '<span class="pre">={TYPE}</span>' in content
+ assert '<span class="pre">={WHERE}-{COUNT}</span></span>' in content
+ assert '<span class="pre">{client_name}</span>' in content
diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py
index 709dce05d..9a325a8d4 100644
--- a/tests/test_build_latex.py
+++ b/tests/test_build_latex.py
@@ -25,7 +25,7 @@ STYLEFILES = ['article.cls', 'fancyhdr.sty', 'titlesec.sty', 'amsmath.sty',
'fncychap.sty', 'geometry.sty', 'kvoptions.sty', 'hyperref.sty']
LATEX_WARNINGS = ENV_WARNINGS + """\
-%(root)s/index.rst:\\d+: WARNING: unknown option: &option
+%(root)s/index.rst:\\d+: WARNING: unknown option: '&option'
%(root)s/index.rst:\\d+: WARNING: citation not found: missing
%(root)s/index.rst:\\d+: WARNING: a suitable image for latex builder not found: foo.\\*
%(root)s/index.rst:\\d+: WARNING: Could not lex literal_block as "c". Highlighting skipped.
diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py
index 772644abe..5c72a3449 100644
--- a/tests/test_build_texinfo.py
+++ b/tests/test_build_texinfo.py
@@ -17,7 +17,7 @@ from sphinx.writers.texinfo import TexinfoTranslator
from .test_build_html import ENV_WARNINGS
TEXINFO_WARNINGS = ENV_WARNINGS + """\
-%(root)s/index.rst:\\d+: WARNING: unknown option: &option
+%(root)s/index.rst:\\d+: WARNING: unknown option: '&option'
%(root)s/index.rst:\\d+: WARNING: citation not found: missing
%(root)s/index.rst:\\d+: WARNING: a suitable image for texinfo builder not found: foo.\\*
%(root)s/index.rst:\\d+: WARNING: a suitable image for texinfo builder not found: \
diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py
index 014067e84..ce1636eb2 100644
--- a/tests/test_domain_py.py
+++ b/tests/test_domain_py.py
@@ -452,6 +452,33 @@ def test_pyfunction_signature_full(app):
[desc_sig_name, pending_xref, "str"])])])
+def test_pyfunction_with_unary_operators(app):
+ text = ".. py:function:: menu(egg=+1, bacon=-1, sausage=~1, spam=not spam)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "egg"],
+ [desc_sig_operator, "="],
+ [nodes.inline, "+1"])],
+ [desc_parameter, ([desc_sig_name, "bacon"],
+ [desc_sig_operator, "="],
+ [nodes.inline, "-1"])],
+ [desc_parameter, ([desc_sig_name, "sausage"],
+ [desc_sig_operator, "="],
+ [nodes.inline, "~1"])],
+ [desc_parameter, ([desc_sig_name, "spam"],
+ [desc_sig_operator, "="],
+ [nodes.inline, "not spam"])])])
+
+
+def test_pyfunction_with_binary_operators(app):
+ text = ".. py:function:: menu(spam=2**64)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "spam"],
+ [desc_sig_operator, "="],
+ [nodes.inline, "2**64"])])])
+
+
@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
@@ -1342,6 +1369,6 @@ def test_python_python_use_unqualified_type_names_disabled(app, status, warning)
@pytest.mark.sphinx('dummy', testroot='domain-py-xref-warning')
def test_warn_missing_reference(app, status, warning):
app.build()
- assert 'index.rst:6: WARNING: undefined label: no-label' in warning.getvalue()
- assert ('index.rst:6: WARNING: Failed to create a cross reference. A title or caption not found: existing-label'
- in warning.getvalue())
+ assert "index.rst:6: WARNING: undefined label: 'no-label'" in warning.getvalue()
+ assert ("index.rst:6: WARNING: Failed to create a cross reference. "
+ "A title or caption not found: 'existing-label'") in warning.getvalue()
diff --git a/tests/test_environment.py b/tests/test_environment.py
index 7ffca7898..c6f6b5aba 100644
--- a/tests/test_environment.py
+++ b/tests/test_environment.py
@@ -49,8 +49,7 @@ def test_images(app):
app.build()
tree = app.env.get_doctree('images')
- htmlbuilder = StandaloneHTMLBuilder(app)
- htmlbuilder.set_environment(app.env)
+ htmlbuilder = StandaloneHTMLBuilder(app, app.env)
htmlbuilder.init()
htmlbuilder.imgpath = 'dummy'
htmlbuilder.post_process_images(tree)
@@ -59,8 +58,7 @@ def test_images(app):
assert set(htmlbuilder.images.values()) == \
{'img.png', 'img1.png', 'simg.png', 'svgimg.svg', 'img.foo.png'}
- latexbuilder = LaTeXBuilder(app)
- latexbuilder.set_environment(app.env)
+ latexbuilder = LaTeXBuilder(app, app.env)
latexbuilder.init()
latexbuilder.post_process_images(tree)
assert set(latexbuilder.images.keys()) == \
diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py
index 588bcac18..60a9826fd 100644
--- a/tests/test_environment_toctree.py
+++ b/tests/test_environment_toctree.py
@@ -156,7 +156,7 @@ def test_get_toc_for(app):
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
def test_get_toc_for_only(app):
app.build()
- builder = StandaloneHTMLBuilder(app)
+ builder = StandaloneHTMLBuilder(app, app.env)
toctree = TocTree(app.env).get_toc_for('index', builder)
assert_node(toctree,
diff --git a/tests/test_ext_autosectionlabel.py b/tests/test_ext_autosectionlabel.py
index f950b8f1d..f99a6d3f6 100644
--- a/tests/test_ext_autosectionlabel.py
+++ b/tests/test_ext_autosectionlabel.py
@@ -74,4 +74,4 @@ def test_autosectionlabel_maxdepth(app, status, warning):
html = '<li><p><span class="xref std std-ref">Linux</span></p></li>'
assert re.search(html, content, re.S)
- assert 'WARNING: undefined label: linux' in warning.getvalue()
+ assert "WARNING: undefined label: 'linux'" in warning.getvalue()
diff --git a/tests/test_markup.py b/tests/test_markup.py
index 9e6165a5f..f15761c5e 100644
--- a/tests/test_markup.py
+++ b/tests/test_markup.py
@@ -106,8 +106,7 @@ def verify_re_html(app, parse):
def verify_re_latex(app, parse):
def verify(rst, latex_expected):
document = parse(rst)
- app.builder = LaTeXBuilder(app)
- app.builder.set_environment(app.env)
+ app.builder = LaTeXBuilder(app, app.env)
app.builder.init()
theme = app.builder.themes.get('manual')
latex_translator = ForgivingLaTeXTranslator(document, app.builder, theme)
diff --git a/tests/test_pycode_ast.py b/tests/test_pycode_ast.py
index 6143105eb..31018baca 100644
--- a/tests/test_pycode_ast.py
+++ b/tests/test_pycode_ast.py
@@ -25,7 +25,7 @@ from sphinx.pycode import ast
("...", "..."), # Ellipsis
("a // b", "a // b"), # FloorDiv
("Tuple[int, int]", "Tuple[int, int]"), # Index, Subscript
- ("~ 1", "~ 1"), # Invert
+ ("~1", "~1"), # Invert
("lambda x, y: x + y",
"lambda x, y: ..."), # Lambda
("[1, 2, 3]", "[1, 2, 3]"), # List
@@ -37,14 +37,14 @@ from sphinx.pycode import ast
("1234", "1234"), # Num
("not a", "not a"), # Not
("a or b", "a or b"), # Or
- ("a ** b", "a ** b"), # Pow
+ ("a**b", "a**b"), # Pow
("a >> b", "a >> b"), # RShift
("{1, 2, 3}", "{1, 2, 3}"), # Set
("a - b", "a - b"), # Sub
("'str'", "'str'"), # Str
- ("+ a", "+ a"), # UAdd
- ("- 1", "- 1"), # UnaryOp
- ("- a", "- a"), # USub
+ ("+a", "+a"), # UAdd
+ ("-1", "-1"), # UnaryOp
+ ("-a", "-a"), # USub
("(1, 2, 3)", "(1, 2, 3)"), # Tuple
("()", "()"), # Tuple (empty)
("(1,)", "(1,)"), # Tuple (single item)
diff --git a/tests/test_pycode_parser.py b/tests/test_pycode_parser.py
index 5d2496ba5..fde648d35 100644
--- a/tests/test_pycode_parser.py
+++ b/tests/test_pycode_parser.py
@@ -111,6 +111,9 @@ def test_complex_assignment():
'f = g = None #: multiple assignment at once\n'
'(theta, phi) = (0, 0.5) #: unpack assignment via tuple\n'
'[x, y] = (5, 6) #: unpack assignment via list\n'
+ 'h, *i, j = (1, 2, 3, 4) #: unpack assignment2\n'
+ 'k, *self.attr = (5, 6, 7) #: unpack assignment3\n'
+ 'l, *m[0] = (8, 9, 0) #: unpack assignment4\n'
)
parser = Parser(source)
parser.parse()
@@ -124,22 +127,11 @@ def test_complex_assignment():
('', 'phi'): 'unpack assignment via tuple',
('', 'x'): 'unpack assignment via list',
('', 'y'): 'unpack assignment via list',
- }
- assert parser.definitions == {}
-
-
-def test_complex_assignment_py3():
- source = ('a, *b, c = (1, 2, 3, 4) #: unpack assignment\n'
- 'd, *self.attr = (5, 6, 7) #: unpack assignment2\n'
- 'e, *f[0] = (8, 9, 0) #: unpack assignment3\n'
- )
- parser = Parser(source)
- parser.parse()
- assert parser.comments == {('', 'a'): 'unpack assignment',
- ('', 'b'): 'unpack assignment',
- ('', 'c'): 'unpack assignment',
- ('', 'd'): 'unpack assignment2',
- ('', 'e'): 'unpack assignment3',
+ ('', 'h'): 'unpack assignment2',
+ ('', 'i'): 'unpack assignment2',
+ ('', 'j'): 'unpack assignment2',
+ ('', 'k'): 'unpack assignment3',
+ ('', 'l'): 'unpack assignment4',
}
assert parser.definitions == {}
diff --git a/tests/test_util_logging.py b/tests/test_util_logging.py
index 49cd2c11e..b9756f947 100644
--- a/tests/test_util_logging.py
+++ b/tests/test_util_logging.py
@@ -2,13 +2,14 @@
import codecs
import os
+import os.path
import pytest
from docutils import nodes
from sphinx.errors import SphinxWarning
from sphinx.testing.util import strip_escseq
-from sphinx.util import logging
+from sphinx.util import logging, osutil
from sphinx.util.console import colorize
from sphinx.util.logging import is_suppressed_warning, prefixed_warnings
from sphinx.util.parallel import ParallelTasks
@@ -379,3 +380,18 @@ def test_prefixed_warnings(app, status, warning):
assert 'WARNING: Another PREFIX: message3' in warning.getvalue()
assert 'WARNING: PREFIX: message4' in warning.getvalue()
assert 'WARNING: message5' in warning.getvalue()
+
+
+def test_get_node_location_abspath():
+ # Ensure that node locations are reported as an absolute path,
+ # even if the source attribute is a relative path.
+
+ relative_filename = os.path.join('relative', 'path.txt')
+ absolute_filename = osutil.abspath(relative_filename)
+
+ n = nodes.Node()
+ n.source = relative_filename
+
+ location = logging.get_node_location(n)
+
+ assert location == absolute_filename + ':'
diff --git a/tox.ini b/tox.ini
index 105a02597..99ea3f9dc 100644
--- a/tox.ini
+++ b/tox.ini
@@ -81,9 +81,9 @@ basepython = python3
description =
Lint documentation.
extras =
- docs
+ lint
commands =
- python utils/doclinter.py CHANGES CONTRIBUTING.rst README.rst doc/
+ sphinx-lint --disable missing-space-after-literal --enable line-too-long --max-line-length 85 CHANGES CONTRIBUTING.rst README.rst doc/
[testenv:twine]
basepython = python3
diff --git a/utils/doclinter.py b/utils/doclinter.py
deleted file mode 100644
index d67a49b05..000000000
--- a/utils/doclinter.py
+++ /dev/null
@@ -1,77 +0,0 @@
-"""A linter for Sphinx docs"""
-
-import os
-import re
-import sys
-from typing import List
-
-MAX_LINE_LENGTH = 85
-LONG_INTERPRETED_TEXT = re.compile(r'^\s*\W*(:(\w+:)+)?`.*`\W*$')
-CODE_BLOCK_DIRECTIVE = re.compile(r'^(\s*)\.\. code-block::')
-LEADING_SPACES = re.compile(r'^(\s*)')
-
-
-def lint(path: str) -> int:
- with open(path, encoding='utf-8') as f:
- document = f.readlines()
-
- errors = 0
- in_code_block = False
- code_block_depth = 0
- for i, line in enumerate(document):
- if line.endswith(' '):
- print('%s:%d: the line ends with whitespace.' %
- (path, i + 1))
- errors += 1
-
- matched = CODE_BLOCK_DIRECTIVE.match(line)
- if matched:
- in_code_block = True
- code_block_depth = len(matched.group(1))
- elif in_code_block:
- if line.strip() == '':
- pass
- else:
- spaces = LEADING_SPACES.match(line).group(1)
- if len(spaces) <= code_block_depth:
- in_code_block = False
- elif LONG_INTERPRETED_TEXT.match(line):
- pass
- elif len(line) > MAX_LINE_LENGTH:
- if re.match(r'^\s*\.\. ', line):
- # ignore directives and hyperlink targets
- pass
- elif re.match(r'^\s*__ ', line):
- # ignore anonymous hyperlink targets
- pass
- elif re.match(r'^\s*``[^`]+``$', line):
- # ignore a very long literal string
- pass
- else:
- print('%s:%d: the line is too long (%d > %d).' %
- (path, i + 1, len(line), MAX_LINE_LENGTH))
- errors += 1
-
- return errors
-
-
-def main(args: List[str]) -> int:
- errors = 0
- for path in args:
- if os.path.isfile(path):
- errors += lint(path)
- elif os.path.isdir(path):
- for root, _dirs, files in os.walk(path):
- for filename in files:
- if filename.endswith('.rst'):
- path = os.path.join(root, filename)
- errors += lint(path)
-
- if errors:
- return 1
- else:
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))