summaryrefslogblamecommitdiffstats
path: root/rtemsspec/applconfig.py
blob: d5b027a77b5500de22340051317e7da2d7e3f601 (plain) (tree)
1
2
3
4


                                                                         
                                                        





















                                                                             
                                                      
 
                                                                          
                                                          

                                                                   

                                                                    
                                                                              
              
 
                         
 

                                                                   


                               

                                                                        
 
 
                          
              
                                 

                     

                                                                              
           


 





                                                                            
 

                                                                 

                              



                                                                    
                                                                       
                                     
                                                          
                                                         


                                              





                                                
                                           




                                   

                                                     
                               
                                                         

                                                    


                                                              
                                                   


                                                        
                                                    


                                                           
                                                


                                                             
                                                         


                                                               
                                                    
 
                                                   
                                  




                                                               














                                                                          
 

                                                   

 











                                                    
                        

                              
                                                                       
                                                              
                                                            




                                                           


                                                            











                                                             


                                                               



                                                               
                                                             
                                                 



                                                                        

                                                        







                                                    
                                                           
                                                
                                      
                                                                      

 

                                                          
                               
                                             

                                          
                                                               


                                                                            


                      


                                                               

                                                        


                                                       
                                           
                                               

 
                                                                          
                                                                
                                         

                                                                      
                                                                       

 


                                        

                                                    


 




















                                                                           

                                                                
                                                          

                                                               
                                                                  
                                  
                                                             

                                                                         
                                        

 







                                                            

                                      
                                                                          
               

 
                                                                           
                                                             

 




                                                                             
                                                                          
                                                            

 
                                                                        
                                                                     

 
                                                        
                                                                       
                                                          
                                                         
                                                                   
                                                                  

                                                    
                                                                          
                                                                              



                                                                             
                                                              
                                                               
                                                                
                                                                 

                                                              






                                                               
                                                            

 

                                                 







                                                                              
                                                                  


                                                            
                                                                 


                                                                    
                                         

                                                          
                             


                                                                     
                                                             

                                                                  
                                                    
                                                                            
                                                             




                                                                      

                                                             
# SPDX-License-Identifier: BSD-2-Clause
""" Functions for application configuration documentation generation. """

# Copyright (C) 2019, 2020 embedded brains GmbH & Co. KG
#
# 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.

from typing import Any, Callable, Dict, List, Optional

from rtemsspec.content import Content, CContent, get_value_double_colon, \
    get_value_doxygen_function, get_value_doxygen_group, \
    get_value_doxygen_ref, get_value_hash, get_value_header_file, \
    get_value_plural
from rtemsspec.sphinxcontent import GenericContent, SphinxContent, \
    SphinxInterfaceMapper
from rtemsspec.items import EmptyItem, Item, ItemCache, ItemGetValueContext, \
    ItemMapper

ItemMap = Dict[str, Item]

_FEATURE = "This configuration option is a boolean feature define."

_OPTION_TYPES = {
    "feature": _FEATURE,
    "feature-enable": _FEATURE,
    "integer": "This configuration option is an integer define.",
    "initializer": "This configuration option is an initializer define."
}

_OPTION_DEFAULT_CONFIG = {
    "feature":
    lambda item: item["default"],
    "feature-enable":
    lambda item:
    """If this configuration option is undefined, then the described feature \
is not
enabled."""
}


class _ContentAdaptor:
    """
    The content adaptor provides a specialized interface to a content class.

    By default, Sphinx content is generated.
    """

    def __init__(self, mapper: ItemMapper, content: Any) -> None:
        self.mapper = mapper
        self.content = content

    def substitute(self, text: Optional[str]) -> str:
        """ Substitutes the optional text using the item mapper. """
        return self.mapper.substitute(text)

    def add_group(self, uid: str, name: str, description: str) -> None:
        """ Adds an option group. """
        self.content.add_automatically_generated_warning()
        self.content.add(f".. Generated from spec:{uid}")
        self.content.add_header(name, level=2)
        self.content.add(description)

    def _add_rubric(self,
                    name: str,
                    text: GenericContent,
                    wrap: bool = False) -> None:
        if not text:
            return
        self.content.add_rubric(f"{name}:")
        if wrap:
            self.content.wrap(text)
        else:
            self.content.add(text)

    def add_option(self, uid: str, name: str,
                   index_entries: List[str]) -> None:
        """ Adds an option. """
        self.content.add(f".. Generated from spec:{uid}")
        with self.content.directive("raw", "latex"):
            self.content.add("\\clearpage")
        self.content.add_index_entries([name] + index_entries)
        self.content.add_label(name)
        self.content.add_header(name, level=3)
        self._add_rubric("CONSTANT", f"``{name}``")

    def add_option_type(self, option_type: str) -> None:
        """ Adds an option type. """
        self._add_rubric("OPTION TYPE", option_type)

    def add_option_default_value(self, value: str) -> None:
        """ Adds an option default value. """
        self._add_rubric("DEFAULT VALUE", value)

    def add_option_default_config(self, config: str) -> None:
        """ Adds an option default configuration. """
        self._add_rubric("DEFAULT CONFIGURATION", config)

    def add_option_description(self, description: str) -> None:
        """ Adds a option description. """
        self._add_rubric("DESCRIPTION", description)

    def add_option_notes(self, notes: str) -> None:
        """ Adds option notes. """
        self._add_rubric("NOTES", notes)

    def add_option_constraints(self, lines: List[str]) -> None:
        """ Adds a option value constraints. """
        self._add_rubric("CONSTRAINTS", lines, wrap=True)

    def add_licence_and_copyrights(self) -> None:
        """ Adds the license and copyrights. """
        self.content.add_licence_and_copyrights()

    def register_license_and_copyrights_of_item(self, item: Item) -> None:
        """ Registers the license and copyrights of the item. """
        self.content.register_license_and_copyrights_of_item(item)

    def write(self, filename: str):
        """ Writes the content to the file specified by the path. """
        self.content.write(filename)


class _SphinxContentAdaptor(_ContentAdaptor):

    def __init__(self, mapper: ItemMapper) -> None:
        super().__init__(mapper, SphinxContent())


class _DoxygenContentAdaptor(_ContentAdaptor):
    # pylint: disable=attribute-defined-outside-init

    def __init__(self, mapper: ItemMapper) -> None:
        super().__init__(mapper, CContent())
        self._reset()

    def _reset(self) -> None:
        self._name = ""
        self._option_type = ""
        self._default_value = ""
        self._default_config = ""
        self._notes = ""
        self._description = ""

    def add_group(self, uid: str, name: str, description: str) -> None:
        identifier = f"RTEMSApplConfig{name.replace(' ', '')}"
        self.content.add(f"/* Generated from spec:{uid} */")
        with self.content.defgroup_block(identifier, name):
            self.content.add("@ingroup RTEMSApplConfig")
            self.content.doxyfy(description)
            self.content.add("@{")

    def add_option(self, uid: str, name: str,
                   _index_entries: List[str]) -> None:
        self.content.add(f"/* Generated from spec:{uid} */")
        self.content.open_doxygen_block()
        self._name = name

    def add_option_type(self, option_type: str) -> None:
        self._option_type = option_type

    def add_option_default_value(self, value: str) -> None:
        self._default_value = value

    def add_option_default_config(self, config: str) -> None:
        self._default_config = config

    def add_option_description(self, description: str) -> None:
        self._description = description

    def add_option_notes(self, notes: str) -> None:
        self._notes = notes

    def add_option_constraints(self, lines: List[str]) -> None:
        self.content.add_brief_description(self._option_type)
        self.content.add(f"@anchor {self._name}")
        self.content.doxyfy(self._description)
        self.content.add_paragraph("Default Value", self._default_value)
        self.content.add_paragraph("Default Configuration",
                                   self._default_config)
        self.content.add_paragraph("Constraints", lines)
        self.content.add_paragraph("Notes", self._notes)
        self.content.close_comment_block()
        self.content.append(f"#define {self._name}")
        self._reset()

    def add_licence_and_copyrights(self) -> None:
        self.content.add("/** @} */")


def _generate_feature(content: _ContentAdaptor, item: Item,
                      option_type: str) -> None:
    content.add_option_default_config(
        content.substitute(_OPTION_DEFAULT_CONFIG[option_type](item)))


def _get_constraints(content: _ContentAdaptor, item: Item,
                     enabled: List[str]) -> List[str]:
    constraints: List[str] = []
    for parent in item.parents("constraint"):
        if not parent.is_enabled(enabled):
            continue
        content.register_license_and_copyrights_of_item(parent)
        constraints.append(
            content.substitute(parent["text"]).replace(
                "application configuration option", "configuration option"))
    return constraints


def _generate_constraints(content: _ContentAdaptor, item: Item,
                          enabled: List[str]) -> None:
    constraints = _get_constraints(content, item, enabled)
    if len(constraints) > 1:
        constraint_list = Content("BSD-2-Clause", False)
        prologue = ("The following constraints apply "
                    "to this configuration option:")
        constraint_list.add_list(constraints, prologue)
        constraints = constraint_list.lines
    content.add_option_constraints(constraints)


def _generate_initializer_or_integer(content: _ContentAdaptor, item: Item,
                                     _option_type: str) -> None:
    default_value = item["default-value"]
    if not isinstance(default_value, str) or " " not in default_value:
        default_value = f"The default value is {default_value}."
    content.add_option_default_value(content.substitute(default_value))


_OPTION_GENERATORS = {
    "feature": _generate_feature,
    "feature-enable": _generate_feature,
    "initializer": _generate_initializer_or_integer,
    "integer": _generate_initializer_or_integer
}


def _document_option(item: Item, enabled: List[str],
                     content: _ContentAdaptor) -> None:
    option_type = item["appl-config-option-type"]
    content.add_option_type(_OPTION_TYPES[option_type])
    _OPTION_GENERATORS[option_type](content, item, option_type)
    content.add_option_description(content.substitute(item["description"]))
    content.add_option_notes(content.substitute(item["notes"]))
    _generate_constraints(content, item, enabled)


def document_option(item: Item, enabled: List[str],
                    mapper: ItemMapper) -> SphinxContent:
    """
    Documents the option specified by the item using the item mapper and
    enabled set.
    """
    adaptor = _SphinxContentAdaptor(mapper)
    _document_option(item, enabled, adaptor)
    return adaptor.content


def _generate(group: Item, options: ItemMap, enabled: List[str],
              content: _ContentAdaptor) -> None:
    content.register_license_and_copyrights_of_item(group)
    content.add_group(group.uid, group["name"],
                      content.substitute(group["description"]))
    for item in sorted(options.values(), key=lambda x: x["name"]):
        content.mapper.item = item
        content.register_license_and_copyrights_of_item(item)
        content.add_option(item.uid, item["name"], item["index-entries"])
        _document_option(item, enabled, content)
    content.add_licence_and_copyrights()


def _get_value(ctx: ItemGetValueContext) -> str:
    return ctx.value[ctx.key]


def _get_value_doxygen_url(
    ctx: ItemGetValueContext,
    get_value: Callable[[ItemGetValueContext],
                        str] = _get_value) -> Optional[str]:
    for ref in ctx.item["references"]:
        if ref["type"] == "url":
            return f"<a href=\"{ref['identifier']}\">{get_value(ctx)}</a>"
    return None


def _get_value_doxygen_unspecified_define(ctx: ItemGetValueContext) -> Any:
    return _get_value_doxygen_url(ctx) or get_value_hash(ctx)


def _get_value_doxygen_unspecified_function(ctx: ItemGetValueContext) -> Any:
    return _get_value_doxygen_url(
        ctx, get_value_doxygen_function) or get_value_doxygen_function(ctx)


def _get_value_doxygen_unspecified_group(ctx: ItemGetValueContext) -> Any:
    return _get_value_doxygen_url(ctx) or ctx.value[ctx.key]


def _get_value_doxygen_unspecfied_type(ctx: ItemGetValueContext) -> Any:
    return _get_value_doxygen_url(ctx) or get_value_double_colon(ctx)


def _add_doxygen_get_values(mapper: ItemMapper) -> None:
    for opt in ["feature-enable", "feature", "initializer", "integer"]:
        name = f"interface/appl-config-option/{opt}:/name"
        mapper.add_get_value(name, get_value_doxygen_ref)
    mapper.add_get_value("glossary/term:/plural", get_value_plural)
    mapper.add_get_value("interface/define:/name", get_value_hash)
    mapper.add_get_value("interface/function:/name",
                         get_value_doxygen_function)
    mapper.add_get_value("interface/group:/name", get_value_doxygen_group)
    mapper.add_get_value("interface/header-file:/path", get_value_header_file)
    mapper.add_get_value("interface/macro:/name", get_value_doxygen_function)
    mapper.add_get_value("interface/struct:/name", get_value_double_colon)
    mapper.add_get_value("interface/typedef:/name", get_value_double_colon)
    mapper.add_get_value("interface/union:/name", get_value_double_colon)
    mapper.add_get_value("interface/unspecified-define:/name",
                         _get_value_doxygen_unspecified_define)
    mapper.add_get_value("interface/unspecified-function:/name",
                         _get_value_doxygen_unspecified_function)
    mapper.add_get_value("interface/unspecified-group:/name",
                         _get_value_doxygen_unspecified_group)
    mapper.add_get_value("interface/unspecified-enum:/name",
                         _get_value_doxygen_unspecfied_type)
    mapper.add_get_value("interface/unspecified-struct:/name",
                         _get_value_doxygen_unspecfied_type)
    mapper.add_get_value("interface/unspecified-typedef:/name",
                         _get_value_doxygen_unspecfied_type)
    mapper.add_get_value("interface/unspecified-union:/name",
                         _get_value_doxygen_unspecfied_type)


def generate(config: dict, group_uids: List[str],
             item_cache: ItemCache) -> None:
    """
    Generates application configuration documentation sources according to the
    configuration.

    :param config: A dictionary with configuration entries.
    :param item_cache: The specification item cache containing the application
                       configuration groups and options.
    """
    sphinx_mapper = SphinxInterfaceMapper(EmptyItem(), group_uids)
    doxygen_mapper = ItemMapper(EmptyItem())
    _add_doxygen_get_values(doxygen_mapper)
    doxygen_content = _DoxygenContentAdaptor(doxygen_mapper)
    doxygen_content.content.add_automatically_generated_warning()
    with doxygen_content.content.defgroup_block(
            "RTEMSApplConfig", "Application Configuration Options"):
        doxygen_content.content.add("@ingroup RTEMSAPI")
    for group_config in config["groups"]:
        group = item_cache[group_config["uid"]]
        assert group.type == "interface/appl-config-group"
        options: ItemMap = {}
        for child in group.children("interface-ingroup"):
            if child.type.startswith("interface/appl-config-option"):
                options[child.uid] = child
        sphinx_content = _SphinxContentAdaptor(sphinx_mapper)
        _generate(group, options, config["enabled-documentation"],
                  sphinx_content)
        sphinx_content.write(group_config["target"])
        _generate(group, options, config["enabled-source"], doxygen_content)
    doxygen_content.content.prepend_copyrights_and_licenses()
    doxygen_content.content.prepend([
        "/**", " * @file", " *", " * @ingroup RTEMSImplDoxygen", " *",
        " * @brief This header file documents "
        "the application configuration options.", " */", ""
    ])
    doxygen_content.content.prepend_spdx_license_identifier()
    doxygen_content.write(config["doxygen-target"])