# SPDX-License-Identifier: BSD-2-Clause
"""
This module provides functions for the generation of interface documentation.
"""
# Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import functools
import os
from typing import Any, Dict, List, Tuple
from rtemsspec.content import CContent
from rtemsspec.sphinxcontent import get_label, get_reference, SphinxContent, \
SphinxInterfaceMapper
from rtemsspec.items import Item, ItemCache, ItemGetValueContext, ItemMapper
ItemMap = Dict[str, Item]
INTERFACE = "Interface"
def _sanitize_name(name: str) -> str:
return name.lstrip("_")
def _forward_declaration(item: Item) -> str:
target = item.parent("interface-target")
return f"{target['interface-type']} {target['name']}"
def _get_reference(name: str) -> str:
return get_reference(get_label(f"{INTERFACE} {name}"))
def _get_value_forward_declaration(ctx: ItemGetValueContext) -> Any:
return _forward_declaration(ctx.item)
class _CodeMapper(ItemMapper):
def __init__(self, item: Item):
super().__init__(item)
self.add_get_value("interface/forward-declaration:/name",
_get_value_forward_declaration)
def _get_param(ctx: ItemGetValueContext) -> Any:
return f"``{_sanitize_name(ctx.value[ctx.key])}``"
class _Mapper(SphinxInterfaceMapper):
def __init__(self, item: Item, group_uids: List[str]):
super().__init__(item)
self._group_uids = set(group_uids)
self.add_get_value("interface/function:/name", self._get_function)
self.add_get_value("interface/function:/params/name", _get_param)
self.add_get_value("interface/group:/name", self._get_group)
self.add_get_value("interface/macro:/name", self._get_function)
self.add_get_value("interface/macro:/params/name", _get_param)
def _get_function(self, ctx: ItemGetValueContext) -> Any:
name = ctx.value[ctx.key]
for group in ctx.item.parents("interface-ingroup"):
if group.uid in self._group_uids:
return _get_reference(name)
return f":c:func:`{name}`"
def _get_group(self, ctx: ItemGetValueContext) -> Any:
if ctx.item.uid in self._group_uids:
return get_reference(ctx.value["identifier"])
return ctx.value[ctx.key]
def _generate_introduction(target: str, group: Item, group_uids: List[str],
items: List[Item]) -> None:
content = SphinxContent()
content.register_license_and_copyrights_of_item(group)
content.add_automatically_generated_warning()
group_name = group["name"]
content.add(f".. Generated from spec:{group.uid}")
with content.section("Introduction", get_label(group_name)):
# This needs to be in front of the list since comment blocks have an
# effect on the list layout in the HTML output
content.add(".. The following list was generated from:")
for item in items:
content.append(f".. spec:{item.uid}")
content.append("")
content.gap = False
mapper = _Mapper(group, group_uids)
content.wrap(mapper.substitute(group["brief"]))
content.wrap(mapper.substitute(group["description"]))
content.paste(f"The directives provided by the {group_name} are:")
for item in items:
content.register_license_and_copyrights_of_item(item)
name = item["name"]
mapper = _Mapper(item, group_uids)
brief = mapper.substitute(item["brief"])
if brief:
brief = f" - {brief}"
else:
brief = ""
ref = _get_reference(name)
content.add_list_item(f"{ref}{brief}")
content.add_licence_and_copyrights()
content.write(target)
def _add_function_definition(content: CContent, mapper: ItemMapper, item: Item,
value: Dict[str, Any]) -> None:
ret = mapper.substitute(value["return"])
name = item["name"]
params = [
mapper.substitute(_sanitize_name(param)) for param in value["params"]
]
content.declare_function(ret, name, params)
def _add_macro_definition(content: CContent, _mapper: ItemMapper, item: Item,
_value: Dict[str, Any]) -> None:
ret = "#define"
name = item["name"]
params = [_sanitize_name(param["name"]) for param in item["params"]]
content.call_function(ret, name, params, semicolon="")
_ADD_DEFINITION = {
"interface/function": _add_function_definition,
"interface/macro": _add_macro_definition,
}
def _add_definition(content: CContent, mapper: ItemMapper, item: Item,
prefix: str, value: Dict[str, Any]) -> None:
# pylint: disable=too-many-arguments
add_definition = _ADD_DEFINITION[item.type]
key = "default"
definition = value[key]
if not definition:
# Assume that all definitions have the same interface
key = "variants"
definition = value["variants"][0]["definition"]
with mapper.prefix(os.path.join(prefix, key)):
add_definition(content, mapper, item, definition)
def _generate_directive(content: SphinxContent, mapper: _Mapper,
code_mapper: _CodeMapper, item: Item) -> None:
content.wrap(mapper.substitute(item["brief"]))
content.add(".. rubric:: CALLING SEQUENCE:")
with content.directive("code-block", "c"):
code = CContent()
_add_definition(code, code_mapper, item, "definition",
item["definition"])
content.add(code)
if item["params"]:
content.add(".. rubric:: PARAMETERS:")
for param in item["params"]:
description = param["description"]
if description:
content.add_definition_item(
f"``{_sanitize_name(param['name'])}``",
mapper.substitute(f"This parameter {description}"),
wrap=True)
if item["description"]:
content.add(".. rubric:: DESCRIPTION:")
content.wrap(mapper.substitute(item["description"]))
ret = item["return"]
if ret["return"] or ret["return-values"]:
content.add(".. rubric:: RETURN VALUES:")
if ret["return-values"]:
for retval in ret["return-values"]:
if isinstance(retval["value"], str):
value = mapper.substitute(str(retval["value"]))
else:
value = f"``{str(retval['value'])}``"
content.add_definition_item(value,
mapper.substitute(
retval["description"]),
wrap=True)
content.wrap(mapper.substitute(ret["return"]))
if item["notes"]:
content.add(".. rubric:: NOTES:")
content.wrap(mapper.substitute(item["notes"]))
constraints = [
mapper.substitute(parent["text"], parent)
for parent in item.parents("constraint")
]
if constraints:
content.add(".. rubric:: CONSTRAINTS:")
content.add_list(constraints,
"The following constraints apply to this directive:")
def _generate_directives(target: str, group: Item, group_uids: List[str],
items: List[Item]) -> None:
content = SphinxContent()
content.register_license_and_copyrights_of_item(group)
content.add_automatically_generated_warning()
group_name = group["name"]
with content.section("Directives", get_label(group_name)):
content.wrap([
f"This section details the directives of the {group_name}.",
"A subsection is dedicated to each of this manager's directives",
"and lists the calling sequence, parameters, description,",
"return values, and notes of the directive."
])
for item in items:
content.register_license_and_copyrights_of_item(item)
name = item["name"]
code_mapper = _CodeMapper(item)
mapper = _Mapper(item, group_uids)
content.add(f".. Generated from spec:{item.uid}")
with content.directive("raw", "latex"):
content.add("\\clearpage")
directive = f"{name}()"
content.add_index_entries([directive] + item["index-entries"])
with content.section(directive, "Interface"):
_generate_directive(content, mapper, code_mapper, item)
content.add_licence_and_copyrights()
content.write(target)
def _directive_key(order: List[Item], item: Item) -> Tuple[int, str]:
try:
index = order.index(item) - len(order)
except ValueError:
index = 1
return (index, item.uid)
def generate(config: list, item_cache: ItemCache) -> None:
"""
Generates interface documentation according to the configuration.
:param config: A dictionary with configuration entries.
:param item_cache: The specification item cache containing the interfaces.
"""
group_uids = [doc_config["group"] for doc_config in config]
for doc_config in config:
items = [] # type: List[Item]
group = item_cache[doc_config["group"]]
assert group.type == "interface/group"
for child in group.children("interface-ingroup"):
if child.type in ["interface/function", "interface/macro"]:
items.append(child)
items.sort(key=functools.partial(
_directive_key, list(group.parents("placement-order"))))
_generate_introduction(doc_config["introduction-target"], group,
group_uids, items)
_generate_directives(doc_config["directives-target"], group,
group_uids, items)