source: rtems-central/rtemsspec/applconfig.py @ d9fa7d6

Last change on this file since d9fa7d6 was e49c759, checked in by Sebastian Huber <sebastian.huber@…>, on 07/15/20 at 08:04:25

Rename "rtemsqual" in "rtemsspec"

  • Property mode set to 100644
File size: 19.7 KB
Line 
1# SPDX-License-Identifier: BSD-2-Clause
2""" Functions for application configuration documentation generation. """
3
4# Copyright (C) 2019, 2020 embedded brains GmbH (http://www.embedded-brains.de)
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
19# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25# POSSIBILITY OF SUCH DAMAGE.
26
27import copy
28from typing import Any, Dict, List, Optional
29
30from rtemsspec.content import CContent, get_value_double_colon, \
31    get_value_doxygen_function, get_value_hash
32from rtemsspec.sphinxcontent import SphinxContent, SphinxMapper
33from rtemsspec.items import EmptyItem, Item, ItemCache, ItemGetValueContext, \
34    ItemMapper
35
36ItemMap = Dict[str, Item]
37
38_FEATURE = "This configuration option is a boolean feature define."
39
40_OPTION_TYPES = {
41    "feature": _FEATURE,
42    "feature-enable": _FEATURE,
43    "integer": "This configuration option is an integer define.",
44    "initializer": "This configuration option is an initializer define."
45}
46
47_OPTION_DEFAULT_CONFIG = {
48    "feature":
49    lambda item: item["default"],
50    "feature-enable":
51    lambda item:
52    """If this configuration option is undefined, then the described feature is not
53enabled."""
54}
55
56
57class _ContentAdaptor:
58    """
59    The content adaptor provides a specialized interface to a content class.
60
61    By default, Sphinx content is generated.
62    """
63    def __init__(self, mapper: ItemMapper, content: Any) -> None:
64        self.mapper = mapper
65        self.content = content
66
67    def substitute(self, text: Optional[str]) -> str:
68        """ Substitutes the optional text using the item mapper. """
69        return self.mapper.substitute(text)
70
71    def add_group(self, name: str, description: str) -> None:
72        """ Adds an option group. """
73        self.content.add_header(name, level=2)
74        self.content.add(description)
75
76    def add_option(self, name: str, index_entries: List[str]) -> None:
77        """ Adds an option. """
78        self.content.add_index_entries([name] + index_entries)
79        self.content.add_label(name)
80        self.content.add_header(name, level=3)
81        self.content.add_definition_item("CONSTANT:", f"``{name}``")
82
83    def add_option_type(self, option_type: str) -> None:
84        """ Adds an option type. """
85        self.content.add_definition_item("OPTION TYPE:", option_type)
86
87    def add_option_default_value(self, value: str) -> None:
88        """ Adds an option default value. """
89        self.content.add_definition_item("DEFAULT VALUE:", value)
90
91    def add_option_default_config(self, config: str) -> None:
92        """ Adds an option default configuration. """
93        self.content.add_definition_item("DEFAULT CONFIGURATION:", config)
94
95    def add_option_value_constraints(self, lines: List[str]) -> None:
96        """ Adds a option value constraints. """
97        self.content.add_definition_item("VALUE CONSTRAINTS:", lines)
98
99    def add_option_description(self, description: str) -> None:
100        """ Adds a option description. """
101        self.content.add_definition_item("DESCRIPTION:", description)
102
103    def add_option_notes(self, notes: Optional[str]) -> None:
104        """ Adds option notes. """
105        if not notes:
106            notes = "None."
107        self.content.add_definition_item("NOTES:", notes)
108
109    def add_licence_and_copyrights(self) -> None:
110        """ Adds the license and copyrights. """
111        self.content.add_licence_and_copyrights()
112
113    def register_license_and_copyrights_of_item(self, item: Item) -> None:
114        """ Registers the license and copyrights of the item. """
115        self.content.register_license_and_copyrights_of_item(item)
116
117    def write(self, filename: str):
118        """ Writes the content to the file specified by the path. """
119        self.content.write(filename)
120
121
122class _SphinxContentAdaptor(_ContentAdaptor):
123    def __init__(self, mapper: ItemMapper) -> None:
124        super().__init__(mapper, SphinxContent())
125
126
127class _DoxygenContentAdaptor(_ContentAdaptor):
128    # pylint: disable=attribute-defined-outside-init
129
130    def __init__(self, mapper: ItemMapper) -> None:
131        super().__init__(mapper, CContent())
132        self._reset()
133
134    def _reset(self) -> None:
135        self._name = ""
136        self._option_type = ""
137        self._default_value = ""
138        self._default_config = ""
139        self._value_constraints = []  # type: List[str]
140        self._description = ""
141
142    def add_group(self, name: str, description: str) -> None:
143        identifier = f"RTEMSApplConfig{name.replace(' ', '')}"
144        with self.content.defgroup_block(identifier, name):
145            self.content.add("@ingroup RTEMSApplConfig")
146            self.content.doxyfy(description)
147            self.content.add("@{")
148
149    def add_option(self, name: str, _index_entries: List[str]) -> None:
150        self.content.open_doxygen_block()
151        self._name = name
152
153    def add_option_type(self, option_type: str) -> None:
154        self._option_type = option_type
155
156    def add_option_default_value(self, value: str) -> None:
157        self._default_value = value
158
159    def add_option_default_config(self, config: str) -> None:
160        self._default_config = config
161
162    def add_option_value_constraints(self, lines: List[str]) -> None:
163        self._value_constraints = lines
164
165    def add_option_description(self, description: str) -> None:
166        self._description = description
167
168    def add_option_notes(self, notes: Optional[str]) -> None:
169        self.content.add_brief_description(self._option_type)
170        self.content.doxyfy(self._description)
171        self.content.add_paragraph("Default Value", self._default_value)
172        self.content.add_paragraph("Default Configuration",
173                                   self._default_config)
174        self.content.add_paragraph("Value Constraints",
175                                   self._value_constraints)
176        self.content.add_paragraph("Notes", notes)
177        self.content.close_comment_block()
178        self.content.append(f"#define {self._name}")
179        self._reset()
180
181    def add_licence_and_copyrights(self) -> None:
182        self.content.add("/** @} */")
183
184
185def _generate_feature(content: _ContentAdaptor, item: Item,
186                      option_type: str) -> None:
187    content.add_option_default_config(
188        content.substitute(_OPTION_DEFAULT_CONFIG[option_type](item)))
189
190
191def _generate_min_max(lines: List[str], value: str, word: str) -> None:
192    lines.append("The value of this configuration option shall be "
193                 f"{word} than or equal to {value}.")
194
195
196def _generate_set(lines: List[str], values: List[Any]) -> None:
197    value_set = "{" + ", ".join([str(x) for x in values]) + "}"
198    lines.append("The value of this configuration option shall be")
199    lines.append(f"an element of {value_set}.")
200
201
202def _start_constraint_list(lines: List[str]) -> None:
203    lines.append("The value of this configuration option shall "
204                 "satisfy all of the following")
205    lines.append("constraints:")
206
207
208def _generate_item_min(lines: List[str], constraints: Dict[str, Any]) -> None:
209    if "min" in constraints:
210        value = constraints["min"]
211        lines.append("")
212        lines.append(f"* It shall be greater than or equal to {value}.")
213
214
215def _generate_item_max(lines: List[str], constraints: Dict[str, Any]) -> None:
216    if "max" in constraints:
217        value = constraints["max"]
218        lines.append("")
219        lines.append(f"* It shall be less than or equal to {value}.")
220
221
222def _generate_item_set(lines: List[str], constraints: Dict[str, Any]) -> None:
223    if "set" in constraints:
224        value_set = constraints["set"]
225        lines.append("")
226        lines.append(f"* It shall be an element of {value_set}.")
227
228
229def _generate_item_texts(lines: List[str], constraints: Dict[str,
230                                                             Any]) -> None:
231    for text in constraints.get("texts", []):
232        lines.append("")
233        text = text.replace("The value of this configuration option", "It")
234        text = text.strip().split("\n")
235        lines.append(f"* {text[0]}")
236        lines.extend([f"  {x}" if x else "" for x in text[1:]])
237
238
239def _resolve_constraint_links(content: _ContentAdaptor, item: Item,
240                              constraints: Dict[str, Any]) -> None:
241    texts = []  # type: List[str]
242    for parent in item.parents("constraint"):
243        content.register_license_and_copyrights_of_item(parent)
244        texts.append(parent["text"])
245    if texts:
246        constraints.setdefault("texts", []).extend(reversed(texts))
247
248
249def _generate_constraint(content: _ContentAdaptor, item: Item) -> None:
250    constraints = copy.deepcopy(item["constraints"])
251    _resolve_constraint_links(content, item, constraints)
252    lines = []  # type: List[str]
253    count = len(constraints)
254    if count == 1:
255        if "min" in constraints:
256            _generate_min_max(lines, constraints["min"], "greater")
257        elif "max" in constraints:
258            _generate_min_max(lines, constraints["max"], "less")
259        elif "set" in constraints:
260            _generate_set(lines, constraints["set"])
261        elif "texts" in constraints:
262            if len(constraints["texts"]) == 1:
263                lines.extend(constraints["texts"][0].strip().split("\n"))
264            else:
265                _start_constraint_list(lines)
266                _generate_item_texts(lines, constraints)
267    elif count == 2 and "min" in constraints and "max" in constraints:
268        minimum = constraints["min"]
269        maximum = constraints["max"]
270        lines.append("The value of this configuration option shall be "
271                     f"greater than or equal to {minimum}")
272        lines.append(f"and less than or equal to {maximum}.")
273    else:
274        _start_constraint_list(lines)
275        _generate_item_min(lines, constraints)
276        _generate_item_max(lines, constraints)
277        _generate_item_set(lines, constraints)
278        _generate_item_texts(lines, constraints)
279    content.add_option_value_constraints(
280        [content.substitute(line) for line in lines])
281
282
283def _generate_initializer_or_integer(content: _ContentAdaptor, item: Item,
284                                     _option_type: str) -> None:
285    default_value = item["default-value"]
286    if not isinstance(default_value, str) or " " not in default_value:
287        default_value = f"The default value is {default_value}."
288    content.add_option_default_value(content.substitute(default_value))
289    _generate_constraint(content, item)
290
291
292_OPTION_GENERATORS = {
293    "feature": _generate_feature,
294    "feature-enable": _generate_feature,
295    "initializer": _generate_initializer_or_integer,
296    "integer": _generate_initializer_or_integer
297}
298
299
300def _generate(group: Item, options: ItemMap, content: _ContentAdaptor) -> None:
301    content.register_license_and_copyrights_of_item(group)
302    content.add_group(group["name"], content.substitute(group["description"]))
303    for item in sorted(options.values(), key=lambda x: x["name"]):
304        content.mapper.item = item
305        name = item["name"]
306        content.register_license_and_copyrights_of_item(item)
307        content.add_option(name, item["index-entries"])
308        option_type = item["appl-config-option-type"]
309        content.add_option_type(_OPTION_TYPES[option_type])
310        _OPTION_GENERATORS[option_type](content, item, option_type)
311        content.add_option_description(content.substitute(item["description"]))
312        content.add_option_notes(content.substitute(item["notes"]))
313    content.add_licence_and_copyrights()
314
315
316def _get_value_none(_ctx: ItemGetValueContext) -> Any:
317    return None
318
319
320def _sphinx_ref(ref: str) -> str:
321    return f":ref:`{ref}`"
322
323
324_PTHREAD_NAME_NP = "http://man7.org/linux/man-pages/man3/" \
325    "pthread_setname_np.3.html"
326
327_SPHINX_DOC_REFS = {
328    "config-scheduler-clustered":
329    _sphinx_ref("ConfigurationSchedulersClustered"),
330    "config-scheduler-table": _sphinx_ref("ConfigurationSchedulerTable"),
331    "config-unlimited-objects": _sphinx_ref("ConfigUnlimitedObjects"),
332    "mp-proxies": _sphinx_ref("MPCIProxies"),
333    "mrsp": _sphinx_ref("MrsP"),
334    "pthread-setname-np": f"`PTHREAD_SETNAME_NP(3) <{_PTHREAD_NAME_NP}>`_",
335    "scheduler-cbs": _sphinx_ref("SchedulerCBS"),
336    "scheduler-concepts": _sphinx_ref("SchedulingConcepts"),
337    "scheduler-edf": _sphinx_ref("SchedulerEDF"),
338    "scheduler-priority": _sphinx_ref("SchedulerPriority"),
339    "scheduler-priority-simple": _sphinx_ref("SchedulerPrioritySimple"),
340    "scheduler-smp-edf": _sphinx_ref("SchedulerSMPEDF"),
341    "scheduler-smp-priority-affinity":
342    _sphinx_ref("SchedulerSMPPriorityAffinity"),
343    "scheduler-smp-priority": _sphinx_ref("SchedulerSMPPriority"),
344    "scheduler-smp-priority-simple": _sphinx_ref("SchedulerSMPPrioritySimple"),
345    "terminate": _sphinx_ref("Terminate"),
346}
347
348
349def _get_value_sphinx_reference(ctx: ItemGetValueContext) -> Any:
350    return _SPHINX_DOC_REFS[ctx.key]
351
352
353def _get_value_sphinx_function(ctx: ItemGetValueContext) -> Any:
354    return f"``{ctx.value[ctx.key]}()``"
355
356
357def _get_value_sphinx_code(ctx: ItemGetValueContext) -> Any:
358    return f"``{ctx.value[ctx.key]}``"
359
360
361def _add_sphinx_get_values(mapper: ItemMapper) -> None:
362    for key in _SPHINX_DOC_REFS:
363        for opt in ["feature-enable", "feature", "initializer", "integer"]:
364            doc_ref = f"interface/appl-config-option/{opt}:/document-reference"
365            mapper.add_get_value(doc_ref, _get_value_none)
366            mapper.add_get_value(f"{doc_ref}/{key}",
367                                 _get_value_sphinx_reference)
368    mapper.add_get_value("interface/function:/name",
369                         _get_value_sphinx_function)
370    mapper.add_get_value("interface/macro:/name", _get_value_sphinx_function)
371    mapper.add_get_value("interface/struct:/name", _get_value_sphinx_code)
372    mapper.add_get_value("interface/typedef:/name", _get_value_sphinx_code)
373    mapper.add_get_value("interface/union:/name", _get_value_sphinx_code)
374
375
376def _c_user_ref(ref: str, name: str) -> str:
377    c_user = "https://docs.rtems.org/branches/master/c-user/"
378    return f"<a href={c_user}{ref}>{name}</a>"
379
380
381_DOXYGEN_DOC_REFS = {
382    "config-scheduler-clustered":
383    _c_user_ref("config/scheduler-clustered.html",
384                "Clustered Scheduler Configuration"),
385    "config-scheduler-table":
386    _c_user_ref(
387        "config/scheduler-clustered.html#configuration-step-3-scheduler-table",
388        "Configuration Step 3 - Scheduler Table"),
389    "config-unlimited-objects":
390    _c_user_ref("config/intro.html#unlimited-objects", "Unlimited Objects"),
391    "mp-proxies":
392    _c_user_ref("multiprocessing.html#proxies", "Proxies"),
393    "mrsp":
394    _c_user_ref(
395        "key_concepts.html#multiprocessor-resource-sharing-protocol-mrsp",
396        "Multiprocessor Resource Sharing Protocol (MrsP)"),
397    "pthread-setname-np":
398    f"<a href={_PTHREAD_NAME_NP}>PTHREAD_SETNAME_NP(3)</a>",
399    "scheduler-cbs":
400    _c_user_ref(
401        "scheduling_concepts.html#constant-bandwidth-server-scheduling-cbs",
402        "Constant Bandwidth Server Scheduling (CBS)"),
403    "scheduler-concepts":
404    _c_user_ref("scheduling_concepts.html", "Scheduling Concepts"),
405    "scheduler-edf":
406    _c_user_ref("scheduling_concepts.html#earliest-deadline-first-scheduler",
407                "Earliest Deadline First Scheduler"),
408    "scheduler-priority":
409    _c_user_ref("scheduling_concepts.html#deterministic-priority-scheduler",
410                "Deterministic Priority Scheduler"),
411    "scheduler-priority-simple":
412    _c_user_ref("scheduling_concepts.html#simple-priority-scheduler",
413                "Simple Priority Scheduler"),
414    "scheduler-smp-edf":
415    _c_user_ref(
416        "scheduling_concepts.html#earliest-deadline-first-smp-scheduler",
417        "Earliest Deadline First SMP Scheduler"),
418    "scheduler-smp-priority-affinity":
419    _c_user_ref(
420        "scheduling_concepts.html"
421        "#arbitrary-processor-affinity-priority-smp-scheduler",
422        "Arbitrary Processor Affinity Priority SMP Scheduler"),
423    "scheduler-smp-priority":
424    _c_user_ref(
425        "scheduling_concepts.html#deterministic-priority-smp-scheduler",
426        "Deterministic Priority SMP Scheduler"),
427    "scheduler-smp-priority-simple":
428    _c_user_ref("scheduling_concepts.html#simple-priority-smp-scheduler",
429                "Simple Priority SMP Scheduler"),
430    "terminate":
431    _c_user_ref("fatal_error.html#announcing-a-fatal-error",
432                "Announcing a Fatal Error"),
433}
434
435
436def _get_value_doxygen_reference(ctx: ItemGetValueContext) -> Any:
437    return _DOXYGEN_DOC_REFS[ctx.key]
438
439
440def _add_doxygen_get_values(mapper: ItemMapper) -> None:
441    for key in _DOXYGEN_DOC_REFS:
442        for opt in ["feature-enable", "feature", "initializer", "integer"]:
443            doc_ref = f"interface/appl-config-option/{opt}:/document-reference"
444            mapper.add_get_value(doc_ref, _get_value_none)
445            mapper.add_get_value(f"{doc_ref}/{key}",
446                                 _get_value_doxygen_reference)
447            name = f"interface/appl-config-option/{opt}:/name"
448            mapper.add_get_value(name, get_value_hash)
449    mapper.add_get_value("interface/function:/name",
450                         get_value_doxygen_function)
451    mapper.add_get_value("interface/macro:/name", get_value_doxygen_function)
452    mapper.add_get_value("interface/struct:/name", get_value_double_colon)
453    mapper.add_get_value("interface/typedef:/name", get_value_double_colon)
454    mapper.add_get_value("interface/union:/name", get_value_double_colon)
455
456
457def generate(config: dict, item_cache: ItemCache) -> None:
458    """
459    Generates application configuration documentation sources according to the
460    configuration.
461
462    :param config: A dictionary with configuration entries.
463    :param item_cache: The specification item cache containing the application
464                       configuration groups and options.
465    """
466    sphinx_mapper = SphinxMapper(EmptyItem())
467    _add_sphinx_get_values(sphinx_mapper)
468    doxygen_mapper = ItemMapper(EmptyItem())
469    _add_doxygen_get_values(doxygen_mapper)
470    doxygen_content = _DoxygenContentAdaptor(doxygen_mapper)
471    with doxygen_content.content.defgroup_block(
472            "RTEMSApplConfig", "Application Configuration Options"):
473        doxygen_content.content.add("@ingroup RTEMSAPI")
474    for group_config in config["groups"]:
475        group = item_cache[group_config["uid"]]
476        assert group.type == "interface/appl-config-group"
477        options = {}  # type: ItemMap
478        for child in group.children("appl-config-group-member"):
479            assert child.type.startswith("interface/appl-config-option")
480            options[child.uid] = child
481        sphinx_content = _SphinxContentAdaptor(sphinx_mapper)
482        _generate(group, options, sphinx_content)
483        sphinx_content.write(group_config["target"])
484        _generate(group, options, doxygen_content)
485    doxygen_content.content.prepend_copyrights_and_licenses()
486    doxygen_content.content.prepend_spdx_license_identifier()
487    doxygen_content.write(config["doxygen-target"])
Note: See TracBrowser for help on using the repository browser.