diff --git a/src/antsibull/jinja2/filters.py b/src/antsibull/jinja2/filters.py
index c68ee3f11..01814e1b6 100644
--- a/src/antsibull/jinja2/filters.py
+++ b/src/antsibull/jinja2/filters.py
@@ -34,10 +34,10 @@
def _option_name_html(matcher):
- parts = matcher.group(1).split('=', 1)
- if len(parts) == 1:
- return f'{parts[0]}'
- return f"{parts[0]}={parts[1]}
"
+ text = matcher.group(1)
+ if '=' not in text and ':' not in text:
+ return f'{text}
'
+ return f'{text}
'
def html_ify(text):
@@ -61,9 +61,9 @@ def html_ify(text):
r"\1
", text)
text, _counts['option-name'] = _SEM_OPTION_NAME.subn(_option_name_html, text)
text, _counts['option-value'] = _SEM_OPTION_VALUE.subn(
- r"\1
", text)
+ r"\1
", text)
text, _counts['environment-var'] = _SEM_ENV_VARIABLE.subn(
- r"\1
", text)
+ r"\1
", text)
text, _counts['ruler'] = _RULER.subn(r"
", text)
text = text.strip()
@@ -98,15 +98,6 @@ def do_max(seq):
# https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#character-level-inline-markup-1
# for further information.
-def _option_name_rst(matcher):
- parts = matcher.group(1).split('=', 1)
- start = f"\\ :strong:`{rst_escape(parts[0], escape_ending_whitespace=True)}`\\ "
- if len(parts) == 1:
- return start
- end = f"\\ :literal:`{rst_escape(parts[1], escape_ending_whitespace=True)}`\\ "
- return f'{start}={end}'
-
-
def _rst_ify_italic(m: 're.Match') -> str:
return f"\\ :emphasis:`{rst_escape(m.group(1), escape_ending_whitespace=True)}`\\ "
@@ -143,6 +134,18 @@ def _rst_ify_const(m: 're.Match') -> str:
return f"\\ :literal:`{rst_escape(m.group(1), escape_ending_whitespace=True)}`\\ "
+def _rst_ify_option_name(m):
+ return f"\\ :ansopt:`{rst_escape(m.group(1), escape_ending_whitespace=True)}`\\ "
+
+
+def _rst_ify_value(m):
+ return f"\\ :ansval:`{rst_escape(m.group(1), escape_ending_whitespace=True)}`\\ "
+
+
+def _rst_ify_envvar(m):
+ return f"\\ :envvar:`{rst_escape(m.group(1), escape_ending_whitespace=True)}`\\ "
+
+
def rst_ify(text):
''' convert symbols like I(this is in italics) to valid restructured text '''
@@ -157,9 +160,9 @@ def rst_ify(text):
text, _counts['url'] = _URL.subn(_rst_ify_url, text)
text, _counts['ref'] = _REF.subn(_rst_ify_ref, text)
text, _counts['const'] = _CONST.subn(_rst_ify_const, text)
- text, _counts['option-name'] = _SEM_OPTION_NAME.subn(_option_name_rst, text)
- text, _counts['option-value'] = _SEM_OPTION_VALUE.subn(_rst_ify_const, text)
- text, _counts['environment-var'] = _SEM_ENV_VARIABLE.subn(_rst_ify_const, text)
+ text, _counts['option-name'] = _SEM_OPTION_NAME.subn(_rst_ify_option_name, text)
+ text, _counts['option-value'] = _SEM_OPTION_VALUE.subn(_rst_ify_value, text)
+ text, _counts['environment-var'] = _SEM_ENV_VARIABLE.subn(_rst_ify_envvar, text)
text, _counts['ruler'] = _RULER.subn('\n\n.. raw:: html\n\n
\n\n', text)
flog.fields(counts=_counts).info('Number of macros converted to rst equivalents')
diff --git a/src/antsibull/lint_extra_docs.py b/src/antsibull/lint_extra_docs.py
index 94dbce6e4..9f80b2f48 100644
--- a/src/antsibull/lint_extra_docs.py
+++ b/src/antsibull/lint_extra_docs.py
@@ -12,6 +12,10 @@
import docutils.utils
import rstcheck
+from docutils.parsers.rst import roles as docutils_roles
+
+from sphinx_antsibull_ext import roles as antsibull_roles
+
from .extra_docs import (
find_extra_docs,
lint_required_conditions,
@@ -49,6 +53,12 @@ def lint_optional_conditions(content: str, path: str, collection_name: str
return [(result[0], 0, result[1]) for result in results]
+def _setup_rstcheck():
+ '''Make sure that rstcheck knows about our roles.'''
+ for name, role in antsibull_roles.ROLES.items():
+ docutils_roles.register_local_role(name, role)
+
+
def lint_collection_extra_docs_files(path_to_collection: str
) -> t.List[t.Tuple[str, int, int, str]]:
try:
@@ -59,6 +69,7 @@ def lint_collection_extra_docs_files(path_to_collection: str
result = []
all_labels = set()
docs = find_extra_docs(path_to_collection)
+ _setup_rstcheck()
for doc in docs:
try:
# Load content
diff --git a/src/sphinx_antsibull_ext/__init__.py b/src/sphinx_antsibull_ext/__init__.py
index 60f75c641..422acc370 100644
--- a/src/sphinx_antsibull_ext/__init__.py
+++ b/src/sphinx_antsibull_ext/__init__.py
@@ -13,6 +13,7 @@
from .assets import setup_assets
+from .roles import setup_roles
def setup(app):
@@ -24,6 +25,9 @@ def setup(app):
# Add assets
setup_assets(app)
+ # Add roles
+ setup_roles(app)
+
return dict(
parallel_read_safe=True,
parallel_write_safe=True,
diff --git a/src/sphinx_antsibull_ext/roles.py b/src/sphinx_antsibull_ext/roles.py
new file mode 100644
index 000000000..0454ebe64
--- /dev/null
+++ b/src/sphinx_antsibull_ext/roles.py
@@ -0,0 +1,68 @@
+# coding: utf-8
+# Author: Felix Fontein
+# License: GPLv3+
+# Copyright: Ansible Project, 2021
+'''
+Add roles for semantic markup.
+'''
+
+from docutils import nodes
+
+
+def option_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
+ """Format Ansible option key, or option key-value.
+
+ Returns 2 part tuple containing list of nodes to insert into the
+ document and a list of system messages. Both are allowed to be
+ empty.
+
+ :param name: The role name used in the document.
+ :param rawtext: The entire markup snippet, with role.
+ :param text: The text marked with the role.
+ :param lineno: The line number where rawtext appears in the input.
+ :param inliner: The inliner instance that called us.
+ :param options: Directive options for customization.
+ :param content: The directive content for customization.
+ """
+ children = []
+ classes = []
+ if '=' not in text and ':' not in text:
+ children.append(nodes.strong(rawtext, text))
+ rawtext = ''
+ text = ''
+ classes.append('ansible-option')
+ else:
+ classes.append('ansible-option-value')
+ return [nodes.literal(rawtext, text, *children, classes=classes)], []
+
+
+def value_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
+ """Format Ansible option value.
+
+ Returns 2 part tuple containing list of nodes to insert into the
+ document and a list of system messages. Both are allowed to be
+ empty.
+
+ :param name: The role name used in the document.
+ :param rawtext: The entire markup snippet, with role.
+ :param text: The text marked with the role.
+ :param lineno: The line number where rawtext appears in the input.
+ :param inliner: The inliner instance that called us.
+ :param options: Directive options for customization.
+ :param content: The directive content for customization.
+ """
+ return [nodes.literal(rawtext, text, classes=['ansible-value'])], []
+
+
+ROLES = {
+ 'ansopt': option_role,
+ 'ansval': value_role,
+}
+
+
+def setup_roles(app):
+ '''
+ Setup roles for a Sphinx app object.
+ '''
+ for name, role in ROLES.items():
+ app.add_role(name, role)