source: rtems-central/rtemsqual/content.py @ a87154a

Last change on this file since a87154a was a87154a, checked in by Sebastian Huber <sebastian.huber@…>, on Apr 17, 2020 at 5:15:26 AM

applconfig: Add unit tests

  • Property mode set to 100644
File size: 8.2 KB
Line 
1# SPDX-License-Identifier: BSD-2-Clause
2""" This module provides classes for content 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 os
28import re
29
30
31class Copyright:
32    """
33    This class represents a copyright holder with its years of substantial
34    contributions.
35    """
36    def __init__(self, holder):
37        self._holder = holder
38        self._years = set()
39
40    def add_year(self, year: str):
41        """
42        Adds a year to the set of substantial contributions of this copyright
43        holder.
44        """
45        self._years.add(year)
46
47    def get_statement(self) -> str:
48        """ Returns a copyright statement. """
49        line = "Copyright (C)"
50        years = sorted(self._years)
51        year_count = len(years)
52        if year_count == 1:
53            line += " " + years[0]
54        elif year_count > 1:
55            line += " " + years[0] + ", " + years[-1]
56        line += " " + self._holder
57        return line
58
59    def __lt__(self, other: "Copyright") -> bool:
60        # pylint: disable=protected-access
61        if self._years and other._years:
62            self_first_year = sorted(self._years)[0]
63            other_first_year = sorted(other._years)[0]
64            if self_first_year == other_first_year:
65                return self._holder > other._holder
66            return self_first_year > other_first_year
67        if self._years or other._years:
68            return True
69        return self._holder > other._holder
70
71
72class Copyrights:
73    """ This class represents a set of copyright holders. """
74    def __init__(self):
75        self.copyrights = {}
76
77    def register(self, statement):
78        """ Registers a copyright statement. """
79        match = re.search(
80            r"^\s*Copyright\s+\(C\)\s+([0-9]+),\s*([0-9]+)\s+(.+)\s*$",
81            statement,
82            flags=re.I,
83        )
84        if match:
85            holder = match.group(3)
86            the_copyright = self.copyrights.setdefault(holder,
87                                                       Copyright(holder))
88            the_copyright.add_year(match.group(1))
89            the_copyright.add_year(match.group(2))
90            return
91        match = re.search(
92            r"^\s*Copyright\s+\(C\)\s+([0-9]+)\s+(.+)\s*$",
93            statement,
94            flags=re.I,
95        )
96        if match:
97            holder = match.group(2)
98            the_copyright = self.copyrights.setdefault(holder,
99                                                       Copyright(holder))
100            the_copyright.add_year(match.group(1))
101            return
102        match = re.search(r"^\s*Copyright\s+\(C\)\s+(.+)\s*$",
103                          statement,
104                          flags=re.I)
105        if match:
106            holder = match.group(1)
107            self.copyrights.setdefault(holder, Copyright(holder))
108            return
109        raise ValueError(statement)
110
111    def get_statements(self):
112        """ Returns all registered copyright statements as a sorted list. """
113        statements = []
114        for the_copyright in sorted(self.copyrights.values()):
115            statements.append(the_copyright.get_statement())
116        return statements
117
118
119def _make_lines(lines):
120    if not isinstance(lines, list):
121        return lines.strip("\n").split("\n")
122    return lines
123
124
125def _make_list(value):
126    if not isinstance(value, list):
127        return [value]
128    return value
129
130
131class SphinxContent:
132    """ This class builds Sphinx content. """
133    def __init__(self):
134        self._content = ""
135        self._license = "CC-BY-SA-4.0"
136        self._copyrights = Copyrights()
137
138    @property
139    def content(self):
140        """ Returns the content. """
141        return self._content
142
143    def add_label(self, label):
144        """ Adds a label to the content. """
145        self._content += ".. _" + label.strip() + ":\n"
146
147    def add_header(self, name, level="="):
148        """ Adds a header to the content. """
149        name = name.strip()
150        self._content += name + "\n" + level * len(name) + "\n"
151
152    def add_blank_line(self):
153        """ Adds a blank line to the content. """
154        self._content += "\n"
155
156    def add_line(self, line, indent=0):
157        """ Adds a line to the content. """
158        if line:
159            self._content += indent * "    " + line + "\n"
160        else:
161            self._content += "\n"
162
163    def add_lines(self, lines, indent=0):
164        """ Adds a lines to the content. """
165        for line in _make_lines(lines):
166            self.add_line(line, indent)
167
168    def add_index_entries(self, entries):
169        """ Adds a list of index entries the content. """
170        first = True
171        for entry in _make_list(entries):
172            if first:
173                first = False
174                self.add_blank_line()
175            self._content += ".. index:: " + entry + "\n"
176
177    def add_definition_item(self, name, lines, indent=0):
178        """ Adds a definition item the content. """
179        first = True
180        for line in _make_lines(lines):
181            if first:
182                first = False
183                self.add_blank_line()
184                self.add_line(name, indent)
185            self.add_line(line, indent=indent + 1)
186
187    def register_license(self, the_license):
188        """ Registers a licence for the content. """
189        licenses = re.split(r"\s+OR\s+", the_license)
190        if self._license not in licenses:
191            raise ValueError(the_license)
192
193    def register_copyright(self, statement):
194        """ Registers a copyright statement for the content. """
195        self._copyrights.register(statement)
196
197    def add_licence_and_copyrights(self):
198        """
199        Adds a licence and copyright block to the content according to the
200        registered licenses and copyrights.
201        """
202        spdx = f".. SPDX-License-Identifier: {self._license}\n"
203        statements = "\n.. ".join(self._copyrights.get_statements())
204        if statements:
205            self._content = f"{spdx}\n.. {statements}\n\n{self._content}"
206        else:
207            self._content = f"{spdx}\n{self._content}"
208
209    def write(self, path):
210        """ Writes the content to the file specified by the path. """
211        directory = os.path.dirname(path)
212        if directory:
213            os.makedirs(directory, exist_ok=True)
214        with open(path, "w+") as out:
215            out.write(self._content)
216
217
218class MacroToSphinx:
219    """ This class expands specification item macros to Sphinx markup. """
220    def __init__(self):
221        self._terms = {}
222
223    def set_terms(self, terms):
224        """ Sets the glossary of terms used for macro expansion. """
225        self._terms = terms
226
227    def substitute(self, text):
228        """
229        Substitutes all specification item macros contained in the text.
230        """
231        return re.sub(r"@@|@([a-z]+){([^}]+)}", self, text)
232
233    def __call__(self, match):
234        name = match.group(1)
235        if name:
236            roles = {
237                "term":
238                lambda x: ":term:`" + self._terms[x]["glossary-term"] + "`"
239            }
240            return roles[name](match.group(2))
241        assert match.group(0) == "@@"
242        return "@"
Note: See TracBrowser for help on using the repository browser.