mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #7777 from tk0miya/master
merge 3.x to master and Fix mypy violations (with mypy-0.780)
This commit is contained in:
commit
ba93a41a72
@ -1,56 +0,0 @@
|
||||
environment:
|
||||
global:
|
||||
TEST: -v --durations 25
|
||||
PYTHONFAULTHANDLER: x
|
||||
PYTHONWARNINGS: all
|
||||
|
||||
matrix:
|
||||
- PYTHON: 37
|
||||
- PYTHON: 37-x64
|
||||
|
||||
install:
|
||||
- C:\Python%PYTHON%\python.exe -m pip install -U pip setuptools
|
||||
- C:\Python%PYTHON%\python.exe -m pip install .[test,websupport]
|
||||
|
||||
cache:
|
||||
- '%LOCALAPPDATA%\pip\Cache'
|
||||
|
||||
# No automatic build, just run python tests
|
||||
build: off
|
||||
|
||||
# Update build information before testing, no warnings during this step
|
||||
before_test:
|
||||
- ps: |
|
||||
$py_warnings = $env:PYTHONWARNINGS
|
||||
$env:PYTHONWARNINGS = 'ignore'
|
||||
Update-AppveyorBuild -Version ((& "C:\Python$($env:PYTHON)\python.exe" -m sphinx --version).Split(' ')[2])
|
||||
$env:PYTHONWARNINGS = $py_warnings
|
||||
|
||||
test_script:
|
||||
- ps: |
|
||||
Push-Location tests
|
||||
$test_ignore = $env:TEST_IGNORE
|
||||
if (-not $test_ignore) { $test_ignore = '' }
|
||||
$tests = $env:TEST
|
||||
if (-not $tests) { $tests = '' }
|
||||
& "C:\Python$($env:PYTHON)\python.exe" -m pytest $test_ignore.Split(' ') --junitxml .junit.xml $tests.Split(' ')
|
||||
Pop-Location
|
||||
if ($LastExitCode -eq 1) { Write-Host "Test Failures Occurred, leaving for test result parsing"; exit $LastExitCode }
|
||||
elseif ($LastExitCode -ne 0) { Write-Host "Other Error Occurred, aborting"; exit $LastExitCode }
|
||||
|
||||
after_test:
|
||||
- ps: (New-Object System.Net.WebClient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path (Join-Path tests .junit.xml)))
|
||||
|
||||
after_build:
|
||||
# Remove old or huge cache files to hopefully not exceed the 1GB cache limit.
|
||||
#
|
||||
# If the cache limit is reached, the cache will not be updated (of not even
|
||||
# created in the first run). So this is a trade of between keeping the cache
|
||||
# current and having a cache at all.
|
||||
# NB: This is done only `on_success` since the cache in uploaded only on
|
||||
# success anyway.
|
||||
- C:\cygwin\bin\find "%LOCALAPPDATA%\pip" -type f -mtime +360 -delete
|
||||
- C:\cygwin\bin\find "%LOCALAPPDATA%\pip" -type f -size +10M -delete
|
||||
- C:\cygwin\bin\find "%LOCALAPPDATA%\pip" -empty -delete
|
||||
# Show size of cache
|
||||
- C:\cygwin\bin\du -hs "%LOCALAPPDATA%\pip\Cache"
|
21
.github/workflows/main.yml
vendored
Normal file
21
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: CI on Windows
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
architecture: [x86, x64]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
architecture: ${{ matrix.architecture }}
|
||||
- name: Install dependencies
|
||||
run: pip install -U tox
|
||||
- name: Run Tox
|
||||
run: tox -e py
|
46
CHANGES
46
CHANGES
@ -41,6 +41,8 @@ Release 3.1.0 (in development)
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
* #7746: mathjax: Update to 2.7.5
|
||||
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
@ -82,9 +84,14 @@ Features added
|
||||
* #7487: autodoc: Allow to generate docs for singledispatch functions by
|
||||
py:autofunction
|
||||
* #7143: autodoc: Support final classes and methods
|
||||
* #7384: autodoc: Support signatures defined by ``__new__()``, metaclasses and
|
||||
builtin base classes
|
||||
* #2106: autodoc: Support multiple signatures on docstring
|
||||
* #4422: autodoc: Support GenericAlias in Python 3.7 or above
|
||||
* #7466: autosummary: headings in generated documents are not translated
|
||||
* #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a
|
||||
caption to the toctree
|
||||
* #7469: autosummary: Support module attributes
|
||||
* #248, #6040: autosummary: Add ``:recursive:`` option to autosummary directive
|
||||
to generate stub files recursively
|
||||
* #4030: autosummary: Add :confval:`autosummary_context` to add template
|
||||
@ -102,6 +109,7 @@ Features added
|
||||
* #7541: html theme: Add a "clearer" at the end of the "body"
|
||||
* #7542: html theme: Make admonition/topic/sidebar scrollable
|
||||
* #7543: html theme: Add top and bottom margins to tables
|
||||
* #7695: html theme: Add viewport meta tag for basic theme
|
||||
* C and C++: allow semicolon in the end of declarations.
|
||||
* C++, parse parameterized noexcept specifiers.
|
||||
* #7294: C++, parse expressions with user-defined literals.
|
||||
@ -110,6 +118,10 @@ Features added
|
||||
:rst:dir:`py:exception:` and :rst:dir:`py:method:` directives
|
||||
* #7596: py domain: Change a type annotation for variables to a hyperlink
|
||||
* #7582: napoleon: a type for attribute are represented like type annotation
|
||||
* #7734: napoleon: overescaped trailing underscore on attribute
|
||||
* #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow
|
||||
handlers to raise specified exceptions
|
||||
* #7295: C++, parse (trailing) requires clauses.
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
@ -131,9 +143,19 @@ Bugs fixed
|
||||
specified
|
||||
* #7650: autodoc: undecorated signature is shown for decorated functions
|
||||
* #7676: autodoc: typo in the default value of autodoc_member_order
|
||||
* #7676: autodoc: wrong value for :member-order: option is ignored silently
|
||||
* #7676: autodoc: member-order="bysource" does not work for C module
|
||||
* #3673: autodoc: member-order="bysource" does not work for a module having
|
||||
__all__
|
||||
* #7668: autodoc: wrong retann value is passed to a handler of
|
||||
autodoc-proccess-signature
|
||||
* #7711: autodoc: fails with ValueError when processing numpy objects
|
||||
* #7551: autosummary: a nested class is indexed as non-nested class
|
||||
* #7661: autosummary: autosummary directive emits warnings twices if failed to
|
||||
import the target module
|
||||
* #7685: autosummary: The template variable "members" contains imported members
|
||||
even if :confval:`autossummary_imported_members` is False
|
||||
* #7671: autosummary: The location of import failure warning is missing
|
||||
* #7535: sphinx-autogen: crashes when custom template uses inheritance
|
||||
* #7536: sphinx-autogen: crashes when template uses i18n feature
|
||||
* #7653: sphinx-quickstart: Fix multiple directory creation for nested relpath
|
||||
@ -143,15 +165,20 @@ Bugs fixed
|
||||
supporting images
|
||||
* #7610: incorrectly renders consecutive backslashes for docutils-0.16
|
||||
* #7646: handle errors on event handlers
|
||||
* #4187: LaTeX: EN DASH disappears from PDF bookmarks in Japanese documents
|
||||
* #7701: LaTeX: Anonymous indirect hyperlink target causes duplicated labels
|
||||
* #7756: py domain: The default value for positional only argument is not shown
|
||||
* C++, fix rendering and xrefs in nested names explicitly starting
|
||||
in global scope, e.g., ``::A::B``.
|
||||
* C, fix rendering and xrefs in nested names explicitly starting
|
||||
in global scope, e.g., ``.A.B``.
|
||||
* #7763: C and C++, don't crash during display stringification of unary
|
||||
expressions and fold expressions.
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.0.4 (in development)
|
||||
Release 3.0.5 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
@ -169,14 +196,21 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #7567: autodoc: parametrized types are shown twice for generic types
|
||||
* #7637: autodoc: system defined TypeVars are shown in Python 3.9
|
||||
* #7611: md5 fails when OpenSSL FIPS is enabled
|
||||
* #7626: release package does not contain ``CODE_OF_CONDUCT``
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.0.4 (released May 27, 2020)
|
||||
=====================================
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #7567: autodoc: parametrized types are shown twice for generic types
|
||||
* #7637: autodoc: system defined TypeVars are shown in Python 3.9
|
||||
* #7696: html: Updated jQuery version from 3.4.1 to 3.5.1 for security reasons
|
||||
* #7611: md5 fails when OpenSSL FIPS is enabled
|
||||
* #7626: release package does not contain ``CODE_OF_CONDUCT``
|
||||
|
||||
Release 3.0.3 (released Apr 26, 2020)
|
||||
=====================================
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
======================
|
||||
Sphinx Code of Conduct
|
||||
======================
|
||||
|
||||
Like the technical community as a whole, the Sphinx team and community is made
|
||||
up of volunteers from all over the world.
|
||||
Diversity is a strength, but it can also lead to communication issues and
|
||||
@ -25,7 +29,7 @@ adhere to.
|
||||
Not all of us will agree all the time, but disagreement is no excuse for poor
|
||||
behavior and poor manners. We might all experience some frustration now and
|
||||
then, but we cannot allow that frustration to turn into a personal attack.
|
||||
It’s important to remember that a community where people feel uncomfortable or
|
||||
It's important to remember that a community where people feel uncomfortable or
|
||||
threatened is not a productive one. Members of the Sphinx community should be
|
||||
respectful when dealing with other members as well as with people outside the
|
||||
Sphinx community.
|
||||
@ -57,20 +61,18 @@ adhere to.
|
||||
* **When we disagree, try to understand why.**
|
||||
Disagreements, both social and technical, happen all the time and Sphinx is no
|
||||
exception. It is important that we resolve disagreements and differing views
|
||||
constructively. Remember that we’re different. Different people have different
|
||||
constructively. Remember that we're different. Different people have different
|
||||
perspectives on issues. Being unable to understand why someone holds a
|
||||
viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to
|
||||
err and blaming each other doesn’t get us anywhere. Instead, focus on helping
|
||||
viewpoint doesn't mean that they're wrong. Don't forget that it is human to
|
||||
err and blaming each other doesn't get us anywhere. Instead, focus on helping
|
||||
to resolve issues and learning from mistakes.
|
||||
|
||||
This isn’t an exhaustive list of things that you can’t do.
|
||||
Rather, take it in the spirit in which it’s intended - a guide to make it easier
|
||||
This isn't an exhaustive list of things that you can't do.
|
||||
Rather, take it in the spirit in which it's intended - a guide to make it easier
|
||||
to enrich all of us and the technical communities in which we participate.
|
||||
This code of conduct applies to all spaces of the Sphinx community.
|
||||
|
||||
Attribution
|
||||
-----------
|
||||
.. rubric:: Attribution
|
||||
|
||||
Original text courtesy of the Speak Up! project:
|
||||
http://web.archive.org/web/20141109123859/http://speakup.io/coc.html.
|
||||
|
||||
|
460
CONTRIBUTING.rst
460
CONTRIBUTING.rst
@ -1,455 +1,17 @@
|
||||
.. highlight:: console
|
||||
|
||||
Sphinx Developer's Guide
|
||||
========================
|
||||
|
||||
.. topic:: Abstract
|
||||
|
||||
This document describes the development process of Sphinx, a documentation
|
||||
system used by developers to document systems used by other developers to
|
||||
develop other systems that may also be documented using Sphinx.
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
The Sphinx source code is managed using Git and is hosted on GitHub.
|
||||
|
||||
git clone git://github.com/sphinx-doc/sphinx
|
||||
|
||||
.. rubric:: Community
|
||||
|
||||
sphinx-users <sphinx-users@googlegroups.com>
|
||||
Mailing list for user support.
|
||||
|
||||
sphinx-dev <sphinx-dev@googlegroups.com>
|
||||
Mailing list for development related discussions.
|
||||
|
||||
#sphinx-doc on irc.freenode.net
|
||||
IRC channel for development questions and user support.
|
||||
|
||||
|
||||
Bug Reports and Feature Requests
|
||||
--------------------------------
|
||||
|
||||
If you have encountered a problem with Sphinx or have an idea for a new
|
||||
feature, please submit it to the `issue tracker`_ on GitHub or discuss it
|
||||
on the `sphinx-dev`_ mailing list.
|
||||
|
||||
For bug reports, please include the output produced during the build process
|
||||
and also the log file Sphinx creates after it encounters an unhandled
|
||||
exception. The location of this file should be shown towards the end of the
|
||||
error message.
|
||||
|
||||
Including or providing a link to the source files involved may help us fix the
|
||||
issue. If possible, try to create a minimal project that produces the error
|
||||
and post that instead.
|
||||
|
||||
.. _`issue tracker`: https://github.com/sphinx-doc/sphinx/issues
|
||||
.. _`sphinx-dev`: mailto:sphinx-dev@googlegroups.com
|
||||
|
||||
|
||||
======================
|
||||
Contributing to Sphinx
|
||||
----------------------
|
||||
======================
|
||||
|
||||
The recommended way for new contributors to submit code to Sphinx is to fork
|
||||
the repository on GitHub and then submit a pull request after
|
||||
committing the changes. The pull request will then need to be approved by one
|
||||
of the core developers before it is merged into the main repository.
|
||||
Interested in contributing to Sphinx? Hurrah! We welcome all forms of
|
||||
contribution, including code patches, documentation improvements and bug
|
||||
reports/feature requests.
|
||||
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a
|
||||
feature idea or a bug.
|
||||
#. If you feel uncomfortable or uncertain about an issue or your changes, feel
|
||||
free to email the *sphinx-dev* mailing list.
|
||||
#. Fork `the repository`_ on GitHub to start making your changes to the
|
||||
``master`` branch for next MAJOR version, or ``A.x`` branch for next
|
||||
MINOR version (see `Branch Model`_).
|
||||
#. Write a test which shows that the bug was fixed or that the feature works
|
||||
as expected.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and
|
||||
published. Make sure to add yourself to AUTHORS_ and the change to
|
||||
CHANGES_.
|
||||
Our contributing guide can be found online at:
|
||||
|
||||
.. _`the repository`: https://github.com/sphinx-doc/sphinx
|
||||
.. _AUTHORS: https://github.com/sphinx-doc/sphinx/blob/master/AUTHORS
|
||||
.. _CHANGES: https://github.com/sphinx-doc/sphinx/blob/master/CHANGES
|
||||
https://www.sphinx-doc.org/en/master/internals/contributing/
|
||||
|
||||
You can also browse it from this repository from
|
||||
``doc/internals/contributing/``
|
||||
|
||||
Getting Started
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
These are the basic steps needed to start developing on Sphinx.
|
||||
|
||||
#. Create an account on GitHub.
|
||||
|
||||
#. Fork the main Sphinx repository (`sphinx-doc/sphinx
|
||||
<https://github.com/sphinx-doc/sphinx>`_) using the GitHub interface.
|
||||
|
||||
#. Clone the forked repository to your machine. ::
|
||||
|
||||
git clone https://github.com/USERNAME/sphinx
|
||||
cd sphinx
|
||||
|
||||
#. Checkout the appropriate branch.
|
||||
|
||||
Sphinx adopts Semantic Versioning 2.0.0 (refs: https://semver.org/ ).
|
||||
|
||||
For changes that preserves backwards-compatibility of API and features,
|
||||
they should be included in the next MINOR release, use the ``A.x`` branch.
|
||||
::
|
||||
|
||||
git checkout A.x
|
||||
|
||||
For incompatible or other substantial changes that should wait until the
|
||||
next MAJOR release, use the ``master`` branch.
|
||||
|
||||
For urgent release, a new PATCH branch must be branched from the newest
|
||||
release tag (see `Branch Model`_ for detail).
|
||||
|
||||
#. Setup a virtual environment.
|
||||
|
||||
This is not necessary for unit testing, thanks to ``tox``, but it is
|
||||
necessary if you wish to run ``sphinx-build`` locally or run unit tests
|
||||
without the help of ``tox``. ::
|
||||
|
||||
virtualenv ~/.venv
|
||||
. ~/.venv/bin/activate
|
||||
pip install -e .
|
||||
|
||||
#. Create a new working branch. Choose any name you like. ::
|
||||
|
||||
git checkout -b feature-xyz
|
||||
|
||||
#. Hack, hack, hack.
|
||||
|
||||
For tips on working with the code, see the `Coding Guide`_.
|
||||
|
||||
#. Test, test, test.
|
||||
|
||||
Testing is best done through ``tox``, which provides a number of targets and
|
||||
allows testing against multiple different Python environments:
|
||||
|
||||
* To list all possible targets::
|
||||
|
||||
tox -av
|
||||
|
||||
* To run unit tests for a specific Python version, such as 3.6::
|
||||
|
||||
tox -e py36
|
||||
|
||||
* To run unit tests for a specific Python version and turn on deprecation
|
||||
warnings on so they're shown in the test output::
|
||||
|
||||
PYTHONWARNINGS=all tox -e py36
|
||||
|
||||
* To run code style and type checks::
|
||||
|
||||
tox -e mypy
|
||||
tox -e flake8
|
||||
|
||||
* Arguments to ``pytest`` can be passed via ``tox``, e.g. in order to run a
|
||||
particular test::
|
||||
|
||||
tox -e py36 tests/test_module.py::test_new_feature
|
||||
|
||||
* To build the documentation::
|
||||
|
||||
tox -e docs
|
||||
|
||||
* To build the documentation in multiple formats::
|
||||
|
||||
tox -e docs -- -b html,latexpdf
|
||||
|
||||
* To run JavaScript tests with `Karma <https://karma-runner.github.io>`_,
|
||||
execute the following commands (requires `Node.js <https://nodejs.org>`_)::
|
||||
|
||||
npm install
|
||||
npm run test
|
||||
|
||||
You can also test by installing dependencies in your local environment. ::
|
||||
|
||||
pip install .[test]
|
||||
|
||||
New unit tests should be included in the ``tests`` directory where
|
||||
necessary:
|
||||
|
||||
* For bug fixes, first add a test that fails without your changes and passes
|
||||
after they are applied.
|
||||
|
||||
* Tests that need a ``sphinx-build`` run should be integrated in one of the
|
||||
existing test modules if possible. New tests that to ``@with_app`` and
|
||||
then ``build_all`` for a few assertions are not good since *the test suite
|
||||
should not take more than a minute to run*.
|
||||
|
||||
#. Please add a bullet point to :file:`CHANGES` if the fix or feature is not
|
||||
trivial (small doc updates, typo fixes). Then commit::
|
||||
|
||||
git commit -m '#42: Add useful new feature that does this.'
|
||||
|
||||
GitHub recognizes certain phrases that can be used to automatically
|
||||
update the issue tracker.
|
||||
|
||||
For example::
|
||||
|
||||
git commit -m 'Closes #42: Fix invalid markup in docstring of Foo.bar.'
|
||||
|
||||
would close issue #42.
|
||||
|
||||
#. Push changes in the branch to your forked repository on GitHub. ::
|
||||
|
||||
git push origin feature-xyz
|
||||
|
||||
#. Submit a pull request from your branch to the respective branch (``master``
|
||||
or ``A.x``).
|
||||
|
||||
#. Wait for a core developer to review your changes.
|
||||
|
||||
|
||||
Translations
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The Sphinx core messages and documentations are translated on `Transifex
|
||||
<https://www.transifex.com/>`_. Please join `Sphinx project on Transifex
|
||||
<https://www.transifex.com/sphinx-doc/>`_ and translate them.
|
||||
|
||||
|
||||
Core Developers
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The core developers of Sphinx have write access to the main repository. They
|
||||
can commit changes, accept/reject pull requests, and manage items on the issue
|
||||
tracker.
|
||||
|
||||
You do not need to be a core developer or have write access to be involved in
|
||||
the development of Sphinx. You can submit patches or create pull requests
|
||||
from forked repositories and have a core developer add the changes for you.
|
||||
|
||||
The following are some general guidelines for core developers:
|
||||
|
||||
* Questionable or extensive changes should be submitted as a pull request
|
||||
instead of being committed directly to the main repository. The pull
|
||||
request should be reviewed by another core developer before it is merged.
|
||||
|
||||
* Trivial changes can be committed directly but be sure to keep the repository
|
||||
in a good working state and that all tests pass before pushing your changes.
|
||||
|
||||
* When committing code written by someone else, please attribute the original
|
||||
author in the commit message and any relevant :file:`CHANGES` entry.
|
||||
|
||||
|
||||
Coding Guide
|
||||
------------
|
||||
|
||||
* Try to use the same code style as used in the rest of the project. See the
|
||||
`Pocoo Styleguide`__ for more information.
|
||||
|
||||
__ http://flask.pocoo.org/docs/styleguide/
|
||||
|
||||
* For non-trivial changes, please update the :file:`CHANGES` file. If your
|
||||
changes alter existing behavior, please document this.
|
||||
|
||||
* New features should be documented. Include examples and use cases where
|
||||
appropriate. If possible, include a sample that is displayed in the
|
||||
generated output.
|
||||
|
||||
* When adding a new configuration variable, be sure to document it and update
|
||||
:file:`sphinx/cmd/quickstart.py` if it's important enough.
|
||||
|
||||
* Add appropriate unit tests.
|
||||
|
||||
|
||||
Debugging Tips
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* Delete the build cache before building documents if you make changes in the
|
||||
code by running the command ``make clean`` or using the
|
||||
:option:`sphinx-build -E` option.
|
||||
|
||||
* Use the :option:`sphinx-build -P` option to run ``pdb`` on exceptions.
|
||||
|
||||
* Use ``node.pformat()`` and ``node.asdom().toxml()`` to generate a printable
|
||||
representation of the document structure.
|
||||
|
||||
* Set the configuration variable :confval:`keep_warnings` to ``True`` so
|
||||
warnings will be displayed in the generated output.
|
||||
|
||||
* Set the configuration variable :confval:`nitpicky` to ``True`` so that Sphinx
|
||||
will complain about references without a known target.
|
||||
|
||||
* Set the debugging options in the `Docutils configuration file
|
||||
<http://docutils.sourceforge.net/docs/user/config.html>`_.
|
||||
|
||||
* JavaScript stemming algorithms in ``sphinx/search/*.py`` (except ``en.py``)
|
||||
are generated by this
|
||||
`modified snowballcode generator <https://github.com/shibukawa/snowball>`_.
|
||||
Generated `JSX <https://jsx.github.io/>`_ files are
|
||||
in `this repository <https://github.com/shibukawa/snowball-stemmer.jsx>`_.
|
||||
You can get the resulting JavaScript files using the following command::
|
||||
|
||||
npm install
|
||||
node_modules/.bin/grunt build # -> dest/*.global.js
|
||||
|
||||
|
||||
Branch Model
|
||||
------------
|
||||
|
||||
Sphinx project uses following branches for developing that conforms to Semantic
|
||||
Versioning 2.0.0 (refs: https://semver.org/ ).
|
||||
|
||||
``master``
|
||||
Development for MAJOR version.
|
||||
All changes including incompatible behaviors and public API updates are
|
||||
allowed.
|
||||
|
||||
``A.x`` (ex. ``2.x``)
|
||||
Where ``A.x`` is the ``MAJOR.MINOR`` release. Used to maintain current
|
||||
MINOR release. All changes are allowed if the change preserves
|
||||
backwards-compatibility of API and features.
|
||||
|
||||
Only the most recent ``MAJOR.MINOR`` branch is currently retained. When a
|
||||
new MAJOR version is released, the old ``MAJOR.MINOR`` branch will be
|
||||
deleted and replaced by an equivalent tag.
|
||||
|
||||
``A.B.x`` (ex. ``2.4.x``)
|
||||
Where ``A.B.x`` is the ``MAJOR.MINOR.PATCH`` release. Only
|
||||
backwards-compatible bug fixes are allowed. In Sphinx project, PATCH
|
||||
version is used for urgent bug fix.
|
||||
|
||||
``MAJOR.MINOR.PATCH`` branch will be branched from the ``v`` prefixed
|
||||
release tag (ex. make 2.3.1 that branched from v2.3.0) when a urgent
|
||||
release is needed. When new PATCH version is released, the branch will be
|
||||
deleted and replaced by an equivalent tag (ex. v2.3.1).
|
||||
|
||||
|
||||
Deprecating a feature
|
||||
---------------------
|
||||
|
||||
There are a couple reasons that code in Sphinx might be deprecated:
|
||||
|
||||
* If a feature has been improved or modified in a backwards-incompatible way,
|
||||
the old feature or behavior will be deprecated.
|
||||
|
||||
* Sometimes Sphinx will include a backport of a Python library that's not
|
||||
included in a version of Python that Sphinx currently supports. When Sphinx
|
||||
no longer needs to support the older version of Python that doesn't include
|
||||
the library, the library will be deprecated in Sphinx.
|
||||
|
||||
As the :ref:`deprecation-policy` describes, the first release of Sphinx that
|
||||
deprecates a feature (``A.B``) should raise a ``RemovedInSphinxXXWarning``
|
||||
(where ``XX`` is the Sphinx version where the feature will be removed) when the
|
||||
deprecated feature is invoked. Assuming we have good test coverage, these
|
||||
warnings are converted to errors when running the test suite with warnings
|
||||
enabled::
|
||||
|
||||
pytest -Wall
|
||||
|
||||
Thus, when adding a ``RemovedInSphinxXXWarning`` you need to eliminate or
|
||||
silence any warnings generated when running the tests.
|
||||
|
||||
.. _deprecation-policy:
|
||||
|
||||
Deprecation policy
|
||||
------------------
|
||||
|
||||
MAJOR and MINOR releases may deprecate certain features from previous
|
||||
releases. If a feature is deprecated in a release A.x, it will continue to
|
||||
work in all A.x.x versions (for all versions of x). It will continue to work
|
||||
in all B.x.x versions but raise deprecation warnings. Deprecated features
|
||||
will be removed at the C.0.0. It means the deprecated feature will work during
|
||||
2 MAJOR releases at least.
|
||||
|
||||
So, for example, if we decided to start the deprecation of a function in
|
||||
Sphinx 2.x:
|
||||
|
||||
* Sphinx 2.x will contain a backwards-compatible replica of the function
|
||||
which will raise a ``RemovedInSphinx40Warning``.
|
||||
This is a subclass of :exc:`python:PendingDeprecationWarning`, i.e. it
|
||||
will not get displayed by default.
|
||||
|
||||
* Sphinx 3.x will still contain the backwards-compatible replica, but
|
||||
``RemovedInSphinx40Warning`` will be a subclass of
|
||||
:exc:`python:DeprecationWarning` then, and gets displayed by default.
|
||||
|
||||
* Sphinx 4.0 will remove the feature outright.
|
||||
|
||||
Deprecation warnings
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sphinx will enable its ``RemovedInNextVersionWarning`` warnings by default,
|
||||
if :envvar:`python:PYTHONWARNINGS` is not set.
|
||||
Therefore you can disable them using:
|
||||
|
||||
* ``PYTHONWARNINGS= make html`` (Linux/Mac)
|
||||
* ``export PYTHONWARNINGS=`` and do ``make html`` (Linux/Mac)
|
||||
* ``set PYTHONWARNINGS=`` and do ``make html`` (Windows)
|
||||
|
||||
But you can also explicitly enable the pending ones using e.g.
|
||||
``PYTHONWARNINGS=default`` (see the
|
||||
:ref:`Python docs on configuring warnings <python:describing-warning-filters>`)
|
||||
for more details.
|
||||
|
||||
Unit Testing
|
||||
------------
|
||||
|
||||
Sphinx has been tested with pytest runner. Sphinx developers write unit tests
|
||||
using pytest notation. Utility functions and pytest fixtures for testing are
|
||||
provided in ``sphinx.testing``. If you are a developer of Sphinx extensions,
|
||||
you can write unit tests with using pytest. At this time, ``sphinx.testing``
|
||||
will help your test implementation.
|
||||
|
||||
How to use pytest fixtures that are provided by ``sphinx.testing``?
|
||||
You can require ``'sphinx.testing.fixtures'`` in your test modules or
|
||||
``conftest.py`` files like this::
|
||||
|
||||
pytest_plugins = 'sphinx.testing.fixtures'
|
||||
|
||||
If you want to know more detailed usage, please refer to ``tests/conftest.py``
|
||||
and other ``test_*.py`` files under ``tests`` directory.
|
||||
|
||||
.. note::
|
||||
|
||||
Prior to Sphinx - 1.5.2, Sphinx was running the test with nose.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
``sphinx.testing`` as a experimental.
|
||||
|
||||
.. versionadded:: 1.8
|
||||
Sphinx also runs JavaScript tests.
|
||||
|
||||
|
||||
Release procedures
|
||||
------------------
|
||||
|
||||
The release procedures are listed on ``utils/release-checklist``.
|
||||
|
||||
|
||||
Locale Updates
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
The parts of messages in Sphinx that go into builds are translated into several
|
||||
locales. The translations are kept as gettext ``.po`` files translated from the
|
||||
master template :file:`sphinx/locale/sphinx.pot`.
|
||||
|
||||
Sphinx uses `Babel <http://babel.pocoo.org/en/latest/>`_ to extract messages
|
||||
and maintain the catalog files. It is integrated in ``setup.py``:
|
||||
|
||||
* Use ``python setup.py extract_messages`` to update the ``.pot`` template.
|
||||
* Use ``python setup.py update_catalog`` to update all existing language
|
||||
catalogs in ``sphinx/locale/*/LC_MESSAGES`` with the current messages in the
|
||||
template file.
|
||||
* Use ``python setup.py compile_catalog`` to compile the ``.po`` files to binary
|
||||
``.mo`` files and ``.js`` files.
|
||||
|
||||
When an updated ``.po`` file is submitted, run compile_catalog to commit both
|
||||
the source and the compiled catalogs.
|
||||
|
||||
When a new locale is submitted, add a new directory with the ISO 639-1 language
|
||||
identifier and put ``sphinx.po`` in there. Don't forget to update the possible
|
||||
values for :confval:`language` in ``doc/usage/configuration.rst``.
|
||||
|
||||
The Sphinx core messages can also be translated on `Transifex
|
||||
<https://www.transifex.com/>`_. There exists a client tool named ``tx`` in the
|
||||
Python package "transifex_client", which can be used to pull translations in
|
||||
``.po`` format from Transifex. To do this, go to ``sphinx/locale`` and then run
|
||||
``tx pull -f -l LANG`` where LANG is an existing language identifier. It is
|
||||
good practice to run ``python setup.py update_catalog`` afterwards to make sure
|
||||
the ``.po`` file has the canonical Babel formatting.
|
||||
Sphinx uses GitHub to host source code, track patches and bugs, and more.
|
||||
Please make an effort to provide as much possible when filing bugs.
|
||||
|
@ -76,7 +76,7 @@ If you wish to install `Sphinx` for development purposes, refer to `the
|
||||
contributors guide`__.
|
||||
|
||||
__ https://pypi.org/project/Sphinx/
|
||||
__ http://www.sphinx-doc.org/en/master/devguide.html
|
||||
__ http://www.sphinx-doc.org/en/master/internals/contributing.html
|
||||
|
||||
Documentation
|
||||
=============
|
||||
@ -110,14 +110,14 @@ For information on running tests locally, refer to `the contributors guide`__.
|
||||
__ https://travis-ci.org/sphinx-doc/sphinx
|
||||
__ https://ci.appveyor.com/project/sphinxdoc/sphinx
|
||||
__ https://circleci.com/gh/sphinx-doc/sphinx
|
||||
__ http://www.sphinx-doc.org/en/master/devguide.html
|
||||
__ http://www.sphinx-doc.org/en/master/internals/contributing.html
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
Refer to `the contributors guide`__.
|
||||
|
||||
__ http://www.sphinx-doc.org/en/master/devguide.html
|
||||
__ http://www.sphinx-doc.org/en/master/internals/contributing.html
|
||||
|
||||
Release signatures
|
||||
==================
|
||||
|
@ -13,3 +13,9 @@ texlive-anyfontsize [platform:rpm]
|
||||
texlive-ctablestack [platform:rpm]
|
||||
texlive-gnu-freefont [platform:rpm]
|
||||
latexmk [platform:rpm]
|
||||
|
||||
texlive-latex-recommended [platform:dpkg]
|
||||
texlive-fonts-recommended [platform:dpkg]
|
||||
texlive-latex-extra [platform:dpkg]
|
||||
texlive-luatex [platform:dpkg]
|
||||
latexmk [platform:dpkg]
|
||||
|
8
doc/_templates/index.html
vendored
8
doc/_templates/index.html
vendored
@ -116,12 +116,12 @@
|
||||
this part of the documentation is for you.{%endtrans%}</p>
|
||||
|
||||
<ul>
|
||||
<li>{%trans path=pathto("devguide")%}<a href="{{ path }}">Sphinx Developer’s Guide</a></li>{%endtrans%}
|
||||
<li>{%trans path=pathto("authors")%}<a href="{{ path }}">Sphinx Authors</a></li>{%endtrans%}
|
||||
<li>{%trans path=pathto("internals/contributing")%}<a href="{{ path }}">Sphinx Contributors’s Guide</a></li>{%endtrans%}
|
||||
<li>{%trans path=pathto("internals/authors")%}<a href="{{ path }}">Sphinx Authors</a></li>{%endtrans%}
|
||||
</ul>
|
||||
|
||||
<h2>{%trans%}Code of Conduct{%endtrans%}</h2>
|
||||
<h2>{%trans%}Code of Conduct{%endtrans%}</h2>
|
||||
|
||||
{%trans path=pathto("code_of_conduct")%}Please adhere to our <a href="{{ path }}">Code of Conduct</a>.{%endtrans%}
|
||||
<p>{%trans path=pathto("code_of_conduct")%}Please adhere to our <a href="{{ path }}">Code of Conduct</a>.{%endtrans%}</p>
|
||||
|
||||
{% endblock %}
|
||||
|
@ -4,7 +4,8 @@
|
||||
|
||||
.. _changes:
|
||||
|
||||
Changes in Sphinx
|
||||
*****************
|
||||
=========
|
||||
Changelog
|
||||
=========
|
||||
|
||||
.. include:: ../CHANGES
|
||||
|
@ -1,8 +0,0 @@
|
||||
:tocdepth: 2
|
||||
|
||||
.. _code_of_conduct:
|
||||
|
||||
Sphinx Code of Conduct
|
||||
======================
|
||||
|
||||
.. include:: ../CODE_OF_CONDUCT
|
@ -1,4 +1,3 @@
|
||||
|
||||
.. _contents:
|
||||
|
||||
Sphinx documentation contents
|
||||
@ -7,35 +6,21 @@ Sphinx documentation contents
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
intro
|
||||
|
||||
usage/installation
|
||||
usage/quickstart
|
||||
usage/restructuredtext/index
|
||||
usage/markdown
|
||||
usage/configuration
|
||||
usage/builders/index
|
||||
usage/extensions/index
|
||||
usage/theming
|
||||
usage/advanced/intl
|
||||
usage/advanced/setuptools
|
||||
usage/advanced/websupport/index
|
||||
|
||||
usage/index
|
||||
development/index
|
||||
man/index
|
||||
|
||||
theming
|
||||
templating
|
||||
latex
|
||||
extdev/index
|
||||
development/tutorials/index
|
||||
|
||||
internals/index
|
||||
|
||||
faq
|
||||
glossary
|
||||
devguide
|
||||
changes
|
||||
examples
|
||||
authors
|
||||
code_of_conduct
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
@ -14,7 +14,8 @@ Sphinx is a maintained by a group of volunteers. We value every contribution!
|
||||
* There is also the #sphinx-doc IRC channel on `freenode
|
||||
<https://freenode.net/>`_.
|
||||
|
||||
For more about our development process and methods, see the :doc:`devguide`.
|
||||
For more about our development process and methods, refer to
|
||||
:doc:`/internals/index`.
|
||||
|
||||
Extensions
|
||||
==========
|
||||
|
13
doc/development/index.rst
Normal file
13
doc/development/index.rst
Normal file
@ -0,0 +1,13 @@
|
||||
================
|
||||
Extending Sphinx
|
||||
================
|
||||
|
||||
This guide is aimed at 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 extensions, refer to :doc:`/usage/index`.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
tutorials/index
|
@ -1 +0,0 @@
|
||||
.. include:: ../CONTRIBUTING.rst
|
@ -230,11 +230,15 @@ connect handlers to the events. Example:
|
||||
|
||||
.. event:: missing-reference (app, env, node, contnode)
|
||||
|
||||
Emitted when a cross-reference to a Python module or object cannot be
|
||||
resolved. If the event handler can resolve the reference, it should return a
|
||||
Emitted when a cross-reference to an object cannot be resolved.
|
||||
If the event handler can resolve the reference, it should return a
|
||||
new docutils node to be inserted in the document tree in place of the node
|
||||
*node*. Usually this node is a :class:`reference` node containing *contnode*
|
||||
as a child.
|
||||
If the handler can not resolve the cross-reference,
|
||||
it can either return ``None`` to let other handlers try,
|
||||
or raise :class:`NoUri` to prevent other handlers in trying and suppress
|
||||
a warning about this cross-reference being unresolved.
|
||||
|
||||
:param env: The build environment (``app.builder.env``).
|
||||
:param node: The :class:`pending_xref` node to be resolved. Its attributes
|
||||
|
37
doc/faq.rst
37
doc/faq.rst
@ -148,6 +148,43 @@ Google Search
|
||||
.. _xhtml to reST: http://docutils.sourceforge.net/sandbox/xhtml2rest/xhtml2rest.py
|
||||
|
||||
|
||||
Sphinx vs. Docutils
|
||||
-------------------
|
||||
|
||||
tl;dr: *docutils* converts reStructuredText to multiple output formats. Sphinx
|
||||
builds upon docutils to allow construction of cross-referenced and indexed
|
||||
bodies of documentation.
|
||||
|
||||
`docutils`__ is a text processing system for converting plain text
|
||||
documentation into other, richer formats. As noted in the `docutils
|
||||
documentation`__, docutils uses *readers* to read a document, *parsers* for
|
||||
parsing plain text formats into an internal tree representation made up of
|
||||
different types of *nodes*, and *writers* to output this tree in various
|
||||
document formats. docutils provides parsers for one plain text format -
|
||||
`reStructuredText`__ - though other, *out-of-tree* parsers have been
|
||||
implemented including Sphinx's :doc:`Markdown parser </usage/markdown>`. On the
|
||||
other hand, it provides writers for many different formats including HTML,
|
||||
LaTeX, man pages, Open Document Format and XML.
|
||||
|
||||
docutils exposes all of its functionality through a variety of `front-end
|
||||
tools`__, such as ``rst2html``, ``rst2odt`` and ``rst2xml``. Crucially though,
|
||||
all of these tools, and docutils itself, are concerned with individual
|
||||
documents. They don't support concepts such as cross-referencing, indexing of
|
||||
documents, or the construction of a document hierarchy (typically manifesting
|
||||
in a table of contents).
|
||||
|
||||
Sphinx builds upon docutils by harnessing docutils' readers and parsers and
|
||||
providing its own :doc:`/usage/builders/index`. As a result, Sphinx wraps some
|
||||
of the *writers* provided by docutils. This allows Sphinx to provide many
|
||||
features that would simply not be possible with docutils, such as those
|
||||
outlined above.
|
||||
|
||||
__ http://docutils.sourceforge.io/
|
||||
__ http://docutils.sourceforge.io/docs/dev/hacking.html
|
||||
__ http://docutils.sourceforge.io/rst.html
|
||||
__ http://docutils.sourceforge.net/docs/user/tools.html
|
||||
|
||||
|
||||
.. _epub-faq:
|
||||
|
||||
Epub info
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
.. _authors:
|
||||
|
||||
==============
|
||||
Sphinx authors
|
||||
==============
|
||||
|
||||
.. include:: ../AUTHORS
|
||||
|
||||
.. include:: ../../AUTHORS
|
5
doc/internals/code-of-conduct.rst
Normal file
5
doc/internals/code-of-conduct.rst
Normal file
@ -0,0 +1,5 @@
|
||||
:tocdepth: 2
|
||||
|
||||
.. _code_of_conduct:
|
||||
|
||||
.. include:: ../../CODE_OF_CONDUCT
|
309
doc/internals/contributing.rst
Normal file
309
doc/internals/contributing.rst
Normal file
@ -0,0 +1,309 @@
|
||||
======================
|
||||
Contributing to Sphinx
|
||||
======================
|
||||
|
||||
There are many ways you can contribute to Sphinx, be it filing bug reports or
|
||||
feature requests, writing new documentation or submitting patches for new or
|
||||
fixed behavior. This guide serves to illustrate how you can get started with
|
||||
this.
|
||||
|
||||
Getting help
|
||||
------------
|
||||
|
||||
The Sphinx community maintains a number of mailing lists and IRC channels.
|
||||
|
||||
sphinx-users <sphinx-users@googlegroups.com>
|
||||
Mailing list for user support.
|
||||
|
||||
sphinx-dev <sphinx-dev@googlegroups.com>
|
||||
Mailing list for development related discussions.
|
||||
|
||||
#sphinx-doc on irc.freenode.net
|
||||
IRC channel for development questions and user support.
|
||||
|
||||
|
||||
Bug Reports and Feature Requests
|
||||
--------------------------------
|
||||
|
||||
If you have encountered a problem with Sphinx or have an idea for a new
|
||||
feature, please submit it to the `issue tracker`_ on GitHub or discuss it
|
||||
on the `sphinx-dev`_ mailing list.
|
||||
|
||||
For bug reports, please include the output produced during the build process
|
||||
and also the log file Sphinx creates after it encounters an unhandled
|
||||
exception. The location of this file should be shown towards the end of the
|
||||
error message.
|
||||
|
||||
Including or providing a link to the source files involved may help us fix the
|
||||
issue. If possible, try to create a minimal project that produces the error
|
||||
and post that instead.
|
||||
|
||||
.. _`issue tracker`: https://github.com/sphinx-doc/sphinx/issues
|
||||
.. _`sphinx-dev`: mailto:sphinx-dev@googlegroups.com
|
||||
|
||||
|
||||
Writing code
|
||||
------------
|
||||
|
||||
The Sphinx source code is managed using Git and is hosted on `GitHub`__. The
|
||||
recommended way for new contributors to submit code to Sphinx is to fork this
|
||||
repository and submit a pull request after committing changes to their fork.
|
||||
The pull request will then need to be approved by one of the core developers
|
||||
before it is merged into the main repository.
|
||||
|
||||
.. __: https://github.com/sphinx-doc/sphinx
|
||||
|
||||
Getting started
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Before starting on a patch, we recommend checking for open issues or open a
|
||||
fresh issue to start a discussion around a feature idea or a bug. If you feel
|
||||
uncomfortable or uncertain about an issue or your changes, feel free to email
|
||||
the *sphinx-dev* mailing list.
|
||||
|
||||
These are the basic steps needed to start developing on Sphinx.
|
||||
|
||||
#. Create an account on GitHub.
|
||||
|
||||
#. Fork the main Sphinx repository (`sphinx-doc/sphinx
|
||||
<https://github.com/sphinx-doc/sphinx>`_) using the GitHub interface.
|
||||
|
||||
#. Clone the forked repository to your machine. ::
|
||||
|
||||
git clone https://github.com/USERNAME/sphinx
|
||||
cd sphinx
|
||||
|
||||
#. Checkout the appropriate branch.
|
||||
|
||||
Sphinx adopts Semantic Versioning 2.0.0 (refs: https://semver.org/ ).
|
||||
|
||||
For changes that preserves backwards-compatibility of API and features,
|
||||
they should be included in the next MINOR release, use the ``A.x`` branch.
|
||||
::
|
||||
|
||||
git checkout A.x
|
||||
|
||||
For incompatible or other substantial changes that should wait until the
|
||||
next MAJOR release, use the ``master`` branch.
|
||||
|
||||
For urgent release, a new PATCH branch must be branched from the newest
|
||||
release tag (see :doc:`release-process` for detail).
|
||||
|
||||
#. Setup a virtual environment.
|
||||
|
||||
This is not necessary for unit testing, thanks to ``tox``, but it is
|
||||
necessary if you wish to run ``sphinx-build`` locally or run unit tests
|
||||
without the help of ``tox``::
|
||||
|
||||
virtualenv ~/.venv
|
||||
. ~/.venv/bin/activate
|
||||
pip install -e .
|
||||
|
||||
#. Create a new working branch. Choose any name you like. ::
|
||||
|
||||
git checkout -b feature-xyz
|
||||
|
||||
#. Hack, hack, hack.
|
||||
|
||||
Write your code along with tests that shows that the bug was fixed or that
|
||||
the feature works as expected.
|
||||
|
||||
#. Add a bullet point to :file:`CHANGES` if the fix or feature is not trivial
|
||||
(small doc updates, typo fixes), then commit::
|
||||
|
||||
git commit -m '#42: Add useful new feature that does this.'
|
||||
|
||||
GitHub recognizes certain phrases that can be used to automatically
|
||||
update the issue tracker. For example::
|
||||
|
||||
git commit -m 'Closes #42: Fix invalid markup in docstring of Foo.bar.'
|
||||
|
||||
would close issue #42.
|
||||
|
||||
#. Push changes in the branch to your forked repository on GitHub::
|
||||
|
||||
git push origin feature-xyz
|
||||
|
||||
#. Submit a pull request from your branch to the respective branch (``master``
|
||||
or ``A.x``).
|
||||
|
||||
#. Wait for a core developer to review your changes.
|
||||
|
||||
Coding style
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Please follow these guidelines when writing code for Sphinx:
|
||||
|
||||
* Try to use the same code style as used in the rest of the project. See the
|
||||
`Pocoo Styleguide`__ for more information.
|
||||
|
||||
__ http://flask.pocoo.org/docs/styleguide/
|
||||
|
||||
* For non-trivial changes, please update the :file:`CHANGES` file. If your
|
||||
changes alter existing behavior, please document this.
|
||||
|
||||
* New features should be documented. Include examples and use cases where
|
||||
appropriate. If possible, include a sample that is displayed in the
|
||||
generated output.
|
||||
|
||||
* When adding a new configuration variable, be sure to document it and update
|
||||
:file:`sphinx/cmd/quickstart.py` if it's important enough.
|
||||
|
||||
* Add appropriate unit tests.
|
||||
|
||||
Style and type checks can be run using ``tox``::
|
||||
|
||||
tox -e mypy
|
||||
tox -e flake8
|
||||
|
||||
Unit tests
|
||||
~~~~~~~~~~
|
||||
|
||||
Sphinx is tested using `pytest`__ for Python code and `Karma`__ for JavaScript.
|
||||
|
||||
.. __: https://docs.pytest.org/en/latest/
|
||||
.. __: https://karma-runner.github.io
|
||||
|
||||
To run Python unit tests, we recommend using ``tox``, which provides a number
|
||||
of targets and allows testing against multiple different Python environments:
|
||||
|
||||
* To list all possible targets::
|
||||
|
||||
tox -av
|
||||
|
||||
* To run unit tests for a specific Python version, such as Python 3.6::
|
||||
|
||||
tox -e py36
|
||||
|
||||
* To run unit tests for a specific Python version and turn on deprecation
|
||||
warnings on so they're shown in the test output::
|
||||
|
||||
PYTHONWARNINGS=all tox -e py36
|
||||
|
||||
* Arguments to ``pytest`` can be passed via ``tox``, e.g. in order to run a
|
||||
particular test::
|
||||
|
||||
tox -e py36 tests/test_module.py::test_new_feature
|
||||
|
||||
You can also test by installing dependencies in your local environment::
|
||||
|
||||
pip install .[test]
|
||||
|
||||
To run JavaScript tests, use ``npm``::
|
||||
|
||||
npm install
|
||||
npm run test
|
||||
|
||||
New unit tests should be included in the ``tests`` directory where
|
||||
necessary:
|
||||
|
||||
* For bug fixes, first add a test that fails without your changes and passes
|
||||
after they are applied.
|
||||
|
||||
* Tests that need a ``sphinx-build`` run should be integrated in one of the
|
||||
existing test modules if possible. New tests that to ``@with_app`` and
|
||||
then ``build_all`` for a few assertions are not good since *the test suite
|
||||
should not take more than a minute to run*.
|
||||
|
||||
.. versionadded:: 1.8
|
||||
|
||||
Sphinx also runs JavaScript tests.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
``sphinx.testing`` is added as a experimental.
|
||||
|
||||
.. versionchanged:: 1.5.2
|
||||
|
||||
Sphinx was switched from nose to pytest.
|
||||
|
||||
.. todo:: The below belongs in the developer guide
|
||||
|
||||
Utility functions and pytest fixtures for testing are provided in
|
||||
``sphinx.testing``. If you are a developer of Sphinx extensions, you can write
|
||||
unit tests with using pytest. At this time, ``sphinx.testing`` will help your
|
||||
test implementation.
|
||||
|
||||
How to use pytest fixtures that are provided by ``sphinx.testing``? You can
|
||||
require ``'sphinx.testing.fixtures'`` in your test modules or ``conftest.py``
|
||||
files like this::
|
||||
|
||||
pytest_plugins = 'sphinx.testing.fixtures'
|
||||
|
||||
If you want to know more detailed usage, please refer to ``tests/conftest.py``
|
||||
and other ``test_*.py`` files under ``tests`` directory.
|
||||
|
||||
|
||||
Writing documentation
|
||||
---------------------
|
||||
|
||||
.. todo:: Add a more extensive documentation contribution guide.
|
||||
|
||||
You can build documentation using ``tox``::
|
||||
|
||||
tox -e docs
|
||||
|
||||
Translations
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The parts of messages in Sphinx that go into builds are translated into several
|
||||
locales. The translations are kept as gettext ``.po`` files translated from the
|
||||
master template :file:`sphinx/locale/sphinx.pot`.
|
||||
|
||||
Sphinx uses `Babel <http://babel.pocoo.org/en/latest/>`_ to extract messages
|
||||
and maintain the catalog files. It is integrated in ``setup.py``:
|
||||
|
||||
* Use ``python setup.py extract_messages`` to update the ``.pot`` template.
|
||||
* Use ``python setup.py update_catalog`` to update all existing language
|
||||
catalogs in ``sphinx/locale/*/LC_MESSAGES`` with the current messages in the
|
||||
template file.
|
||||
* Use ``python setup.py compile_catalog`` to compile the ``.po`` files to binary
|
||||
``.mo`` files and ``.js`` files.
|
||||
|
||||
When an updated ``.po`` file is submitted, run compile_catalog to commit both
|
||||
the source and the compiled catalogs.
|
||||
|
||||
When a new locale is submitted, add a new directory with the ISO 639-1 language
|
||||
identifier and put ``sphinx.po`` in there. Don't forget to update the possible
|
||||
values for :confval:`language` in ``doc/usage/configuration.rst``.
|
||||
|
||||
The Sphinx core messages can also be translated on `Transifex
|
||||
<https://www.transifex.com/sphinx-doc/>`_. There ``tx`` client tool, which is
|
||||
provided by the ``transifex_client`` Python package, can be used to pull
|
||||
translations in ``.po`` format from Transifex. To do this, go to
|
||||
``sphinx/locale`` and then run ``tx pull -f -l LANG`` where ``LANG`` is an
|
||||
existing language identifier. It is good practice to run ``python setup.py
|
||||
update_catalog`` afterwards to make sure the ``.po`` file has the canonical
|
||||
Babel formatting.
|
||||
|
||||
|
||||
Debugging tips
|
||||
--------------
|
||||
|
||||
* Delete the build cache before building documents if you make changes in the
|
||||
code by running the command ``make clean`` or using the
|
||||
:option:`sphinx-build -E` option.
|
||||
|
||||
* Use the :option:`sphinx-build -P` option to run ``pdb`` on exceptions.
|
||||
|
||||
* Use ``node.pformat()`` and ``node.asdom().toxml()`` to generate a printable
|
||||
representation of the document structure.
|
||||
|
||||
* Set the configuration variable :confval:`keep_warnings` to ``True`` so
|
||||
warnings will be displayed in the generated output.
|
||||
|
||||
* Set the configuration variable :confval:`nitpicky` to ``True`` so that Sphinx
|
||||
will complain about references without a known target.
|
||||
|
||||
* Set the debugging options in the `Docutils configuration file
|
||||
<http://docutils.sourceforge.net/docs/user/config.html>`_.
|
||||
|
||||
* JavaScript stemming algorithms in ``sphinx/search/*.py`` (except ``en.py``)
|
||||
are generated by this `modified snowballcode generator
|
||||
<https://github.com/shibukawa/snowball>`_. Generated `JSX
|
||||
<https://jsx.github.io/>`_ files are in `this repository
|
||||
<https://github.com/shibukawa/snowball-stemmer.jsx>`_. You can get the
|
||||
resulting JavaScript files using the following command::
|
||||
|
||||
npm install
|
||||
node_modules/.bin/grunt build # -> dest/*.global.js
|
16
doc/internals/index.rst
Normal file
16
doc/internals/index.rst
Normal file
@ -0,0 +1,16 @@
|
||||
================
|
||||
Sphinx internals
|
||||
================
|
||||
|
||||
This guide contains information about the Sphinx open source project itself.
|
||||
This is where you can find information about how Sphinx is managed and learn
|
||||
how to contribute to the project.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
contributing
|
||||
release-process
|
||||
organization
|
||||
code-of-conduct
|
||||
authors
|
56
doc/internals/organization.rst
Normal file
56
doc/internals/organization.rst
Normal file
@ -0,0 +1,56 @@
|
||||
==================================
|
||||
Organization of the Sphinx project
|
||||
==================================
|
||||
|
||||
The guide explains how the Sphinx project is organized.
|
||||
|
||||
Core developers
|
||||
---------------
|
||||
|
||||
The core developers of Sphinx have write access to the main repository. They
|
||||
can commit changes, accept/reject pull requests, and manage items on the issue
|
||||
tracker.
|
||||
|
||||
Guidelines
|
||||
~~~~~~~~~~
|
||||
|
||||
The following are some general guidelines for core developers:
|
||||
|
||||
* Questionable or extensive changes should be submitted as a pull request
|
||||
instead of being committed directly to the main repository. The pull
|
||||
request should be reviewed by another core developer before it is merged.
|
||||
|
||||
* Trivial changes can be committed directly but be sure to keep the repository
|
||||
in a good working state and that all tests pass before pushing your changes.
|
||||
|
||||
* When committing code written by someone else, please attribute the original
|
||||
author in the commit message and any relevant :file:`CHANGES` entry.
|
||||
|
||||
Membership
|
||||
~~~~~~~~~~
|
||||
|
||||
Core membership is predicated on continued active contribution to the project.
|
||||
In general, prospective cores should demonstrate:
|
||||
|
||||
- a good understanding of one of more components of Sphinx
|
||||
|
||||
- a history of helpful, constructive contributions
|
||||
|
||||
- a willingness to invest time improving Sphinx
|
||||
|
||||
Refer to :doc:`contributing` for more information on how you can get started.
|
||||
|
||||
Other contributors
|
||||
------------------
|
||||
|
||||
You do not need to be a core developer or have write access to be involved in
|
||||
the development of Sphinx. You can submit patches or create pull requests
|
||||
from forked repositories and have a core developer add the changes for you.
|
||||
|
||||
Similarly, contributions are not limited to code patches. We also welcome help
|
||||
triaging bugs, input on design decisions, reviews of existing patches and
|
||||
documentation improvements. More information can be found in
|
||||
:doc:`contributing`.
|
||||
|
||||
A list of people that have contributed to Sphinx can be found in
|
||||
:doc:`authors`.
|
106
doc/internals/release-process.rst
Normal file
106
doc/internals/release-process.rst
Normal file
@ -0,0 +1,106 @@
|
||||
========================
|
||||
Sphinx's release process
|
||||
========================
|
||||
|
||||
Branch Model
|
||||
------------
|
||||
|
||||
Sphinx project uses following branches for developing that conforms to Semantic
|
||||
Versioning 2.0.0 (refs: https://semver.org/ ).
|
||||
|
||||
``master``
|
||||
Development for MAJOR version.
|
||||
All changes including incompatible behaviors and public API updates are
|
||||
allowed.
|
||||
|
||||
``A.x`` (ex. ``2.x``)
|
||||
Where ``A.x`` is the ``MAJOR.MINOR`` release. Used to maintain current
|
||||
MINOR release. All changes are allowed if the change preserves
|
||||
backwards-compatibility of API and features.
|
||||
|
||||
Only the most recent ``MAJOR.MINOR`` branch is currently retained. When a
|
||||
new MAJOR version is released, the old ``MAJOR.MINOR`` branch will be
|
||||
deleted and replaced by an equivalent tag.
|
||||
|
||||
``A.B.x`` (ex. ``2.4.x``)
|
||||
Where ``A.B.x`` is the ``MAJOR.MINOR.PATCH`` release. Only
|
||||
backwards-compatible bug fixes are allowed. In Sphinx project, PATCH
|
||||
version is used for urgent bug fix.
|
||||
|
||||
``MAJOR.MINOR.PATCH`` branch will be branched from the ``v`` prefixed
|
||||
release tag (ex. make 2.3.1 that branched from v2.3.0) when a urgent
|
||||
release is needed. When new PATCH version is released, the branch will be
|
||||
deleted and replaced by an equivalent tag (ex. v2.3.1).
|
||||
|
||||
|
||||
Deprecating a feature
|
||||
---------------------
|
||||
|
||||
There are a couple reasons that code in Sphinx might be deprecated:
|
||||
|
||||
* If a feature has been improved or modified in a backwards-incompatible way,
|
||||
the old feature or behavior will be deprecated.
|
||||
|
||||
* Sometimes Sphinx will include a backport of a Python library that's not
|
||||
included in a version of Python that Sphinx currently supports. When Sphinx
|
||||
no longer needs to support the older version of Python that doesn't include
|
||||
the library, the library will be deprecated in Sphinx.
|
||||
|
||||
As the :ref:`deprecation-policy` describes, the first release of Sphinx that
|
||||
deprecates a feature (``A.B``) should raise a ``RemovedInSphinxXXWarning``
|
||||
(where ``XX`` is the Sphinx version where the feature will be removed) when the
|
||||
deprecated feature is invoked. Assuming we have good test coverage, these
|
||||
warnings are converted to errors when running the test suite with warnings
|
||||
enabled::
|
||||
|
||||
pytest -Wall
|
||||
|
||||
Thus, when adding a ``RemovedInSphinxXXWarning`` you need to eliminate or
|
||||
silence any warnings generated when running the tests.
|
||||
|
||||
|
||||
.. _deprecation-policy:
|
||||
|
||||
Deprecation policy
|
||||
------------------
|
||||
|
||||
MAJOR and MINOR releases may deprecate certain features from previous
|
||||
releases. If a feature is deprecated in a release A.x, it will continue to
|
||||
work in all A.x.x versions (for all versions of x). It will continue to work
|
||||
in all B.x.x versions but raise deprecation warnings. Deprecated features
|
||||
will be removed at the C.0.0. It means the deprecated feature will work during
|
||||
2 MAJOR releases at least.
|
||||
|
||||
So, for example, if we decided to start the deprecation of a function in
|
||||
Sphinx 2.x:
|
||||
|
||||
* Sphinx 2.x will contain a backwards-compatible replica of the function
|
||||
which will raise a ``RemovedInSphinx40Warning``.
|
||||
This is a subclass of :exc:`python:PendingDeprecationWarning`, i.e. it
|
||||
will not get displayed by default.
|
||||
|
||||
* Sphinx 3.x will still contain the backwards-compatible replica, but
|
||||
``RemovedInSphinx40Warning`` will be a subclass of
|
||||
:exc:`python:DeprecationWarning` then, and gets displayed by default.
|
||||
|
||||
* Sphinx 4.0 will remove the feature outright.
|
||||
|
||||
Deprecation warnings
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sphinx will enable its ``RemovedInNextVersionWarning`` warnings by default, if
|
||||
:envvar:`python:PYTHONWARNINGS` is not set. Therefore you can disable them
|
||||
using:
|
||||
|
||||
* ``PYTHONWARNINGS= make html`` (Linux/Mac)
|
||||
* ``export PYTHONWARNINGS=`` and do ``make html`` (Linux/Mac)
|
||||
* ``set PYTHONWARNINGS=`` and do ``make html`` (Windows)
|
||||
|
||||
But you can also explicitly enable the pending ones using e.g.
|
||||
``PYTHONWARNINGS=default`` (see the :ref:`Python docs on configuring warnings
|
||||
<python:describing-warning-filters>`) for more details.
|
||||
|
||||
Release procedures
|
||||
------------------
|
||||
|
||||
The release procedures are listed in ``utils/release-checklist``.
|
@ -1,71 +0,0 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
This is the documentation for the Sphinx documentation builder. Sphinx is a
|
||||
tool that translates a set of reStructuredText_ source files into various output
|
||||
formats, automatically producing cross-references, indices, etc. That is, if
|
||||
you have a directory containing a bunch of reST-formatted documents (and
|
||||
possibly subdirectories of docs in there as well), Sphinx can generate a
|
||||
nicely-organized arrangement of HTML files (in some other directory) for easy
|
||||
browsing and navigation. But from the same source, it can also generate a PDF
|
||||
file using LaTeX.
|
||||
|
||||
The focus is on hand-written documentation, rather than auto-generated API docs.
|
||||
Though there is support for that kind of documentation as well (which is
|
||||
intended to be freely mixed with hand-written content), if you need pure API
|
||||
docs have a look at `Epydoc <http://epydoc.sourceforge.net/>`_, which also
|
||||
understands reST.
|
||||
|
||||
For a great "introduction" to writing docs in general -- the whys and hows, see
|
||||
also `Write the docs`_, written by Eric Holscher.
|
||||
|
||||
.. _rinohtype: https://github.com/brechtm/rinohtype
|
||||
.. _Write the docs: http://www.writethedocs.org/guide/writing/beginners-guide-to-docs/
|
||||
|
||||
Conversion from other systems
|
||||
-----------------------------
|
||||
|
||||
This section is intended to collect helpful hints for those wanting to migrate
|
||||
to reStructuredText/Sphinx from other documentation systems.
|
||||
|
||||
* Gerard Flanagan has written a script to convert pure HTML to reST; it can be
|
||||
found at the `Python Package Index <https://pypi.org/project/html2rest/>`_.
|
||||
|
||||
* For converting the old Python docs to Sphinx, a converter was written which
|
||||
can be found at `the Python SVN repository
|
||||
<https://svn.python.org/projects/doctools/converter/>`_. It contains generic
|
||||
code to convert Python-doc-style LaTeX markup to Sphinx reST.
|
||||
|
||||
* Marcin Wojdyr has written a script to convert Docbook to reST with Sphinx
|
||||
markup; it is at `GitHub <https://github.com/wojdyr/db2rst>`_.
|
||||
|
||||
* Christophe de Vienne wrote a tool to convert from Open/LibreOffice documents
|
||||
to Sphinx: `odt2sphinx <https://pypi.org/project/odt2sphinx/>`_.
|
||||
|
||||
* To convert different markups, `Pandoc <https://pandoc.org/>`_ is
|
||||
a very helpful tool.
|
||||
|
||||
|
||||
Use with other systems
|
||||
----------------------
|
||||
|
||||
See the :ref:`pertinent section in the FAQ list <usingwith>`.
|
||||
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
Sphinx needs at least **Python 3.6** to run.
|
||||
It also depends on 3rd party libraries such as docutils_ and jinja2_, but they
|
||||
are automatically installed when sphinx is installed.
|
||||
|
||||
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||
.. _docutils: http://docutils.sourceforge.net/
|
||||
.. _Jinja2: http://jinja.pocoo.org/
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
See :doc:`/usage/quickstart` for an introduction. It also contains links to
|
||||
more advanced sections in this manual for the topics it discusses.
|
1161
doc/latex.rst
1161
doc/latex.rst
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
||||
|
||||
.. _templating:
|
||||
|
||||
==========
|
||||
Templating
|
||||
==========
|
||||
|
||||
@ -69,9 +70,10 @@ following contents::
|
||||
By prefixing the name of the overridden template with an exclamation mark,
|
||||
Sphinx will load the layout template from the underlying HTML theme.
|
||||
|
||||
**Important**: If you override a block, call ``{{ super() }}`` somewhere to
|
||||
render the block's original content in the extended template -- unless you
|
||||
don't want that content to show up.
|
||||
.. important::
|
||||
If you override a block, call ``{{ super() }}`` somewhere to render the
|
||||
block's original content in the extended template -- unless you don't want
|
||||
that content to show up.
|
||||
|
||||
|
||||
Working with the builtin templates
|
||||
@ -85,72 +87,68 @@ Blocks
|
||||
|
||||
The following blocks exist in the ``layout.html`` template:
|
||||
|
||||
`doctype`
|
||||
``doctype``
|
||||
The doctype of the output format. By default this is XHTML 1.0 Transitional
|
||||
as this is the closest to what Sphinx and Docutils generate and it's a good
|
||||
idea not to change it unless you want to switch to HTML 5 or a different but
|
||||
compatible XHTML doctype.
|
||||
|
||||
`linktags`
|
||||
``linktags``
|
||||
This block adds a couple of ``<link>`` tags to the head section of the
|
||||
template.
|
||||
|
||||
`extrahead`
|
||||
``extrahead``
|
||||
This block is empty by default and can be used to add extra contents into
|
||||
the ``<head>`` tag of the generated HTML file. This is the right place to
|
||||
add references to JavaScript or extra CSS files.
|
||||
|
||||
`relbar1` / `relbar2`
|
||||
``relbar1``, ``relbar2``
|
||||
This block contains the *relation bar*, the list of related links (the
|
||||
parent documents on the left, and the links to index, modules etc. on the
|
||||
right). `relbar1` appears before the document, `relbar2` after the
|
||||
right). ``relbar1`` appears before the document, ``relbar2`` after the
|
||||
document. By default, both blocks are filled; to show the relbar only
|
||||
before the document, you would override `relbar2` like this::
|
||||
|
||||
{% block relbar2 %}{% endblock %}
|
||||
|
||||
`rootrellink` / `relbaritems`
|
||||
Inside the relbar there are three sections: The `rootrellink`, the links
|
||||
from the documentation and the custom `relbaritems`. The `rootrellink` is a
|
||||
block that by default contains a list item pointing to the master document
|
||||
by default, the `relbaritems` is an empty block. If you override them to
|
||||
add extra links into the bar make sure that they are list items and end with
|
||||
the :data:`reldelim1`.
|
||||
``rootrellink``, ``relbaritems``
|
||||
Inside the relbar there are three sections: The ``rootrellink``, the links
|
||||
from the documentation and the custom ``relbaritems``. The ``rootrellink``
|
||||
is a block that by default contains a list item pointing to the master
|
||||
document by default, the ``relbaritems`` is an empty block. If you
|
||||
override them to add extra links into the bar make sure that they are list
|
||||
items and end with the :data:`reldelim1`.
|
||||
|
||||
`document`
|
||||
The contents of the document itself. It contains the block "body" where the
|
||||
individual content is put by subtemplates like ``page.html``.
|
||||
``document``
|
||||
The contents of the document itself. It contains the block "body" where
|
||||
the individual content is put by subtemplates like ``page.html``.
|
||||
|
||||
.. note::
|
||||
In order for the built-in JavaScript search to show a page preview on
|
||||
the results page, the document or body content should be wrapped in an
|
||||
HTML element containing the ``role="main"`` attribute. For example:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
HTML element containing the ``role="main"`` attribute. For example::
|
||||
|
||||
<div role="main">
|
||||
{% block document %}{% endblock %}
|
||||
</div>
|
||||
|
||||
`sidebar1` / `sidebar2`
|
||||
A possible location for a sidebar. `sidebar1` appears before the document
|
||||
and is empty by default, `sidebar2` after the document and contains the
|
||||
``sidebar1``, ``sidebar2``
|
||||
A possible location for a sidebar. ``sidebar1`` appears before the document
|
||||
and is empty by default, ``sidebar2`` after the document and contains the
|
||||
default sidebar. If you want to swap the sidebar location override this and
|
||||
call the `sidebar` helper:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
call the ``sidebar`` helper::
|
||||
|
||||
{% block sidebar1 %}{{ sidebar() }}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
||||
|
||||
(The `sidebar2` location for the sidebar is needed by the ``sphinxdoc.css``
|
||||
(The ``sidebar2`` location for the sidebar is needed by the ``sphinxdoc.css``
|
||||
stylesheet, for example.)
|
||||
|
||||
`sidebarlogo`
|
||||
``sidebarlogo``
|
||||
The logo location within the sidebar. Override this if you want to place
|
||||
some content at the top of the sidebar.
|
||||
|
||||
`footer`
|
||||
``footer``
|
||||
The block for the footer div. If you want a custom footer or markup before
|
||||
or after it, override this one.
|
||||
|
||||
@ -159,23 +157,23 @@ list of custom sidebars in the :confval:`html_sidebars` config value. Their use
|
||||
is deprecated in favor of separate sidebar templates, which can be included via
|
||||
:confval:`html_sidebars`.
|
||||
|
||||
`sidebartoc`
|
||||
``sidebartoc``
|
||||
The table of contents within the sidebar.
|
||||
|
||||
.. deprecated:: 1.0
|
||||
|
||||
`sidebarrel`
|
||||
``sidebarrel``
|
||||
The relation links (previous, next document) within the sidebar.
|
||||
|
||||
.. deprecated:: 1.0
|
||||
|
||||
`sidebarsourcelink`
|
||||
``sidebarsourcelink``
|
||||
The "Show source" link within the sidebar (normally only shown if this is
|
||||
enabled by :confval:`html_show_sourcelink`).
|
||||
|
||||
.. deprecated:: 1.0
|
||||
|
||||
`sidebarsearch`
|
||||
``sidebarsearch``
|
||||
The search box within the sidebar. Override this if you want to place some
|
||||
content at the bottom of the sidebar.
|
||||
|
||||
@ -392,7 +390,6 @@ are in HTML form), these variables are also available:
|
||||
<a href="{{ next.link|e }}">{{ next.title }}</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
.. data:: page_source_suffix
|
||||
|
||||
The suffix of the file that was rendered. Since we support a list of
|
||||
@ -424,14 +421,19 @@ are in HTML form), these variables are also available:
|
||||
A callable yielding the global TOC tree containing the current page, rendered
|
||||
as HTML bullet lists. Optional keyword arguments:
|
||||
|
||||
* ``collapse`` (``True`` by default): if true, all TOC entries that are not
|
||||
ancestors of the current page are collapsed
|
||||
``collapse``
|
||||
If true, all TOC entries that are not ancestors of the current page are
|
||||
collapsed.
|
||||
``True`` by default.
|
||||
|
||||
* ``maxdepth`` (defaults to the max depth selected in the toctree directive):
|
||||
the maximum depth of the tree; set it to ``-1`` to allow unlimited depth
|
||||
``maxdepth``
|
||||
The maximum depth of the tree. Set it to ``-1`` to allow unlimited depth.
|
||||
Defaults to the max depth selected in the toctree directive.
|
||||
|
||||
* ``titles_only`` (``False`` by default): if true, put only toplevel document
|
||||
titles in the tree
|
||||
``titles_only``
|
||||
If true, put only top-level document titles in the tree.
|
||||
``False`` by default.
|
||||
|
||||
* ``includehidden`` (``False`` by default): if true, the TOC tree will also
|
||||
contain hidden entries.
|
||||
``includehidden``
|
||||
If true, the ToC tree will also contain hidden entries.
|
||||
``False`` by default.
|
||||
|
@ -574,7 +574,7 @@ General configuration
|
||||
A dictionary of options that modify how the lexer specified by
|
||||
:confval:`highlight_language` generates highlighted source code. These are
|
||||
lexer-specific; for the options understood by each, see the
|
||||
`Pygments documentation <https://pygments.org/docs/lexers.html>`_.
|
||||
`Pygments documentation <https://pygments.org/docs/lexers>`_.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
|
@ -454,7 +454,14 @@ There are also config values that you can set:
|
||||
looks like a signature, use the line as the signature and remove it from the
|
||||
docstring content.
|
||||
|
||||
If the signature line ends with backslash, autodoc considers the function has
|
||||
multiple signatures and look at the next line of the docstring. It is useful
|
||||
for overloaded function.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
.. versionchanged:: 3.1
|
||||
|
||||
Support overloaded signatures
|
||||
|
||||
.. confval:: autodoc_mock_imports
|
||||
|
||||
|
@ -285,8 +285,12 @@ The following variables available in the templates:
|
||||
|
||||
.. data:: attributes
|
||||
|
||||
List containing names of "public" attributes in the class. Only available
|
||||
for classes.
|
||||
List containing names of "public" attributes in the class/module. Only
|
||||
available for classes and modules.
|
||||
|
||||
.. versionchanged:: 3.1
|
||||
|
||||
Attributes of modules are supported.
|
||||
|
||||
.. data:: modules
|
||||
|
||||
|
23
doc/usage/index.rst
Normal file
23
doc/usage/index.rst
Normal file
@ -0,0 +1,23 @@
|
||||
============
|
||||
Using Sphinx
|
||||
============
|
||||
|
||||
This guide serves to demonstrate how one can get started with Sphinx and covers
|
||||
everything from installing Sphinx and configuring your first Sphinx project to
|
||||
using some of the advanced features Sphinx provides out-of-the-box. If you are
|
||||
looking for guidance on extending Sphinx, refer to :doc:`/development/index`.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
quickstart
|
||||
installation
|
||||
restructuredtext/index
|
||||
markdown
|
||||
configuration
|
||||
builders/index
|
||||
extensions/index
|
||||
theming
|
||||
advanced/intl
|
||||
advanced/setuptools
|
||||
advanced/websupport/index
|
@ -12,9 +12,13 @@ Installing Sphinx
|
||||
Overview
|
||||
--------
|
||||
|
||||
Sphinx is written in `Python`__ and supports Python 3.6+.
|
||||
Sphinx is written in `Python`__ and supports Python 3.6+. It builds upon the
|
||||
shoulders of many third-party libraries such as `Docutils`__ and `Jinja`__,
|
||||
which are installed when Sphinx is installed.
|
||||
|
||||
__ https://docs.python-guide.org/
|
||||
__ https://docutils.sourceforge.io/
|
||||
__ https://jinja.palletsprojects.com/
|
||||
|
||||
|
||||
Linux
|
||||
|
@ -2,21 +2,38 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
Once Sphinx is :doc:`installed </usage/installation>`, you can proceed with
|
||||
setting up your first Sphinx project. To ease the process of getting started,
|
||||
Sphinx provides a tool, :program:`sphinx-quickstart`, which will generate a
|
||||
documentation source directory and populate it with some defaults. We're going
|
||||
to use the :program:`sphinx-quickstart` tool here, though its use is by no means
|
||||
necessary.
|
||||
Sphinx is a *documentation generator* or a tool that translates a set of plain
|
||||
text source files into various output formats, automatically producing
|
||||
cross-references, indices, etc. That is, if you have a directory containing a
|
||||
bunch of :doc:`/usage/restructuredtext/index` or :doc:`/usage/markdown`
|
||||
documents, Sphinx can generate a series of HTML files, a PDF file (via LaTeX),
|
||||
man pages and much more.
|
||||
|
||||
Sphinx focuses on documentation, in particular handwritten documentation,
|
||||
however, Sphinx can also be used to generate blogs, homepages and even books.
|
||||
Much of Sphinx's power comes from the richness of its default plain-text markup
|
||||
format, :doc:`reStructuredText </usage/restructuredtext/index>`, along with
|
||||
it's :doc:`significant extensibility capabilities </development/index>`.
|
||||
|
||||
The goal of this document is to give you a quick taste of what Sphinx it is and
|
||||
how you might use it. When you're done here, you can check out the
|
||||
:doc:`installation guide </usage/installation>` followed by the intro to the
|
||||
default markup format used by Sphinx, :doc:`reStucturedText
|
||||
</usage/restructuredtext/index>`.
|
||||
|
||||
For a great "introduction" to writing docs in general -- the whys and hows, see
|
||||
also `Write the docs`__, written by Eric Holscher.
|
||||
|
||||
.. __: http://www.writethedocs.org/guide/writing/beginners-guide-to-docs/
|
||||
|
||||
|
||||
Setting up the documentation sources
|
||||
------------------------------------
|
||||
|
||||
The root directory of a Sphinx collection of :term:`reStructuredText` document
|
||||
sources is called the :term:`source directory`. This directory also contains
|
||||
the Sphinx configuration file :file:`conf.py`, where you can configure all
|
||||
aspects of how Sphinx reads your sources and builds your documentation. [#]_
|
||||
The root directory of a Sphinx collection of plain-text document sources is
|
||||
called the :term:`source directory`. This directory also contains the Sphinx
|
||||
configuration file :file:`conf.py`, where you can configure all aspects of how
|
||||
Sphinx reads your sources and builds your documentation. [#]_
|
||||
|
||||
Sphinx comes with a script called :program:`sphinx-quickstart` that sets up a
|
||||
source directory and creates a default :file:`conf.py` with the most useful
|
||||
@ -26,9 +43,6 @@ configuration values from a few questions it asks you. To use this, run:
|
||||
|
||||
$ sphinx-quickstart
|
||||
|
||||
There is also an automatic "API documentation" generator called
|
||||
:program:`sphinx-apidoc`; see :doc:`/man/sphinx-apidoc` for details.
|
||||
|
||||
|
||||
Defining document structure
|
||||
---------------------------
|
||||
|
@ -159,7 +159,7 @@ Doctest blocks
|
||||
Doctest blocks (:duref:`ref <doctest-blocks>`) are interactive Python sessions
|
||||
cut-and-pasted into docstrings. They do not require the
|
||||
:ref:`literal blocks <rst-literal-blocks>` syntax. The doctest block must end
|
||||
with a blank line and should *not* end with with an unused prompt::
|
||||
with a blank line and should *not* end with an unused prompt::
|
||||
|
||||
>>> 1 + 1
|
||||
2
|
||||
|
@ -1,5 +1,7 @@
|
||||
.. highlight:: python
|
||||
|
||||
.. _html-themes:
|
||||
|
||||
HTML
|
||||
====
|
||||
|
||||
|
3
setup.py
3
setup.py
@ -44,7 +44,7 @@ extras_require = {
|
||||
'lint': [
|
||||
'flake8>=3.5.0',
|
||||
'flake8-import-order',
|
||||
'mypy>=0.770',
|
||||
'mypy>=0.780',
|
||||
'docutils-stubs',
|
||||
],
|
||||
'test': [
|
||||
@ -201,6 +201,7 @@ setup(
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
'Framework :: Setuptools Plugin',
|
||||
|
@ -432,22 +432,32 @@ class Sphinx:
|
||||
logger.debug('[app] disconnecting event: [id=%s]', listener_id)
|
||||
self.events.disconnect(listener_id)
|
||||
|
||||
def emit(self, event: str, *args: Any) -> List:
|
||||
def emit(self, event: str, *args: Any,
|
||||
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> List:
|
||||
"""Emit *event* and pass *arguments* to the callback functions.
|
||||
|
||||
Return the return values of all callbacks as a list. Do not emit core
|
||||
Sphinx events in extensions!
|
||||
"""
|
||||
return self.events.emit(event, *args)
|
||||
|
||||
def emit_firstresult(self, event: str, *args: Any) -> Any:
|
||||
.. versionchanged:: 3.1
|
||||
|
||||
Added *allowed_exceptions* to specify path-through exceptions
|
||||
"""
|
||||
return self.events.emit(event, *args, allowed_exceptions=allowed_exceptions)
|
||||
|
||||
def emit_firstresult(self, event: str, *args: Any,
|
||||
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> Any:
|
||||
"""Emit *event* and pass *arguments* to the callback functions.
|
||||
|
||||
Return the result of the first callback that doesn't return ``None``.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
.. versionchanged:: 3.1
|
||||
|
||||
Added *allowed_exceptions* to specify path-through exceptions
|
||||
"""
|
||||
return self.events.emit_firstresult(event, *args)
|
||||
return self.events.emit_firstresult(event, *args,
|
||||
allowed_exceptions=allowed_exceptions)
|
||||
|
||||
# registering addon parts
|
||||
|
||||
@ -929,12 +939,14 @@ class Sphinx:
|
||||
if hasattr(self.builder, 'add_css_file'):
|
||||
self.builder.add_css_file(filename, **kwargs) # type: ignore
|
||||
|
||||
def add_latex_package(self, packagename: str, options: str = None) -> None:
|
||||
def add_latex_package(self, packagename: str, options: str = None,
|
||||
after_hyperref: bool = False) -> None:
|
||||
r"""Register a package to include in the LaTeX source code.
|
||||
|
||||
Add *packagename* to the list of packages that LaTeX source code will
|
||||
include. If you provide *options*, it will be taken to `\usepackage`
|
||||
declaration.
|
||||
declaration. If you set *after_hyperref* truthy, the package will be
|
||||
loaded after ``hyperref`` package.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@ -944,8 +956,11 @@ class Sphinx:
|
||||
# => \usepackage[foo,bar]{mypackage}
|
||||
|
||||
.. versionadded:: 1.3
|
||||
.. versionadded:: 3.1
|
||||
|
||||
*after_hyperref* option.
|
||||
"""
|
||||
self.registry.add_latex_package(packagename, options)
|
||||
self.registry.add_latex_package(packagename, options, after_hyperref)
|
||||
|
||||
def add_lexer(self, alias: str, lexer: Type[Lexer]) -> None:
|
||||
"""Register a new lexer for source code.
|
||||
|
@ -127,6 +127,7 @@ class LaTeXBuilder(Builder):
|
||||
self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]]
|
||||
self.themes = ThemeFactory(self.app)
|
||||
self.usepackages = self.app.registry.latex_packages
|
||||
self.usepackages_after_hyperref = self.app.registry.latex_packages_after_hyperref
|
||||
texescape.init()
|
||||
|
||||
self.init_context()
|
||||
@ -178,6 +179,7 @@ class LaTeXBuilder(Builder):
|
||||
|
||||
# Apply extension settings to context
|
||||
self.context['packages'] = self.usepackages
|
||||
self.context['packages_after_hyperref'] = self.usepackages_after_hyperref
|
||||
|
||||
# Apply user settings to context
|
||||
self.context.update(self.config.latex_elements)
|
||||
@ -456,6 +458,12 @@ def validate_latex_theme_options(app: Sphinx, config: Config) -> None:
|
||||
config.latex_theme_options.pop(key)
|
||||
|
||||
|
||||
def install_pakcages_for_ja(app: Sphinx) -> None:
|
||||
"""Install packages for Japanese."""
|
||||
if app.config.language == 'ja':
|
||||
app.add_latex_package('pxjahyper', after_hyperref=True)
|
||||
|
||||
|
||||
def default_latex_engine(config: Config) -> str:
|
||||
""" Better default latex_engine settings for specific languages. """
|
||||
if config.language == 'ja':
|
||||
@ -503,6 +511,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_builder(LaTeXBuilder)
|
||||
app.connect('config-inited', validate_config_values, priority=800)
|
||||
app.connect('config-inited', validate_latex_theme_options, priority=800)
|
||||
app.connect('builder-inited', install_pakcages_for_ja)
|
||||
|
||||
app.add_config_value('latex_engine', default_latex_engine, None,
|
||||
ENUM('pdflatex', 'xelatex', 'lualatex', 'platex', 'uplatex'))
|
||||
|
@ -126,7 +126,7 @@ class Config:
|
||||
'rst_epilog': (None, 'env', [str]),
|
||||
'rst_prolog': (None, 'env', [str]),
|
||||
'trim_doctest_flags': (True, 'env', []),
|
||||
'primary_domain': ('py', 'env', [NoneType]), # type: ignore
|
||||
'primary_domain': ('py', 'env', [NoneType]),
|
||||
'needs_sphinx': (None, None, [str]),
|
||||
'needs_extensions': ({}, None, []),
|
||||
'manpages_url': (None, 'env', []),
|
||||
|
@ -432,9 +432,9 @@ class ASTUnaryOpExpr(ASTExpression):
|
||||
|
||||
def _stringify(self, transform: StringifyTransform) -> str:
|
||||
if self.op[0] in 'cn':
|
||||
return transform(self.op) + " " + transform(self.expr)
|
||||
return self.op + " " + transform(self.expr)
|
||||
else:
|
||||
return transform(self.op) + transform(self.expr)
|
||||
return self.op + transform(self.expr)
|
||||
|
||||
def describe_signature(self, signode: TextElement, mode: str,
|
||||
env: "BuildEnvironment", symbol: "Symbol") -> None:
|
||||
|
@ -951,12 +951,12 @@ class ASTFoldExpr(ASTExpression):
|
||||
if self.leftExpr:
|
||||
res.append(transform(self.leftExpr))
|
||||
res.append(' ')
|
||||
res.append(transform(self.op))
|
||||
res.append(self.op)
|
||||
res.append(' ')
|
||||
res.append('...')
|
||||
if self.rightExpr:
|
||||
res.append(' ')
|
||||
res.append(transform(self.op))
|
||||
res.append(self.op)
|
||||
res.append(' ')
|
||||
res.append(transform(self.rightExpr))
|
||||
res.append(')')
|
||||
@ -1223,9 +1223,9 @@ class ASTUnaryOpExpr(ASTExpression):
|
||||
|
||||
def _stringify(self, transform: StringifyTransform) -> str:
|
||||
if self.op[0] in 'cn':
|
||||
return transform(self.op) + " " + transform(self.expr)
|
||||
return self.op + " " + transform(self.expr)
|
||||
else:
|
||||
return transform(self.op) + transform(self.expr)
|
||||
return self.op + transform(self.expr)
|
||||
|
||||
def get_id(self, version: int) -> str:
|
||||
return _id_operator_unary_v2[self.op] + self.expr.get_id(version)
|
||||
@ -3538,6 +3538,8 @@ class ASTTemplateIntroduction(ASTBase):
|
||||
signode += nodes.Text('}')
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
class ASTTemplateDeclarationPrefix(ASTBase):
|
||||
def __init__(self,
|
||||
templates: List[Union[ASTTemplateParams,
|
||||
@ -3566,18 +3568,35 @@ class ASTTemplateDeclarationPrefix(ASTBase):
|
||||
t.describe_signature_as_introducer(signode, 'lastIsName', env, symbol, lineSpec)
|
||||
|
||||
|
||||
class ASTRequiresClause(ASTBase):
|
||||
def __init__(self, expr: ASTExpression) -> None:
|
||||
self.expr = expr
|
||||
|
||||
def _stringify(self, transform: StringifyTransform) -> str:
|
||||
return 'requires ' + transform(self.expr)
|
||||
|
||||
def describe_signature(self, signode: addnodes.desc_signature_line, mode: str,
|
||||
env: "BuildEnvironment", symbol: "Symbol") -> None:
|
||||
signode += nodes.Text('requires ', 'requires ')
|
||||
self.expr.describe_signature(signode, mode, env, symbol)
|
||||
|
||||
|
||||
################################################################################
|
||||
################################################################################
|
||||
|
||||
class ASTDeclaration(ASTBase):
|
||||
def __init__(self, objectType: str, directiveType: str, visibility: str,
|
||||
templatePrefix: ASTTemplateDeclarationPrefix, declaration: Any,
|
||||
templatePrefix: ASTTemplateDeclarationPrefix,
|
||||
requiresClause: ASTRequiresClause, declaration: Any,
|
||||
trailingRequiresClause: ASTRequiresClause,
|
||||
semicolon: bool = False) -> None:
|
||||
self.objectType = objectType
|
||||
self.directiveType = directiveType
|
||||
self.visibility = visibility
|
||||
self.templatePrefix = templatePrefix
|
||||
self.requiresClause = requiresClause
|
||||
self.declaration = declaration
|
||||
self.trailingRequiresClause = trailingRequiresClause
|
||||
self.semicolon = semicolon
|
||||
|
||||
self.symbol = None # type: Symbol
|
||||
@ -3585,13 +3604,14 @@ class ASTDeclaration(ASTBase):
|
||||
self.enumeratorScopedSymbol = None # type: Symbol
|
||||
|
||||
def clone(self) -> "ASTDeclaration":
|
||||
if self.templatePrefix:
|
||||
templatePrefixClone = self.templatePrefix.clone()
|
||||
else:
|
||||
templatePrefixClone = None
|
||||
return ASTDeclaration(self.objectType, self.directiveType,
|
||||
self.visibility, templatePrefixClone,
|
||||
self.declaration.clone(), self.semicolon)
|
||||
templatePrefixClone = self.templatePrefix.clone() if self.templatePrefix else None
|
||||
requiresClasueClone = self.requiresClause.clone() if self.requiresClause else None
|
||||
trailingRequiresClasueClone = self.trailingRequiresClause.clone() \
|
||||
if self.trailingRequiresClause else None
|
||||
return ASTDeclaration(self.objectType, self.directiveType, self.visibility,
|
||||
templatePrefixClone, requiresClasueClone,
|
||||
self.declaration.clone(), trailingRequiresClasueClone,
|
||||
self.semicolon)
|
||||
|
||||
@property
|
||||
def name(self) -> ASTNestedName:
|
||||
@ -3619,6 +3639,17 @@ class ASTDeclaration(ASTBase):
|
||||
res = []
|
||||
if self.templatePrefix:
|
||||
res.append(self.templatePrefix.get_id(version))
|
||||
if self.requiresClause or self.trailingRequiresClause:
|
||||
if version < 4:
|
||||
raise NoOldIdError()
|
||||
res.append('IQ')
|
||||
if self.requiresClause and self.trailingRequiresClause:
|
||||
res.append('aa')
|
||||
if self.requiresClause:
|
||||
res.append(self.requiresClause.expr.get_id(version))
|
||||
if self.trailingRequiresClause:
|
||||
res.append(self.trailingRequiresClause.expr.get_id(version))
|
||||
res.append('E')
|
||||
res.append(self.declaration.get_id(version, self.objectType, self.symbol))
|
||||
return ''.join(res)
|
||||
|
||||
@ -3632,7 +3663,13 @@ class ASTDeclaration(ASTBase):
|
||||
res.append(' ')
|
||||
if self.templatePrefix:
|
||||
res.append(transform(self.templatePrefix))
|
||||
if self.requiresClause:
|
||||
res.append(transform(self.requiresClause))
|
||||
res.append(' ')
|
||||
res.append(transform(self.declaration))
|
||||
if self.trailingRequiresClause:
|
||||
res.append(' ')
|
||||
res.append(transform(self.trailingRequiresClause))
|
||||
if self.semicolon:
|
||||
res.append(';')
|
||||
return ''.join(res)
|
||||
@ -3653,6 +3690,11 @@ class ASTDeclaration(ASTBase):
|
||||
self.templatePrefix.describe_signature(signode, mode, env,
|
||||
symbol=self.symbol,
|
||||
lineSpec=options.get('tparam-line-spec'))
|
||||
if self.requiresClause:
|
||||
reqNode = addnodes.desc_signature_line()
|
||||
reqNode.sphinx_line_type = 'requiresClause'
|
||||
signode.append(reqNode)
|
||||
self.requiresClause.describe_signature(reqNode, 'markType', env, self.symbol)
|
||||
signode += mainDeclNode
|
||||
if self.visibility and self.visibility != "public":
|
||||
mainDeclNode += addnodes.desc_annotation(self.visibility + " ",
|
||||
@ -3688,8 +3730,16 @@ class ASTDeclaration(ASTBase):
|
||||
else:
|
||||
assert False
|
||||
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
|
||||
lastDeclNode = mainDeclNode
|
||||
if self.trailingRequiresClause:
|
||||
trailingReqNode = addnodes.desc_signature_line()
|
||||
trailingReqNode.sphinx_line_type = 'trailingRequiresClause'
|
||||
signode.append(trailingReqNode)
|
||||
lastDeclNode = trailingReqNode
|
||||
self.trailingRequiresClause.describe_signature(
|
||||
trailingReqNode, 'markType', env, self.symbol)
|
||||
if self.semicolon:
|
||||
mainDeclNode += nodes.Text(';')
|
||||
lastDeclNode += nodes.Text(';')
|
||||
|
||||
|
||||
class ASTNamespace(ASTBase):
|
||||
@ -3808,7 +3858,7 @@ class Symbol:
|
||||
continue
|
||||
# only add a declaration if we our self are from a declaration
|
||||
if self.declaration:
|
||||
decl = ASTDeclaration('templateParam', None, None, None, tp)
|
||||
decl = ASTDeclaration('templateParam', None, None, None, None, tp, None)
|
||||
else:
|
||||
decl = None
|
||||
nne = ASTNestedNameElement(tp.get_identifier(), None)
|
||||
@ -3823,7 +3873,7 @@ class Symbol:
|
||||
if nn is None:
|
||||
continue
|
||||
# (comparing to the template params: we have checked that we are a declaration)
|
||||
decl = ASTDeclaration('functionParam', None, None, None, fp)
|
||||
decl = ASTDeclaration('functionParam', None, None, None, None, fp, None)
|
||||
assert not nn.rooted
|
||||
assert len(nn.names) == 1
|
||||
self._add_symbols(nn, [], decl, self.docname)
|
||||
@ -6297,8 +6347,61 @@ class DefinitionParser(BaseParser):
|
||||
'Expected ",", or "}".')
|
||||
return ASTTemplateIntroduction(concept, params)
|
||||
|
||||
def _parse_requires_clause(self) -> Optional[ASTRequiresClause]:
|
||||
# requires-clause -> 'requires' constraint-logical-or-expression
|
||||
# constraint-logical-or-expression
|
||||
# -> constraint-logical-and-expression
|
||||
# | constraint-logical-or-expression '||' constraint-logical-and-expression
|
||||
# constraint-logical-and-expression
|
||||
# -> primary-expression
|
||||
# | constraint-logical-and-expression '&&' primary-expression
|
||||
self.skip_ws()
|
||||
if not self.skip_word('requires'):
|
||||
return None
|
||||
|
||||
def parse_and_expr(self: DefinitionParser) -> ASTExpression:
|
||||
andExprs = []
|
||||
ops = []
|
||||
andExprs.append(self._parse_primary_expression())
|
||||
while True:
|
||||
self.skip_ws()
|
||||
oneMore = False
|
||||
if self.skip_string('&&'):
|
||||
oneMore = True
|
||||
ops.append('&&')
|
||||
elif self.skip_word('and'):
|
||||
oneMore = True
|
||||
ops.append('and')
|
||||
if not oneMore:
|
||||
break
|
||||
andExprs.append(self._parse_primary_expression())
|
||||
if len(andExprs) == 1:
|
||||
return andExprs[0]
|
||||
else:
|
||||
return ASTBinOpExpr(andExprs, ops)
|
||||
|
||||
orExprs = []
|
||||
ops = []
|
||||
orExprs.append(parse_and_expr(self))
|
||||
while True:
|
||||
self.skip_ws()
|
||||
oneMore = False
|
||||
if self.skip_string('||'):
|
||||
oneMore = True
|
||||
ops.append('||')
|
||||
elif self.skip_word('or'):
|
||||
oneMore = True
|
||||
ops.append('or')
|
||||
if not oneMore:
|
||||
break
|
||||
orExprs.append(parse_and_expr(self))
|
||||
if len(orExprs) == 1:
|
||||
return ASTRequiresClause(orExprs[0])
|
||||
else:
|
||||
return ASTRequiresClause(ASTBinOpExpr(orExprs, ops))
|
||||
|
||||
def _parse_template_declaration_prefix(self, objectType: str
|
||||
) -> ASTTemplateDeclarationPrefix:
|
||||
) -> Optional[ASTTemplateDeclarationPrefix]:
|
||||
templates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]]
|
||||
while 1:
|
||||
self.skip_ws()
|
||||
@ -6377,6 +6480,8 @@ class DefinitionParser(BaseParser):
|
||||
raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
|
||||
visibility = None
|
||||
templatePrefix = None
|
||||
requiresClause = None
|
||||
trailingRequiresClause = None
|
||||
declaration = None # type: Any
|
||||
|
||||
self.skip_ws()
|
||||
@ -6385,6 +6490,8 @@ class DefinitionParser(BaseParser):
|
||||
|
||||
if objectType in ('type', 'concept', 'member', 'function', 'class'):
|
||||
templatePrefix = self._parse_template_declaration_prefix(objectType)
|
||||
if objectType == 'function' and templatePrefix is not None:
|
||||
requiresClause = self._parse_requires_clause()
|
||||
|
||||
if objectType == 'type':
|
||||
prevErrors = []
|
||||
@ -6410,6 +6517,8 @@ class DefinitionParser(BaseParser):
|
||||
declaration = self._parse_type_with_init(named=True, outer='member')
|
||||
elif objectType == 'function':
|
||||
declaration = self._parse_type(named=True, outer='function')
|
||||
if templatePrefix is not None:
|
||||
trailingRequiresClause = self._parse_requires_clause()
|
||||
elif objectType == 'class':
|
||||
declaration = self._parse_class()
|
||||
elif objectType == 'union':
|
||||
@ -6427,7 +6536,8 @@ class DefinitionParser(BaseParser):
|
||||
self.skip_ws()
|
||||
semicolon = self.skip_string(';')
|
||||
return ASTDeclaration(objectType, directiveType, visibility,
|
||||
templatePrefix, declaration, semicolon)
|
||||
templatePrefix, requiresClause, declaration,
|
||||
trailingRequiresClause, semicolon)
|
||||
|
||||
def parse_namespace_object(self) -> ASTNamespace:
|
||||
templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
|
||||
|
@ -116,7 +116,8 @@ class PycodeError(Exception):
|
||||
|
||||
|
||||
class NoUri(Exception):
|
||||
"""Raised by builder.get_relative_uri() if there is no URI available."""
|
||||
"""Raised by builder.get_relative_uri() or from missing-reference handlers
|
||||
if there is no URI available."""
|
||||
pass
|
||||
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
from collections import defaultdict
|
||||
from operator import attrgetter
|
||||
from typing import Any, Callable, Dict, List, NamedTuple
|
||||
from typing import Any, Callable, Dict, List, NamedTuple, Tuple, Type
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sphinx.errors import ExtensionError, SphinxError
|
||||
@ -83,7 +83,8 @@ class EventManager:
|
||||
if listener.id == listener_id:
|
||||
listeners.remove(listener)
|
||||
|
||||
def emit(self, name: str, *args: Any) -> List:
|
||||
def emit(self, name: str, *args: Any,
|
||||
allowed_exceptions: Tuple[Type[Exception], ...] = ()) -> List:
|
||||
"""Emit a Sphinx event."""
|
||||
try:
|
||||
logger.debug('[app] emitting event: %r%s', name, repr(args)[:100])
|
||||
@ -97,6 +98,9 @@ class EventManager:
|
||||
for listener in listeners:
|
||||
try:
|
||||
results.append(listener.handler(self.app, *args))
|
||||
except allowed_exceptions:
|
||||
# pass through the errors specified as *allowed_exceptions*
|
||||
raise
|
||||
except SphinxError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
@ -104,12 +108,13 @@ class EventManager:
|
||||
(listener.handler, name)) from exc
|
||||
return results
|
||||
|
||||
def emit_firstresult(self, name: str, *args: Any) -> Any:
|
||||
def emit_firstresult(self, name: str, *args: Any,
|
||||
allowed_exceptions: Tuple[Type[Exception], ...] = ()) -> Any:
|
||||
"""Emit a Sphinx event and returns first result.
|
||||
|
||||
This returns the result of the first handler that doesn't return ``None``.
|
||||
"""
|
||||
for result in self.emit(name, *args):
|
||||
for result in self.emit(name, *args, allowed_exceptions=allowed_exceptions):
|
||||
if result is not None:
|
||||
return result
|
||||
return None
|
||||
|
@ -13,9 +13,11 @@
|
||||
import importlib
|
||||
import re
|
||||
import warnings
|
||||
from inspect import Parameter
|
||||
from inspect import Parameter, Signature
|
||||
from types import ModuleType
|
||||
from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union
|
||||
from typing import (
|
||||
Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from docutils.statemachine import StringList
|
||||
@ -91,6 +93,16 @@ def inherited_members_option(arg: Any) -> Union[object, Set[str]]:
|
||||
return arg
|
||||
|
||||
|
||||
def member_order_option(arg: Any) -> Optional[str]:
|
||||
"""Used to convert the :members: option to auto directives."""
|
||||
if arg is None:
|
||||
return None
|
||||
elif arg in ('alphabetical', 'bysource', 'groupwise'):
|
||||
return arg
|
||||
else:
|
||||
raise ValueError(__('invalid value for member-order option: %s') % arg)
|
||||
|
||||
|
||||
SUPPRESS = object()
|
||||
|
||||
|
||||
@ -379,6 +391,17 @@ class Documenter:
|
||||
# directives of course)
|
||||
return '.'.join(self.objpath) or self.modname
|
||||
|
||||
def _call_format_args(self, **kwargs: Any) -> str:
|
||||
if kwargs:
|
||||
try:
|
||||
return self.format_args(**kwargs)
|
||||
except TypeError:
|
||||
# avoid chaining exceptions, by putting nothing here
|
||||
pass
|
||||
|
||||
# retry without arguments for old documenters
|
||||
return self.format_args()
|
||||
|
||||
def format_signature(self, **kwargs: Any) -> str:
|
||||
"""Format the signature (arguments and return annotation) of the object.
|
||||
|
||||
@ -387,21 +410,22 @@ class Documenter:
|
||||
if self.args is not None:
|
||||
# signature given explicitly
|
||||
args = "(%s)" % self.args
|
||||
retann = self.retann
|
||||
else:
|
||||
# try to introspect the signature
|
||||
try:
|
||||
try:
|
||||
args = self.format_args(**kwargs)
|
||||
except TypeError:
|
||||
# retry without arguments for old documenters
|
||||
args = self.format_args()
|
||||
retann = None
|
||||
args = self._call_format_args(**kwargs)
|
||||
if args:
|
||||
matched = re.match(r'^(\(.*\))\s+->\s+(.*)$', args)
|
||||
if matched:
|
||||
args = matched.group(1)
|
||||
retann = matched.group(2)
|
||||
except Exception:
|
||||
logger.warning(__('error while formatting arguments for %s:') %
|
||||
self.fullname, type='autodoc', exc_info=True)
|
||||
args = None
|
||||
|
||||
retann = self.retann
|
||||
|
||||
result = self.env.events.emit_firstresult('autodoc-process-signature',
|
||||
self.objtype, self.fullname,
|
||||
self.object, self.options, args, retann)
|
||||
@ -516,12 +540,12 @@ class Documenter:
|
||||
else:
|
||||
logger.warning(__('missing attribute %s in object %s') %
|
||||
(name, self.fullname), type='autodoc')
|
||||
return False, sorted(selected)
|
||||
return False, selected
|
||||
elif self.options.inherited_members:
|
||||
return False, sorted((m.name, m.value) for m in members.values())
|
||||
return False, [(m.name, m.value) for m in members.values()]
|
||||
else:
|
||||
return False, sorted((m.name, m.value) for m in members.values()
|
||||
if m.directly_defined)
|
||||
return False, [(m.name, m.value) for m in members.values()
|
||||
if m.directly_defined]
|
||||
|
||||
def filter_members(self, members: List[Tuple[str, Any]], want_all: bool
|
||||
) -> List[Tuple[str, Any, bool]]:
|
||||
@ -691,20 +715,9 @@ class Documenter:
|
||||
'.'.join(self.objpath + [mname])
|
||||
documenter = classes[-1](self.directive, full_mname, self.indent)
|
||||
memberdocumenters.append((documenter, isattr))
|
||||
member_order = self.options.member_order or \
|
||||
self.env.config.autodoc_member_order
|
||||
if member_order == 'groupwise':
|
||||
# sort by group; relies on stable sort to keep items in the
|
||||
# same group sorted alphabetically
|
||||
memberdocumenters.sort(key=lambda e: e[0].member_order)
|
||||
elif member_order == 'bysource' and self.analyzer:
|
||||
# sort by source order, by virtue of the module analyzer
|
||||
tagorder = self.analyzer.tagorder
|
||||
|
||||
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
|
||||
fullname = entry[0].name.split('::')[1]
|
||||
return tagorder.get(fullname, len(tagorder))
|
||||
memberdocumenters.sort(key=keyfunc)
|
||||
member_order = self.options.member_order or self.env.config.autodoc_member_order
|
||||
memberdocumenters = self.sort_members(memberdocumenters, member_order)
|
||||
|
||||
for documenter, isattr in memberdocumenters:
|
||||
documenter.generate(
|
||||
@ -715,6 +728,31 @@ class Documenter:
|
||||
self.env.temp_data['autodoc:module'] = None
|
||||
self.env.temp_data['autodoc:class'] = None
|
||||
|
||||
def sort_members(self, documenters: List[Tuple["Documenter", bool]],
|
||||
order: str) -> List[Tuple["Documenter", bool]]:
|
||||
"""Sort the given member list."""
|
||||
if order == 'groupwise':
|
||||
# sort by group; alphabetically within groups
|
||||
documenters.sort(key=lambda e: (e[0].member_order, e[0].name))
|
||||
elif order == 'bysource':
|
||||
if self.analyzer:
|
||||
# sort by source order, by virtue of the module analyzer
|
||||
tagorder = self.analyzer.tagorder
|
||||
|
||||
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
|
||||
fullname = entry[0].name.split('::')[1]
|
||||
return tagorder.get(fullname, len(tagorder))
|
||||
documenters.sort(key=keyfunc)
|
||||
else:
|
||||
# Assume that member discovery order matches source order.
|
||||
# This is a reasonable assumption in Python 3.6 and up, where
|
||||
# module.__dict__ is insertion-ordered.
|
||||
pass
|
||||
else: # alphabetical
|
||||
documenters.sort(key=lambda e: e[0].name)
|
||||
|
||||
return documenters
|
||||
|
||||
def generate(self, more_content: Any = None, real_modname: str = None,
|
||||
check_module: bool = False, all_members: bool = False) -> None:
|
||||
"""Generate reST for the object given by *self.name*, and possibly for
|
||||
@ -812,7 +850,7 @@ class ModuleDocumenter(Documenter):
|
||||
'noindex': bool_option, 'inherited-members': inherited_members_option,
|
||||
'show-inheritance': bool_option, 'synopsis': identity,
|
||||
'platform': identity, 'deprecated': bool_option,
|
||||
'member-order': identity, 'exclude-members': members_set_option,
|
||||
'member-order': member_order_option, 'exclude-members': members_set_option,
|
||||
'private-members': bool_option, 'special-members': members_option,
|
||||
'imported-members': bool_option, 'ignore-module-all': bool_option
|
||||
} # type: Dict[str, Callable]
|
||||
@ -820,6 +858,7 @@ class ModuleDocumenter(Documenter):
|
||||
def __init__(self, *args: Any) -> None:
|
||||
super().__init__(*args)
|
||||
merge_special_members_option(self.options)
|
||||
self.__all__ = None
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
@ -842,6 +881,30 @@ class ModuleDocumenter(Documenter):
|
||||
type='autodoc')
|
||||
return ret
|
||||
|
||||
def import_object(self) -> Any:
|
||||
def is_valid_module_all(__all__: Any) -> bool:
|
||||
"""Check the given *__all__* is valid for a module."""
|
||||
if (isinstance(__all__, (list, tuple)) and
|
||||
all(isinstance(e, str) for e in __all__)):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
ret = super().import_object()
|
||||
|
||||
if not self.options.ignore_module_all:
|
||||
__all__ = getattr(self.object, '__all__', None)
|
||||
if is_valid_module_all(__all__):
|
||||
# valid __all__ found. copy it to self.__all__
|
||||
self.__all__ = __all__
|
||||
elif __all__:
|
||||
# invalid __all__ found.
|
||||
logger.warning(__('__all__ should be a list of strings, not %r '
|
||||
'(in module %s) -- ignoring __all__') %
|
||||
(__all__, self.fullname), type='autodoc')
|
||||
|
||||
return ret
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
Documenter.add_directive_header(self, sig)
|
||||
|
||||
@ -857,24 +920,12 @@ class ModuleDocumenter(Documenter):
|
||||
|
||||
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]:
|
||||
if want_all:
|
||||
if (self.options.ignore_module_all or not
|
||||
hasattr(self.object, '__all__')):
|
||||
if self.__all__:
|
||||
memberlist = self.__all__
|
||||
else:
|
||||
# for implicit module members, check __module__ to avoid
|
||||
# documenting imported objects
|
||||
return True, get_module_members(self.object)
|
||||
else:
|
||||
memberlist = self.object.__all__
|
||||
# Sometimes __all__ is broken...
|
||||
if not isinstance(memberlist, (list, tuple)) or not \
|
||||
all(isinstance(entry, str) for entry in memberlist):
|
||||
logger.warning(
|
||||
__('__all__ should be a list of strings, not %r '
|
||||
'(in module %s) -- ignoring __all__') %
|
||||
(memberlist, self.fullname),
|
||||
type='autodoc'
|
||||
)
|
||||
# fall back to all members
|
||||
return True, get_module_members(self.object)
|
||||
else:
|
||||
memberlist = self.options.members or []
|
||||
ret = []
|
||||
@ -890,6 +941,25 @@ class ModuleDocumenter(Documenter):
|
||||
)
|
||||
return False, ret
|
||||
|
||||
def sort_members(self, documenters: List[Tuple["Documenter", bool]],
|
||||
order: str) -> List[Tuple["Documenter", bool]]:
|
||||
if order == 'bysource' and self.__all__:
|
||||
# Sort alphabetically first (for members not listed on the __all__)
|
||||
documenters.sort(key=lambda e: e[0].name)
|
||||
|
||||
# Sort by __all__
|
||||
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
|
||||
name = entry[0].name.split('::')[1]
|
||||
if self.__all__ and name in self.__all__:
|
||||
return self.__all__.index(name)
|
||||
else:
|
||||
return len(self.__all__)
|
||||
documenters.sort(key=keyfunc)
|
||||
|
||||
return documenters
|
||||
else:
|
||||
return super().sort_members(documenters, order)
|
||||
|
||||
|
||||
class ModuleLevelDocumenter(Documenter):
|
||||
"""
|
||||
@ -961,41 +1031,71 @@ class DocstringSignatureMixin:
|
||||
Mixin for FunctionDocumenter and MethodDocumenter to provide the
|
||||
feature of reading the signature from the docstring.
|
||||
"""
|
||||
_new_docstrings = None # type: List[List[str]]
|
||||
_signatures = None # type: List[str]
|
||||
|
||||
def _find_signature(self) -> Tuple[str, str]:
|
||||
# candidates of the object name
|
||||
valid_names = [self.objpath[-1]] # type: ignore
|
||||
if isinstance(self, ClassDocumenter):
|
||||
valid_names.append('__init__')
|
||||
if hasattr(self.object, '__mro__'):
|
||||
valid_names.extend(cls.__name__ for cls in self.object.__mro__)
|
||||
|
||||
docstrings = self.get_doc()
|
||||
self._new_docstrings = docstrings[:]
|
||||
self._signatures = []
|
||||
result = None
|
||||
for i, doclines in enumerate(docstrings):
|
||||
# no lines in docstring, no match
|
||||
if not doclines:
|
||||
continue
|
||||
# match first line of docstring against signature RE
|
||||
match = py_ext_sig_re.match(doclines[0])
|
||||
if not match:
|
||||
continue
|
||||
exmod, path, base, args, retann = match.groups()
|
||||
# the base name must match ours
|
||||
valid_names = [self.objpath[-1]] # type: ignore
|
||||
if isinstance(self, ClassDocumenter):
|
||||
valid_names.append('__init__')
|
||||
if hasattr(self.object, '__mro__'):
|
||||
valid_names.extend(cls.__name__ for cls in self.object.__mro__)
|
||||
if base not in valid_names:
|
||||
continue
|
||||
# re-prepare docstring to ignore more leading indentation
|
||||
tab_width = self.directive.state.document.settings.tab_width # type: ignore
|
||||
self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:]),
|
||||
tabsize=tab_width)
|
||||
result = args, retann
|
||||
# don't look any further
|
||||
break
|
||||
for j, line in enumerate(doclines):
|
||||
if not line:
|
||||
# no lines in docstring, no match
|
||||
break
|
||||
|
||||
if line.endswith('\\'):
|
||||
multiline = True
|
||||
line = line.rstrip('\\').rstrip()
|
||||
else:
|
||||
multiline = False
|
||||
|
||||
# match first line of docstring against signature RE
|
||||
match = py_ext_sig_re.match(line)
|
||||
if not match:
|
||||
continue
|
||||
exmod, path, base, args, retann = match.groups()
|
||||
|
||||
# the base name must match ours
|
||||
if base not in valid_names:
|
||||
continue
|
||||
|
||||
# re-prepare docstring to ignore more leading indentation
|
||||
tab_width = self.directive.state.document.settings.tab_width # type: ignore
|
||||
self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[j + 1:]),
|
||||
tabsize=tab_width)
|
||||
|
||||
if result is None:
|
||||
# first signature
|
||||
result = args, retann
|
||||
else:
|
||||
# subsequent signatures
|
||||
self._signatures.append("(%s) -> %s" % (args, retann))
|
||||
|
||||
if multiline:
|
||||
# the signature have multiple signatures on docstring
|
||||
continue
|
||||
else:
|
||||
# don't look any further
|
||||
break
|
||||
|
||||
if result:
|
||||
# finish the loop when signature found
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
def get_doc(self, ignore: int = None) -> List[List[str]]:
|
||||
lines = getattr(self, '_new_docstrings', None)
|
||||
if lines is not None:
|
||||
return lines
|
||||
if self._new_docstrings is not None:
|
||||
return self._new_docstrings
|
||||
return super().get_doc(ignore) # type: ignore
|
||||
|
||||
def format_signature(self, **kwargs: Any) -> str:
|
||||
@ -1005,7 +1105,11 @@ class DocstringSignatureMixin:
|
||||
result = self._find_signature()
|
||||
if result is not None:
|
||||
self.args, self.retann = result
|
||||
return super().format_signature(**kwargs) # type: ignore
|
||||
sig = super().format_signature(**kwargs) # type: ignore
|
||||
if self._signatures:
|
||||
return "\n".join([sig] + self._signatures)
|
||||
else:
|
||||
return sig
|
||||
|
||||
|
||||
class DocstringStripSignatureMixin(DocstringSignatureMixin):
|
||||
@ -1087,6 +1191,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
|
||||
documenter = FunctionDocumenter(self.directive, '')
|
||||
documenter.object = func
|
||||
documenter.objpath = [None]
|
||||
sigs.append(documenter.format_signature())
|
||||
|
||||
return "\n".join(sigs)
|
||||
@ -1128,6 +1233,14 @@ class DecoratorDocumenter(FunctionDocumenter):
|
||||
return None
|
||||
|
||||
|
||||
# Types which have confusing metaclass signatures it would be best not to show.
|
||||
# These are listed by name, rather than storing the objects themselves, to avoid
|
||||
# needing to import the modules.
|
||||
_METACLASS_CALL_BLACKLIST = [
|
||||
'enum.EnumMeta.__call__',
|
||||
]
|
||||
|
||||
|
||||
class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
|
||||
"""
|
||||
Specialized Documenter subclass for classes.
|
||||
@ -1137,7 +1250,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
option_spec = {
|
||||
'members': members_option, 'undoc-members': bool_option,
|
||||
'noindex': bool_option, 'inherited-members': inherited_members_option,
|
||||
'show-inheritance': bool_option, 'member-order': identity,
|
||||
'show-inheritance': bool_option, 'member-order': member_order_option,
|
||||
'exclude-members': members_set_option,
|
||||
'private-members': bool_option, 'special-members': members_option,
|
||||
} # type: Dict[str, Callable]
|
||||
@ -1162,27 +1275,83 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
self.doc_as_attr = True
|
||||
return ret
|
||||
|
||||
def _get_signature(self) -> Optional[Signature]:
|
||||
def get_user_defined_function_or_method(obj: Any, attr: str) -> Any:
|
||||
""" Get the `attr` function or method from `obj`, if it is user-defined. """
|
||||
if inspect.is_builtin_class_method(obj, attr):
|
||||
return None
|
||||
attr = self.get_attr(obj, attr, None)
|
||||
if not (inspect.ismethod(attr) or inspect.isfunction(attr)):
|
||||
return None
|
||||
return attr
|
||||
|
||||
# This sequence is copied from inspect._signature_from_callable.
|
||||
# ValueError means that no signature could be found, so we keep going.
|
||||
|
||||
# First, let's see if it has an overloaded __call__ defined
|
||||
# in its metaclass
|
||||
call = get_user_defined_function_or_method(type(self.object), '__call__')
|
||||
|
||||
if call is not None:
|
||||
if "{0.__module__}.{0.__qualname__}".format(call) in _METACLASS_CALL_BLACKLIST:
|
||||
call = None
|
||||
|
||||
if call is not None:
|
||||
self.env.app.emit('autodoc-before-process-signature', call, True)
|
||||
try:
|
||||
return inspect.signature(call, bound_method=True)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Now we check if the 'obj' class has a '__new__' method
|
||||
new = get_user_defined_function_or_method(self.object, '__new__')
|
||||
if new is not None:
|
||||
self.env.app.emit('autodoc-before-process-signature', new, True)
|
||||
try:
|
||||
return inspect.signature(new, bound_method=True)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Finally, we should have at least __init__ implemented
|
||||
init = get_user_defined_function_or_method(self.object, '__init__')
|
||||
if init is not None:
|
||||
self.env.app.emit('autodoc-before-process-signature', init, True)
|
||||
try:
|
||||
return inspect.signature(init, bound_method=True)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# None of the attributes are user-defined, so fall back to let inspect
|
||||
# handle it.
|
||||
# We don't know the exact method that inspect.signature will read
|
||||
# the signature from, so just pass the object itself to our hook.
|
||||
self.env.app.emit('autodoc-before-process-signature', self.object, False)
|
||||
try:
|
||||
return inspect.signature(self.object, bound_method=False)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Still no signature: happens e.g. for old-style classes
|
||||
# with __init__ in C and no `__text_signature__`.
|
||||
return None
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
if self.env.config.autodoc_typehints in ('none', 'description'):
|
||||
kwargs.setdefault('show_annotation', False)
|
||||
|
||||
# for classes, the relevant signature is the __init__ method's
|
||||
initmeth = self.get_attr(self.object, '__init__', None)
|
||||
# classes without __init__ method, default __init__ or
|
||||
# __init__ written in C?
|
||||
if initmeth is None or \
|
||||
inspect.is_builtin_class_method(self.object, '__init__') or \
|
||||
not(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)):
|
||||
return None
|
||||
try:
|
||||
self.env.app.emit('autodoc-before-process-signature', initmeth, True)
|
||||
sig = inspect.signature(initmeth, bound_method=True)
|
||||
return stringify_signature(sig, show_return_annotation=False, **kwargs)
|
||||
except TypeError:
|
||||
# still not possible: happens e.g. for old-style classes
|
||||
# with __init__ in C
|
||||
sig = self._get_signature()
|
||||
except TypeError as exc:
|
||||
# __signature__ attribute contained junk
|
||||
logger.warning(__("Failed to get a constructor signature for %s: %s"),
|
||||
self.fullname, exc)
|
||||
return None
|
||||
|
||||
if sig is None:
|
||||
return None
|
||||
|
||||
return stringify_signature(sig, show_return_annotation=False, **kwargs)
|
||||
|
||||
def format_signature(self, **kwargs: Any) -> str:
|
||||
if self.doc_as_attr:
|
||||
return ''
|
||||
@ -1393,6 +1562,30 @@ class DataDeclarationDocumenter(DataDocumenter):
|
||||
super().add_content(more_content, no_docstring=True)
|
||||
|
||||
|
||||
class GenericAliasDocumenter(DataDocumenter):
|
||||
"""
|
||||
Specialized Documenter subclass for GenericAliases.
|
||||
"""
|
||||
|
||||
objtype = 'genericalias'
|
||||
directivetype = 'data'
|
||||
priority = DataDocumenter.priority + 1
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
) -> bool:
|
||||
return inspect.isgenericalias(member)
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
self.options.annotation = SUPPRESS # type: ignore
|
||||
super().add_directive_header(sig)
|
||||
|
||||
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
|
||||
name = stringify_typehint(self.object)
|
||||
content = StringList([_('alias of %s') % name], source='')
|
||||
super().add_content(content)
|
||||
|
||||
|
||||
class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore
|
||||
"""
|
||||
Specialized Documenter subclass for methods (normal, static and class).
|
||||
@ -1751,6 +1944,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_autodocumenter(ExceptionDocumenter)
|
||||
app.add_autodocumenter(DataDocumenter)
|
||||
app.add_autodocumenter(DataDeclarationDocumenter)
|
||||
app.add_autodocumenter(GenericAliasDocumenter)
|
||||
app.add_autodocumenter(FunctionDocumenter)
|
||||
app.add_autodocumenter(DecoratorDocumenter)
|
||||
app.add_autodocumenter(MethodDocumenter)
|
||||
@ -1774,7 +1968,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_event('autodoc-process-signature')
|
||||
app.add_event('autodoc-skip-member')
|
||||
|
||||
app.connect('config-inited', migrate_autodoc_member_order)
|
||||
app.connect('config-inited', migrate_autodoc_member_order, priority=800)
|
||||
|
||||
app.setup_extension('sphinx.ext.autodoc.type_comment')
|
||||
app.setup_extension('sphinx.ext.autodoc.typehints')
|
||||
|
@ -15,6 +15,7 @@ from docutils import nodes
|
||||
from docutils.nodes import Node
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.domains.std import StandardDomain
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.nodes import clean_astext
|
||||
@ -33,8 +34,7 @@ def get_node_depth(node: Node) -> int:
|
||||
|
||||
|
||||
def register_sections_as_label(app: Sphinx, document: Node) -> None:
|
||||
labels = app.env.domaindata['std']['labels']
|
||||
anonlabels = app.env.domaindata['std']['anonlabels']
|
||||
domain = cast(StandardDomain, app.env.get_domain('std'))
|
||||
for node in document.traverse(nodes.section):
|
||||
if (app.config.autosectionlabel_maxdepth and
|
||||
get_node_depth(node) >= app.config.autosectionlabel_maxdepth):
|
||||
@ -49,13 +49,13 @@ def register_sections_as_label(app: Sphinx, document: Node) -> None:
|
||||
name = nodes.fully_normalize_name(ref_name)
|
||||
sectname = clean_astext(title)
|
||||
|
||||
if name in labels:
|
||||
if name in domain.labels:
|
||||
logger.warning(__('duplicate label %s, other instance in %s'),
|
||||
name, app.env.doc2path(labels[name][0]),
|
||||
name, app.env.doc2path(domain.labels[name][0]),
|
||||
location=node, type='autosectionlabel', subtype=docname)
|
||||
|
||||
anonlabels[name] = docname, labelid
|
||||
labels[name] = docname, labelid, sectname
|
||||
domain.anonlabels[name] = docname, labelid
|
||||
domain.labels[name] = docname, labelid, sectname
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
|
@ -250,14 +250,13 @@ class Autosummary(SphinxDirective):
|
||||
docname = posixpath.join(tree_prefix, real_name)
|
||||
docname = posixpath.normpath(posixpath.join(dirname, docname))
|
||||
if docname not in self.env.found_docs:
|
||||
location = self.state_machine.get_source_and_line(self.lineno)
|
||||
if excluded(self.env.doc2path(docname, None)):
|
||||
msg = __('autosummary references excluded document %r. Ignored.')
|
||||
else:
|
||||
msg = __('autosummary: stub file not found %r. '
|
||||
'Check your autosummary_generate setting.')
|
||||
|
||||
logger.warning(msg, real_name, location=location)
|
||||
logger.warning(msg, real_name, location=self.get_source_info())
|
||||
continue
|
||||
|
||||
docnames.append(docname)
|
||||
@ -298,7 +297,8 @@ class Autosummary(SphinxDirective):
|
||||
with mock(self.config.autosummary_mock_imports):
|
||||
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
|
||||
except ImportError:
|
||||
logger.warning(__('autosummary: failed to import %s'), name)
|
||||
logger.warning(__('autosummary: failed to import %s'), name,
|
||||
location=self.get_source_info())
|
||||
continue
|
||||
|
||||
self.bridge.result = StringList() # initialize for each documenter
|
||||
@ -312,11 +312,13 @@ class Autosummary(SphinxDirective):
|
||||
doccls = get_documenter(self.env.app, obj, parent)
|
||||
documenter = doccls(self.bridge, full_name)
|
||||
if not documenter.parse_name():
|
||||
logger.warning(__('failed to parse name %s'), real_name)
|
||||
logger.warning(__('failed to parse name %s'), real_name,
|
||||
location=self.get_source_info())
|
||||
items.append((display_name, '', '', real_name))
|
||||
continue
|
||||
if not documenter.import_object():
|
||||
logger.warning(__('failed to import object %s'), real_name)
|
||||
logger.warning(__('failed to import object %s'), real_name,
|
||||
location=self.get_source_info())
|
||||
items.append((display_name, '', '', real_name))
|
||||
continue
|
||||
if documenter.options.members and not documenter.check_module():
|
||||
|
@ -18,6 +18,7 @@
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
import locale
|
||||
import os
|
||||
import pkgutil
|
||||
@ -42,6 +43,7 @@ from sphinx.deprecation import RemovedInSphinx50Warning
|
||||
from sphinx.ext.autodoc import Documenter
|
||||
from sphinx.ext.autosummary import import_by_name, get_documenter
|
||||
from sphinx.locale import __
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.registry import SphinxComponentRegistry
|
||||
from sphinx.util import logging
|
||||
from sphinx.util import rst
|
||||
@ -85,14 +87,14 @@ def setup_documenters(app: Any) -> None:
|
||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
||||
SlotsAttributeDocumenter, DataDeclarationDocumenter,
|
||||
SlotsAttributeDocumenter, DataDeclarationDocumenter, GenericAliasDocumenter,
|
||||
SingledispatchFunctionDocumenter,
|
||||
)
|
||||
documenters = [
|
||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
||||
SlotsAttributeDocumenter, DataDeclarationDocumenter,
|
||||
SlotsAttributeDocumenter, DataDeclarationDocumenter, GenericAliasDocumenter,
|
||||
SingledispatchFunctionDocumenter,
|
||||
] # type: List[Type[Documenter]]
|
||||
for documenter in documenters:
|
||||
@ -137,11 +139,11 @@ class AutosummaryRenderer:
|
||||
if isinstance(app, (Sphinx, DummyApplication)):
|
||||
if app.translator:
|
||||
self.env.add_extension("jinja2.ext.i18n")
|
||||
self.env.install_gettext_translations(app.translator) # type: ignore
|
||||
self.env.install_gettext_translations(app.translator)
|
||||
elif isinstance(app, Builder):
|
||||
if app.app.translator:
|
||||
self.env.add_extension("jinja2.ext.i18n")
|
||||
self.env.install_gettext_translations(app.app.translator) # type: ignore
|
||||
self.env.install_gettext_translations(app.app.translator)
|
||||
|
||||
def exists(self, template_name: str) -> bool:
|
||||
"""Check if template file exists."""
|
||||
@ -171,6 +173,56 @@ class AutosummaryRenderer:
|
||||
# -- Generating output ---------------------------------------------------------
|
||||
|
||||
|
||||
class ModuleScanner:
|
||||
def __init__(self, app: Any, obj: Any) -> None:
|
||||
self.app = app
|
||||
self.object = obj
|
||||
|
||||
def get_object_type(self, name: str, value: Any) -> str:
|
||||
return get_documenter(self.app, value, self.object).objtype
|
||||
|
||||
def is_skipped(self, name: str, value: Any, objtype: str) -> bool:
|
||||
try:
|
||||
return self.app.emit_firstresult('autodoc-skip-member', objtype,
|
||||
name, value, False, {})
|
||||
except Exception as exc:
|
||||
logger.warning(__('autosummary: failed to determine %r to be documented, '
|
||||
'the following exception was raised:\n%s'),
|
||||
name, exc, type='autosummary')
|
||||
return False
|
||||
|
||||
def scan(self, imported_members: bool) -> List[str]:
|
||||
members = []
|
||||
for name in dir(self.object):
|
||||
try:
|
||||
value = safe_getattr(self.object, name)
|
||||
except AttributeError:
|
||||
value = None
|
||||
|
||||
objtype = self.get_object_type(name, value)
|
||||
if self.is_skipped(name, value, objtype):
|
||||
continue
|
||||
|
||||
try:
|
||||
if inspect.ismodule(value):
|
||||
imported = True
|
||||
elif safe_getattr(value, '__module__') != self.object.__name__:
|
||||
imported = True
|
||||
else:
|
||||
imported = False
|
||||
except AttributeError:
|
||||
imported = False
|
||||
|
||||
if imported_members:
|
||||
# list all members up
|
||||
members.append(name)
|
||||
elif imported is False:
|
||||
# list not-imported members up
|
||||
members.append(name)
|
||||
|
||||
return members
|
||||
|
||||
|
||||
def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
template: AutosummaryRenderer, template_name: str,
|
||||
imported_members: bool, app: Any,
|
||||
@ -214,6 +266,21 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
public.append(name)
|
||||
return public, items
|
||||
|
||||
def get_module_attrs(members: Any) -> Tuple[List[str], List[str]]:
|
||||
"""Find module attributes with docstrings."""
|
||||
attrs, public = [], []
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(name)
|
||||
attr_docs = analyzer.find_attr_docs()
|
||||
for namespace, attr_name in attr_docs:
|
||||
if namespace == '' and attr_name in members:
|
||||
attrs.append(attr_name)
|
||||
if not attr_name.startswith('_'):
|
||||
public.append(attr_name)
|
||||
except PycodeError:
|
||||
pass # give up if ModuleAnalyzer fails to parse code
|
||||
return public, attrs
|
||||
|
||||
def get_modules(obj: Any) -> Tuple[List[str], List[str]]:
|
||||
items = [] # type: List[str]
|
||||
for _, modname, ispkg in pkgutil.iter_modules(obj.__path__):
|
||||
@ -226,13 +293,16 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
ns.update(context)
|
||||
|
||||
if doc.objtype == 'module':
|
||||
ns['members'] = dir(obj)
|
||||
scanner = ModuleScanner(app, obj)
|
||||
ns['members'] = scanner.scan(imported_members)
|
||||
ns['functions'], ns['all_functions'] = \
|
||||
get_members(obj, {'function'}, imported=imported_members)
|
||||
ns['classes'], ns['all_classes'] = \
|
||||
get_members(obj, {'class'}, imported=imported_members)
|
||||
ns['exceptions'], ns['all_exceptions'] = \
|
||||
get_members(obj, {'exception'}, imported=imported_members)
|
||||
ns['attributes'], ns['all_attributes'] = \
|
||||
get_module_attrs(ns['members'])
|
||||
ispackage = hasattr(obj, '__path__')
|
||||
if ispackage and recursive:
|
||||
ns['modules'], ns['all_modules'] = get_modules(obj)
|
||||
|
@ -2,6 +2,17 @@
|
||||
|
||||
.. automodule:: {{ fullname }}
|
||||
|
||||
{% block attributes %}
|
||||
{% if attributes %}
|
||||
.. rubric:: Module Attributes
|
||||
|
||||
.. autosummary::
|
||||
{% for item in attributes %}
|
||||
{{ item }}
|
||||
{%- endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block functions %}
|
||||
{% if functions %}
|
||||
.. rubric:: {{ _('Functions') }}
|
||||
|
@ -96,7 +96,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
# more information for mathjax secure url is here:
|
||||
# https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn
|
||||
app.add_config_value('mathjax_path',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?'
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?'
|
||||
'config=TeX-AMS-MML_HTMLorMML', 'html')
|
||||
app.add_config_value('mathjax_options', {}, 'html')
|
||||
app.add_config_value('mathjax_inline', [r'\(', r'\)'], 'html')
|
||||
|
@ -314,7 +314,7 @@ class GoogleDocstring:
|
||||
return [line[min_indent:] for line in lines]
|
||||
|
||||
def _escape_args_and_kwargs(self, name: str) -> str:
|
||||
if name.endswith('_'):
|
||||
if name.endswith('_') and getattr(self._config, 'strip_signature_backslash', False):
|
||||
name = name[:-1] + r'\_'
|
||||
|
||||
if name[:2] == '**':
|
||||
|
@ -186,7 +186,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
|
||||
self.environment.globals['accesskey'] = contextfunction(accesskey)
|
||||
self.environment.globals['idgen'] = idgen
|
||||
if use_i18n:
|
||||
self.environment.install_gettext_translations(builder.app.translator) # type: ignore # NOQA
|
||||
self.environment.install_gettext_translations(builder.app.translator)
|
||||
|
||||
def render(self, template: str, context: Dict) -> str: # type: ignore
|
||||
return self.environment.get_template(template).render(context)
|
||||
|
@ -97,6 +97,8 @@ class SphinxComponentRegistry:
|
||||
#: LaTeX packages; list of package names and its options
|
||||
self.latex_packages = [] # type: List[Tuple[str, str]]
|
||||
|
||||
self.latex_packages_after_hyperref = [] # type: List[Tuple[str, str]]
|
||||
|
||||
#: post transforms; list of transforms
|
||||
self.post_transforms = [] # type: List[Type[Transform]]
|
||||
|
||||
@ -362,9 +364,12 @@ class SphinxComponentRegistry:
|
||||
logger.debug('[app] adding js_file: %r, %r', filename, attributes)
|
||||
self.js_files.append((filename, attributes))
|
||||
|
||||
def add_latex_package(self, name: str, options: str) -> None:
|
||||
def add_latex_package(self, name: str, options: str, after_hyperref: bool = False) -> None:
|
||||
logger.debug('[app] adding latex package: %r', name)
|
||||
self.latex_packages.append((name, options))
|
||||
if after_hyperref:
|
||||
self.latex_packages_after_hyperref.append((name, options))
|
||||
else:
|
||||
self.latex_packages.append((name, options))
|
||||
|
||||
def add_enumerable_node(self, node: "Type[Node]", figtype: str,
|
||||
title_getter: TitleGetter = None, override: bool = False) -> None:
|
||||
|
@ -288,8 +288,8 @@ class IndexBuilder:
|
||||
frozen.get('envversion') != self.env.version:
|
||||
raise ValueError('old format')
|
||||
index2fn = frozen['docnames']
|
||||
self._filenames = dict(zip(index2fn, frozen['filenames']))
|
||||
self._titles = dict(zip(index2fn, frozen['titles']))
|
||||
self._filenames = dict(zip(index2fn, frozen['filenames'])) # type: ignore
|
||||
self._titles = dict(zip(index2fn, frozen['titles'])) # type: ignore
|
||||
|
||||
def load_terms(mapping: Dict[str, Any]) -> Dict[str, Set[str]]:
|
||||
rv = {}
|
||||
@ -350,13 +350,13 @@ class IndexBuilder:
|
||||
def get_terms(self, fn2index: Dict) -> Tuple[Dict[str, List[str]], Dict[str, List[str]]]:
|
||||
rvs = {}, {} # type: Tuple[Dict[str, List[str]], Dict[str, List[str]]]
|
||||
for rv, mapping in zip(rvs, (self._mapping, self._title_mapping)):
|
||||
for k, v in mapping.items():
|
||||
for k, v in mapping.items(): # type: ignore
|
||||
if len(v) == 1:
|
||||
fn, = v
|
||||
if fn in fn2index:
|
||||
rv[k] = fn2index[fn]
|
||||
rv[k] = fn2index[fn] # type: ignore
|
||||
else:
|
||||
rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index])
|
||||
rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index]) # type: ignore # NOQA
|
||||
return rvs
|
||||
|
||||
def freeze(self) -> Dict[str, Any]:
|
||||
|
@ -46,6 +46,14 @@
|
||||
<%- endfor %>
|
||||
|
||||
<%= hyperref %>
|
||||
<%- for name, option in packages_after_hyperref %>
|
||||
<%- if option %>
|
||||
\usepackage[<%= option %>]{<%= name %>}
|
||||
<%- else %>
|
||||
\usepackage{<%= name %>}
|
||||
<%- endif %>
|
||||
<%- endfor %>
|
||||
|
||||
<%= contentsname %>
|
||||
\usepackage{sphinxmessages}
|
||||
<%= tocdepth %>
|
||||
|
@ -27,9 +27,9 @@ if "%1" == "help" (
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and an HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. qthelp to make HTML files and a Qt help project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. epub to make an EPUB
|
||||
echo. latex to make LaTeX files (you can set PAPER=a4 or PAPER=letter)
|
||||
echo. latexpdf to make LaTeX files and then PDFs out of them
|
||||
echo. text to make text files
|
||||
@ -69,7 +69,7 @@ if errorlevel 9009 (
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
echo.https://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
|
@ -120,6 +120,7 @@
|
||||
{%- else %}
|
||||
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
|
||||
{%- endif %}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{- metatags }}
|
||||
{%- block htmltitle %}
|
||||
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||
|
File diff suppressed because it is too large
Load Diff
4
sphinx/themes/basic/static/jquery.js
vendored
4
sphinx/themes/basic/static/jquery.js
vendored
File diff suppressed because one or more lines are too long
@ -14,10 +14,6 @@
|
||||
<script src="{{ pathto('_static/bizstyle.js', 1) }}"></script>
|
||||
{%- endblock %}
|
||||
|
||||
{# put the sidebar before the body #}
|
||||
{% block sidebar1 %}{{ sidebar() }}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
||||
|
||||
{# doctype override #}
|
||||
{%- block doctype %}
|
||||
<!doctype html>
|
||||
|
@ -39,6 +39,11 @@ div.document {
|
||||
-webkit-box-shadow: 2px 2px 5px #000;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 240px;
|
||||
border-left: 1px solid #ccc;
|
||||
@ -105,7 +110,6 @@ div.sphinxsidebarwrapper {
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
margin: 0;
|
||||
padding: 0.5em 12px 12px 12px;
|
||||
width: {{ theme_sidebarwidth|todim }};
|
||||
{%- if theme_rightsidebar|tobool %}
|
||||
|
@ -1,14 +0,0 @@
|
||||
{#
|
||||
sphinxdoc/layout.html
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sphinx layout template for the sphinxdoc theme.
|
||||
|
||||
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
#}
|
||||
{%- extends "basic/layout.html" %}
|
||||
|
||||
{# put the sidebar before the body #}
|
||||
{% block sidebar1 %}{{ sidebar() }}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
@ -37,6 +37,11 @@ div.document {
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 calc({{ theme_sidebarwidth|todim }} + 10px) 0 0;
|
||||
border-right: 1px solid #ccc;
|
||||
@ -86,7 +91,6 @@ div.sphinxsidebarwrapper {
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
margin: 0;
|
||||
padding: 0.5em 15px 15px 0;
|
||||
width: calc({{ theme_sidebarwidth|todim }} - 20px);
|
||||
float: right;
|
||||
|
@ -92,7 +92,8 @@ class ReferencesResolver(SphinxPostTransform):
|
||||
# no new node found? try the missing-reference event
|
||||
if newnode is None:
|
||||
newnode = self.app.emit_firstresult('missing-reference', self.env,
|
||||
node, contnode)
|
||||
node, contnode,
|
||||
allowed_exceptions=(NoUri,))
|
||||
# still not found? warn if node wishes to be warned about or
|
||||
# we are in nit-picky mode
|
||||
if newnode is None:
|
||||
|
@ -35,8 +35,7 @@ def get_terminal_width() -> int:
|
||||
import termios
|
||||
import fcntl
|
||||
import struct
|
||||
call = fcntl.ioctl(0, termios.TIOCGWINSZ, # type: ignore
|
||||
struct.pack('hhhh', 0, 0, 0, 0))
|
||||
call = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hhhh', 0, 0, 0, 0))
|
||||
height, width = struct.unpack('hhhh', call)[:2]
|
||||
terminal_width = width
|
||||
except Exception:
|
||||
|
@ -54,7 +54,7 @@ def get_image_size(filename: str) -> Optional[Tuple[int, int]]:
|
||||
|
||||
|
||||
def guess_mimetype_for_stream(stream: IO, default: Optional[str] = None) -> Optional[str]:
|
||||
imgtype = imghdr.what(stream) # type: ignore
|
||||
imgtype = imghdr.what(stream)
|
||||
if imgtype:
|
||||
return 'image/' + imgtype
|
||||
else:
|
||||
|
@ -13,6 +13,7 @@ import enum
|
||||
import inspect
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
import typing
|
||||
import warnings
|
||||
from functools import partial, partialmethod
|
||||
@ -304,6 +305,18 @@ def isproperty(obj: Any) -> bool:
|
||||
return isinstance(obj, property)
|
||||
|
||||
|
||||
def isgenericalias(obj: Any) -> bool:
|
||||
"""Check if the object is GenericAlias."""
|
||||
if (hasattr(typing, '_GenericAlias') and # only for py37+
|
||||
isinstance(obj, typing._GenericAlias)): # type: ignore
|
||||
return True
|
||||
elif (hasattr(types, 'GenericAlias') and # only for py39+
|
||||
isinstance(obj, types.GenericAlias)): # type: ignore
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
|
||||
"""A getattr() that turns all exceptions into AttributeErrors."""
|
||||
try:
|
||||
@ -501,19 +514,34 @@ def signature_from_str(signature: str) -> inspect.Signature:
|
||||
|
||||
# parameters
|
||||
args = definition.args
|
||||
defaults = list(args.defaults)
|
||||
params = []
|
||||
if hasattr(args, "posonlyargs"):
|
||||
posonlyargs = len(args.posonlyargs) # type: ignore
|
||||
positionals = posonlyargs + len(args.args)
|
||||
else:
|
||||
posonlyargs = 0
|
||||
positionals = len(args.args)
|
||||
|
||||
for _ in range(len(defaults), positionals):
|
||||
defaults.insert(0, Parameter.empty)
|
||||
|
||||
if hasattr(args, "posonlyargs"):
|
||||
for arg in args.posonlyargs: # type: ignore
|
||||
for i, arg in enumerate(args.posonlyargs): # type: ignore
|
||||
if defaults[i] is Parameter.empty:
|
||||
default = Parameter.empty
|
||||
else:
|
||||
default = ast_unparse(defaults[i])
|
||||
|
||||
annotation = ast_unparse(arg.annotation) or Parameter.empty
|
||||
params.append(Parameter(arg.arg, Parameter.POSITIONAL_ONLY,
|
||||
annotation=annotation))
|
||||
default=default, annotation=annotation))
|
||||
|
||||
for i, arg in enumerate(args.args):
|
||||
if len(args.args) - i <= len(args.defaults):
|
||||
default = ast_unparse(args.defaults[-len(args.args) + i])
|
||||
else:
|
||||
if defaults[i + posonlyargs] is Parameter.empty:
|
||||
default = Parameter.empty
|
||||
else:
|
||||
default = ast_unparse(defaults[i + posonlyargs])
|
||||
|
||||
annotation = ast_unparse(arg.annotation) or Parameter.empty
|
||||
params.append(Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD,
|
||||
@ -561,7 +589,7 @@ def getdoc(obj: Any, attrgetter: Callable = safe_getattr,
|
||||
# This tries to obtain the docstring from super classes.
|
||||
for basecls in getattr(cls, '__mro__', []):
|
||||
meth = safe_getattr(basecls, name, None)
|
||||
if meth:
|
||||
if meth is not None:
|
||||
doc = inspect.getdoc(meth)
|
||||
if doc:
|
||||
break
|
||||
|
@ -25,6 +25,7 @@ from sphinx.util import logging
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.domain import IndexEntry
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.utils.tags import Tags
|
||||
|
||||
@ -303,7 +304,7 @@ def get_prev_node(node: Node) -> Node:
|
||||
return None
|
||||
|
||||
|
||||
def traverse_translatable_index(doctree: Element) -> Iterable[Tuple[Element, List[str]]]:
|
||||
def traverse_translatable_index(doctree: Element) -> Iterable[Tuple[Element, List["IndexEntry"]]]: # NOQA
|
||||
"""Traverse translatable index node from a document tree."""
|
||||
for node in doctree.traverse(NodeMatcher(addnodes.index, inline=False)): # type: addnodes.index # NOQA
|
||||
if 'raw_entries' in node:
|
||||
|
@ -28,7 +28,7 @@ class BaseRenderer:
|
||||
def __init__(self, loader: BaseLoader = None) -> None:
|
||||
self.env = SandboxedEnvironment(loader=loader, extensions=['jinja2.ext.i18n'])
|
||||
self.env.filters['repr'] = repr
|
||||
self.env.install_gettext_translations(get_translator()) # type: ignore
|
||||
self.env.install_gettext_translations(get_translator())
|
||||
|
||||
def render(self, template_name: str, context: Dict) -> str:
|
||||
return self.env.get_template(template_name).render(context)
|
||||
|
@ -53,7 +53,7 @@ def stringify(annotation: Any) -> str:
|
||||
return annotation.__name__
|
||||
elif not annotation:
|
||||
return repr(annotation)
|
||||
elif annotation is NoneType: # type: ignore
|
||||
elif annotation is NoneType:
|
||||
return 'None'
|
||||
elif (getattr(annotation, '__module__', None) == 'builtins' and
|
||||
hasattr(annotation, '__qualname__')):
|
||||
@ -91,7 +91,7 @@ def _stringify_py37(annotation: Any) -> str:
|
||||
|
||||
if getattr(annotation, '__args__', None):
|
||||
if qualname == 'Union':
|
||||
if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType: # type: ignore # NOQA
|
||||
if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType:
|
||||
if len(annotation.__args__) > 2:
|
||||
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
|
||||
return 'Optional[Union[%s]]' % args
|
||||
@ -159,7 +159,7 @@ def _stringify_py36(annotation: Any) -> str:
|
||||
annotation.__origin__ is typing.Union):
|
||||
params = annotation.__args__
|
||||
if params is not None:
|
||||
if len(params) > 1 and params[-1] is NoneType: # type: ignore
|
||||
if len(params) > 1 and params[-1] is NoneType:
|
||||
if len(params) > 2:
|
||||
param_str = ", ".join(stringify(p) for p in params[:-1])
|
||||
return 'Optional[Union[%s]]' % param_str
|
||||
|
@ -1428,6 +1428,8 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
|
||||
if 'refuri' in node:
|
||||
return
|
||||
if 'anonymous' in node:
|
||||
return
|
||||
if node.get('refid'):
|
||||
prev_node = get_prev_node(node)
|
||||
if isinstance(prev_node, nodes.reference) and node['refid'] == prev_node['refid']:
|
||||
|
@ -17,3 +17,9 @@ class C:
|
||||
class D:
|
||||
def __init__(self):
|
||||
"""D(foo, bar, baz)"""
|
||||
|
||||
|
||||
class E:
|
||||
def __init__(self):
|
||||
"""E(foo: int, bar: int, baz: int) -> None \\
|
||||
E(foo: str, bar: str, baz: str) -> None"""
|
||||
|
6
tests/roots/test-ext-autodoc/target/genericalias.py
Normal file
6
tests/roots/test-ext-autodoc/target/genericalias.py
Normal file
@ -0,0 +1,6 @@
|
||||
from typing import List, Callable
|
||||
|
||||
#: A list of int
|
||||
T = List[int]
|
||||
|
||||
C = Callable[[int], None] # a generic alias not having a doccomment
|
25
tests/roots/test-ext-autodoc/target/sort_by_all.py
Normal file
25
tests/roots/test-ext-autodoc/target/sort_by_all.py
Normal file
@ -0,0 +1,25 @@
|
||||
__all__ = ['baz', 'foo', 'Bar']
|
||||
|
||||
|
||||
def foo():
|
||||
pass
|
||||
|
||||
|
||||
class Bar:
|
||||
pass
|
||||
|
||||
|
||||
def baz():
|
||||
pass
|
||||
|
||||
|
||||
def qux():
|
||||
pass
|
||||
|
||||
|
||||
class Quux:
|
||||
pass
|
||||
|
||||
|
||||
def foobar():
|
||||
pass
|
@ -37,6 +37,26 @@ def tuple_args(x: Tuple[int, Union[int, str]]) -> Tuple[int, int]:
|
||||
pass
|
||||
|
||||
|
||||
class NewAnnotation:
|
||||
def __new__(cls, i: int) -> 'NewAnnotation':
|
||||
pass
|
||||
|
||||
|
||||
class NewComment:
|
||||
def __new__(cls, i):
|
||||
# type: (int) -> NewComment
|
||||
pass
|
||||
|
||||
|
||||
class _MetaclassWithCall(type):
|
||||
def __call__(cls, a: int):
|
||||
pass
|
||||
|
||||
|
||||
class SignatureFromMetaclass(metaclass=_MetaclassWithCall):
|
||||
pass
|
||||
|
||||
|
||||
def complex_func(arg1, arg2, arg3=None, *args, **kwargs):
|
||||
# type: (str, List[int], Tuple[int, Union[str, Unknown]], *str, **str) -> None
|
||||
pass
|
||||
@ -48,4 +68,3 @@ def missing_attr(c,
|
||||
):
|
||||
# type: (...) -> str
|
||||
return a + (b or "")
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from os import * # NOQA
|
||||
from os import path # NOQA
|
||||
from typing import Union
|
||||
|
||||
|
||||
@ -17,5 +17,25 @@ class Foo:
|
||||
pass
|
||||
|
||||
|
||||
def bar(x: Union[int, str], y: int = 1):
|
||||
class _Baz:
|
||||
pass
|
||||
|
||||
|
||||
def bar(x: Union[int, str], y: int = 1) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def _quux():
|
||||
pass
|
||||
|
||||
|
||||
class Exc(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class _Exc(Exception):
|
||||
pass
|
||||
|
||||
|
||||
#: a module-level attribute
|
||||
qux = 2
|
||||
|
@ -11,4 +11,5 @@
|
||||
autosummary_dummy_module.Foo
|
||||
autosummary_dummy_module.Foo.Bar
|
||||
autosummary_dummy_module.bar
|
||||
autosummary_dummy_module.qux
|
||||
autosummary_importfail
|
||||
|
@ -126,6 +126,15 @@ def test_expressions():
|
||||
print("Result: ", res)
|
||||
print("Expected: ", output)
|
||||
raise DefinitionError("")
|
||||
displayString = ast.get_display_string()
|
||||
if res != displayString:
|
||||
# note: if the expression contains an anon name then this will trigger a falsely
|
||||
print("")
|
||||
print("Input: ", expr)
|
||||
print("Result: ", res)
|
||||
print("Display: ", displayString)
|
||||
raise DefinitionError("")
|
||||
|
||||
# type expressions
|
||||
exprCheck('int*')
|
||||
exprCheck('int *const*')
|
||||
@ -400,7 +409,7 @@ def test_function_definitions():
|
||||
output='void f(int arr[static volatile const 42])')
|
||||
|
||||
|
||||
class test_nested_name():
|
||||
def test_nested_name():
|
||||
check('struct', '{key}.A', {1: "A"})
|
||||
check('struct', '{key}.A.B', {1: "A.B"})
|
||||
check('function', 'void f(.A a)', {1: "f"})
|
||||
|
@ -154,6 +154,15 @@ def test_expressions():
|
||||
print("Input: ", expr)
|
||||
print("Result: ", res)
|
||||
raise DefinitionError("")
|
||||
displayString = ast.get_display_string()
|
||||
if res != displayString:
|
||||
# note: if the expression contains an anon name then this will trigger a falsely
|
||||
print("")
|
||||
print("Input: ", expr)
|
||||
print("Result: ", res)
|
||||
print("Display: ", displayString)
|
||||
raise DefinitionError("")
|
||||
|
||||
# primary
|
||||
exprCheck('nullptr', 'LDnE')
|
||||
exprCheck('true', 'L1E')
|
||||
@ -662,7 +671,7 @@ def test_operators():
|
||||
check('function', 'void operator[]()', {1: "subscript-operator", 2: "ixv"})
|
||||
|
||||
|
||||
class test_nested_name():
|
||||
def test_nested_name():
|
||||
check('class', '{key}::A', {1: "A", 2: "1A"})
|
||||
check('class', '{key}::A::B', {1: "A::B", 2: "N1A1BE"})
|
||||
check('function', 'void f(::A a)', {1: "f__A", 2: "1f1A"})
|
||||
@ -818,6 +827,15 @@ def test_templates():
|
||||
check('type', 'template<C T = int&> {key}A', {2: 'I_1CE1A'}, key='using')
|
||||
|
||||
|
||||
def test_requires_clauses():
|
||||
check('function', 'template<typename T> requires A auto f() -> void requires B',
|
||||
{4: 'I0EIQaa1A1BE1fvv'})
|
||||
check('function', 'template<typename T> requires A || B or C void f()',
|
||||
{4: 'I0EIQoo1Aoo1B1CE1fvv'})
|
||||
check('function', 'template<typename T> requires A && B || C and D void f()',
|
||||
{4: 'I0EIQooaa1A1Baa1C1DE1fvv'})
|
||||
|
||||
|
||||
def test_template_args():
|
||||
# from breathe#218
|
||||
check('function',
|
||||
|
@ -8,6 +8,9 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from sphinx.errors import ExtensionError
|
||||
from sphinx.events import EventManager
|
||||
|
||||
|
||||
@ -22,3 +25,19 @@ def test_event_priority():
|
||||
|
||||
events.emit('builder-inited')
|
||||
assert result == [3, 1, 2, 5, 4]
|
||||
|
||||
|
||||
def test_event_allowed_exceptions():
|
||||
def raise_error(app):
|
||||
raise RuntimeError
|
||||
|
||||
events = EventManager(object()) # pass an dummy object as an app
|
||||
events.connect('builder-inited', raise_error, priority=500)
|
||||
|
||||
# all errors are conveted to ExtensionError
|
||||
with pytest.raises(ExtensionError):
|
||||
events.emit('builder-inited')
|
||||
|
||||
# Allow RuntimeError (pass-through)
|
||||
with pytest.raises(RuntimeError):
|
||||
events.emit('builder-inited', allowed_exceptions=(RuntimeError,))
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""
|
||||
test_autodoc
|
||||
~~~~~~~~~~~~
|
||||
test_ext_autodoc
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the autodoc extension. This tests mainly the Documenters; the auto
|
||||
directives are tested in a test source file translated by test_build.
|
||||
@ -162,7 +162,6 @@ def test_format_signature(app):
|
||||
pass
|
||||
assert formatsig('function', 'f', f, None, None) == '(a, b, c=1, **d)'
|
||||
assert formatsig('function', 'f', f, 'a, b, c, d', None) == '(a, b, c, d)'
|
||||
assert formatsig('function', 'f', f, None, 'None') == '(a, b, c=1, **d) -> None'
|
||||
assert formatsig('function', 'g', g, None, None) == r"(a='\n')"
|
||||
|
||||
# test for classes
|
||||
@ -170,21 +169,64 @@ def test_format_signature(app):
|
||||
pass
|
||||
|
||||
class E:
|
||||
pass
|
||||
# no signature for classes without __init__
|
||||
for C in (D, E):
|
||||
assert formatsig('class', 'D', C, None, None) == ''
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# an empty init and no init are the same
|
||||
for C in (D, E):
|
||||
assert formatsig('class', 'D', C, None, None) == '()'
|
||||
|
||||
|
||||
class SomeMeta(type):
|
||||
def __call__(cls, a, b=None):
|
||||
return type.__call__(cls, a, b)
|
||||
|
||||
# these three are all equivalent
|
||||
class F:
|
||||
def __init__(self, a, b=None):
|
||||
pass
|
||||
|
||||
class FNew:
|
||||
def __new__(cls, a, b=None):
|
||||
return super().__new__(cls)
|
||||
|
||||
class FMeta(metaclass=SomeMeta):
|
||||
pass
|
||||
|
||||
# and subclasses should always inherit
|
||||
class G(F):
|
||||
pass
|
||||
for C in (F, G):
|
||||
|
||||
class GNew(FNew):
|
||||
pass
|
||||
|
||||
class GMeta(FMeta):
|
||||
pass
|
||||
|
||||
# subclasses inherit
|
||||
for C in (F, FNew, FMeta, G, GNew, GMeta):
|
||||
assert formatsig('class', 'C', C, None, None) == '(a, b=None)'
|
||||
assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X'
|
||||
|
||||
|
||||
class ListSubclass(list):
|
||||
pass
|
||||
|
||||
# only supported if the python implementation decides to document it
|
||||
if getattr(list, '__text_signature__', None) is not None:
|
||||
assert formatsig('class', 'C', ListSubclass, None, None) == '(iterable=(), /)'
|
||||
else:
|
||||
assert formatsig('class', 'C', ListSubclass, None, None) == ''
|
||||
|
||||
|
||||
class ExceptionSubclass(Exception):
|
||||
pass
|
||||
|
||||
# Exception has no __text_signature__ at least in Python 3.8
|
||||
if getattr(Exception, '__text_signature__', None) is None:
|
||||
assert formatsig('class', 'C', ExceptionSubclass, None, None) == ''
|
||||
|
||||
|
||||
# __init__ have signature at first line of docstring
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
|
||||
@ -247,6 +289,27 @@ def test_format_signature(app):
|
||||
'(b, c=42, *d, **e)'
|
||||
|
||||
|
||||
def test_autodoc_process_signature_typehints(app):
|
||||
captured = []
|
||||
|
||||
def process_signature(*args):
|
||||
captured.append(args)
|
||||
|
||||
app.connect('autodoc-process-signature', process_signature)
|
||||
|
||||
def func(x: int, y: int) -> int:
|
||||
pass
|
||||
|
||||
directive = make_directive_bridge(app.env)
|
||||
inst = app.registry.documenters['function'](directive, 'func')
|
||||
inst.fullname = 'func'
|
||||
inst.object = func
|
||||
inst.objpath = ['func']
|
||||
inst.format_signature()
|
||||
assert captured == [(app, 'function', 'func', func,
|
||||
directive.genopt, '(x: int, y: int)', 'int')]
|
||||
|
||||
|
||||
def test_get_doc(app):
|
||||
directive = make_directive_bridge(app.env)
|
||||
|
||||
@ -477,14 +540,14 @@ def test_autodoc_members(app):
|
||||
# default (no-members)
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base')
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
'.. py:class:: Base()',
|
||||
]
|
||||
|
||||
# default ALL-members
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
'.. py:class:: Base()',
|
||||
' .. py:method:: Base.inheritedclassmeth()',
|
||||
' .. py:method:: Base.inheritedmeth()',
|
||||
' .. py:method:: Base.inheritedstaticmeth(cls)'
|
||||
@ -494,7 +557,7 @@ def test_autodoc_members(app):
|
||||
options = {"members": "inheritedmeth,inheritedstaticmeth"}
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
'.. py:class:: Base()',
|
||||
' .. py:method:: Base.inheritedmeth()',
|
||||
' .. py:method:: Base.inheritedstaticmeth(cls)'
|
||||
]
|
||||
@ -506,7 +569,7 @@ def test_autodoc_exclude_members(app):
|
||||
"exclude-members": "inheritedmeth,inheritedstaticmeth"}
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
'.. py:class:: Base()',
|
||||
' .. py:method:: Base.inheritedclassmeth()'
|
||||
]
|
||||
|
||||
@ -515,7 +578,7 @@ def test_autodoc_exclude_members(app):
|
||||
"exclude-members": "inheritedmeth"}
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
'.. py:class:: Base()',
|
||||
]
|
||||
|
||||
|
||||
@ -659,10 +722,10 @@ def test_autodoc_ignore_module_all(app):
|
||||
assert list(filter(lambda l: 'class::' in l, actual)) == [
|
||||
'.. py:class:: Class(arg)',
|
||||
'.. py:class:: CustomDict',
|
||||
'.. py:class:: InnerChild',
|
||||
'.. py:class:: InnerChild()',
|
||||
'.. py:class:: InstAttCls()',
|
||||
'.. py:class:: Outer',
|
||||
' .. py:class:: Outer.Inner',
|
||||
'.. py:class:: Outer()',
|
||||
' .. py:class:: Outer.Inner()',
|
||||
'.. py:class:: StrRepr'
|
||||
]
|
||||
|
||||
@ -683,7 +746,7 @@ def test_autodoc_noindex(app):
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Base',
|
||||
'.. py:class:: Base()',
|
||||
' :noindex:',
|
||||
' :module: target.inheritance',
|
||||
''
|
||||
@ -710,13 +773,13 @@ def test_autodoc_inner_class(app):
|
||||
actual = do_autodoc(app, 'class', 'target.Outer', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Outer',
|
||||
'.. py:class:: Outer()',
|
||||
' :module: target',
|
||||
'',
|
||||
' Foo',
|
||||
'',
|
||||
'',
|
||||
' .. py:class:: Outer.Inner',
|
||||
' .. py:class:: Outer.Inner()',
|
||||
' :module: target',
|
||||
'',
|
||||
' Foo',
|
||||
@ -737,7 +800,7 @@ def test_autodoc_inner_class(app):
|
||||
actual = do_autodoc(app, 'class', 'target.Outer.Inner', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Outer.Inner',
|
||||
'.. py:class:: Outer.Inner()',
|
||||
' :module: target',
|
||||
'',
|
||||
' Foo',
|
||||
@ -754,7 +817,7 @@ def test_autodoc_inner_class(app):
|
||||
actual = do_autodoc(app, 'class', 'target.InnerChild', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: InnerChild',
|
||||
'.. py:class:: InnerChild()',
|
||||
' :module: target', '',
|
||||
' Bases: :class:`target.Outer.Inner`',
|
||||
'',
|
||||
@ -798,7 +861,7 @@ def test_autodoc_descriptor(app):
|
||||
actual = do_autodoc(app, 'class', 'target.descriptor.Class', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Class',
|
||||
'.. py:class:: Class()',
|
||||
' :module: target.descriptor',
|
||||
'',
|
||||
'',
|
||||
@ -817,21 +880,6 @@ def test_autodoc_descriptor(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_c_module(app):
|
||||
actual = do_autodoc(app, 'function', 'time.asctime')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: asctime([tuple]) -> string',
|
||||
' :module: time',
|
||||
'',
|
||||
" Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.",
|
||||
' When the time tuple is not present, current time as returned by localtime()',
|
||||
' is used.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_member_order(app):
|
||||
# case member-order='bysource'
|
||||
@ -909,6 +957,40 @@ def test_autodoc_member_order(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_module_member_order(app):
|
||||
# case member-order='bysource'
|
||||
options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
|
||||
'member-order': 'bysource',
|
||||
"undoc-members": True}
|
||||
actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:module:: target.sort_by_all',
|
||||
'.. py:function:: baz()',
|
||||
'.. py:function:: foo()',
|
||||
'.. py:class:: Bar()',
|
||||
'.. py:class:: Quux()',
|
||||
'.. py:function:: foobar()',
|
||||
'.. py:function:: qux()',
|
||||
]
|
||||
|
||||
# case member-order='bysource' and ignore-module-all
|
||||
options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
|
||||
'member-order': 'bysource',
|
||||
"undoc-members": True,
|
||||
"ignore-module-all": True}
|
||||
actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:module:: target.sort_by_all',
|
||||
'.. py:function:: foo()',
|
||||
'.. py:class:: Bar()',
|
||||
'.. py:function:: baz()',
|
||||
'.. py:function:: qux()',
|
||||
'.. py:class:: Quux()',
|
||||
'.. py:function:: foobar()',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_module_scope(app):
|
||||
app.env.temp_data['autodoc:module'] = 'target'
|
||||
@ -947,7 +1029,7 @@ def test_class_attributes(app):
|
||||
actual = do_autodoc(app, 'class', 'target.AttCls', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: AttCls',
|
||||
'.. py:class:: AttCls()',
|
||||
' :module: target',
|
||||
'',
|
||||
'',
|
||||
@ -1067,7 +1149,7 @@ def test_slots(app):
|
||||
' :module: target.slots',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Foo',
|
||||
'.. py:class:: Foo()',
|
||||
' :module: target.slots',
|
||||
'',
|
||||
'',
|
||||
@ -1083,7 +1165,7 @@ def test_enum_class(app):
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: EnumCls',
|
||||
'.. py:class:: EnumCls(value)',
|
||||
' :module: target.enum',
|
||||
'',
|
||||
' this is enum class',
|
||||
@ -1166,96 +1248,6 @@ def test_descriptor_class(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autofunction_for_classes(app):
|
||||
actual = do_autodoc(app, 'function', 'target.classes.Foo')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: Foo()',
|
||||
' :module: target.classes',
|
||||
'',
|
||||
]
|
||||
|
||||
actual = do_autodoc(app, 'function', 'target.classes.Bar')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: Bar(x, y)',
|
||||
' :module: target.classes',
|
||||
'',
|
||||
]
|
||||
|
||||
actual = do_autodoc(app, 'function', 'target.classes.Baz')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: Baz(x, y)',
|
||||
' :module: target.classes',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autofunction_for_callable(app):
|
||||
actual = do_autodoc(app, 'function', 'target.callable.function')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: function(arg1, arg2, **kwargs)',
|
||||
' :module: target.callable',
|
||||
'',
|
||||
' A callable object that behaves like a function.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autofunction_for_method(app):
|
||||
actual = do_autodoc(app, 'function', 'target.callable.method')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: method(arg1, arg2)',
|
||||
' :module: target.callable',
|
||||
'',
|
||||
' docstring of Callable.method().',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autofunction_for_builtin(app):
|
||||
actual = do_autodoc(app, 'function', 'os.umask')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: umask(mask, /)',
|
||||
' :module: os',
|
||||
'',
|
||||
' Set the current numeric umask and return the previous umask.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autofunction_for_methoddescriptor(app):
|
||||
actual = do_autodoc(app, 'function', 'builtins.int.__add__')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: int.__add__(self, value, /)',
|
||||
' :module: builtins',
|
||||
'',
|
||||
' Return self+value.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autofunction_for_decorated(app):
|
||||
actual = do_autodoc(app, 'function', 'target.decorator.foo')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: foo()',
|
||||
' :module: target.decorator',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_automethod_for_builtin(app):
|
||||
actual = do_autodoc(app, 'method', 'builtins.int.__add__')
|
||||
@ -1290,7 +1282,7 @@ def test_abstractmethods(app):
|
||||
'.. py:module:: target.abstractmethods',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Base',
|
||||
'.. py:class:: Base()',
|
||||
' :module: target.abstractmethods',
|
||||
'',
|
||||
'',
|
||||
@ -1407,7 +1399,7 @@ def test_coroutine(app):
|
||||
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: AsyncClass',
|
||||
'.. py:class:: AsyncClass()',
|
||||
' :module: target.coroutine',
|
||||
'',
|
||||
'',
|
||||
@ -1449,7 +1441,7 @@ def test_coroutine(app):
|
||||
def test_partialmethod(app):
|
||||
expected = [
|
||||
'',
|
||||
'.. py:class:: Cell',
|
||||
'.. py:class:: Cell()',
|
||||
' :module: target.partialmethod',
|
||||
'',
|
||||
' An example for partialmethod.',
|
||||
@ -1475,24 +1467,11 @@ def test_partialmethod(app):
|
||||
assert list(actual) == expected
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_wrappedfunction(app):
|
||||
actual = do_autodoc(app, 'function', 'target.wrappedfunction.slow_function')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: slow_function(message, timeout)',
|
||||
' :module: target.wrappedfunction',
|
||||
'',
|
||||
' This function is slow.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_partialmethod_undoc_members(app):
|
||||
expected = [
|
||||
'',
|
||||
'.. py:class:: Cell',
|
||||
'.. py:class:: Cell()',
|
||||
' :module: target.partialmethod',
|
||||
'',
|
||||
' An example for partialmethod.',
|
||||
@ -1608,6 +1587,37 @@ def test_autodoc_typed_instance_variables(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_GenericAlias(app):
|
||||
options = {"members": None,
|
||||
"undoc-members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.genericalias', options)
|
||||
if sys.version_info < (3, 7):
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.genericalias',
|
||||
'',
|
||||
'',
|
||||
'.. py:attribute:: T',
|
||||
' :module: target.genericalias',
|
||||
'',
|
||||
' alias of :class:`typing.List`',
|
||||
]
|
||||
else:
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.genericalias',
|
||||
'',
|
||||
'',
|
||||
'.. py:data:: T',
|
||||
' :module: target.genericalias',
|
||||
'',
|
||||
' A list of int',
|
||||
'',
|
||||
' alias of List[int]',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 9), reason='py39+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_Annotated(app):
|
||||
@ -1668,22 +1678,6 @@ def test_singledispatch(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_singledispatch_autofunction(app):
|
||||
options = {}
|
||||
actual = do_autodoc(app, 'function', 'target.singledispatch.func', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: func(arg, kwarg=None)',
|
||||
' func(arg: int, kwarg=None)',
|
||||
' func(arg: str, kwarg=None)',
|
||||
' :module: target.singledispatch',
|
||||
'',
|
||||
' A function for general use.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8),
|
||||
reason='singledispatchmethod is available since python3.8')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
@ -1695,7 +1689,7 @@ def test_singledispatchmethod(app):
|
||||
'.. py:module:: target.singledispatchmethod',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Foo',
|
||||
'.. py:class:: Foo()',
|
||||
' :module: target.singledispatchmethod',
|
||||
'',
|
||||
' docstring',
|
||||
@ -1740,7 +1734,7 @@ def test_cython(app):
|
||||
'.. py:module:: target.cython',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Class',
|
||||
'.. py:class:: Class()',
|
||||
' :module: target.cython',
|
||||
'',
|
||||
' Docstring.',
|
||||
@ -1771,7 +1765,7 @@ def test_final(app):
|
||||
'.. py:module:: target.final',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Class',
|
||||
'.. py:class:: Class()',
|
||||
' :module: target.final',
|
||||
' :final:',
|
||||
'',
|
||||
|
148
tests/test_ext_autodoc_autofunction.py
Normal file
148
tests/test_ext_autodoc_autofunction.py
Normal file
@ -0,0 +1,148 @@
|
||||
"""
|
||||
test_ext_autodoc_autofunction
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the autodoc extension. This tests mainly the Documenters; the auto
|
||||
directives are tested in a test source file translated by test_build.
|
||||
|
||||
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from test_ext_autodoc import do_autodoc
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_classes(app):
|
||||
actual = do_autodoc(app, 'function', 'target.classes.Foo')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: Foo()',
|
||||
' :module: target.classes',
|
||||
'',
|
||||
]
|
||||
|
||||
actual = do_autodoc(app, 'function', 'target.classes.Bar')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: Bar(x, y)',
|
||||
' :module: target.classes',
|
||||
'',
|
||||
]
|
||||
|
||||
actual = do_autodoc(app, 'function', 'target.classes.Baz')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: Baz(x, y)',
|
||||
' :module: target.classes',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_callable(app):
|
||||
actual = do_autodoc(app, 'function', 'target.callable.function')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: function(arg1, arg2, **kwargs)',
|
||||
' :module: target.callable',
|
||||
'',
|
||||
' A callable object that behaves like a function.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_method(app):
|
||||
actual = do_autodoc(app, 'function', 'target.callable.method')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: method(arg1, arg2)',
|
||||
' :module: target.callable',
|
||||
'',
|
||||
' docstring of Callable.method().',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_builtin_function(app):
|
||||
actual = do_autodoc(app, 'function', 'os.umask')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: umask(mask, /)',
|
||||
' :module: os',
|
||||
'',
|
||||
' Set the current numeric umask and return the previous umask.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_methoddescriptor(app):
|
||||
actual = do_autodoc(app, 'function', 'builtins.int.__add__')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: int.__add__(self, value, /)',
|
||||
' :module: builtins',
|
||||
'',
|
||||
' Return self+value.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_decorated(app):
|
||||
actual = do_autodoc(app, 'function', 'target.decorator.foo')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: foo()',
|
||||
' :module: target.decorator',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_singledispatch(app):
|
||||
options = {}
|
||||
actual = do_autodoc(app, 'function', 'target.singledispatch.func', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: func(arg, kwarg=None)',
|
||||
' func(arg: int, kwarg=None)',
|
||||
' func(arg: str, kwarg=None)',
|
||||
' :module: target.singledispatch',
|
||||
'',
|
||||
' A function for general use.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_cfunction(app):
|
||||
actual = do_autodoc(app, 'function', 'time.asctime')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: asctime([tuple]) -> string',
|
||||
' :module: time',
|
||||
'',
|
||||
" Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.",
|
||||
' When the time tuple is not present, current time as returned by localtime()',
|
||||
' is used.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_wrapped_function(app):
|
||||
actual = do_autodoc(app, 'function', 'target.wrappedfunction.slow_function')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: slow_function(message, timeout)',
|
||||
' :module: target.wrappedfunction',
|
||||
'',
|
||||
' This function is slow.',
|
||||
'',
|
||||
]
|
@ -9,6 +9,7 @@
|
||||
"""
|
||||
|
||||
import platform
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
@ -27,7 +28,7 @@ def test_autoclass_content_class(app):
|
||||
'.. py:module:: target.autoclass_content',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: A',
|
||||
'.. py:class:: A()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, no __new__',
|
||||
@ -45,13 +46,13 @@ def test_autoclass_content_class(app):
|
||||
' A class having __init__, no __new__',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: D',
|
||||
'.. py:class:: D()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__(no docstring)',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: E',
|
||||
'.. py:class:: E()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__',
|
||||
@ -87,7 +88,7 @@ def test_autoclass_content_init(app):
|
||||
'.. py:module:: target.autoclass_content',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: A',
|
||||
'.. py:class:: A()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, no __new__',
|
||||
@ -105,13 +106,13 @@ def test_autoclass_content_init(app):
|
||||
' __init__ docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: D',
|
||||
'.. py:class:: D()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__(no docstring)',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: E',
|
||||
'.. py:class:: E()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' __new__ docstring',
|
||||
@ -147,7 +148,7 @@ def test_autoclass_content_both(app):
|
||||
'.. py:module:: target.autoclass_content',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: A',
|
||||
'.. py:class:: A()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, no __new__',
|
||||
@ -167,13 +168,13 @@ def test_autoclass_content_both(app):
|
||||
' __init__ docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: D',
|
||||
'.. py:class:: D()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__(no docstring)',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: E',
|
||||
'.. py:class:: E()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__',
|
||||
@ -237,7 +238,7 @@ def test_autodoc_docstring_signature(app):
|
||||
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: DocstringSig',
|
||||
'.. py:class:: DocstringSig()',
|
||||
' :module: target',
|
||||
'',
|
||||
'',
|
||||
@ -279,7 +280,7 @@ def test_autodoc_docstring_signature(app):
|
||||
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: DocstringSig',
|
||||
'.. py:class:: DocstringSig()',
|
||||
' :module: target',
|
||||
'',
|
||||
'',
|
||||
@ -345,6 +346,10 @@ def test_autoclass_content_and_docstring_signature_class(app):
|
||||
'',
|
||||
'.. py:class:: D()',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: E()',
|
||||
' :module: target.docstring_signature',
|
||||
''
|
||||
]
|
||||
|
||||
@ -374,6 +379,11 @@ def test_autoclass_content_and_docstring_signature_init(app):
|
||||
'',
|
||||
'.. py:class:: D(foo, bar, baz)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: E(foo: int, bar: int, baz: int) -> None',
|
||||
' E(foo: str, bar: str, baz: str) -> None',
|
||||
' :module: target.docstring_signature',
|
||||
''
|
||||
]
|
||||
|
||||
@ -408,6 +418,11 @@ def test_autoclass_content_and_docstring_signature_both(app):
|
||||
'.. py:class:: D(foo, bar, baz)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: E(foo: int, bar: int, baz: int) -> None',
|
||||
' E(foo: str, bar: str, baz: str) -> None',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@ -435,7 +450,7 @@ def test_mocked_module_imports(app, warning):
|
||||
'.. py:module:: target.need_mocks',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: TestAutodoc',
|
||||
'.. py:class:: TestAutodoc()',
|
||||
' :module: target.need_mocks',
|
||||
'',
|
||||
' TestAutodoc docstring.',
|
||||
@ -493,6 +508,18 @@ def test_autodoc_typehints_signature(app):
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: NewAnnotation(i: int)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: NewComment(i: int)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: SignatureFromMetaclass(a: int)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, '
|
||||
'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None',
|
||||
' :module: target.typehints',
|
||||
@ -547,6 +574,18 @@ def test_autodoc_typehints_none(app):
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: NewAnnotation(i)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: NewComment(i)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: SignatureFromMetaclass(a)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
|
@ -19,7 +19,10 @@ from sphinx import addnodes
|
||||
from sphinx.ext.autosummary import (
|
||||
autosummary_table, autosummary_toc, mangle_signature, import_by_name, extract_summary
|
||||
)
|
||||
from sphinx.ext.autosummary.generate import AutosummaryEntry, generate_autosummary_docs, main as autogen_main
|
||||
from sphinx.ext.autosummary.generate import (
|
||||
AutosummaryEntry, generate_autosummary_content, generate_autosummary_docs,
|
||||
main as autogen_main
|
||||
)
|
||||
from sphinx.testing.util import assert_node, etree_parse
|
||||
from sphinx.util.docutils import new_document
|
||||
from sphinx.util.osutil import cd
|
||||
@ -189,6 +192,83 @@ def test_escaping(app, status, warning):
|
||||
assert str_content(title) == 'underscore_module_'
|
||||
|
||||
|
||||
@pytest.mark.sphinx(testroot='ext-autosummary')
|
||||
def test_autosummary_generate_content_for_module(app):
|
||||
import autosummary_dummy_module
|
||||
template = Mock()
|
||||
|
||||
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
|
||||
template, None, False, app, False, {})
|
||||
assert template.render.call_args[0][0] == 'module'
|
||||
|
||||
context = template.render.call_args[0][1]
|
||||
assert context['members'] == ['Exc', 'Foo', '_Baz', '_Exc', '__builtins__',
|
||||
'__cached__', '__doc__', '__file__', '__name__',
|
||||
'__package__', '_quux', 'bar', 'qux']
|
||||
assert context['functions'] == ['bar']
|
||||
assert context['all_functions'] == ['_quux', 'bar']
|
||||
assert context['classes'] == ['Foo']
|
||||
assert context['all_classes'] == ['Foo', '_Baz']
|
||||
assert context['exceptions'] == ['Exc']
|
||||
assert context['all_exceptions'] == ['Exc', '_Exc']
|
||||
assert context['attributes'] == ['qux']
|
||||
assert context['all_attributes'] == ['qux']
|
||||
assert context['fullname'] == 'autosummary_dummy_module'
|
||||
assert context['module'] == 'autosummary_dummy_module'
|
||||
assert context['objname'] == ''
|
||||
assert context['name'] == ''
|
||||
assert context['objtype'] == 'module'
|
||||
|
||||
|
||||
@pytest.mark.sphinx(testroot='ext-autosummary')
|
||||
def test_autosummary_generate_content_for_module_skipped(app):
|
||||
import autosummary_dummy_module
|
||||
template = Mock()
|
||||
|
||||
def skip_member(app, what, name, obj, skip, options):
|
||||
if name in ('Foo', 'bar', 'Exc'):
|
||||
return True
|
||||
|
||||
app.connect('autodoc-skip-member', skip_member)
|
||||
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
|
||||
template, None, False, app, False, {})
|
||||
context = template.render.call_args[0][1]
|
||||
assert context['members'] == ['_Baz', '_Exc', '__builtins__', '__cached__', '__doc__',
|
||||
'__file__', '__name__', '__package__', '_quux', 'qux']
|
||||
assert context['functions'] == []
|
||||
assert context['classes'] == []
|
||||
assert context['exceptions'] == []
|
||||
|
||||
|
||||
@pytest.mark.sphinx(testroot='ext-autosummary')
|
||||
def test_autosummary_generate_content_for_module_imported_members(app):
|
||||
import autosummary_dummy_module
|
||||
template = Mock()
|
||||
|
||||
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
|
||||
template, None, True, app, False, {})
|
||||
assert template.render.call_args[0][0] == 'module'
|
||||
|
||||
context = template.render.call_args[0][1]
|
||||
assert context['members'] == ['Exc', 'Foo', 'Union', '_Baz', '_Exc', '__builtins__',
|
||||
'__cached__', '__doc__', '__file__', '__loader__',
|
||||
'__name__', '__package__', '__spec__', '_quux',
|
||||
'bar', 'path', 'qux']
|
||||
assert context['functions'] == ['bar']
|
||||
assert context['all_functions'] == ['_quux', 'bar']
|
||||
assert context['classes'] == ['Foo']
|
||||
assert context['all_classes'] == ['Foo', '_Baz']
|
||||
assert context['exceptions'] == ['Exc']
|
||||
assert context['all_exceptions'] == ['Exc', '_Exc']
|
||||
assert context['attributes'] == ['qux']
|
||||
assert context['all_attributes'] == ['qux']
|
||||
assert context['fullname'] == 'autosummary_dummy_module'
|
||||
assert context['module'] == 'autosummary_dummy_module'
|
||||
assert context['objname'] == ''
|
||||
assert context['name'] == ''
|
||||
assert context['objtype'] == 'module'
|
||||
|
||||
|
||||
@pytest.mark.sphinx('dummy', testroot='ext-autosummary')
|
||||
def test_autosummary_generate(app, status, warning):
|
||||
app.builder.build_all()
|
||||
@ -203,16 +283,18 @@ def test_autosummary_generate(app, status, warning):
|
||||
[autosummary_table, nodes.table, nodes.tgroup, (nodes.colspec,
|
||||
nodes.colspec,
|
||||
[nodes.tbody, (nodes.row,
|
||||
nodes.row,
|
||||
nodes.row,
|
||||
nodes.row,
|
||||
nodes.row)])])
|
||||
assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary")
|
||||
|
||||
assert len(doctree[3][0][0][2]) == 4
|
||||
assert len(doctree[3][0][0][2]) == 5
|
||||
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
|
||||
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
|
||||
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar\n\n'
|
||||
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar()\n\n'
|
||||
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
|
||||
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
|
||||
|
||||
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
|
||||
assert (' .. autosummary::\n'
|
||||
@ -237,6 +319,11 @@ def test_autosummary_generate(app, status, warning):
|
||||
'\n'
|
||||
'.. autoclass:: Foo.Bar\n' in FooBar)
|
||||
|
||||
qux = (app.srcdir / 'generated' / 'autosummary_dummy_module.qux.rst').read_text()
|
||||
assert ('.. currentmodule:: autosummary_dummy_module\n'
|
||||
'\n'
|
||||
'.. autodata:: qux' in qux)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('dummy', testroot='ext-autosummary',
|
||||
confoverrides={'autosummary_generate_overwrite': False})
|
||||
|
@ -71,7 +71,7 @@ def test_mathjax_options(app, status, warning):
|
||||
|
||||
content = (app.outdir / 'index.html').read_text()
|
||||
assert ('<script async="async" integrity="sha384-0123456789" '
|
||||
'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?'
|
||||
'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?'
|
||||
'config=TeX-AMS-MML_HTMLorMML"></script>' in content)
|
||||
|
||||
|
||||
|
@ -1394,6 +1394,26 @@ Summary
|
||||
Attributes
|
||||
----------
|
||||
|
||||
arg_ : type
|
||||
some description
|
||||
"""
|
||||
|
||||
expected = """
|
||||
:ivar arg_: some description
|
||||
:vartype arg_: type
|
||||
"""
|
||||
|
||||
config = Config(napoleon_use_ivar=True)
|
||||
app = mock.Mock()
|
||||
actual = str(NumpyDocstring(docstring, config, app, "class"))
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_underscore_in_attribute_strip_signature_backslash(self):
|
||||
docstring = """
|
||||
Attributes
|
||||
----------
|
||||
|
||||
arg_ : type
|
||||
some description
|
||||
"""
|
||||
@ -1404,6 +1424,7 @@ arg_ : type
|
||||
"""
|
||||
|
||||
config = Config(napoleon_use_ivar=True)
|
||||
config.strip_signature_backslash = True
|
||||
app = mock.Mock()
|
||||
actual = str(NumpyDocstring(docstring, config, app, "class"))
|
||||
|
||||
|
@ -58,8 +58,8 @@ def test_project_path2doc(app):
|
||||
assert project.path2doc('index.foo') is None # unknown extension
|
||||
assert project.path2doc('index.foo.rst') == 'index.foo'
|
||||
assert project.path2doc('index') is None
|
||||
assert project.path2doc('/path/to/index.rst') == PathComparer('/path/to/index')
|
||||
assert project.path2doc(app.srcdir / '/to/index.rst') == PathComparer('/to/index')
|
||||
assert project.path2doc('path/to/index.rst') == 'path/to/index'
|
||||
assert project.path2doc(app.srcdir / 'to/index.rst') == 'to/index'
|
||||
|
||||
|
||||
@pytest.mark.sphinx(srcdir='project_doc2path', testroot='basic')
|
||||
|
@ -74,7 +74,7 @@ def test_js_source(app, status, warning):
|
||||
|
||||
app.builder.build(['contents'])
|
||||
|
||||
v = '3.4.1'
|
||||
v = '3.5.1'
|
||||
msg = 'jquery.js version does not match to {v}'.format(v=v)
|
||||
jquery_min = (app.outdir / '_static' / 'jquery.js').read_text()
|
||||
assert 'jQuery v{v}'.format(v=v) in jquery_min, msg
|
||||
|
@ -29,12 +29,14 @@ def test_signature():
|
||||
with pytest.raises(TypeError):
|
||||
inspect.signature('')
|
||||
|
||||
# builitin classes
|
||||
with pytest.raises(ValueError):
|
||||
inspect.signature(int)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
inspect.signature(str)
|
||||
# builtins are supported on a case-by-case basis, depending on whether
|
||||
# they define __text_signature__
|
||||
if getattr(list, '__text_signature__', None):
|
||||
sig = inspect.stringify_signature(inspect.signature(list))
|
||||
assert sig == '(iterable=(), /)'
|
||||
else:
|
||||
with pytest.raises(ValueError):
|
||||
inspect.signature(list)
|
||||
|
||||
# normal function
|
||||
def func(a, b, c=1, d=2, *e, **f):
|
||||
@ -333,10 +335,14 @@ def test_signature_from_str_kwonly_args():
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8),
|
||||
reason='python-3.8 or above is required')
|
||||
def test_signature_from_str_positionaly_only_args():
|
||||
sig = inspect.signature_from_str('(a, /, b)')
|
||||
assert list(sig.parameters.keys()) == ['a', 'b']
|
||||
sig = inspect.signature_from_str('(a, b=0, /, c=1)')
|
||||
assert list(sig.parameters.keys()) == ['a', 'b', 'c']
|
||||
assert sig.parameters['a'].kind == Parameter.POSITIONAL_ONLY
|
||||
assert sig.parameters['b'].kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||
assert sig.parameters['a'].default == Parameter.empty
|
||||
assert sig.parameters['b'].kind == Parameter.POSITIONAL_ONLY
|
||||
assert sig.parameters['b'].default == '0'
|
||||
assert sig.parameters['c'].kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||
assert sig.parameters['c'].default == '1'
|
||||
|
||||
|
||||
def test_signature_from_str_invalid():
|
||||
@ -558,6 +564,18 @@ def test_isproperty(app):
|
||||
assert inspect.isproperty(func) is False # function
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
|
||||
@pytest.mark.sphinx(testroot='ext-autodoc')
|
||||
def test_isgenericalias(app):
|
||||
from target.genericalias import C, T
|
||||
from target.methods import Base
|
||||
|
||||
assert inspect.isgenericalias(C) is True
|
||||
assert inspect.isgenericalias(T) is True
|
||||
assert inspect.isgenericalias(object()) is False
|
||||
assert inspect.isgenericalias(Base) is False
|
||||
|
||||
|
||||
def test_unpartial():
|
||||
def func1(a, b, c):
|
||||
pass
|
||||
|
Loading…
Reference in New Issue
Block a user