From 3e479d772bbf084ccf1fa4f46af05e2762231e24 Mon Sep 17 00:00:00 2001 From: Daniel Hofmann Date: Wed, 19 Aug 2020 16:41:20 +0200 Subject: [PATCH 01/15] Closes #8123: Fix plus-handling (+) in search terms for basic html theme search Note, that the default splitter will not index +, so this isn't of much of much use, unless the splitter of the search-language is reconfigured. --- CHANGES | 3 +++ karma.conf.js | 1 + sphinx/themes/basic/static/doctools.js | 7 ++++- sphinx/themes/basic/static/searchtools.js | 12 +++++++-- tests/js/doctools.js | 4 +++ tests/js/searchtools.js | 32 +++++++++++++++++++++++ 6 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 tests/js/searchtools.js diff --git a/CHANGES b/CHANGES index 5176d8372..b1d679a01 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,9 @@ Features added Bugs fixed ---------- +* #8123: html-search: fix searching for terms containing + (Requires a + custom search language that does not split on +) + Testing -------- diff --git a/karma.conf.js b/karma.conf.js index d0c11aa21..82be18a71 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -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' ], diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index daccd209d..a98364ae1 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -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, ' ')); }; /** diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 970d0d975..889eaf57a 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -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}) } } diff --git a/tests/js/doctools.js b/tests/js/doctools.js index 54246f635..a28f3ceb3 100644 --- a/tests/js/doctools.js +++ b/tests/js/doctools.js @@ -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() { diff --git a/tests/js/searchtools.js b/tests/js/searchtools.js new file mode 100644 index 000000000..007eeb7db --- /dev/null +++ b/tests/js/searchtools.js @@ -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:["<no title>"], + titleterms:{} + } + Search.setIndex(index); + searchterms = ['c++']; + excluded = []; + terms = index.terms; + titleterms = index.titleterms; + + hits = [[ + "index", + "<no title>", + "", + null, + 2, + "index.rst" + ]]; + expect(Search.performTermsSearch(searchterms, excluded, terms, titleterms)).toEqual(hits); + }); + + }); + +}); From 3a3a479f1e739d688e831744ecc96b310b524f1d Mon Sep 17 00:00:00 2001 From: jfbu Date: Sun, 24 Jan 2021 00:41:59 +0100 Subject: [PATCH 02/15] Improve Sphinx own pdf docs Prior to this the depth of the bookmarks is too low, one does not even get to see for example what are the built-in extensions offered by Sphinx. Similarly the LaTeX created table of contents has not enough depth. Dozens of contiguous pages from our documentation get only a single link, it is very hard for newcomer to get some feeling of the scope of Sphinx. With a more detailed table of contents (be it inside the PDF or via the collapsable bookmark panel of PDF viewer) learning Sphinx is easier. --- doc/changes.rst | 5 +++++ doc/conf.py | 11 +++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/doc/changes.rst b/doc/changes.rst index b4872784a..829c7f7ed 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -8,4 +8,9 @@ Changelog ========= +.. raw:: latex + + \hypersetup{bookmarksdepth=1}% pdf bookmarks + \addtocontents{toc}{\protect\setcounter{tocdepth}{1}}% + .. include:: ../CHANGES diff --git a/doc/conf.py b/doc/conf.py index 9f018bc7b..53f036d3e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -69,8 +69,15 @@ latex_elements = { \substitutefont{X2}{\sfdefault}{cmss} \substitutefont{X2}{\ttdefault}{cmtt} ''', - 'passoptionstopackages': '\\PassOptionsToPackage{svgnames}{xcolor}', - 'preamble': '\\DeclareUnicodeCharacter{229E}{\\ensuremath{\\boxplus}}', + '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''' From a78c6b799f0dab414dadc79fa3920d0581c01279 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 24 Jan 2021 02:08:19 +0900 Subject: [PATCH 03/15] Fix #8134: autodoc: crashes when mocked decorator takes arguments autodoc crashed when a decorator in mocked module takes arguments because mock system returns the first argument for the decorator as a decorated object. This changes the approach for mocking decorators that remembers arguments for each decoration, and fetch the latest argument on generating document. --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 6 ++++- sphinx/ext/autodoc/importer.py | 4 +++ sphinx/ext/autodoc/mock.py | 27 ++++++++++++++----- .../test-ext-autodoc/target/need_mocks.py | 2 +- tests/test_ext_autodoc_mock.py | 19 ++++++++----- 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 9f7ffb7f3..53d10cdf2 100644 --- a/CHANGES +++ b/CHANGES @@ -66,6 +66,7 @@ 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 (-, diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 83c7d28c4..1490d1551 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -27,7 +27,7 @@ from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warni 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 @@ -422,6 +422,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: @@ -1054,6 +1056,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: diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index ffcb27ecc..d7c9b93f5 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -15,6 +15,7 @@ from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tup from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warning, deprecated_alias) +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, @@ -285,6 +286,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: diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index 3d4f76410..d3f4291c2 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -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, 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 diff --git a/tests/roots/test-ext-autodoc/target/need_mocks.py b/tests/roots/test-ext-autodoc/target/need_mocks.py index bc227f246..a29954184 100644 --- a/tests/roots/test-ext-autodoc/target/need_mocks.py +++ b/tests/roots/test-ext-autodoc/target/need_mocks.py @@ -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 diff --git a/tests/test_ext_autodoc_mock.py b/tests/test_ext_autodoc_mock.py index 08157ac45..497bd8a6b 100644 --- a/tests/test_ext_autodoc_mock.py +++ b/tests/test_ext_autodoc_mock.py @@ -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(): From 34417831c5f03ef5f820fe1413328c21cc01e9d1 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 24 Jan 2021 16:45:50 +0900 Subject: [PATCH 04/15] doc: Add hyperlinks to classifiers for sphinx extensions and themes --- doc/usage/extensions/index.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/usage/extensions/index.rst b/doc/usage/extensions/index.rst index 3d076fb3a..0d446cb61 100644 --- a/doc/usage/extensions/index.rst +++ b/doc/usage/extensions/index.rst @@ -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? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 54df51e86f8853f4d6215954e5ec0bcd5f9e4533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Sun, 24 Jan 2021 11:52:04 +0100 Subject: [PATCH 05/15] =?UTF-8?q?Linkcheck:=20Don=E2=80=99t=20repeatedly?= =?UTF-8?q?=20open/close=20log=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opening and closing a file requires processing from the operating system. Repeatedly opening and closing wastes system resources and hinders buffering, causing a flush (disk I/O) after each write operation. Using a context manager ensures the logs are flushed to disk and files are properly closed whether the program exists successfully or an exception occurs. Compared to the previous implementation, a brutal shutdown of the machine (e.g. power cord disconnected) could cause some log lines not to be written. That should not be an issue in practice. Now, files are created and truncated when linkcheck submitted the links to check to the threads and is ready to process the results, instead of when the builder is constructed. It keeps the file operations closer to their use. --- sphinx/builders/linkcheck.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index f813922ec..9015b706e 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -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,13 +431,11 @@ 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('') @@ -452,9 +446,11 @@ class CheckExternalLinksBuilder(DummyBuilder): n += 1 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 < n: + self.process_result(self.rqueue.get()) + done += 1 if self._broken: self.app.statuscode = 1 From 1121e5e8bc60fc9a25959f28b2c8a5890acb2d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Sun, 24 Jan 2021 12:24:52 +0100 Subject: [PATCH 06/15] Linkcheck: Derive number of links from the post-transform result The number of links to check is the number of links in self.hyperlinks, populated by the post-transform. --- sphinx/builders/linkcheck.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index f813922ec..f48a41802 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -445,14 +445,13 @@ class CheckExternalLinksBuilder(DummyBuilder): 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: + while done < total_links: self.process_result(self.rqueue.get()) done += 1 From b5ff272f77bec90ba97cef0cfa42330e56c4a543 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 23 Jan 2021 23:50:06 +0900 Subject: [PATCH 07/15] Close #7642: std domain: Optimize case-insensitive match of term Since 3.0.1, the term role has matched to the words in glossary case-sensitively. It's important change for preventing conflicts by word cases. But, it also brings a problem for references in natural text. This optimizes the case-insensitive match of the term role. It allows to search glossary words twice with no performance penalty; the first search is case sensitive and another is case insenstive. --- CHANGES | 1 + sphinx/domains/std.py | 39 ++++++++++++++++++++++++++------------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 604689ed8..c7cfe7db8 100644 --- a/CHANGES +++ b/CHANGES @@ -51,6 +51,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 diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 5b1dab2b8..352e5c7f3 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -323,7 +323,7 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index term['ids'].append(node_id) 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() @@ -694,6 +694,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 @@ -714,6 +728,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] @@ -729,6 +746,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 @@ -967,19 +987,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, @@ -1147,7 +1160,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, } From 949ec8737f38333302abf671ce906bebcaa8f2d9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 24 Jan 2021 20:21:39 +0900 Subject: [PATCH 08/15] Close #1638: html: Add permalink icons to glossary terms --- CHANGES | 1 + sphinx/writers/html.py | 4 ++++ sphinx/writers/html5.py | 4 ++++ tests/test_build_html.py | 1 + tests/test_markup.py | 8 ++++++-- 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 604689ed8..2de9448b1 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,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 diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index e4da7d857..c8da10eca 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -394,6 +394,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('') # overwritten diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index 12f3f423a..455633f93 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -345,6 +345,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('') # overwritten diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 9abfa19aa..6b720b3ef 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -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'), diff --git a/tests/test_markup.py b/tests/test_markup.py index 8341b8826..18c40ec1d 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -363,8 +363,12 @@ def get_verifier(verify, verify_re): # glossary (description list): multiple terms 'verify', '.. glossary::\n\n term1\n term2\n description', - ('
\n
term1
' - '
term2
description
\n
'), + ('
\n' + '
term1
' + '
term2
' + '
description
\n
'), None, ), ]) From 81ba9273cabc4a7c2a420b0aac94c18935e6a583 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 25 Jan 2021 00:22:42 +0900 Subject: [PATCH 09/15] Fix #8745: i18n: KeyError if a new auto footnote_ref in translations Some writers will be crashed by KeyError because of lack of the refid if a new auto footnote reference is added to the message catalog by translation misses. This detects the invalid footnote references and removes them on the translation phase. --- CHANGES | 2 ++ sphinx/transforms/i18n.py | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 604689ed8..de601073a 100644 --- a/CHANGES +++ b/CHANGES @@ -78,6 +78,8 @@ Bugs fixed * #8629: html: A type warning for html_use_opensearch is shown twice * #8714: html: kbd role with "Caps Lock" rendered incorrectly * #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 * #8094: texinfo: image files on the different directory with document are not diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index 8520cfc69..d588f0411 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -311,6 +311,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) From 8dc8c0142584e29f81440eea7c7470f4563c4014 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 25 Jan 2021 01:50:21 +0900 Subject: [PATCH 10/15] Fix #8756: viewcode: highlighted code is generated even if not referenced viewcode does not purge unreferenced modules on incremental build. This adds env-purge-doc handler to clean them. --- CHANGES | 1 + sphinx/ext/viewcode.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGES b/CHANGES index 604689ed8..30128b559 100644 --- a/CHANGES +++ b/CHANGES @@ -84,6 +84,7 @@ Bugs fixed 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. diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index baf86dbbf..21cff6a03 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -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') From 9a156baa87a9d7306868e40a7693c6d2aa700bb6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 24 Jan 2021 22:23:18 +0900 Subject: [PATCH 11/15] Fix #7118: quickstart: got Mojibake if libreadline unavailable Do not output escape sequence for libreadline (\1 and \2) when libreadline is unavailable. --- CHANGES | 1 + sphinx/cmd/quickstart.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b7acc1480..537c0b46d 100644 --- a/CHANGES +++ b/CHANGES @@ -83,6 +83,7 @@ Bugs fixed * #8665: html theme: Could not override globaltoc_maxdepth in theme.conf * #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 diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index 7d1c48f31..4234c039c 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -29,6 +29,7 @@ try: readline.parse_and_bind("tab: complete") USE_LIBEDIT = False except ImportError: + readline = None USE_LIBEDIT = False from docutils.utils import column_width @@ -169,8 +170,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 From 84ef92226f56f17eb17e0aea4e6a28dbb75f8eee Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 25 Jan 2021 23:19:48 +0900 Subject: [PATCH 12/15] doc: Apply :params: to some APIs in app class --- sphinx/application.py | 124 +++++++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 55 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 69887682f..f61a6a549 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -499,31 +499,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 ())) @@ -535,6 +538,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) @@ -565,6 +570,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'`` @@ -586,9 +596,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. """ @@ -608,24 +615,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 """ @@ -684,7 +688,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 @@ -725,11 +729,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 @@ -744,8 +746,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 @@ -760,8 +765,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 @@ -773,11 +781,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 @@ -898,6 +907,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 @@ -930,6 +941,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) @@ -1230,9 +1243,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 """ @@ -1243,7 +1257,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' From 2772909861afb01fca4f66fbe59b4f0d1070c3ff Mon Sep 17 00:00:00 2001 From: jfbu Date: Tue, 26 Jan 2021 10:24:09 +0100 Subject: [PATCH 13/15] Fix #8442: missing index entries in pdf output with memoir + xindy --- CHANGES | 2 ++ sphinx/templates/latex/latex.tex_t | 3 +++ 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 789899d7f..2cb81ac6e 100644 --- a/CHANGES +++ b/CHANGES @@ -100,6 +100,8 @@ Bugs fixed specified * #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 -------- diff --git a/sphinx/templates/latex/latex.tex_t b/sphinx/templates/latex/latex.tex_t index 5082254e7..e288dda93 100644 --- a/sphinx/templates/latex/latex.tex_t +++ b/sphinx/templates/latex/latex.tex_t @@ -17,6 +17,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} From 5c96add19309921d6c69ea80f960330e4346535a Mon Sep 17 00:00:00 2001 From: jfbu Date: Tue, 26 Jan 2021 15:59:18 +0100 Subject: [PATCH 14/15] LaTeX: sync with upstream footnotehyper This fixes #7576 --- CHANGES | 2 ++ sphinx/texinputs/footnotehyper-sphinx.sty | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 789899d7f..23813d388 100644 --- a/CHANGES +++ b/CHANGES @@ -98,6 +98,8 @@ 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``" * #8735: LaTeX: wrong internal links in pdf to captioned code-blocks when :confval:`numfig` is not True diff --git a/sphinx/texinputs/footnotehyper-sphinx.sty b/sphinx/texinputs/footnotehyper-sphinx.sty index b6692cfb8..c66e9a548 100644 --- a/sphinx/texinputs/footnotehyper-sphinx.sty +++ b/sphinx/texinputs/footnotehyper-sphinx.sty @@ -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 From f937facc69042a163a94f8d84635efeca0783cd0 Mon Sep 17 00:00:00 2001 From: jfbu Date: Tue, 26 Jan 2021 18:53:45 +0100 Subject: [PATCH 15/15] Fix #8214: duplicate entries in latex index if term also in glossary This makes .idx files contain \spxentry{..} with no space, whether or not the \index latex command is encountered in main text or e.g. in a label of a description list (like happens for terms from a glossary). Xindy does not care about spaces, but Makeindex doesn't merge entries whose typesetting is to be done via \spxentry{..} or \spxentry {..}. An alternative work-around would be for Sphinx LaTeX writer to use some wrapper, say, \sphinxindexwrap, only to fetch the argument and force TeX tokenization and then hand over it to \index command. But we choose to let \spxentry expand to its own name, with no trailing space (it gets its final definition only from the python.ist file in case of Makeindex). In case both the :index: role and the glossary are on same page, Xindy and Makeindex behave differently: the former gives only once the page number, the latter issues them twice (but the indexed term only once), and there is warning in the .ilg file. But this is unrelated and we can't do here anything about it. --- CHANGES | 2 ++ sphinx/texinputs/sphinx.sty | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 2cb81ac6e..f9e953e98 100644 --- a/CHANGES +++ b/CHANGES @@ -98,6 +98,8 @@ 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 +* #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 diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 343924753..f406b1089 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -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%