mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '5.x'
# Conflicts: # .github/workflows/builddoc.yml # .github/workflows/lint.yml # sphinx/registry.py
This commit is contained in:
commit
7e38d2dad8
6
.github/workflows/builddoc.yml
vendored
6
.github/workflows/builddoc.yml
vendored
@ -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
|
||||
|
4
.github/workflows/coverage.yml
vendored
4
.github/workflows/coverage.yml
vendored
@ -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
|
||||
|
||||
|
2
.github/workflows/create-release.yml
vendored
2
.github/workflows/create-release.yml
vendored
@ -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/')
|
||||
|
6
.github/workflows/docutils-latest.yml
vendored
6
.github/workflows/docutils-latest.yml
vendored
@ -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
|
||||
|
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@ -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
|
||||
|
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -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
|
||||
|
4
.github/workflows/nodejs.yml
vendored
4
.github/workflows/nodejs.yml
vendored
@ -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"
|
||||
|
8
.github/workflows/transifex.yml
vendored
8
.github/workflows/transifex.yml
vendored
@ -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
|
||||
|
4
AUTHORS
4
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
|
||||
|
16
CHANGES
16
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
|
||||
|
2
Makefile
2
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:
|
||||
|
@ -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`.
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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()``
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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/
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -118,7 +118,7 @@ Chocolatey
|
||||
::
|
||||
|
||||
$ choco install sphinx
|
||||
|
||||
|
||||
You would need to `install Chocolatey
|
||||
<https://chocolatey.org/install>`_
|
||||
before running this.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 =
|
||||
|
1
setup.py
1
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",
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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] = []
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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__)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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())
|
||||
|
@ -36,7 +36,7 @@
|
||||
{%- macro agogo_sidebar() %}
|
||||
{%- block sidebartoc %}
|
||||
<h3>{{ _('Table of Contents') }}</h3>
|
||||
{{ toctree() }}
|
||||
{{ toctree(includehidden=True) }}
|
||||
{%- endblock %}
|
||||
{%- block sidebarsearch %}
|
||||
<div role="search">
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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]
|
@ -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
|
||||
|
@ -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
|
||||
===========
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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: \
|
||||
|
@ -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()
|
||||
|
@ -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()) == \
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 == {}
|
||||
|
||||
|
@ -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 + ':'
|
||||
|
4
tox.ini
4
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
|
||||
|
@ -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:]))
|
Loading…
Reference in New Issue
Block a user