diff --git a/CHANGES b/CHANGES index 20585e834..9488aedff 100644 --- a/CHANGES +++ b/CHANGES @@ -116,6 +116,7 @@ Deprecated * ``sphinx.io.SphinxRSTFileInput`` * ``sphinx.registry.SphinxComponentRegistry.add_source_input()`` * ``sphinx.roles.abbr_role()`` +* ``sphinx.roles.index_role()`` * ``sphinx.testing.util.remove_unicode_literal()`` * ``sphinx.util.attrdict`` * ``sphinx.util.force_decode()`` diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 7203ec6fa..c3061852a 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -380,6 +380,11 @@ The following is a list of deprecated interfaces. - 4.0 - ``sphinx.roles.Abbreviation`` + * - ``sphinx.roles.index_role()`` + - 2.0 + - 4.0 + - ``sphinx.roles.Index`` + * - ``sphinx.testing.util.remove_unicode_literal()`` - 2.0 - 4.0 diff --git a/sphinx/roles.py b/sphinx/roles.py index e9a8786ca..d2e64c40c 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -18,7 +18,7 @@ from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.errors import SphinxError from sphinx.locale import _ from sphinx.util import ws_re -from sphinx.util.docutils import SphinxRole +from sphinx.util.docutils import ReferenceRole, SphinxRole from sphinx.util.nodes import split_explicit_title, process_index_entry, \ set_role_source_info @@ -371,6 +371,8 @@ class Abbreviation(SphinxRole): def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA + warnings.warn('index_role() is deprecated. Please use Index class instead.', + RemovedInSphinx40Warning, stacklevel=2) # create new reference target env = inliner.document.settings.env targetid = 'index-%s' % env.new_serialno('index') @@ -398,6 +400,30 @@ def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): return [indexnode, targetnode, textnode], [] +class Index(ReferenceRole): + def run(self): + # type: () -> Tuple[List[nodes.Node], List[nodes.system_message]] + target_id = 'index-%s' % self.env.new_serialno('index') + if self.has_explicit_title: + # if an explicit target is given, process it as a full entry + title = self.title + entries = process_index_entry(self.target, target_id) + else: + # otherwise we just create a single entry + if self.target.startswith('!'): + title = self.title[1:] + entries = [('single', self.target[1:], target_id, 'main', None)] + else: + title = self.title + entries = [('single', self.target, target_id, '', None)] + + index = addnodes.index(entries=entries) + target = nodes.target('', '', ids=[target_id]) + text = nodes.Text(title, title) + self.set_source_info(index) + return [index, target, text], [] + + specific_docroles = { # links to download references 'download': XRefRole(nodeclass=addnodes.download_reference), @@ -411,7 +437,7 @@ specific_docroles = { 'file': emph_literal_role, 'samp': emph_literal_role, 'abbr': Abbreviation(), - 'index': index_role, + 'index': Index(), } # type: Dict[str, RoleFunction] diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index ba5a9d0ea..71e8db268 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -421,6 +421,39 @@ class SphinxRole: """Reference to the :class:`.Config` object.""" return self.env.config + def set_source_info(self, node, lineno=None): + # type: (nodes.Node, int) -> None + if lineno is None: + lineno = self.lineno + + source_info = self.inliner.reporter.get_source_and_line(lineno) # type: ignore + node.source, node.line = source_info + + +class ReferenceRole(SphinxRole): + """A base class for reference roles. + + The reference roles can accpet ``link title `` style as a text for + the role. The parsed result: link title and target will be stored to + ``self.title`` and ``self.target``. + """ + # \x00 means the "<" was backslash-escaped + explicit_title_re = re.compile(r'^(.+?)\s*(?$', re.DOTALL) + + def __call__(self, typ, rawtext, text, lineno, inliner, options={}, content=[]): + # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA + matched = self.explicit_title_re.match(text) + if matched: + self.has_explicit_title = True + self.title = unescape(matched.group(1)) + self.target = unescape(matched.group(2)) + else: + self.has_explicit_title = False + self.title = unescape(text) + self.target = unescape(text) + + return super().__call__(typ, rawtext, text, lineno, inliner, options, content) + class SphinxTranslator(nodes.NodeVisitor): """A base class for Sphinx translators.