source: rtems-central/rtemsqual/content.py @ 9dad293

Last change on this file since 9dad293 was 5932488, checked in by Sebastian Huber <sebastian.huber@…>, on 04/24/20 at 06:20:16

content: Add CContent

  • Property mode set to 100644
File size: 13.1 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
29import textwrap
30
31from typing import List, Union
32
33
34class Copyright:
35    """
36    This class represents a copyright holder with its years of substantial
37    contributions.
38    """
39    def __init__(self, holder):
40        self._holder = holder
41        self._years = set()
42
43    def add_year(self, year: str):
44        """
45        Adds a year to the set of substantial contributions of this copyright
46        holder.
47        """
48        self._years.add(year)
49
50    def get_statement(self) -> str:
51        """ Returns a copyright statement. """
52        line = "Copyright (C)"
53        years = sorted(self._years)
54        year_count = len(years)
55        if year_count == 1:
56            line += " " + years[0]
57        elif year_count > 1:
58            line += " " + years[0] + ", " + years[-1]
59        line += " " + self._holder
60        return line
61
62    def __lt__(self, other: "Copyright") -> bool:
63        # pylint: disable=protected-access
64        if self._years and other._years:
65            self_first_year = sorted(self._years)[0]
66            other_first_year = sorted(other._years)[0]
67            if self_first_year == other_first_year:
68                return self._holder > other._holder
69            return self_first_year > other_first_year
70        if self._years or other._years:
71            return True
72        return self._holder > other._holder
73
74
75class Copyrights:
76    """ This class represents a set of copyright holders. """
77    def __init__(self):
78        self.copyrights = {}
79
80    def register(self, statement):
81        """ Registers a copyright statement. """
82        match = re.search(
83            r"^\s*Copyright\s+\(C\)\s+([0-9]+),\s*([0-9]+)\s+(.+)\s*$",
84            statement,
85            flags=re.I,
86        )
87        if match:
88            holder = match.group(3)
89            the_copyright = self.copyrights.setdefault(holder,
90                                                       Copyright(holder))
91            the_copyright.add_year(match.group(1))
92            the_copyright.add_year(match.group(2))
93            return
94        match = re.search(
95            r"^\s*Copyright\s+\(C\)\s+([0-9]+)\s+(.+)\s*$",
96            statement,
97            flags=re.I,
98        )
99        if match:
100            holder = match.group(2)
101            the_copyright = self.copyrights.setdefault(holder,
102                                                       Copyright(holder))
103            the_copyright.add_year(match.group(1))
104            return
105        match = re.search(r"^\s*Copyright\s+\(C\)\s+(.+)\s*$",
106                          statement,
107                          flags=re.I)
108        if match:
109            holder = match.group(1)
110            self.copyrights.setdefault(holder, Copyright(holder))
111            return
112        raise ValueError(statement)
113
114    def get_statements(self):
115        """ Returns all registered copyright statements as a sorted list. """
116        statements = []
117        for the_copyright in sorted(self.copyrights.values()):
118            statements.append(the_copyright.get_statement())
119        return statements
120
121
122def _make_lines(lines: Union[str, List[str]]) -> List[str]:
123    if not isinstance(lines, list):
124        return lines.strip("\n").split("\n")
125    return lines
126
127
128def _make_list(value):
129    if not isinstance(value, list):
130        return [value]
131    return value
132
133
134class Content:
135    """ This class builds content. """
136    def __init__(self, the_license):
137        self._content = ""
138        self._license = the_license
139        self._copyrights = Copyrights()
140
141    def add(self, content: str) -> None:
142        """ Adds content to the content. """
143        self._content += content
144
145    def add_blank_line(self):
146        """ Adds a blank line to the content. """
147        self.add("\n")
148
149    @property
150    def content(self):
151        """ Returns the content. """
152        return self._content
153
154    def register_license(self, the_license):
155        """ Registers a licence for the content. """
156        licenses = re.split(r"\s+OR\s+", the_license)
157        if self._license not in licenses:
158            raise ValueError(the_license)
159
160    def register_copyright(self, statement):
161        """ Registers a copyright statement for the content. """
162        self._copyrights.register(statement)
163
164    def write(self, path):
165        """ Writes the content to the file specified by the path. """
166        directory = os.path.dirname(path)
167        if directory:
168            os.makedirs(directory, exist_ok=True)
169        with open(path, "w+") as out:
170            out.write(self._content)
171
172
173class SphinxContent(Content):
174    """ This class builds Sphinx content. """
175    def __init__(self):
176        super().__init__("CC-BY-SA-4.0")
177
178    def add_label(self, label):
179        """ Adds a label to the content. """
180        self._content += ".. _" + label.strip() + ":\n"
181
182    def add_header(self, name, level="="):
183        """ Adds a header to the content. """
184        name = name.strip()
185        self._content += name + "\n" + level * len(name) + "\n"
186
187    def add_line(self, line, indent=0):
188        """ Adds a line to the content. """
189        if line:
190            self._content += indent * "    " + line + "\n"
191        else:
192            self._content += "\n"
193
194    def add_lines(self, lines, indent=0):
195        """ Adds a lines to the content. """
196        for line in _make_lines(lines):
197            self.add_line(line, indent)
198
199    def add_index_entries(self, entries):
200        """ Adds a list of index entries the content. """
201        first = True
202        for entry in _make_list(entries):
203            if first:
204                first = False
205                self.add_blank_line()
206            self._content += ".. index:: " + entry + "\n"
207
208    def add_definition_item(self, name, lines, indent=0):
209        """ Adds a definition item the content. """
210        first = True
211        for line in _make_lines(lines):
212            if first:
213                first = False
214                self.add_blank_line()
215                self.add_line(name, indent)
216            self.add_line(line, indent=indent + 1)
217
218    def add_licence_and_copyrights(self):
219        """
220        Adds a licence and copyright block to the content according to the
221        registered licenses and copyrights.
222        """
223        spdx = f".. SPDX-License-Identifier: {self._license}\n"
224        statements = "\n.. ".join(self._copyrights.get_statements())
225        if statements:
226            self._content = f"{spdx}\n.. {statements}\n\n{self._content}"
227        else:
228            self._content = f"{spdx}\n{self._content}"
229
230
231class MacroToSphinx:
232    """ This class expands specification item macros to Sphinx markup. """
233    def __init__(self):
234        self._terms = {}
235
236    def set_terms(self, terms):
237        """ Sets the glossary of terms used for macro expansion. """
238        self._terms = terms
239
240    def substitute(self, text):
241        """
242        Substitutes all specification item macros contained in the text.
243        """
244        return re.sub(r"@@|@([a-z]+){([^}]+)}", self, text)
245
246    def __call__(self, match):
247        name = match.group(1)
248        if name:
249            roles = {
250                "term":
251                lambda x: ":term:`" + self._terms[x]["glossary-term"] + "`"
252            }
253            return roles[name](match.group(2))
254        assert match.group(0) == "@@"
255        return "@"
256
257
258_BSD_2_CLAUSE_LICENSE = """
259 *
260 * Redistribution and use in source and binary forms, with or without
261 * modification, are permitted provided that the following conditions
262 * are met:
263 * 1. Redistributions of source code must retain the above copyright
264 *    notice, this list of conditions and the following disclaimer.
265 * 2. Redistributions in binary form must reproduce the above copyright
266 *    notice, this list of conditions and the following disclaimer in the
267 *    documentation and/or other materials provided with the distribution.
268 *
269 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
270 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
271 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
272 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
273 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
274 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
275 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
276 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
277 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
278 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
279 * POSSIBILITY OF SUCH DAMAGE.
280 */
281"""
282
283
284class CContent(Content):
285    """ This class builds C content. """
286    def __init__(self):
287        super().__init__("BSD-2-Clause")
288
289    def add_spdx_license_identifier(self):
290        """
291        Adds an SPDX License Identifier to the content according to the
292        registered licenses.
293        """
294        spdx = f"/* SPDX-License-Identifier: {self._license} */"
295        self.add(f"{spdx}\n{self._content}")
296
297    def add_copyrights_and_licenses(self):
298        """
299        Adds the copyrights and licenses to the content according to the
300        registered copyrights and licenses.
301        """
302        self.add("\n/*\n * ")
303        self.add("\n * ".join(self._copyrights.get_statements()))
304        self.add(_BSD_2_CLAUSE_LICENSE)
305
306    def add_have_config(self):
307        """ Adds a guarded config.h include. """
308        self.add("""
309#ifdef HAVE_CONFIG_H
310#include "config.h"
311#endif
312""")
313
314    def add_lines(self, lines: Union[str, List[str]], indent: int = 0) -> None:
315        """ Adds the lines to the content. """
316        if lines:
317            sep = "\n" + indent * "  "
318            self.add(indent * "  " + sep.join(_make_lines(lines)) + "\n")
319
320    def add_line_block(self,
321                       lines: Union[str, List[str]],
322                       indent: int = 0) -> None:
323        """ Adds a block of lines to the content. """
324        if lines:
325            sep = "\n" + indent * "  "
326            self.add(sep + sep.join(_make_lines(lines)) + "\n")
327
328    def add_includes(self, includes: List[str], local: bool = False) -> None:
329        """ Adds a block of includes. """
330        class IncludeKey:  # pylint: disable=too-few-public-methods
331            """ Provides a key to sort includes. """
332            def __init__(self, inc: str):
333                self._inc = inc
334
335            def __lt__(self, other: "IncludeKey") -> bool:
336                left = self._inc.split("/")
337                right = other._inc.split("/")
338                left_len = len(left)
339                right_len = len(right)
340                if left_len == right_len:
341                    for left_part, right_part in zip(left[:-1], right[:-1]):
342                        if left_part != right_part:
343                            return left_part < right_part
344                    return left[-1] < right[-1]
345                return left_len < right_len
346
347        left = "\"" if local else "<"
348        right = "\"" if local else ">"
349        incs = [f"#include {left}{inc}{right}" for inc in includes]
350        self.add_line_block(sorted(set(incs), key=IncludeKey))
351
352    def add_comment_content(self,
353                            content: str,
354                            indent: int = 0,
355                            intro: str = "") -> None:
356        """ Adds comment content to the content. """
357        if content:
358            text = textwrap.TextWrapper()
359            text.drop_whitespace = True
360            sep = indent * "  " + " * "
361            text.initial_indent = sep + intro
362            text.subsequent_indent = sep + len(intro) * " "
363            self.add(indent * "  " + " *\n" + "\n".join(text.wrap(content)) +
364                     "\n")
365
366    def add_brief_description(self, description: str, indent: int = 0) -> None:
367        """ Adds a brief description to the content. """
368        self.add_comment_content(description, indent, intro="@brief ")
Note: See TracBrowser for help on using the repository browser.