[c0ac12a] | 1 | # SPDX-License-Identifier: BSD-2-Clause |
---|
| 2 | """ This module provides functions for glossary of terms 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 | |
---|
| 27 | import glob |
---|
| 28 | import re |
---|
[a5f3cc1] | 29 | from typing import Any, Dict, Optional |
---|
[c0ac12a] | 30 | |
---|
[a5f3cc1] | 31 | from rtemsqual.content import SphinxContent, SphinxMapper |
---|
| 32 | from rtemsqual.items import Item, ItemCache, ItemMapper |
---|
[c0ac12a] | 33 | |
---|
| 34 | ItemMap = Dict[str, Item] |
---|
| 35 | |
---|
| 36 | |
---|
| 37 | def _gather_glossary_groups(item: Item, glossary_groups: ItemMap) -> None: |
---|
[9dad293] | 38 | for child in item.children(): |
---|
[c0ac12a] | 39 | _gather_glossary_groups(child, glossary_groups) |
---|
| 40 | if item["type"] == "glossary" and item["glossary-type"] == "group": |
---|
| 41 | glossary_groups[item.uid] = item |
---|
| 42 | |
---|
| 43 | |
---|
| 44 | def _gather_glossary_terms(item: Item, glossary_terms: ItemMap) -> None: |
---|
[9dad293] | 45 | for child in item.children(): |
---|
[c0ac12a] | 46 | _gather_glossary_terms(child, glossary_terms) |
---|
| 47 | if item["type"] == "glossary" and item["glossary-type"] == "term": |
---|
| 48 | glossary_terms[item.uid] = item |
---|
| 49 | |
---|
| 50 | |
---|
| 51 | def _generate_glossary_content(terms: ItemMap) -> SphinxContent: |
---|
| 52 | content = SphinxContent() |
---|
| 53 | content.add_header("Glossary", level="*") |
---|
[520ba1dd] | 54 | content.add(".. glossary::") |
---|
| 55 | with content.indent(): |
---|
[99c6449] | 56 | content.add(":sorted:") |
---|
| 57 | for item in sorted(terms.values(), |
---|
| 58 | key=lambda x: x["glossary-term"].lower()): |
---|
| 59 | content.register_license_and_copyrights_of_item(item) |
---|
[a5f3cc1] | 60 | text = SphinxMapper(item).substitute(item["text"]) |
---|
[520ba1dd] | 61 | content.add_definition_item(item["glossary-term"], text) |
---|
[c0ac12a] | 62 | content.add_licence_and_copyrights() |
---|
| 63 | return content |
---|
| 64 | |
---|
| 65 | |
---|
| 66 | def _make_glossary_term_uid(term: str) -> str: |
---|
[71d8b3c] | 67 | return "/glos/term/" + re.sub(r"[^a-zA-Z0-9]+", "", term.replace( |
---|
| 68 | "+", "X")).lower() |
---|
[c0ac12a] | 69 | |
---|
| 70 | |
---|
| 71 | def _find_glossary_terms(path: str, document_terms: ItemMap, |
---|
| 72 | project_terms: ItemMap) -> None: |
---|
| 73 | for src in glob.glob(path + "/**/*.rst", recursive=True): |
---|
| 74 | if src.endswith("glossary.rst"): |
---|
| 75 | continue |
---|
| 76 | with open(src, "r") as out: |
---|
| 77 | for term in re.findall(":term:`([^`]+)`", out.read()): |
---|
| 78 | uid = _make_glossary_term_uid(term) |
---|
| 79 | document_terms[uid] = project_terms[uid] |
---|
| 80 | |
---|
| 81 | |
---|
[a5f3cc1] | 82 | class _GlossaryMapper(ItemMapper): |
---|
| 83 | def __init__(self, item: Item, document_terms: ItemMap): |
---|
| 84 | super().__init__(item) |
---|
| 85 | self._document_terms = document_terms |
---|
[c0ac12a] | 86 | |
---|
[a5f3cc1] | 87 | def get_value(self, item: Item, _path: str, _value: Any, key: str, |
---|
| 88 | _index: Optional[int]) -> Any: |
---|
| 89 | """ Recursively adds glossary terms to the document terms. """ |
---|
| 90 | if key == "glossary-term": |
---|
| 91 | if item.uid not in self._document_terms: |
---|
| 92 | self._document_terms[item.uid] = item |
---|
| 93 | _GlossaryMapper(item, |
---|
| 94 | self._document_terms).substitute(item["text"]) |
---|
| 95 | # The value of this substitute is unused. |
---|
| 96 | return "" |
---|
[c0ac12a] | 97 | |
---|
[a5f3cc1] | 98 | |
---|
| 99 | def _resolve_glossary_terms(document_terms: ItemMap) -> None: |
---|
[c0ac12a] | 100 | for term in list(document_terms.values()): |
---|
[a5f3cc1] | 101 | _GlossaryMapper(term, document_terms).substitute(term["text"]) |
---|
[c0ac12a] | 102 | |
---|
| 103 | |
---|
| 104 | def _generate_project_glossary(target: str, project_terms: ItemMap) -> None: |
---|
[a4e08c5] | 105 | content = _generate_glossary_content(project_terms) |
---|
| 106 | content.write(target) |
---|
[c0ac12a] | 107 | |
---|
| 108 | |
---|
| 109 | def _generate_document_glossary(config: dict, project_terms: ItemMap) -> None: |
---|
| 110 | document_terms = {} # type: ItemMap |
---|
| 111 | for path in config["rest-source-paths"]: |
---|
| 112 | _find_glossary_terms(path, document_terms, project_terms) |
---|
[a5f3cc1] | 113 | _resolve_glossary_terms(document_terms) |
---|
[c0ac12a] | 114 | content = _generate_glossary_content(document_terms) |
---|
| 115 | content.write(config["target"]) |
---|
| 116 | |
---|
| 117 | |
---|
| 118 | def generate(config: dict, item_cache: ItemCache) -> None: |
---|
| 119 | """ |
---|
| 120 | Generates glossaries of terms according to the configuration. |
---|
| 121 | |
---|
| 122 | :param config: A dictionary with configuration entries. |
---|
| 123 | :param item_cache: The specification item cache containing the glossary |
---|
| 124 | groups and terms. |
---|
| 125 | """ |
---|
| 126 | groups = {} # type: ItemMap |
---|
| 127 | for item in item_cache.top_level.values(): |
---|
| 128 | _gather_glossary_groups(item, groups) |
---|
| 129 | |
---|
| 130 | project_terms = {} # type: ItemMap |
---|
| 131 | for group in config["project-groups"]: |
---|
| 132 | _gather_glossary_terms(groups[group], project_terms) |
---|
| 133 | |
---|
| 134 | _generate_project_glossary(config["project-target"], project_terms) |
---|
| 135 | |
---|
| 136 | for document_config in config["documents"]: |
---|
| 137 | _generate_document_glossary(document_config, project_terms) |
---|