Skip to content

Add new semantic markup support #4

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

Merged
merged 1 commit into from
Mar 9, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/4-semantic-markup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
major_changes:
- Support new semantic markup in documentation (https://github.com/ansible-community/antsibull-docs/pull/4).
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ ansible-pygments = "*"
antsibull-core = ">= 1.2.0, < 3.0.0"
asyncio-pool = "*"
docutils = "*"
jinja2 = "*"
jinja2 = ">= 3.0"
packaging = "*"
rstcheck = ">= 3.0.0, < 7.0.0"
sphinx = "*"
Expand Down
13 changes: 12 additions & 1 deletion src/antsibull_docs/jinja2/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,24 @@
from collections.abc import Mapping, Sequence

from antsibull_core.logging import log
from jinja2.runtime import Undefined
from jinja2.runtime import Context, Undefined

mlog = log.fields(mod=__name__)

_EMAIL_ADDRESS = re.compile(r"(?:<{mail}>|\({mail}\)|{mail})".format(mail=r"[\w.+-]+@[\w.-]+\.\w+"))


def extract_plugin_data(context: Context) -> t.Tuple[t.Optional[str], t.Optional[str]]:
plugin_fqcn = context.get('plugin_name')
plugin_type = context.get('plugin_type')
if plugin_fqcn is None or plugin_type is None:
return None, None
# if plugin_type == 'role':
# entry_point = context.get('entry_point', 'main')
# # FIXME: use entry_point
return plugin_fqcn, plugin_type


def documented_type(text) -> str:
''' Convert any python type to a type for documentation '''

Expand Down
121 changes: 118 additions & 3 deletions src/antsibull_docs/jinja2/htmlify.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
from urllib.parse import quote

from antsibull_core.logging import log
from jinja2.runtime import Context
from jinja2.utils import pass_context

from ..semantic_helper import parse_option, parse_return_value
from .filters import extract_plugin_data
from .parser import Command, CommandSet, convert_text

mlog = log.fields(mod=__name__)
Expand All @@ -33,9 +37,13 @@ def _create_error(text: str, error: str) -> str:


class _Context:
j2_context: Context
counts: t.Dict[str, int]
plugin_fqcn: t.Optional[str]
plugin_type: t.Optional[str]

def __init__(self):
def __init__(self, j2_context: Context):
self.j2_context = j2_context
self.counts = {
'italic': 0,
'bold': 0,
Expand All @@ -51,6 +59,7 @@ def __init__(self):
'return-value': 0,
'ruler': 0,
}
self.plugin_fqcn, self.plugin_type = extract_plugin_data(j2_context)


# In the following, we make heavy use of escaped whitespace ("\ ") being removed from the output.
Expand Down Expand Up @@ -157,6 +166,107 @@ def handle(self, parameters: t.List[str], context: t.Any) -> str:
return f"<code class='docutils literal notranslate'>{html_escape(parameters[0])}</code>"


class _OptionName(Command):
command = 'O'
parameter_count = 1
escaped_content = True

def handle(self, parameters: t.List[str], context: t.Any) -> str:
context.counts['option-name'] += 1
if context.plugin_fqcn is None or context.plugin_type is None:
raise Exception('The markup O(...) cannot be used outside a plugin or role')
text = parameters[0]
try:
plugin_fqcn, plugin_type, option_link, option, value = parse_option(
text, context.plugin_fqcn, context.plugin_type, require_plugin=False)
except ValueError as exc:
return _create_error(f'O({text})', str(exc))
if value is None:
cls = 'ansible-option'
text = f'{option}'
strong_start = '<strong>'
strong_end = '</strong>'
else:
cls = 'ansible-option-value'
text = f'{option}={value}'
strong_start = ''
strong_end = ''
if plugin_fqcn and plugin_type and plugin_fqcn.count('.') >= 2:
# TODO: handle role arguments (entrypoint!)
namespace, name, plugin = plugin_fqcn.split('.', 2)
url = f'../../{namespace}/{name}/{plugin}_{plugin_type}.html'
fragment = f'parameter-{quote(option_link.replace(".", "/"))}'
link_start = (
f'<a class="reference internal" href="{url}#{fragment}">'
'<span class="std std-ref"><span class="pre">'
)
link_end = '</span></span></a>'
else:
link_start = ''
link_end = ''
return (
f'<code class="{cls} literal notranslate">'
f'{strong_start}{link_start}{text}{link_end}{strong_end}</code>'
)


class _OptionValue(Command):
command = 'V'
parameter_count = 1
escaped_content = True

def handle(self, parameters: t.List[str], context: t.Any) -> str:
context.counts['option-value'] += 1
text = parameters[0]
return f'<code class="ansible-value literal notranslate">{html_escape(text)}</code>'


class _EnvVariable(Command):
command = 'E'
parameter_count = 1
escaped_content = True

def handle(self, parameters: t.List[str], context: t.Any) -> str:
context.counts['environment-var'] += 1
text = parameters[0]
return f'<code class="xref std std-envvar literal notranslate">{html_escape(text)}</code>'


class _RetValue(Command):
command = 'RV'
parameter_count = 1
escaped_content = True

def handle(self, parameters: t.List[str], context: t.Any) -> str:
context.counts['return-value'] += 1
if context.plugin_fqcn is None or context.plugin_type is None:
raise Exception('The markup RV(...) cannot be used outside a plugin or role')
text = parameters[0]
try:
plugin_fqcn, plugin_type, rv_link, rv, value = parse_return_value(
text, context.plugin_fqcn, context.plugin_type, require_plugin=False)
except ValueError as exc:
return _create_error(f'RV({text})', str(exc))
cls = 'ansible-return-value'
if value is None:
text = f'{rv}'
else:
text = f'{rv}={value}'
if plugin_fqcn and plugin_type and plugin_fqcn.count('.') >= 2:
namespace, name, plugin = plugin_fqcn.split('.', 2)
url = f'../../{namespace}/{name}/{plugin}_{plugin_type}.html'
fragment = f'return-{quote(rv_link.replace(".", "/"))}'
link_start = (
f'<a class="reference internal" href="{url}#{fragment}">'
'<span class="std std-ref"><span class="pre">'
)
link_end = '</span></span></a>'
else:
link_start = ''
link_end = ''
return f'<code class="{cls} literal notranslate">{link_start}{text}{link_end}</code>'


class _HorizontalLine(Command):
command = 'HORIZONTALLINE'
parameter_count = 0
Expand All @@ -176,16 +286,21 @@ def handle(self, parameters: t.List[str], context: t.Any) -> str:
_Link(),
_Ref(),
_Const(),
_OptionName(),
_OptionValue(),
_EnvVariable(),
_RetValue(),
_HorizontalLine(),
])


def html_ify(text: str) -> str:
@pass_context
def html_ify(context: Context, text: str) -> str:
''' convert symbols like I(this is in italics) to valid HTML '''
flog = mlog.fields(func='html_ify')
flog.fields(text=text).debug('Enter')

our_context = _Context()
our_context = _Context(context)

try:
text = convert_text(text, _COMMAND_SET, html_escape, our_context)
Expand Down
66 changes: 63 additions & 3 deletions src/antsibull_docs/jinja2/rstify.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
from urllib.parse import quote

from antsibull_core.logging import log
from jinja2.runtime import Context
from jinja2.utils import pass_context

from ..semantic_helper import augment_plugin_name_type
from .filters import extract_plugin_data
from .parser import Command, CommandSet, convert_text

mlog = log.fields(mod=__name__)
Expand Down Expand Up @@ -61,9 +65,13 @@ def _create_error(text: str, error: str) -> str:


class _Context:
j2_context: Context
counts: t.Dict[str, int]
plugin_fqcn: t.Optional[str]
plugin_type: t.Optional[str]

def __init__(self):
def __init__(self, j2_context: Context):
self.j2_context = j2_context
self.counts = {
'italic': 0,
'bold': 0,
Expand All @@ -79,6 +87,7 @@ def __init__(self):
'return-value': 0,
'ruler': 0,
}
self.plugin_fqcn, self.plugin_type = extract_plugin_data(j2_context)


# In the following, we make heavy use of escaped whitespace ("\ ") being removed from the output.
Expand Down Expand Up @@ -180,6 +189,52 @@ def handle(self, parameters: t.List[str], context: t.Any) -> str:
return f"\\ :literal:`{rst_escape(parameters[0], escape_ending_whitespace=True)}`\\ "


class _OptionName(Command):
command = 'O'
parameter_count = 1
escaped_content = True

def handle(self, parameters: t.List[str], context: t.Any) -> str:
context.counts['option-name'] += 1
if context.plugin_fqcn is None or context.plugin_type is None:
raise Exception('The markup O(...) cannot be used outside a plugin or role')
text = augment_plugin_name_type(parameters[0], context.plugin_fqcn, context.plugin_type)
return f"\\ :ansopt:`{rst_escape(text, escape_ending_whitespace=True)}`\\ "


class _OptionValue(Command):
command = 'V'
parameter_count = 1
escaped_content = True

def handle(self, parameters: t.List[str], context: t.Any) -> str:
context.counts['option-value'] += 1
return f"\\ :ansval:`{rst_escape(parameters[0], escape_ending_whitespace=True)}`\\ "


class _EnvVariable(Command):
command = 'E'
parameter_count = 1
escaped_content = True

def handle(self, parameters: t.List[str], context: t.Any) -> str:
context.counts['environment-var'] += 1
return f"\\ :envvar:`{rst_escape(parameters[0], escape_ending_whitespace=True)}`\\ "


class _RetValue(Command):
command = 'RV'
parameter_count = 1
escaped_content = True

def handle(self, parameters: t.List[str], context: t.Any) -> str:
context.counts['return-value'] += 1
if context.plugin_fqcn is None or context.plugin_type is None:
raise Exception('The markup RV(...) cannot be used outside a plugin or role')
text = augment_plugin_name_type(parameters[0], context.plugin_fqcn, context.plugin_type)
return f"\\ :ansretval:`{rst_escape(text, escape_ending_whitespace=True)}`\\ "


class _HorizontalLine(Command):
command = 'HORIZONTALLINE'
parameter_count = 0
Expand All @@ -199,16 +254,21 @@ def handle(self, parameters: t.List[str], context: t.Any) -> str:
_Link(),
_Ref(),
_Const(),
_OptionName(),
_OptionValue(),
_EnvVariable(),
_RetValue(),
_HorizontalLine(),
])


def rst_ify(text: str) -> str:
@pass_context
def rst_ify(context: Context, text: str) -> str:
''' convert symbols like I(this is in italics) to valid restructured text '''
flog = mlog.fields(func='rst_ify')
flog.fields(text=text).debug('Enter')

our_context = _Context()
our_context = _Context(context)

try:
text = convert_text(text, _COMMAND_SET, rst_escape, our_context)
Expand Down
Loading