summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES1
-rw-r--r--sphinx/builders/html/__init__.py3
-rw-r--r--sphinx/builders/html/transforms.py69
-rw-r--r--tests/test_markup.py28
4 files changed, 101 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
index abd7c1dd1..a10de4a17 100644
--- a/CHANGES
+++ b/CHANGES
@@ -49,6 +49,7 @@ Features added
to generate stub files recursively
* #4030: autosummary: Add :confval:`autosummary_context` to add template
variables for custom templates
+* #7530: html: Support nested <kbd> elements
* #7481: html theme: Add right margin to footnote/citation labels
* #7482: html theme: CSS spacing for code blocks with captions and line numbers
* #7443: html theme: Add new options :confval:`globaltoc_collapse` and
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 320c7feb6..7ad87ffa9 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -1243,6 +1243,9 @@ def setup(app: Sphinx) -> Dict[str, Any]:
# load default math renderer
app.setup_extension('sphinx.ext.mathjax')
+ # load transforms for HTML builder
+ app.setup_extension('sphinx.builders.html.transforms')
+
return {
'version': 'builtin',
'parallel_read_safe': True,
diff --git a/sphinx/builders/html/transforms.py b/sphinx/builders/html/transforms.py
new file mode 100644
index 000000000..c91da57e9
--- /dev/null
+++ b/sphinx/builders/html/transforms.py
@@ -0,0 +1,69 @@
+"""
+ sphinx.builders.html.transforms
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Transforms for HTML builder.
+
+ :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+from typing import Any, Dict
+
+from docutils import nodes
+
+from sphinx.application import Sphinx
+from sphinx.transforms.post_transforms import SphinxPostTransform
+from sphinx.util.nodes import NodeMatcher
+
+
+class KeyboardTransform(SphinxPostTransform):
+ """Transform :kbd: role to more detailed form.
+
+ Before::
+
+ <literal class="kbd">
+ Control-x
+
+ After::
+
+ <literal class="kbd">
+ <literal class="kbd">
+ Control
+ -
+ <literal class="kbd">
+ x
+ """
+ default_priority = 400
+ builders = ('html',)
+ pattern = re.compile(r'(-|\+|\^|\s+)')
+
+ def run(self, **kwargs: Any) -> None:
+ matcher = NodeMatcher(nodes.literal, classes=["kbd"])
+ for node in self.document.traverse(matcher): # type: nodes.literal
+ parts = self.pattern.split(node[-1].astext())
+ if len(parts) == 1:
+ continue
+
+ node.pop()
+ while parts:
+ key = parts.pop(0)
+ node += nodes.literal('', key, classes=["kbd"])
+
+ try:
+ # key separator (ex. -, +, ^)
+ sep = parts.pop(0)
+ node += nodes.Text(sep)
+ except IndexError:
+ pass
+
+
+def setup(app: Sphinx) -> Dict[str, Any]:
+ app.add_post_transform(KeyboardTransform)
+
+ return {
+ 'version': 'builtin',
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }
diff --git a/tests/test_markup.py b/tests/test_markup.py
index b6d99db90..1d5c81bfa 100644
--- a/tests/test_markup.py
+++ b/tests/test_markup.py
@@ -16,6 +16,7 @@ from docutils.parsers.rst import Parser as RstParser
from docutils.transforms.universal import SmartQuotes
from sphinx import addnodes
+from sphinx.builders.html.transforms import KeyboardTransform
from sphinx.builders.latex import LaTeXBuilder
from sphinx.roles import XRefRole
from sphinx.testing.util import Struct, assert_node
@@ -94,6 +95,7 @@ class ForgivingLaTeXTranslator(LaTeXTranslator, ForgivingTranslator):
def verify_re_html(app, parse):
def verify(rst, html_expected):
document = parse(rst)
+ KeyboardTransform(document).apply()
html_translator = ForgivingHTMLTranslator(document, app.builder)
document.walkabout(html_translator)
html_translated = ''.join(html_translator.fragment).strip()
@@ -238,6 +240,32 @@ def get_verifier(verify, verify_re):
'\\sphinxkeyboard{\\sphinxupquote{space}}',
),
(
+ # kbd role
+ 'verify',
+ ':kbd:`Control+X`',
+ ('<p><kbd class="kbd docutils literal notranslate">'
+ '<kbd class="kbd docutils literal notranslate">Control</kbd>'
+ '+'
+ '<kbd class="kbd docutils literal notranslate">X</kbd>'
+ '</kbd></p>'),
+ '\\sphinxkeyboard{\\sphinxupquote{Control+X}}',
+ ),
+ (
+ # kbd role
+ 'verify',
+ ':kbd:`M-x M-s`',
+ ('<p><kbd class="kbd docutils literal notranslate">'
+ '<kbd class="kbd docutils literal notranslate">M</kbd>'
+ '-'
+ '<kbd class="kbd docutils literal notranslate">x</kbd>'
+ ' '
+ '<kbd class="kbd docutils literal notranslate">M</kbd>'
+ '-'
+ '<kbd class="kbd docutils literal notranslate">s</kbd>'
+ '</kbd></p>'),
+ '\\sphinxkeyboard{\\sphinxupquote{M\\sphinxhyphen{}x M\\sphinxhyphen{}s}}',
+ ),
+ (
# non-interpolation of dashes in option role
'verify_re',
':option:`--with-option`',