Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA 2021-01-28 00:56:46 +09:00
commit c9480f9943
27 changed files with 301 additions and 118 deletions

15
CHANGES
View File

@ -122,6 +122,7 @@ Features added
:event:`html-page-context` event
* #6550: html: Allow to use HTML permalink texts via
:confval:`html_permalinks_icon`
* #1638: html: Add permalink icons to glossary terms
* #8649: imgconverter: Skip availability check if builder supports the image
type
* #8573: napoleon: Allow to change the style of custom sections using
@ -130,6 +131,7 @@ Features added
references when :confval:`napoleon_preprocess_types` enabled
* #6241: mathjax: Include mathjax.js only on the document using equations
* #8651: std domain: cross-reference for a rubric having inline item is broken
* #7642: std domain: Optimize case-insensitive match of term
* #8681: viewcode: Support incremental build
* #8132: Add :confval:`project_copyright` as an alias of :confval:`copyright`
* #207: Now :confval:`highlight_language` supports multiple languages
@ -150,19 +152,26 @@ Bugs fixed
* #8652: autodoc: All variable comments in the module are ignored if the module
contains invalid type comments
* #8693: autodoc: Default values for overloaded functions are rendered as string
* #8134: autodoc: crashes when mocked decorator takes arguments
* #8306: autosummary: mocked modules are documented as empty page when using
:recursive: option
* #8618: html: kbd role produces incorrect HTML when compound-key separators (-,
+ or ^) are used as keystrokes
* #8629: html: A type warning for html_use_opensearch is shown twice
* #8714: html: kbd role with "Caps Lock" rendered incorrectly
* #8123: html search: fix searching for terms containing + (Requires a custom
search language that does not split on +)
* #8665: html theme: Could not override globaltoc_maxdepth in theme.conf
* #8745: i18n: crashes with KeyError when translation message adds a new auto
footnote reference
* #4304: linkcheck: Fix race condition that could lead to checking the
availability of the same URL twice
* #7118: sphinx-quickstart: questionare got Mojibake if libreadline unavailable
* #8094: texinfo: image files on the different directory with document are not
copied
* #8720: viewcode: module pages are generated for epub on incremental build
* #8704: viewcode: anchors are generated in incremental build after singlehtml
* #8756: viewcode: highlighted code is generated even if not referenced
* #8671: :confval:`highlight_options` is not working
* #8341: C, fix intersphinx lookup types for names in declarations.
* C, C++: in general fix intersphinx and role lookup types.
@ -170,8 +179,14 @@ Bugs fixed
* #8683: :confval:`html_last_updated_fmt` generates wrong time zone for %Z
* #1112: ``download`` role creates duplicated copies when relative path is
specified
* #7576: LaTeX with French babel and memoir crash: "Illegal parameter number
in definition of ``\FNH@prefntext``"
* #8214: LaTeX: The :rst:role:`index` role and the glossary generate duplicate
entries in the LaTeX index (if both used for same term)
* #8735: LaTeX: wrong internal links in pdf to captioned code-blocks when
:confval:`numfig` is not True
* #8442: LaTeX: some indexed terms are ignored when using xelatex engine
(or pdflatex and :confval:`latex_use_xindy` set to True) with memoir class
Testing
--------

View File

@ -8,4 +8,9 @@
Changelog
=========
.. raw:: latex
\hypersetup{bookmarksdepth=1}% pdf bookmarks
\addtocontents{toc}{\protect\setcounter{tocdepth}{1}}%
.. include:: ../CHANGES

View File

@ -58,8 +58,27 @@ latex_documents = [('contents', 'sphinx.tex', 'Sphinx Documentation',
latex_logo = '_static/sphinx.png'
latex_elements = {
'fontenc': r'\usepackage[LGR,X2,T1]{fontenc}',
'passoptionstopackages': '\\PassOptionsToPackage{svgnames}{xcolor}',
'preamble': '\\DeclareUnicodeCharacter{229E}{\\ensuremath{\\boxplus}}',
'fontpkg': r'''
\usepackage[sc]{mathpazo}
\usepackage[scaled]{helvet}
\usepackage{courier}
\substitutefont{LGR}{\rmdefault}{cmr}
\substitutefont{LGR}{\sfdefault}{cmss}
\substitutefont{LGR}{\ttdefault}{cmtt}
\substitutefont{X2}{\rmdefault}{cmr}
\substitutefont{X2}{\sfdefault}{cmss}
\substitutefont{X2}{\ttdefault}{cmtt}
''',
'passoptionstopackages': r'''
\PassOptionsToPackage{svgnames}{xcolor}
\PassOptionsToPackage{bookmarksdepth=3}{hyperref}% depth of pdf bookmarks
''',
'preamble': r'''
\DeclareUnicodeCharacter{229E}{\ensuremath{\boxplus}}
\setcounter{tocdepth}{3}% depth of what is kept from toc file
\setcounter{secnumdepth}{1}% depth of section numbering
''',
'fvset': '\\fvset{fontsize=auto}',
# fix missing index entry due to RTD doing only once pdflatex after makeindex
'printindex': r'''
\IfFileExists{\jobname.ind}

View File

@ -46,14 +46,15 @@ organization. If you wish to include your extension in this organization,
simply follow the instructions provided in the `github-administration`__
project. This is optional and there are several extensions hosted elsewhere.
The `awesome-sphinxdoc`__ project contains a curated list of Sphinx packages,
and many packages use the ``Framework :: Sphinx :: Extension`` and
``Framework :: Sphinx :: Theme`` `trove classifiers`__ for Sphinx extensions
and themes, respectively.
and many packages use the `Framework :: Sphinx :: Extension`__ and
`Framework :: Sphinx :: Theme`__ trove classifiers for Sphinx extensions and
themes, respectively.
.. __: https://github.com/sphinx-contrib/
.. __: https://github.com/sphinx-contrib/github-administration
.. __: https://github.com/yoloseem/awesome-sphinxdoc
.. __: https://pypi.org/classifiers/
.. __: https://pypi.org/search/?c=Framework+%3A%3A+Sphinx+%3A%3A+Extension
.. __: https://pypi.org/search/?c=Framework+%3A%3A+Sphinx+%3A%3A+Theme
Where to put your own extensions?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -18,6 +18,7 @@ module.exports = function(config) {
'sphinx/themes/basic/static/underscore.js',
'sphinx/themes/basic/static/jquery.js',
'sphinx/themes/basic/static/doctools.js',
'sphinx/themes/basic/static/searchtools.js',
'tests/js/*.js'
],

View File

@ -494,31 +494,34 @@ class Sphinx:
"""Register a configuration value.
This is necessary for Sphinx to recognize new values and set default
values accordingly. The *name* should be prefixed with the extension
name, to avoid clashes. The *default* value can be any Python object.
The string value *rebuild* must be one of those values:
values accordingly.
* ``'env'`` if a change in the setting only takes effect when a
document is parsed -- this means that the whole environment must be
rebuilt.
* ``'html'`` if a change in the setting needs a full rebuild of HTML
documents.
* ``''`` if a change in the setting will not need any special rebuild.
The *types* value takes a list of types that describes the type of
configuration value. For example, ``[str]`` is used to describe a
configuration that takes string value.
:param name: The name of configuration value. It is recommended to be prefixed
with the extension name (ex. ``html_logo``, ``epub_title``)
:param default: The default value of the configuration.
:param rebuild: The condition of rebuild. It must be one of those values:
.. versionchanged:: 0.6
Changed *rebuild* from a simple boolean (equivalent to ``''`` or
``'env'``) to a string. However, booleans are still accepted and
converted internally.
* ``'env'`` if a change in the setting only takes effect when a
document is parsed -- this means that the whole environment must be
rebuilt.
* ``'html'`` if a change in the setting needs a full rebuild of HTML
documents.
* ``''`` if a change in the setting will not need any special rebuild.
:param types: The type of configuration value. A list of types can be specified. For
example, ``[str]`` is used to describe a configuration that takes string
value.
.. versionchanged:: 0.4
If the *default* value is a callable, it will be called with the
config object as its argument in order to get the default value.
This can be used to implement config values whose default depends on
other values.
.. versionchanged:: 0.6
Changed *rebuild* from a simple boolean (equivalent to ``''`` or
``'env'``) to a string. However, booleans are still accepted and
converted internally.
"""
logger.debug('[app] adding config value: %r',
(name, default, rebuild) + ((types,) if types else ()))
@ -530,6 +533,8 @@ class Sphinx:
"""Register an event called *name*.
This is needed to be able to emit it.
:param name: The name of the event
"""
logger.debug('[app] adding event: %r', name)
self.events.add(name)
@ -560,6 +565,11 @@ class Sphinx:
This is necessary for Docutils internals. It may also be used in the
future to validate nodes in the parsed documents.
:param node: A node class
:param kwargs: Visitor functions for each builder (see below)
:param override: If true, install the node forcedly even if another node is already
installed as the same name
Node visitor functions for the Sphinx HTML, LaTeX, text and manpage
writers can be given as keyword arguments: the keyword should be one or
more of ``'html'``, ``'latex'``, ``'text'``, ``'man'``, ``'texinfo'``
@ -581,9 +591,6 @@ class Sphinx:
Obviously, translators for which you don't specify visitor methods will
choke on the node when encountered in a document to translate.
If *override* is True, the given *node* is forcedly installed even if
a node having the same name is already installed.
.. versionchanged:: 0.5
Added the support for keyword arguments giving visit functions.
"""
@ -603,24 +610,21 @@ class Sphinx:
Sphinx numbers the node automatically. And then the users can refer it
using :rst:role:`numref`.
*figtype* is a type of enumerable nodes. Each figtypes have individual
numbering sequences. As a system figtypes, ``figure``, ``table`` and
``code-block`` are defined. It is able to add custom nodes to these
default figtypes. It is also able to define new custom figtype if new
figtype is given.
*title_getter* is a getter function to obtain the title of node. It
takes an instance of the enumerable node, and it must return its title
as string. The title is used to the default title of references for
:rst:role:`ref`. By default, Sphinx searches
``docutils.nodes.caption`` or ``docutils.nodes.title`` from the node as
a title.
Other keyword arguments are used for node visitor functions. See the
:meth:`.Sphinx.add_node` for details.
If *override* is True, the given *node* is forcedly installed even if
a node having the same name is already installed.
:param node: A node class
:param figtype: The type of enumerable nodes. Each figtypes have individual numbering
sequences. As a system figtypes, ``figure``, ``table`` and
``code-block`` are defined. It is able to add custom nodes to these
default figtypes. It is also able to define new custom figtype if new
figtype is given.
:param title_getter: A getter function to obtain the title of node. It takes an
instance of the enumerable node, and it must return its title as
string. The title is used to the default title of references for
:rst:role:`ref`. By default, Sphinx searches
``docutils.nodes.caption`` or ``docutils.nodes.title`` from the
node as a title.
:param kwargs: Visitor functions for each builder (same as :meth:`add_node`)
:param override: If true, install the node forcedly even if another node is already
installed as the same name
.. versionadded:: 1.4
"""
@ -679,7 +683,7 @@ class Sphinx:
"""Register a Docutils role.
:param name: The name of role
:param cls: A role function
:param role: A role function
:param override: If true, install the role forcedly even if another role is already
installed as the same name
@ -720,11 +724,9 @@ class Sphinx:
def add_domain(self, domain: "Type[Domain]", override: bool = False) -> None:
"""Register a domain.
Make the given *domain* (which must be a class; more precisely, a
subclass of :class:`~sphinx.domains.Domain`) known to Sphinx.
If *override* is True, the given *domain* is forcedly installed even if
a domain having the same name is already installed.
:param domain: A domain class
:param override: If true, install the domain forcedly even if another domain
is already installed as the same name
.. versionadded:: 1.0
.. versionchanged:: 1.8
@ -739,8 +741,11 @@ class Sphinx:
Like :meth:`add_directive`, but the directive is added to the domain
named *domain*.
If *override* is True, the given *directive* is forcedly installed even if
a directive named as *name* is already installed.
:param domain: The name of target domain
:param name: A name of directive
:param cls: A directive class
:param override: If true, install the directive forcedly even if another directive
is already installed as the same name
.. versionadded:: 1.0
.. versionchanged:: 1.8
@ -755,8 +760,11 @@ class Sphinx:
Like :meth:`add_role`, but the role is added to the domain named
*domain*.
If *override* is True, the given *role* is forcedly installed even if
a role named as *name* is already installed.
:param domain: The name of target domain
:param name: A name of role
:param role: A role function
:param override: If true, install the role forcedly even if another role is already
installed as the same name
.. versionadded:: 1.0
.. versionchanged:: 1.8
@ -768,11 +776,12 @@ class Sphinx:
) -> None:
"""Register a custom index for a domain.
Add a custom *index* class to the domain named *domain*. *index* must
be a subclass of :class:`~sphinx.domains.Index`.
Add a custom *index* class to the domain named *domain*.
If *override* is True, the given *index* is forcedly installed even if
an index having the same name is already installed.
:param domain: The name of target domain
:param index: A index class
:param override: If true, install the index forcedly even if another index is
already installed as the same name
.. versionadded:: 1.0
.. versionchanged:: 1.8
@ -893,6 +902,8 @@ class Sphinx:
the list of transforms that are applied after Sphinx parses a reST
document.
:param transform: A transform class
.. list-table:: priority range categories for Sphinx transforms
:widths: 20,80
@ -925,6 +936,8 @@ class Sphinx:
Add the standard docutils :class:`Transform` subclass *transform* to
the list of transforms that are applied before Sphinx writes a
document.
:param transform: A transform class
"""
self.registry.add_post_transform(transform)
@ -1196,9 +1209,10 @@ class Sphinx:
def add_message_catalog(self, catalog: str, locale_dir: str) -> None:
"""Register a message catalog.
The *catalog* is a name of catalog, and *locale_dir* is a base path
of message catalog. For more details, see
:func:`sphinx.locale.get_translation()`.
:param catalog: A name of catalog
:param locale_dir: The base path of message catalog
For more details, see :func:`sphinx.locale.get_translation()`.
.. versionadded:: 1.8
"""
@ -1209,7 +1223,7 @@ class Sphinx:
def is_parallel_allowed(self, typ: str) -> bool:
"""Check parallel processing is allowed or not.
``typ`` is a type of processing; ``'read'`` or ``'write'``.
:param typ: A type of processing; ``'read'`` or ``'write'``.
"""
if typ == 'read':
attrname = 'parallel_read_safe'

View File

@ -118,10 +118,6 @@ class CheckExternalLinksBuilder(DummyBuilder):
self._redirected = {} # type: Dict[str, Tuple[str, int]]
# set a timeout for non-responding servers
socket.setdefaulttimeout(5.0)
# create output file
open(path.join(self.outdir, 'output.txt'), 'w').close()
# create JSON output file
open(path.join(self.outdir, 'output.json'), 'w').close()
# create queues and worker threads
self.rate_limits = {} # type: Dict[str, RateLimit]
@ -435,26 +431,25 @@ class CheckExternalLinksBuilder(DummyBuilder):
def write_entry(self, what: str, docname: str, filename: str, line: int,
uri: str) -> None:
with open(path.join(self.outdir, 'output.txt'), 'a') as output:
output.write("%s:%s: [%s] %s\n" % (filename, line, what, uri))
self.txt_outfile.write("%s:%s: [%s] %s\n" % (filename, line, what, uri))
def write_linkstat(self, data: dict) -> None:
with open(path.join(self.outdir, 'output.json'), 'a') as output:
output.write(json.dumps(data))
output.write('\n')
self.json_outfile.write(json.dumps(data))
self.json_outfile.write('\n')
def finish(self) -> None:
logger.info('')
n = 0
for hyperlink in self.hyperlinks.values():
self.wqueue.put(hyperlink, False)
n += 1
total_links = len(self.hyperlinks)
done = 0
while done < n:
self.process_result(self.rqueue.get())
done += 1
with open(path.join(self.outdir, 'output.txt'), 'w') as self.txt_outfile,\
open(path.join(self.outdir, 'output.json'), 'w') as self.json_outfile:
while done < total_links:
self.process_result(self.rqueue.get())
done += 1
if self._broken:
self.app.statuscode = 1

View File

@ -27,6 +27,7 @@ try:
readline.parse_and_bind("tab: complete")
USE_LIBEDIT = False
except ImportError:
readline = None
USE_LIBEDIT = False
from docutils.utils import column_width
@ -139,8 +140,11 @@ def do_prompt(text: str, default: str = None, validator: Callable[[str], Any] =
# sequence (see #5335). To avoid the problem, all prompts are not colored
# on libedit.
pass
else:
elif readline:
# pass input_mode=True if readline available
prompt = colorize(COLOR_QUESTION, prompt, input_mode=True)
else:
prompt = colorize(COLOR_QUESTION, prompt, input_mode=False)
x = term_input(prompt).strip()
if default and not x:
x = default

View File

@ -308,7 +308,7 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index
document.note_explicit_target(term)
std = cast(StandardDomain, env.get_domain('std'))
std.note_object('term', termtext, node_id, location=term)
std._note_term(termtext, node_id, location=term)
# add an index entry too
indexnode = addnodes.index()
@ -679,6 +679,20 @@ class StandardDomain(Domain):
RemovedInSphinx50Warning, stacklevel=2)
self.objects[objtype, name] = (docname, labelid)
@property
def _terms(self) -> Dict[str, Tuple[str, str]]:
""".. note:: Will be removed soon. internal use only."""
return self.data.setdefault('terms', {}) # (name) -> docname, labelid
def _note_term(self, term: str, labelid: str, location: Any = None) -> None:
"""Note a term for cross reference.
.. note:: Will be removed soon. internal use only.
"""
self.note_object('term', term, labelid, location)
self._terms[term.lower()] = (self.env.docname, labelid)
@property
def progoptions(self) -> Dict[Tuple[str, str], Tuple[str, str]]:
return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid
@ -699,6 +713,9 @@ class StandardDomain(Domain):
for key, (fn, _l) in list(self.objects.items()):
if fn == docname:
del self.objects[key]
for key, (fn, _l) in list(self._terms.items()):
if fn == docname:
del self._terms[key]
for key, (fn, _l, _l) in list(self.labels.items()):
if fn == docname:
del self.labels[key]
@ -714,6 +731,9 @@ class StandardDomain(Domain):
for key, data in otherdata['objects'].items():
if data[0] in docnames:
self.objects[key] = data
for key, data in otherdata['terms'].items():
if data[0] in docnames:
self._terms[key] = data
for key, data in otherdata['labels'].items():
if data[0] in docnames:
self.labels[key] = data
@ -947,19 +967,12 @@ class StandardDomain(Domain):
if result:
return result
else:
for objtype, term in self.objects:
if objtype == 'term' and term.lower() == target.lower():
docname, labelid = self.objects[objtype, term]
logger.warning(__('term %s not found in case sensitive match.'
'made a reference to %s instead.'),
target, term, location=node, type='ref', subtype='term')
break
# fallback to case insentive match
if target.lower() in self._terms:
docname, labelid = self._terms[target.lower()]
return make_refnode(builder, fromdocname, docname, labelid, contnode)
else:
docname, labelid = '', ''
if not docname:
return None
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def _resolve_obj_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
@ -1115,7 +1128,7 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
return {
'version': 'builtin',
'env_version': 1,
'env_version': 2,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@ -26,7 +26,7 @@ from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warnin
from sphinx.environment import BuildEnvironment
from sphinx.ext.autodoc.importer import (get_class_members, get_object_members, import_module,
import_object)
from sphinx.ext.autodoc.mock import ismock, mock
from sphinx.ext.autodoc.mock import ismock, mock, undecorate
from sphinx.locale import _, __
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import inspect, logging
@ -418,6 +418,8 @@ class Documenter:
attrgetter=self.get_attr,
warningiserror=self.config.autodoc_warningiserror)
self.module, self.parent, self.object_name, self.object = ret
if ismock(self.object):
self.object = undecorate(self.object)
return True
except ImportError as exc:
if raiseerror:
@ -1046,6 +1048,8 @@ class ModuleDocumenter(Documenter):
for name in dir(self.object):
try:
value = safe_getattr(self.object, name, None)
if ismock(value):
value = undecorate(value)
docstring = attr_docs.get(('', name), [])
members[name] = ObjectMember(name, value, docstring="\n".join(docstring))
except AttributeError:

View File

@ -14,6 +14,7 @@ import warnings
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple
from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.ext.autodoc.mock import ismock, undecorate
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import logging
from sphinx.util.inspect import (getannotations, getmro, getslots, isclass, isenumclass,
@ -273,6 +274,9 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable
for name in dir(subject):
try:
value = attrgetter(subject, name)
if ismock(value):
value = undecorate(value)
unmangled = unmangle(subject, name)
if unmangled and unmangled not in members:
if name in obj_dict:

View File

@ -13,7 +13,7 @@ import os
import sys
from importlib.abc import Loader, MetaPathFinder
from importlib.machinery import ModuleSpec
from types import FunctionType, MethodType, ModuleType
from types import ModuleType
from typing import Any, Generator, Iterator, List, Optional, Sequence, Tuple, Union
from sphinx.util import logging
@ -27,6 +27,7 @@ class _MockObject:
__display_name__ = '_MockObject'
__sphinx_mock__ = True
__sphinx_decorator_args__ = () # type: Tuple[Any, ...]
def __new__(cls, *args: Any, **kwargs: Any) -> Any:
if len(args) == 3 and isinstance(args[1], tuple):
@ -60,18 +61,19 @@ class _MockObject:
return _make_subclass(key, self.__display_name__, self.__class__)()
def __call__(self, *args: Any, **kwargs: Any) -> Any:
if args and type(args[0]) in [type, FunctionType, MethodType]:
# Appears to be a decorator, pass through unchanged
return args[0]
return self
call = self.__class__()
call.__sphinx_decorator_args__ = args
return call
def __repr__(self) -> str:
return self.__display_name__
def _make_subclass(name: str, module: str, superclass: Any = _MockObject,
attributes: Any = None) -> Any:
attrs = {'__module__': module, '__display_name__': module + '.' + name}
attributes: Any = None, decorator_args: Tuple = ()) -> Any:
attrs = {'__module__': module,
'__display_name__': module + '.' + name,
'__sphinx_decorator_args__': decorator_args}
attrs.update(attributes or {})
return type(name, (superclass,), attrs)
@ -172,3 +174,14 @@ def ismock(subject: Any) -> bool:
pass
return False
def undecorate(subject: _MockObject) -> Any:
"""Unwrap mock if *subject* is decorated by mocked object.
If not decorated, returns given *subject* itself.
"""
if ismock(subject) and subject.__sphinx_decorator_args__:
return subject.__sphinx_decorator_args__[0]
else:
return subject

View File

@ -149,6 +149,18 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
env._viewcode_modules.update(other._viewcode_modules) # type: ignore
def env_purge_doc(app: Sphinx, env: BuildEnvironment, docname: str) -> None:
modules = getattr(env, '_viewcode_modules', {})
for modname, (code, tags, used, refname) in list(modules.items()):
for fullname in list(used):
if used[fullname] == docname:
used.pop(fullname)
if len(used) == 0:
modules.pop(modname)
class ViewcodeAnchorTransform(SphinxPostTransform):
"""Convert or remove viewcode_anchor nodes depends on builder."""
default_priority = 100
@ -323,6 +335,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('viewcode_follow_imported_members', True, False)
app.connect('doctree-read', doctree_read)
app.connect('env-merge-info', env_merge_info)
app.connect('env-purge-doc', env_purge_doc)
app.connect('html-collect-pages', collect_pages)
app.connect('missing-reference', missing_reference)
# app.add_config_value('viewcode_include_modules', [], 'env')

View File

@ -20,6 +20,9 @@
%% turn off hyperref patch of \index as sphinx.xdy xindy module takes care of
%% suitable \hyperpage mark-up, working around hyperref-xindy incompatibility
\PassOptionsToPackage{hyperindex=false}{hyperref}
%% memoir class requires extra handling
\makeatletter\@ifclassloaded{memoir}
{\ifdefined\memhyperindexfalse\memhyperindexfalse\fi}{}\makeatother
<% endif -%>
<%= passoptionstopackages %>
\PassOptionsToPackage{warn}{textcomp}

View File

@ -1,9 +1,9 @@
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{footnotehyper-sphinx}%
[2017/10/27 v1.7 hyperref aware footnote.sty for sphinx (JFB)]
[2021/01/26 v1.1b hyperref aware footnote.sty for sphinx (JFB)]
%%
%% Package: footnotehyper-sphinx
%% Version: based on footnotehyper.sty 2017/03/07 v1.0
%% Version: based on footnotehyper.sty 2021/01/26 v1.1b
%% as available at https://www.ctan.org/pkg/footnotehyper
%% License: the one applying to Sphinx
%%
@ -16,7 +16,7 @@
%% 3. use of \sphinxunactivateextrasandspace from sphinx.sty,
%% 4. macro definition \sphinxfootnotemark,
%% 5. macro definition \sphinxlongtablepatch
%% 6. replaced an \undefined by \@undefined
%% 6. replaced some \undefined by \@undefined
\DeclareOption*{\PackageWarning{footnotehyper-sphinx}{Option `\CurrentOption' is unknown}}%
\ProcessOptions\relax
\newbox\FNH@notes
@ -206,9 +206,20 @@
\FNH@@@1.2!3?4,\FNH@@@\relax
}%
\long\def\FNH@check@a #11.2!3?4,#2\FNH@@@#3{%
\ifx\relax#3\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
\FNH@bad@makefntext@alert
{\def\FNH@prefntext{#1}\def\FNH@postfntext{#2}\FNH@check@b}%
\ifx\relax#3\FNH@bad@makefntext@alert
\else
\edef\FNH@restore@{\catcode`\noexpand\@\the\catcode`\@\relax}%
\makeatletter
\ifx\@makefntextFB\@undefined
\expandafter\@gobble\else\expandafter\@firstofone\fi
{\@ifclassloaded{memoir}%
{\ifFBFrenchFootnotes\expandafter\@gobble\fi}%
{}}%
\@secondoftwo
\scantokens{\def\FNH@prefntext{#1}\def\FNH@postfntext{#2}}%
\FNH@restore@
\expandafter\FNH@check@b
\fi
}%
\def\FNH@check@b #1\relax{%
\expandafter\expandafter\expandafter\FNH@check@c

View File

@ -1859,8 +1859,8 @@
\def\sphinxstyleindexextra #1{ (\emph{#1})}
\def\sphinxstyleindexpageref #1{, \pageref{#1}}
\def\sphinxstyleindexpagemain#1{\textbf{#1}}
\protected\def\spxentry#1{#1}% will get \let to \sphinxstyleindexentry in index
\protected\def\spxextra#1{#1}% will get \let to \sphinxstyleindexextra in index
\def\spxentry{\@backslashchar spxentry}% let to \sphinxstyleindexentry in index
\def\spxextra{\@backslashchar spxextra}% let to \sphinxstyleindexextra in index
\def\sphinxstyleindexlettergroup #1%
{{\Large\sffamily#1}\nopagebreak\vspace{1mm}}
\def\sphinxstyleindexlettergroupDefault #1%

View File

@ -29,9 +29,14 @@ if (!window.console || !console.firebug) {
/**
* small helper function to urldecode strings
*
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
*/
jQuery.urldecode = function(x) {
return decodeURIComponent(x).replace(/\+/g, ' ');
if (!x) {
return x
}
return decodeURIComponent(x.replace(/\+/g, ' '));
};
/**

View File

@ -379,6 +379,13 @@ var Search = {
return results;
},
/**
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
*/
escapeRegExp : function(string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
},
/**
* search for full-text terms in the index
*/
@ -402,13 +409,14 @@ var Search = {
];
// add support for partial matches
if (word.length > 2) {
var word_regex = this.escapeRegExp(word);
for (var w in terms) {
if (w.match(word) && !terms[word]) {
if (w.match(word_regex) && !terms[word]) {
_o.push({files: terms[w], score: Scorer.partialTerm})
}
}
for (var w in titleterms) {
if (w.match(word) && !titleterms[word]) {
if (w.match(word_regex) && !titleterms[word]) {
_o.push({files: titleterms[w], score: Scorer.partialTitle})
}
}

View File

@ -312,6 +312,7 @@ class Locale(SphinxTransform):
refname = newf.get('refname')
refs = old_foot_namerefs.get(refname, [])
if not refs:
newf.parent.remove(newf)
continue
oldf = refs.pop(0)

View File

@ -386,6 +386,10 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
# there's a classifier.
pass
else:
if isinstance(node.parent.parent.parent, addnodes.glossary):
# add permalink if glossary terms
self.add_permalink_ref(node, _('Permalink to this term'))
self.body.append('</dt>')
# overwritten

View File

@ -337,6 +337,10 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
# there's a classifier.
pass
else:
if isinstance(node.parent.parent.parent, addnodes.glossary):
# add permalink if glossary terms
self.add_permalink_ref(node, _('Permalink to this term'))
self.body.append('</dt>')
# overwritten

View File

@ -12,6 +12,10 @@ describe('jQuery extensions', function() {
expect(jQuery.urldecode(test_encoded_string)).toEqual(test_decoded_string);
});
it('+ should result in " "', function() {
expect(jQuery.urldecode('+')).toEqual(' ');
});
});
describe('getQueryParameters', function() {

32
tests/js/searchtools.js Normal file
View File

@ -0,0 +1,32 @@
describe('Basic html theme search', function() {
describe('terms search', function() {
it('should find "C++" when in index', function() {
index = {
docnames:["index"],
filenames:["index.rst"],
terms:{'c++':0},
titles:["&lt;no title&gt;"],
titleterms:{}
}
Search.setIndex(index);
searchterms = ['c++'];
excluded = [];
terms = index.terms;
titleterms = index.titleterms;
hits = [[
"index",
"&lt;no title&gt;",
"",
null,
2,
"index.rst"
]];
expect(Search.performTermsSearch(searchterms, excluded, terms, titleterms)).toEqual(hits);
});
});
});

View File

@ -9,7 +9,7 @@ import sphinx.missing_module4 # NOQA
from sphinx.missing_module4 import missing_name2 # NOQA
@missing_name
@missing_name(int)
def decoratedFunction():
"""decoratedFunction docstring"""
return None

View File

@ -254,6 +254,7 @@ def test_html4_output(app, status, warning):
(".//p[@class='centered']/strong", 'LICENSE'),
# a glossary
(".//dl/dt[@id='term-boson']", 'boson'),
(".//dl/dt[@id='term-boson']/a", ''),
# a production list
(".//pre/strong", 'try_stmt'),
(".//pre/a[@href='#grammar-token-try1_stmt']/code/span", 'try1_stmt'),

View File

@ -15,7 +15,7 @@ from typing import TypeVar
import pytest
from sphinx.ext.autodoc.mock import _MockModule, _MockObject, ismock, mock
from sphinx.ext.autodoc.mock import _MockModule, _MockObject, ismock, mock, undecorate
def test_MockModule():
@ -115,20 +115,25 @@ def test_mock_decorator():
@mock.function_deco
def func():
"""docstring"""
pass
class Foo:
@mock.method_deco
def meth(self):
"""docstring"""
pass
@mock.class_deco
class Bar:
"""docstring"""
pass
assert func.__doc__ == "docstring"
assert Foo.meth.__doc__ == "docstring"
assert Bar.__doc__ == "docstring"
@mock.funcion_deco(Foo)
class Baz:
pass
assert undecorate(func).__name__ == "func"
assert undecorate(Foo.meth).__name__ == "meth"
assert undecorate(Bar).__name__ == "Bar"
assert undecorate(Baz).__name__ == "Baz"
def test_ismock():

View File

@ -363,8 +363,12 @@ def get_verifier(verify, verify_re):
# glossary (description list): multiple terms
'verify',
'.. glossary::\n\n term1\n term2\n description',
('<dl class="glossary docutils">\n<dt id="term-term1">term1</dt>'
'<dt id="term-term2">term2</dt><dd>description</dd>\n</dl>'),
('<dl class="glossary docutils">\n'
'<dt id="term-term1">term1<a class="headerlink" href="#term-term1"'
' title="Permalink to this term">¶</a></dt>'
'<dt id="term-term2">term2<a class="headerlink" href="#term-term2"'
' title="Permalink to this term">¶</a></dt>'
'<dd>description</dd>\n</dl>'),
None,
),
])