Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sphinx.ext.extlinks: Allow %s in link caption string #8898

Merged
merged 4 commits into from Apr 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 14 additions & 10 deletions doc/usage/extensions/extlinks.rst
Expand Up @@ -23,29 +23,33 @@ The extension adds a config value:
.. confval:: extlinks

This config value must be a dictionary of external sites, mapping unique
short alias names to a base URL and a *prefix*. For example, to create an
short alias names to a *base URL* and a *caption*. For example, to create an
alias for the above mentioned issues, you would add ::

extlinks = {'issue': ('https://github.com/sphinx-doc/sphinx/issues/%s',
'issue ')}
'issue %s')}

Now, you can use the alias name as a new role, e.g. ``:issue:`123```. This
then inserts a link to https://github.com/sphinx-doc/sphinx/issues/123.
As you can see, the target given in the role is substituted in the base URL
As you can see, the target given in the role is substituted in the *base URL*
in the place of ``%s``.

The link *caption* depends on the second item in the tuple, the *prefix*:
The link caption depends on the second item in the tuple, the *caption*:

- If the prefix is ``None``, the link caption is the full URL.
- If the prefix is the empty string, the link caption is the partial URL
given in the role content (``123`` in this case.)
- If the prefix is a non-empty string, the link caption is the partial URL,
prepended by the prefix -- in the above example, the link caption would be
- If *caption* is ``None``, the link caption is the full URL.
- If *caption* is a string, then it must contain ``%s`` exactly once. In
tk0miya marked this conversation as resolved.
Show resolved Hide resolved
this case the link caption is *caption* with the partial URL substituted
for ``%s`` -- in the above example, the link caption would be
``issue 123``.

To produce a literal ``%`` in either *base URL* or *caption*, use ``%%``::

extlinks = {'KnR': ('https://example.org/K%%26R/page/%s',
'[K&R; page %s]')}

You can also use the usual "explicit title" syntax supported by other roles
that generate links, i.e. ``:issue:`this issue <123>```. In this case, the
*prefix* is not relevant.
*caption* is not relevant.

.. note::

Expand Down
52 changes: 35 additions & 17 deletions sphinx/ext/extlinks.py
Expand Up @@ -7,22 +7,25 @@
This adds a new config value called ``extlinks`` that is created like this::
extlinks = {'exmpl': ('https://example.invalid/%s.html', prefix), ...}
extlinks = {'exmpl': ('https://example.invalid/%s.html', caption), ...}
Now you can use e.g. :exmpl:`foo` in your documents. This will create a
link to ``https://example.invalid/foo.html``. The link caption depends on
the *prefix* value given:
the *caption* value given:
- If it is ``None``, the caption will be the full URL.
- If it is a string (empty or not), the caption will be the prefix prepended
to the role content.
- If it is a string, it must contain ``%s`` exactly once. In this case the
caption will be *caption* with the role content substituted for ``%s``.
You can also give an explicit caption, e.g. :exmpl:`Foo <foo>`.
Both, the url string and the caption string must escape ``%`` as ``%%``.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""

import warnings
from typing import Any, Dict, List, Tuple

from docutils import nodes, utils
Expand All @@ -31,37 +34,52 @@

import sphinx
from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx60Warning
from sphinx.util.nodes import split_explicit_title
from sphinx.util.typing import RoleFunction


def make_link_role(base_url: str, prefix: str) -> RoleFunction:
def make_link_role(name: str, base_url: str, caption: str) -> RoleFunction:
# Check whether we have base_url and caption strings have an '%s' for
# expansion. If not, fall back the the old behaviour and use the string as
# a prefix.
# Remark: It is an implementation detail that we use Pythons %-formatting.
# So far we only expose ``%s`` and require quoting of ``%`` using ``%%``.
try:
base_url % 'dummy'
except (TypeError, ValueError):
warnings.warn('extlinks: Sphinx-6.0 will require base URL to '
'contain exactly one \'%s\' and all other \'%\' need '
'to be escaped as \'%%\'.', RemovedInSphinx60Warning)
base_url = base_url.replace('%', '%%') + '%s'
if caption is not None:
try:
caption % 'dummy'
except (TypeError, ValueError):
warnings.warn('extlinks: Sphinx-6.0 will require a caption string to '
'contain exactly one \'%s\' and all other \'%\' need '
'to be escaped as \'%%\'.', RemovedInSphinx60Warning)
caption = caption.replace('%', '%%') + '%s'

def role(typ: str, rawtext: str, text: str, lineno: int,
inliner: Inliner, options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
text = utils.unescape(text)
has_explicit_title, title, part = split_explicit_title(text)
try:
full_url = base_url % part
except (TypeError, ValueError):
inliner.reporter.warning(
'unable to expand %s extlink with base URL %r, please make '
'sure the base contains \'%%s\' exactly once'
% (typ, base_url), line=lineno)
full_url = base_url + part
full_url = base_url % part
if not has_explicit_title:
if prefix is None:
if caption is None:
title = full_url
else:
title = prefix + part
title = caption % part
pnode = nodes.reference(title, title, internal=False, refuri=full_url)
return [pnode], []
return role


def setup_link_roles(app: Sphinx) -> None:
for name, (base_url, prefix) in app.config.extlinks.items():
app.add_role(name, make_link_role(base_url, prefix))
for name, (base_url, caption) in app.config.extlinks.items():
app.add_role(name, make_link_role(name, base_url, caption))


def setup(app: Sphinx) -> Dict[str, Any]:
Expand Down