# 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, Callable, Dict, List, Tuple
from rtemsspec.content import CContent, enabled_by_to_exp, ExpressionMapper
from rtemsspec.sphinxcontent import get_label, get_reference, SphinxContent, \
SphinxMapper
from rtemsspec.items import Item, ItemCache, ItemGetValueContext, ItemMapper
ItemMap = Dict[str, Item]
AddDefinition = Callable[[CContent, ItemMapper, Item, Dict[str, Any]], None]
INTERFACE = "Interface"
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_value_function(ctx: ItemGetValueContext) -> Any:
return _get_reference(ctx.value[ctx.key])
class _Mapper(SphinxMapper):
def __init__(self, item: Item):
super().__init__(item)
self.add_get_value("interface/function:/name", _get_value_function)
def _generate_introduction(target: str, group: Item,
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
content.wrap(group["brief"])
content.wrap(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"]
brief = 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:
name = item["name"]
ret = mapper.substitute(value["return"])
params = [mapper.substitute(param) for param in value["params"]]
content.declare_function(ret, name, params)
def _add_definition(content: CContent, mapper: ItemMapper, item: Item,
prefix: str, value: Dict[str, Any],
add_definition: AddDefinition) -> None:
# pylint: disable=too-many-arguments
assert item.type == "interface/function"
default = value["default"]
variants = value["variants"]
if variants:
ifelse = "#if "
with mapper.prefix(os.path.join(prefix, "variants")):
for variant in variants:
enabled_by = enabled_by_to_exp(variant["enabled-by"],
ExpressionMapper())
content.append(f"{ifelse}{enabled_by}")
with content.indent():
add_definition(content, mapper, item,
variant["definition"])
ifelse = "#elif "
if default is not None:
content.append("#else")
with mapper.prefix(os.path.join(prefix, "default")):
with content.indent():
add_definition(content, mapper, item, default)
content.append("#endif")
else:
with mapper.prefix(os.path.join(prefix, "default")):
add_definition(content, mapper, item, default)
def _generate_directive(content: SphinxContent, mapper: _Mapper,
code_mapper: _CodeMapper, item: Item) -> None:
content.wrap(item["brief"])
content.add(".. rubric:: CALLING SEQUENCE:")
with content.directive("code-block", "c"):
code = CContent()
_add_definition(code, code_mapper, item, "definition",
item["definition"], _add_function_definition)
content.add(code)
if item["params"]:
content.add(".. rubric:: PARAMETERS:")
for param in item["params"]:
param_name = mapper.substitute(param["name"])
content.add_definition_item(
f"``{param_name}``",
mapper.substitute(f"This parameter {param['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"]))
def _generate_directives(target: str, group: Item, 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)
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.
"""
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 == "interface/function":
items.append(child)
items.sort(key=functools.partial(
_directive_key, list(group.parents("placement-order"))))
_generate_introduction(doc_config["introduction-target"], group, items)
_generate_directives(doc_config["directives-target"], group, items)