mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch 'master' into 3570_drop_typing_from_typehints
This commit is contained in:
commit
c50b21af0e
7
CHANGES
7
CHANGES
@ -1,6 +1,11 @@
|
|||||||
Release 1.7 (in development)
|
Release 1.7 (in development)
|
||||||
============================
|
============================
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Add ``packaging`` package
|
||||||
|
|
||||||
Incompatible changes
|
Incompatible changes
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
@ -67,6 +72,8 @@ Features added
|
|||||||
* #4093: sphinx-build creates empty directories for unknown targets/builders
|
* #4093: sphinx-build creates empty directories for unknown targets/builders
|
||||||
* Add ``top-classes`` option for the ``sphinx.ext.inheritance_diagram``
|
* Add ``top-classes`` option for the ``sphinx.ext.inheritance_diagram``
|
||||||
extension to limit the scope of inheritance graphs.
|
extension to limit the scope of inheritance graphs.
|
||||||
|
* #4183: doctest: ``:pyversion:`` option also follows PEP-440 specification
|
||||||
|
* #4235: html: Add :confval:`manpages_url` to make manpage roles to hyperlinks
|
||||||
* #3570: autodoc: Do not display 'typing.' module for type hints
|
* #3570: autodoc: Do not display 'typing.' module for type hints
|
||||||
|
|
||||||
Features removed
|
Features removed
|
||||||
|
@ -293,6 +293,24 @@ General configuration
|
|||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
|
||||||
|
.. confval:: manpages_url
|
||||||
|
|
||||||
|
A URL to cross-reference :rst:role:`manpage` directives. If this is
|
||||||
|
defined to ``https://manpages.debian.org/{path}``, the
|
||||||
|
:literal:`:manpage:`man(1)`` role will like to
|
||||||
|
<https://manpages.debian.org/man(1)>. The patterns available are:
|
||||||
|
|
||||||
|
* ``page`` - the manual page (``man``)
|
||||||
|
* ``section`` - the manual section (``1``)
|
||||||
|
* ``path`` - the original manual page and section specified (``man(1)``)
|
||||||
|
|
||||||
|
This also supports manpages specified as ``man.1``.
|
||||||
|
|
||||||
|
.. note:: This currently affects only HTML writers but could be
|
||||||
|
expanded in the future.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
.. confval:: nitpicky
|
.. confval:: nitpicky
|
||||||
|
|
||||||
If true, Sphinx will warn about *all* references where the target cannot be
|
If true, Sphinx will warn about *all* references where the target cannot be
|
||||||
|
@ -80,12 +80,24 @@ a comma-separated list of group names.
|
|||||||
.. doctest::
|
.. doctest::
|
||||||
:pyversion: > 3.3
|
:pyversion: > 3.3
|
||||||
|
|
||||||
The supported operands are ``<``, ``<=``, ``==``, ``>=``, ``>``, and
|
The following operands are supported:
|
||||||
comparison is performed by `distutils.version.LooseVersion
|
|
||||||
<https://www.python.org/dev/peps/pep-0386/#distutils>`__.
|
* ``~=``: Compatible release clause
|
||||||
|
* ``==``: Version matching clause
|
||||||
|
* ``!=``: Version exclusion clause
|
||||||
|
* ``<=``, ``>=``: Inclusive ordered comparison clause
|
||||||
|
* ``<``, ``>``: Exclusive ordered comparison clause
|
||||||
|
* ``===``: Arbitrary equality clause.
|
||||||
|
|
||||||
|
``pyversion`` option is followed `PEP-440: Version Specifiers
|
||||||
|
<https://www.python.org/dev/peps/pep-0440/#version-specifiers>`__.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
Supported PEP-440 operands and notations
|
||||||
|
|
||||||
Note that like with standard doctests, you have to use ``<BLANKLINE>`` to
|
Note that like with standard doctests, you have to use ``<BLANKLINE>`` to
|
||||||
signal a blank line in the expected output. The ``<BLANKLINE>`` is removed
|
signal a blank line in the expected output. The ``<BLANKLINE>`` is removed
|
||||||
when building presentation output (HTML, LaTeX etc.).
|
when building presentation output (HTML, LaTeX etc.).
|
||||||
|
@ -355,7 +355,8 @@ in a different style:
|
|||||||
.. rst:role:: manpage
|
.. rst:role:: manpage
|
||||||
|
|
||||||
A reference to a Unix manual page including the section,
|
A reference to a Unix manual page including the section,
|
||||||
e.g. ``:manpage:`ls(1)```.
|
e.g. ``:manpage:`ls(1)```. Creates a hyperlink to an external site
|
||||||
|
rendering the manpage if :confval:`manpages_url` is defined.
|
||||||
|
|
||||||
.. rst:role:: menuselection
|
.. rst:role:: menuselection
|
||||||
|
|
||||||
|
1
setup.py
1
setup.py
@ -26,6 +26,7 @@ requires = [
|
|||||||
'imagesize',
|
'imagesize',
|
||||||
'requests>=2.0.0',
|
'requests>=2.0.0',
|
||||||
'setuptools',
|
'setuptools',
|
||||||
|
'packaging',
|
||||||
'sphinxcontrib-websupport',
|
'sphinxcontrib-websupport',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -125,6 +125,7 @@ class Config(object):
|
|||||||
primary_domain = ('py', 'env', [NoneType]),
|
primary_domain = ('py', 'env', [NoneType]),
|
||||||
needs_sphinx = (None, None, string_classes),
|
needs_sphinx = (None, None, string_classes),
|
||||||
needs_extensions = ({}, None),
|
needs_extensions = ({}, None),
|
||||||
|
manpages_url = (None, 'env'),
|
||||||
nitpicky = (False, None),
|
nitpicky = (False, None),
|
||||||
nitpick_ignore = ([], None),
|
nitpick_ignore = ([], None),
|
||||||
numfig = (False, 'env'),
|
numfig = (False, 'env'),
|
||||||
|
@ -20,7 +20,8 @@ from os import path
|
|||||||
import doctest
|
import doctest
|
||||||
|
|
||||||
from six import itervalues, StringIO, binary_type, text_type, PY2
|
from six import itervalues, StringIO, binary_type, text_type, PY2
|
||||||
from distutils.version import LooseVersion
|
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.parsers.rst import Directive, directives
|
from docutils.parsers.rst import Directive, directives
|
||||||
@ -57,28 +58,23 @@ else:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def compare_version(ver1, ver2, operand):
|
def is_allowed_version(spec, version):
|
||||||
# type: (unicode, unicode, unicode) -> bool
|
# type: (unicode, unicode) -> bool
|
||||||
"""Compare `ver1` to `ver2`, relying on `operand`.
|
"""Check `spec` satisfies `version` or not.
|
||||||
|
|
||||||
|
This obeys PEP-440 specifiers:
|
||||||
|
https://www.python.org/dev/peps/pep-0440/#version-specifiers
|
||||||
|
|
||||||
Some examples:
|
Some examples:
|
||||||
|
|
||||||
>>> compare_version('3.3', '3.5', '<=')
|
>>> is_allowed_version('3.3', '<=3.5')
|
||||||
True
|
True
|
||||||
>>> compare_version('3.3', '3.2', '<=')
|
>>> is_allowed_version('3.3', '<=3.2')
|
||||||
False
|
False
|
||||||
>>> compare_version('3.3a0', '3.3', '<=')
|
>>> is_allowed_version('3.3', '>3.2, <4.0')
|
||||||
True
|
True
|
||||||
"""
|
"""
|
||||||
if operand not in ('<=', '<', '==', '>=', '>'):
|
return Version(version) in SpecifierSet(spec)
|
||||||
raise ValueError("'%s' is not a valid operand.")
|
|
||||||
v1 = LooseVersion(ver1)
|
|
||||||
v2 = LooseVersion(ver2)
|
|
||||||
return ((operand == '<=' and (v1 <= v2)) or
|
|
||||||
(operand == '<' and (v1 < v2)) or
|
|
||||||
(operand == '==' and (v1 == v2)) or
|
|
||||||
(operand == '>=' and (v1 >= v2)) or
|
|
||||||
(operand == '>' and (v1 > v2)))
|
|
||||||
|
|
||||||
|
|
||||||
# set up the necessary directives
|
# set up the necessary directives
|
||||||
@ -143,16 +139,13 @@ class TestDirective(Directive):
|
|||||||
node['options'][flag] = (option[0] == '+')
|
node['options'][flag] = (option[0] == '+')
|
||||||
if self.name == 'doctest' and 'pyversion' in self.options:
|
if self.name == 'doctest' and 'pyversion' in self.options:
|
||||||
try:
|
try:
|
||||||
option = self.options['pyversion']
|
spec = self.options['pyversion']
|
||||||
# :pyversion: >= 3.6 --> operand='>=', option_version='3.6'
|
if not is_allowed_version(spec, platform.python_version()):
|
||||||
operand, option_version = [item.strip() for item in option.split()]
|
|
||||||
running_version = platform.python_version()
|
|
||||||
if not compare_version(running_version, option_version, operand):
|
|
||||||
flag = doctest.OPTIONFLAGS_BY_NAME['SKIP']
|
flag = doctest.OPTIONFLAGS_BY_NAME['SKIP']
|
||||||
node['options'][flag] = True # Skip the test
|
node['options'][flag] = True # Skip the test
|
||||||
except ValueError:
|
except InvalidSpecifier:
|
||||||
self.state.document.reporter.warning(
|
self.state.document.reporter.warning(
|
||||||
_("'%s' is not a valid pyversion option") % option,
|
_("'%s' is not a valid pyversion option") % spec,
|
||||||
line=self.lineno)
|
line=self.lineno)
|
||||||
return [node]
|
return [node]
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ from sphinx.transforms import (
|
|||||||
ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences,
|
ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences,
|
||||||
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds,
|
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds,
|
||||||
AutoNumbering, AutoIndexUpgrader, FilterSystemMessages,
|
AutoNumbering, AutoIndexUpgrader, FilterSystemMessages,
|
||||||
UnreferencedFootnotesDetector, SphinxSmartQuotes
|
UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink
|
||||||
)
|
)
|
||||||
from sphinx.transforms.compact_bullet_list import RefOnlyBulletListTransform
|
from sphinx.transforms.compact_bullet_list import RefOnlyBulletListTransform
|
||||||
from sphinx.transforms.i18n import (
|
from sphinx.transforms.i18n import (
|
||||||
@ -80,7 +80,7 @@ class SphinxStandaloneReader(SphinxBaseReader):
|
|||||||
Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets,
|
Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets,
|
||||||
HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds,
|
HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds,
|
||||||
RemoveTranslatableInline, PreserveTranslatableMessages, FilterSystemMessages,
|
RemoveTranslatableInline, PreserveTranslatableMessages, FilterSystemMessages,
|
||||||
RefOnlyBulletListTransform, UnreferencedFootnotesDetector
|
RefOnlyBulletListTransform, UnreferencedFootnotesDetector, ManpageLink
|
||||||
] # type: List[Transform]
|
] # type: List[Transform]
|
||||||
|
|
||||||
def __init__(self, app, *args, **kwargs):
|
def __init__(self, app, *args, **kwargs):
|
||||||
@ -110,7 +110,7 @@ class SphinxI18nReader(SphinxBaseReader):
|
|||||||
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks,
|
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks,
|
||||||
AutoNumbering, SortIds, RemoveTranslatableInline,
|
AutoNumbering, SortIds, RemoveTranslatableInline,
|
||||||
FilterSystemMessages, RefOnlyBulletListTransform,
|
FilterSystemMessages, RefOnlyBulletListTransform,
|
||||||
UnreferencedFootnotesDetector]
|
UnreferencedFootnotesDetector, ManpageLink]
|
||||||
|
|
||||||
def set_lineno_for_reporter(self, lineno):
|
def set_lineno_for_reporter(self, lineno):
|
||||||
# type: (int) -> None
|
# type: (int) -> None
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.transforms import Transform, Transformer
|
from docutils.transforms import Transform, Transformer
|
||||||
from docutils.transforms.parts import ContentsFilter
|
from docutils.transforms.parts import ContentsFilter
|
||||||
@ -348,3 +350,21 @@ class SphinxSmartQuotes(SmartQuotes):
|
|||||||
for txtnode in txtnodes:
|
for txtnode in txtnodes:
|
||||||
notsmartquotable = not is_smartquotable(txtnode)
|
notsmartquotable = not is_smartquotable(txtnode)
|
||||||
yield (texttype[notsmartquotable], txtnode.astext())
|
yield (texttype[notsmartquotable], txtnode.astext())
|
||||||
|
|
||||||
|
|
||||||
|
class ManpageLink(SphinxTransform):
|
||||||
|
"""Find manpage section numbers and names"""
|
||||||
|
default_priority = 999
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
for node in self.document.traverse(addnodes.manpage):
|
||||||
|
manpage = ' '.join([str(x) for x in node.children
|
||||||
|
if isinstance(x, nodes.Text)])
|
||||||
|
pattern = r'^(?P<path>(?P<page>.+)[\(\.](?P<section>[1-9]\w*)?\)?)$' # noqa
|
||||||
|
info = {'path': manpage,
|
||||||
|
'page': manpage,
|
||||||
|
'section': ''}
|
||||||
|
r = re.match(pattern, manpage)
|
||||||
|
if r:
|
||||||
|
info = r.groupdict()
|
||||||
|
node.attributes.update(info)
|
||||||
|
@ -79,6 +79,7 @@ class HTMLTranslator(BaseTranslator):
|
|||||||
self.highlightopts = builder.config.highlight_options
|
self.highlightopts = builder.config.highlight_options
|
||||||
self.highlightlinenothreshold = sys.maxsize
|
self.highlightlinenothreshold = sys.maxsize
|
||||||
self.docnames = [builder.current_docname] # for singlehtml builder
|
self.docnames = [builder.current_docname] # for singlehtml builder
|
||||||
|
self.manpages_url = builder.config.manpages_url
|
||||||
self.protect_literal_text = 0
|
self.protect_literal_text = 0
|
||||||
self.permalink_text = builder.config.html_add_permalinks
|
self.permalink_text = builder.config.html_add_permalinks
|
||||||
# support backwards-compatible setting to a bool
|
# support backwards-compatible setting to a bool
|
||||||
@ -816,9 +817,14 @@ class HTMLTranslator(BaseTranslator):
|
|||||||
def visit_manpage(self, node):
|
def visit_manpage(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
self.visit_literal_emphasis(node)
|
self.visit_literal_emphasis(node)
|
||||||
|
if self.manpages_url:
|
||||||
|
node['refuri'] = self.manpages_url.format(**node.attributes)
|
||||||
|
self.visit_reference(node)
|
||||||
|
|
||||||
def depart_manpage(self, node):
|
def depart_manpage(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
|
if self.manpages_url:
|
||||||
|
self.depart_reference(node)
|
||||||
self.depart_literal_emphasis(node)
|
self.depart_literal_emphasis(node)
|
||||||
|
|
||||||
# overwritten to add even/odd classes
|
# overwritten to add even/odd classes
|
||||||
|
@ -49,6 +49,7 @@ class HTML5Translator(BaseTranslator):
|
|||||||
self.highlightopts = builder.config.highlight_options
|
self.highlightopts = builder.config.highlight_options
|
||||||
self.highlightlinenothreshold = sys.maxsize
|
self.highlightlinenothreshold = sys.maxsize
|
||||||
self.docnames = [builder.current_docname] # for singlehtml builder
|
self.docnames = [builder.current_docname] # for singlehtml builder
|
||||||
|
self.manpages_url = builder.config.manpages_url
|
||||||
self.protect_literal_text = 0
|
self.protect_literal_text = 0
|
||||||
self.permalink_text = builder.config.html_add_permalinks
|
self.permalink_text = builder.config.html_add_permalinks
|
||||||
# support backwards-compatible setting to a bool
|
# support backwards-compatible setting to a bool
|
||||||
@ -758,9 +759,14 @@ class HTML5Translator(BaseTranslator):
|
|||||||
def visit_manpage(self, node):
|
def visit_manpage(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
self.visit_literal_emphasis(node)
|
self.visit_literal_emphasis(node)
|
||||||
|
if self.manpages_url:
|
||||||
|
node['refuri'] = self.manpages_url.format(**dict(node))
|
||||||
|
self.visit_reference(node)
|
||||||
|
|
||||||
def depart_manpage(self, node):
|
def depart_manpage(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
|
if self.manpages_url:
|
||||||
|
self.depart_reference(node)
|
||||||
self.depart_literal_emphasis(node)
|
self.depart_literal_emphasis(node)
|
||||||
|
|
||||||
# overwritten to add even/odd classes
|
# overwritten to add even/odd classes
|
||||||
|
5
tests/roots/test-manpage_url/conf.py
Normal file
5
tests/roots/test-manpage_url/conf.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
master_doc = 'index'
|
||||||
|
html_theme = 'classic'
|
||||||
|
exclude_patterns = ['_build']
|
3
tests/roots/test-manpage_url/index.rst
Normal file
3
tests/roots/test-manpage_url/index.rst
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
* :manpage:`man(1)`
|
||||||
|
* :manpage:`ls.1`
|
||||||
|
* :manpage:`sphinx`
|
@ -1243,3 +1243,16 @@ def test_html_sidebar(app, status, warning):
|
|||||||
assert '<h3>Related Topics</h3>' not in result
|
assert '<h3>Related Topics</h3>' not in result
|
||||||
assert '<h3>This Page</h3>' not in result
|
assert '<h3>This Page</h3>' not in result
|
||||||
assert '<h3>Quick search</h3>' not in result
|
assert '<h3>Quick search</h3>' not in result
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('fname,expect', flat_dict({
|
||||||
|
'index.html': [(".//em/a[@href='https://example.com/man.1']", "", True),
|
||||||
|
(".//em/a[@href='https://example.com/ls.1']", "", True),
|
||||||
|
(".//em/a[@href='https://example.com/sphinx.']", "", True)]
|
||||||
|
}))
|
||||||
|
@pytest.mark.sphinx('html', testroot='manpage_url', confoverrides={
|
||||||
|
'manpages_url': 'https://example.com/{page}.{section}'})
|
||||||
|
@pytest.mark.test_params(shared_result='test_build_html_manpage_url')
|
||||||
|
def test_html_manpage(app, cached_etree_parse, fname, expect):
|
||||||
|
app.build()
|
||||||
|
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
from sphinx.ext.doctest import compare_version
|
from sphinx.ext.doctest import is_allowed_version
|
||||||
|
from packaging.version import InvalidVersion
|
||||||
|
from packaging.specifiers import InvalidSpecifier
|
||||||
|
|
||||||
cleanup_called = 0
|
cleanup_called = 0
|
||||||
|
|
||||||
@ -26,19 +28,28 @@ def test_build(app, status, warning):
|
|||||||
assert cleanup_called == 3, 'testcleanup did not get executed enough times'
|
assert cleanup_called == 3, 'testcleanup did not get executed enough times'
|
||||||
|
|
||||||
|
|
||||||
def test_compare_version():
|
def test_is_allowed_version():
|
||||||
assert compare_version('3.3', '3.4', '<') is True
|
assert is_allowed_version('<3.4', '3.3') is True
|
||||||
assert compare_version('3.3', '3.2', '<') is False
|
assert is_allowed_version('<3.4', '3.3') is True
|
||||||
assert compare_version('3.3', '3.4', '<=') is True
|
assert is_allowed_version('<3.2', '3.3') is False
|
||||||
assert compare_version('3.3', '3.2', '<=') is False
|
assert is_allowed_version('<=3.4', '3.3') is True
|
||||||
assert compare_version('3.3', '3.3', '==') is True
|
assert is_allowed_version('<=3.2', '3.3') is False
|
||||||
assert compare_version('3.3', '3.4', '==') is False
|
assert is_allowed_version('==3.3', '3.3') is True
|
||||||
assert compare_version('3.3', '3.2', '>=') is True
|
assert is_allowed_version('==3.4', '3.3') is False
|
||||||
assert compare_version('3.3', '3.4', '>=') is False
|
assert is_allowed_version('>=3.2', '3.3') is True
|
||||||
assert compare_version('3.3', '3.2', '>') is True
|
assert is_allowed_version('>=3.4', '3.3') is False
|
||||||
assert compare_version('3.3', '3.4', '>') is False
|
assert is_allowed_version('>3.2', '3.3') is True
|
||||||
with pytest.raises(ValueError):
|
assert is_allowed_version('>3.4', '3.3') is False
|
||||||
compare_version('3.3', '3.4', '+')
|
assert is_allowed_version('~=3.4', '3.4.5') is True
|
||||||
|
assert is_allowed_version('~=3.4', '3.5.0') is True
|
||||||
|
|
||||||
|
# invalid spec
|
||||||
|
with pytest.raises(InvalidSpecifier):
|
||||||
|
is_allowed_version('&3.4', '3.5')
|
||||||
|
|
||||||
|
# invalid version
|
||||||
|
with pytest.raises(InvalidVersion):
|
||||||
|
is_allowed_version('>3.4', 'Sphinx')
|
||||||
|
|
||||||
|
|
||||||
def cleanup_call():
|
def cleanup_call():
|
||||||
|
Loading…
Reference in New Issue
Block a user