summaryrefslogblamecommitdiffstats
path: root/rtemsspec/applconfig.py
blob: 830b3222bed06b46c29a34a55dd4743f9eb9a979 (plain) (tree)

























                                                                               
           
                                            
 
                                                                 
                                              

                                                                              
              
 
                         
 

                                                                   


                               

                                                                        
 
 
                          
              
                                 



                                                                                   


 





                                                                            

                                                                 

                              



                                                                    

                                                             
                                                          

















































                                                                          

                                                   

 

























































                                                                        
                                                           
                                                
                                      
                                                                      


                                                                       
                                                                   




                                                               
                                                                   



                                                     
                                                                



                                                


                                                                              
                        
                                                                        

 


                                                                              
                        
                                                                     

 


                                                                              



                                                                 


                                                                           
                        



                                                                           

 
                                                                   

                                                                   


                                                               
             
                                                                   

 
                                                                       
                                                    
                                                         
                                 
                            
                  





                                                                   


                                                                         

                                             
                                                        


                                                                      
                                                                       



                                                             


                                              
                                                

                                                     

 
                                                                          
                                                                
                                         

                                                                      
                                                                       
                                       

 


                                        

                                                    


 
                                                                               
                                                          
                                                                              
                                                                  
                                  
                           
                                                             
                                                       
                                                     
                                                           
                                                                   

                                                                               
                                        

 




































                                                                               



                                                              
                                                                
                                            

 

                                                            

 















                                                                          






                                                                               
                                                                            


                                                                             



                                                                           
                                                              


                                                                
                                                            

 































































                                                                               















                                                                          








                                                                               
                                                                  





                                                                             

                                                              


                                                                
                                                            

 








                                                                              

                                             


                                                            
                                                                 


                                                                    
                                         

                                                          
                                     


                                                                        
                                                             

                                                    



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

# Copyright (C) 2019, 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 copy
from typing import Any, Dict, List, Optional

from rtemsspec.content import CContent, get_value_double_colon, \
    get_value_doxygen_function, get_value_hash
from rtemsspec.sphinxcontent import SphinxContent, SphinxMapper
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, name: str, description: str) -> None:
        """ Adds an option group. """
        self.content.add_automatically_generated_warning()
        self.content.add_header(name, level=2)
        self.content.add(description)

    def add_option(self, name: str, index_entries: List[str]) -> None:
        """ Adds an option. """
        self.content.add_index_entries([name] + index_entries)
        self.content.add_label(name)
        self.content.add_header(name, level=3)
        self.content.add_definition_item("CONSTANT:", f"``{name}``")

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

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

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

    def add_option_value_constraints(self, lines: List[str]) -> None:
        """ Adds a option value constraints. """
        self.content.add_definition_item("VALUE CONSTRAINTS:", lines)

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

    def add_option_notes(self, notes: Optional[str]) -> None:
        """ Adds option notes. """
        if not notes:
            notes = "None."
        self.content.add_definition_item("NOTES:", notes)

    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._value_constraints = []  # type: List[str]
        self._description = ""

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

    def add_option(self, name: str, _index_entries: List[str]) -> None:
        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_value_constraints(self, lines: List[str]) -> None:
        self._value_constraints = lines

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

    def add_option_notes(self, notes: Optional[str]) -> None:
        self.content.add_brief_description(self._option_type)
        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("Value Constraints",
                                   self._value_constraints)
        self.content.add_paragraph("Notes", 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 _generate_min_max(lines: List[str], value: str, word: str) -> None:
    lines.append("The value of this configuration option shall be "
                 f"{word} than or equal to {value}.")


def _generate_set(lines: List[str], values: List[Any]) -> None:
    value_set = "{" + ", ".join([str(x) for x in values]) + "}"
    lines.append("The value of this configuration option shall be")
    lines.append(f"an element of {value_set}.")


def _start_constraint_list(lines: List[str]) -> None:
    lines.append("The value of this configuration option shall "
                 "satisfy all of the following")
    lines.append("constraints:")


def _generate_item_min(lines: List[str], constraints: Dict[str, Any]) -> None:
    if "min" in constraints:
        value = constraints["min"]
        lines.append("")
        lines.append(f"* It shall be greater than or equal to {value}.")


def _generate_item_max(lines: List[str], constraints: Dict[str, Any]) -> None:
    if "max" in constraints:
        value = constraints["max"]
        lines.append("")
        lines.append(f"* It shall be less than or equal to {value}.")


def _generate_item_set(lines: List[str], constraints: Dict[str, Any]) -> None:
    if "set" in constraints:
        value_set = constraints["set"]
        lines.append("")
        lines.append(f"* It shall be an element of {value_set}.")


def _generate_item_texts(lines: List[str], constraints: Dict[str,
                                                             Any]) -> None:
    for text in constraints.get("texts", []):
        lines.append("")
        text = text.replace("The value of this configuration option", "It")
        text = text.strip().split("\n")
        lines.append(f"* {text[0]}")
        lines.extend([f"  {x}" if x else "" for x in text[1:]])


def _resolve_constraint_links(content: _ContentAdaptor, item: Item,
                              constraints: Dict[str, Any]) -> None:
    texts = []  # type: List[str]
    for parent in item.parents("constraint"):
        content.register_license_and_copyrights_of_item(parent)
        texts.append(parent["text"])
    if texts:
        constraints.setdefault("texts", []).extend(reversed(texts))


def _generate_constraint(content: _ContentAdaptor, item: Item) -> None:
    constraints = copy.deepcopy(item["constraints"])
    _resolve_constraint_links(content, item, constraints)
    lines = []  # type: List[str]
    count = len(constraints)
    if count == 1:
        if "min" in constraints:
            _generate_min_max(lines, constraints["min"], "greater")
        elif "max" in constraints:
            _generate_min_max(lines, constraints["max"], "less")
        elif "set" in constraints:
            _generate_set(lines, constraints["set"])
        elif "texts" in constraints:
            if len(constraints["texts"]) == 1:
                lines.extend(constraints["texts"][0].strip().split("\n"))
            else:
                _start_constraint_list(lines)
                _generate_item_texts(lines, constraints)
    elif count == 2 and "min" in constraints and "max" in constraints:
        minimum = constraints["min"]
        maximum = constraints["max"]
        lines.append("The value of this configuration option shall be "
                     f"greater than or equal to {minimum}")
        lines.append(f"and less than or equal to {maximum}.")
    else:
        _start_constraint_list(lines)
        _generate_item_min(lines, constraints)
        _generate_item_max(lines, constraints)
        _generate_item_set(lines, constraints)
        _generate_item_texts(lines, constraints)
    content.add_option_value_constraints(
        [content.substitute(line) for line in lines])


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))
    _generate_constraint(content, item)


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


def _generate(group: Item, options: ItemMap, content: _ContentAdaptor) -> None:
    content.register_license_and_copyrights_of_item(group)
    content.add_group(group["name"], content.substitute(group["description"]))
    for item in sorted(options.values(), key=lambda x: x["name"]):
        content.mapper.item = item
        name = item["name"]
        content.register_license_and_copyrights_of_item(item)
        content.add_option(name, item["index-entries"])
        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"]))
    content.add_licence_and_copyrights()


def _get_value_none(_ctx: ItemGetValueContext) -> Any:
    return None


def _sphinx_ref(ref: str) -> str:
    return f":ref:`{ref}`"


_PTHREAD_NAME_NP = "http://man7.org/linux/man-pages/man3/" \
    "pthread_setname_np.3.html"

_SPHINX_DOC_REFS = {
    "config-scheduler-clustered":
    _sphinx_ref("ConfigurationSchedulersClustered"),
    "config-scheduler-table": _sphinx_ref("ConfigurationSchedulerTable"),
    "config-unlimited-objects": _sphinx_ref("ConfigUnlimitedObjects"),
    "mp-proxies": _sphinx_ref("MPCIProxies"),
    "mrsp": _sphinx_ref("MrsP"),
    "pthread-setname-np": f"`PTHREAD_SETNAME_NP(3) <{_PTHREAD_NAME_NP}>`_",
    "scheduler-cbs": _sphinx_ref("SchedulerCBS"),
    "scheduler-concepts": _sphinx_ref("SchedulingConcepts"),
    "scheduler-edf": _sphinx_ref("SchedulerEDF"),
    "scheduler-priority": _sphinx_ref("SchedulerPriority"),
    "scheduler-priority-simple": _sphinx_ref("SchedulerPrioritySimple"),
    "scheduler-smp-edf": _sphinx_ref("SchedulerSMPEDF"),
    "scheduler-smp-priority-affinity":
    _sphinx_ref("SchedulerSMPPriorityAffinity"),
    "scheduler-smp-priority": _sphinx_ref("SchedulerSMPPriority"),
    "scheduler-smp-priority-simple": _sphinx_ref("SchedulerSMPPrioritySimple"),
    "terminate": _sphinx_ref("Terminate"),
}


def _get_value_sphinx_reference(ctx: ItemGetValueContext) -> Any:
    return _SPHINX_DOC_REFS[ctx.key]


def _get_value_sphinx_define(ctx: ItemGetValueContext) -> Any:
    return f":c:macro:`{ctx.value[ctx.key]}`"


def _get_value_sphinx_function(ctx: ItemGetValueContext) -> Any:
    return f":c:func:`{ctx.value[ctx.key]}`"


def _get_value_sphinx_type(ctx: ItemGetValueContext) -> Any:
    return f":c:type:`{ctx.value[ctx.key]}`"


def _get_value_sphinx_url(ctx: ItemGetValueContext) -> Any:
    return f"`{ctx.value[ctx.key]} <{ctx.item['reference']}>`_"


def _get_value_sphinx_unspecified_define(ctx: ItemGetValueContext) -> Any:
    if ctx.item["reference"]:
        return _get_value_sphinx_url(ctx)
    return _get_value_sphinx_define(ctx)


def _get_value_sphinx_unspecified_type(ctx: ItemGetValueContext) -> Any:
    if ctx.item["reference"]:
        return _get_value_sphinx_url(ctx)
    return _get_value_sphinx_type(ctx)


def _add_sphinx_get_values(mapper: ItemMapper) -> None:
    for key in _SPHINX_DOC_REFS:
        for opt in ["feature-enable", "feature", "initializer", "integer"]:
            doc_ref = f"interface/appl-config-option/{opt}:/document-reference"
            mapper.add_get_value(doc_ref, _get_value_none)
            mapper.add_get_value(f"{doc_ref}/{key}",
                                 _get_value_sphinx_reference)
    mapper.add_get_value("interface/define:/name", _get_value_sphinx_define)
    mapper.add_get_value("interface/function:/name",
                         _get_value_sphinx_function)
    mapper.add_get_value("interface/macro:/name", _get_value_sphinx_function)
    mapper.add_get_value("interface/struct:/name", _get_value_sphinx_type)
    mapper.add_get_value("interface/typedef:/name", _get_value_sphinx_type)
    mapper.add_get_value("interface/union:/name", _get_value_sphinx_type)
    mapper.add_get_value("interface/unspecified-define:/name",
                         _get_value_sphinx_unspecified_define)
    mapper.add_get_value("interface/unspecified-function:/name",
                         _get_value_sphinx_function)
    mapper.add_get_value("interface/unspecified-type:/name",
                         _get_value_sphinx_unspecified_type)


def _c_user_ref(ref: str, name: str) -> str:
    c_user = "https://docs.rtems.org/branches/master/c-user/"
    return f"<a href={c_user}{ref}>{name}</a>"


_DOXYGEN_DOC_REFS = {
    "config-scheduler-clustered":
    _c_user_ref("config/scheduler-clustered.html",
                "Clustered Scheduler Configuration"),
    "config-scheduler-table":
    _c_user_ref(
        "config/scheduler-clustered.html#configuration-step-3-scheduler-table",
        "Configuration Step 3 - Scheduler Table"),
    "config-unlimited-objects":
    _c_user_ref("config/intro.html#unlimited-objects", "Unlimited Objects"),
    "mp-proxies":
    _c_user_ref("multiprocessing.html#proxies", "Proxies"),
    "mrsp":
    _c_user_ref(
        "key_concepts.html#multiprocessor-resource-sharing-protocol-mrsp",
        "Multiprocessor Resource Sharing Protocol (MrsP)"),
    "pthread-setname-np":
    f"<a href={_PTHREAD_NAME_NP}>PTHREAD_SETNAME_NP(3)</a>",
    "scheduler-cbs":
    _c_user_ref(
        "scheduling_concepts.html#constant-bandwidth-server-scheduling-cbs",
        "Constant Bandwidth Server Scheduling (CBS)"),
    "scheduler-concepts":
    _c_user_ref("scheduling_concepts.html", "Scheduling Concepts"),
    "scheduler-edf":
    _c_user_ref("scheduling_concepts.html#earliest-deadline-first-scheduler",
                "Earliest Deadline First Scheduler"),
    "scheduler-priority":
    _c_user_ref("scheduling_concepts.html#deterministic-priority-scheduler",
                "Deterministic Priority Scheduler"),
    "scheduler-priority-simple":
    _c_user_ref("scheduling_concepts.html#simple-priority-scheduler",
                "Simple Priority Scheduler"),
    "scheduler-smp-edf":
    _c_user_ref(
        "scheduling_concepts.html#earliest-deadline-first-smp-scheduler",
        "Earliest Deadline First SMP Scheduler"),
    "scheduler-smp-priority-affinity":
    _c_user_ref(
        "scheduling_concepts.html"
        "#arbitrary-processor-affinity-priority-smp-scheduler",
        "Arbitrary Processor Affinity Priority SMP Scheduler"),
    "scheduler-smp-priority":
    _c_user_ref(
        "scheduling_concepts.html#deterministic-priority-smp-scheduler",
        "Deterministic Priority SMP Scheduler"),
    "scheduler-smp-priority-simple":
    _c_user_ref("scheduling_concepts.html#simple-priority-smp-scheduler",
                "Simple Priority SMP Scheduler"),
    "terminate":
    _c_user_ref("fatal_error.html#announcing-a-fatal-error",
                "Announcing a Fatal Error"),
}


def _get_value_doxygen_reference(ctx: ItemGetValueContext) -> Any:
    return _DOXYGEN_DOC_REFS[ctx.key]


def _get_value_doxygen_url(ctx: ItemGetValueContext) -> Any:
    return f"<a href=\"{ctx.item['reference']}\">{ctx.value[ctx.key]}</a>"


def _get_value_doxygen_unspecfied_define(ctx: ItemGetValueContext) -> Any:
    if ctx.item["reference"]:
        return _get_value_doxygen_url(ctx)
    return get_value_hash(ctx)


def _get_value_doxygen_unspecfied_type(ctx: ItemGetValueContext) -> Any:
    if ctx.item["reference"]:
        return _get_value_doxygen_url(ctx)
    return get_value_double_colon(ctx)


def _add_doxygen_get_values(mapper: ItemMapper) -> None:
    for key in _DOXYGEN_DOC_REFS:
        for opt in ["feature-enable", "feature", "initializer", "integer"]:
            doc_ref = f"interface/appl-config-option/{opt}:/document-reference"
            mapper.add_get_value(doc_ref, _get_value_none)
            mapper.add_get_value(f"{doc_ref}/{key}",
                                 _get_value_doxygen_reference)
            name = f"interface/appl-config-option/{opt}:/name"
            mapper.add_get_value(name, get_value_hash)
    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/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_unspecfied_define)
    mapper.add_get_value("interface/unspecified-function:/name",
                         get_value_doxygen_function)
    mapper.add_get_value("interface/unspecified-type:/name",
                         _get_value_doxygen_unspecfied_type)


def generate(config: dict, 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 = SphinxMapper(EmptyItem())
    _add_sphinx_get_values(sphinx_mapper)
    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 = {}  # type: ItemMap
        for child in group.children("appl-config-group-member"):
            assert child.type.startswith("interface/appl-config-option")
            options[child.uid] = child
        sphinx_content = _SphinxContentAdaptor(sphinx_mapper)
        _generate(group, options, sphinx_content)
        sphinx_content.write(group_config["target"])
        _generate(group, options, doxygen_content)
    doxygen_content.content.prepend_copyrights_and_licenses()
    doxygen_content.content.prepend_spdx_license_identifier()
    doxygen_content.write(config["doxygen-target"])