#
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# Copyright (C) 2018-2019 OpenCyphal Development Team <opencyphal.org>
# This software is distributed under the terms of the MIT License.
#
"""
Filters for generating docs. All filters in this
module will be available in the template's global namespace as ``ln.html``.
"""
import html
import logging
import re
import typing
import pydsdl
import nunavut
from nunavut._templates import template_volatile_filter
from nunavut.jinja.jinja2 import TemplateAssertionError
from nunavut.lang._common import UniqueNameGenerator
logger = logging.getLogger(__name__)
[docs]def filter_extent(instance: pydsdl.Any) -> int:
try:
return instance.extent or 0
except TypeError as e:
raise TemplateAssertionError(e)
[docs]def filter_max_bit_length(instance: pydsdl.Any) -> int:
try:
return instance.bit_length_set.max or 0
except TypeError as e:
raise TemplateAssertionError(e)
[docs]def filter_tag_id(instance: pydsdl.Any) -> str:
if isinstance(instance, pydsdl.ArrayType):
return "{}_array".format(str(instance.element_type).replace(".", "_").replace(" ", "_"))
else:
return "{}_{}_{}".format(
instance.full_name.replace(".", "_"),
instance.version[0],
instance.version[1],
)
[docs]def filter_url_from_type(instance: pydsdl.Any) -> str:
root_ns = instance.root_namespace
tag_id = "{}_{}_{}".format(instance.full_name.replace(".", "_"), instance.version[0], instance.version[1])
return "../{}/#{}".format(root_ns, tag_id)
[docs]@template_volatile_filter
def filter_make_unique(_: typing.Any, base_token: str) -> str:
"""
Filter that takes a base token and forms a name that is very
likely to be unique within the template the filter is invoked.
.. IMPORTANT::
The exact tokens generated may change between major or minor versions
of this library. The only guarantee provided is that the tokens
will be stable for the same version of this library given the same
input.
Also note that name uniqueness is only likely within a given template.
Between templates there is no guarantee of uniqueness and,
since this library does not lex generated source, there is no guarantee
that the generated name does not conflict with a name generated by
another means.
.. invisible-code-block: python
from nunavut.lang.html import filter_make_unique
from nunavut.lang._common import UniqueNameGenerator
.. code-block:: python
# Given
template = '{{ "foo" | make_unique }},{{ "Foo" | make_unique }},'
template += '{{ "fOO" | make_unique }}'
# then
rendered = 'foo0,foo1,fOO0'
.. invisible-code-block: python
UniqueNameGenerator.reset()
jinja_filter_tester(filter_make_unique, template, rendered, 'html')
.. code-block:: python
# Given
template = '{{ "coffee > tea" | make_unique }}'
# then
rendered = 'coffee > tea0'
.. invisible-code-block: python
UniqueNameGenerator.reset()
jinja_filter_tester(filter_make_unique, template, rendered, 'html')
:param str base_token: A token to include in the base name.
:return: A name that is likely to be unique within the file generated by the current
template.
"""
if len(base_token) > 0:
adj_base_token = base_token[0:1].lower() + base_token[1:]
else:
adj_base_token = base_token
escaped_base_token = html.escape(adj_base_token)
return UniqueNameGenerator.get_instance()("html", escaped_base_token, "", "")
[docs]def filter_namespace_doc(ns: nunavut.Namespace) -> str:
result = ""
for t, _ in ns.get_nested_types():
if t.short_name == "_":
result = t.doc
break
return result
[docs]def filter_display_type(instance: pydsdl.Any) -> str:
# TODO: this whole thing needs to be in the template.
if isinstance(instance, pydsdl.FixedLengthArrayType):
capacity = '<span style="color: green">[{}]</span>'.format(instance.capacity)
return filter_display_type(instance.element_type) + capacity
elif isinstance(instance, pydsdl.VariableLengthArrayType):
capacity = '<span style="color: green">[<={}]</span>'.format(instance.capacity)
return filter_display_type(instance.element_type) + capacity
elif isinstance(instance, pydsdl.PaddingField):
return '<span style="color: gray">{}</span>'.format(instance)
elif isinstance(instance, pydsdl.Field):
return "{} {}".format(filter_display_type(instance.data_type), instance.name)
elif isinstance(instance, pydsdl.Constant):
name = '<span style="color: darkmagenta">{}</span>'.format(instance.name)
value = '<span style="color: darkcyan">{}</span>'.format(instance.value)
return "{} {} = {}".format(filter_display_type(instance.data_type), name, value)
elif isinstance(instance, pydsdl.PrimitiveType):
if instance.cast_mode == instance.cast_mode.SATURATED:
is_saturated = '<span style="color: gray">saturated</span> '
else:
is_saturated = '<span style="color: orange">truncated</span> '
type_name = '<span style="color: green">{}</span>'.format(str(instance).split()[-1])
return "{}{}".format(is_saturated, type_name)
else:
return str(instance)
def _natural_sort(instance: typing.List[pydsdl.Any], key: typing.Callable = lambda s: s) -> typing.List[pydsdl.Any]:
def natural_sort_key(s: str, _nsre: typing.Pattern = re.compile("([0-9]+)")) -> typing.List[pydsdl.Any]:
_key = key(s)
return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(_key)]
return sorted(instance, key=natural_sort_key)
[docs]def filter_natural_sort_namespace(instance: typing.List[pydsdl.Any]) -> typing.List[pydsdl.Any]:
"""
Namespaces come in plain lists; sort by name only.
"""
return _natural_sort(instance, key=lambda s: s.full_name)
[docs]def filter_natural_sort_type(instance: pydsdl.Any) -> typing.List[pydsdl.Any]:
"""
Types come in tuples (type, path). Sort by type name.
"""
return _natural_sort(instance, key=lambda s: s[0].full_name)