Changeset 520ba1d in rtems-central


Ignore:
Timestamp:
Apr 28, 2020, 11:27:38 AM (6 months ago)
Author:
Sebastian Huber <sebastian.huber@…>
Branches:
master
Children:
ddbc8f7
Parents:
22ec40f
git-author:
Sebastian Huber <sebastian.huber@…> (04/28/20 11:27:38)
git-committer:
Sebastian Huber <sebastian.huber@…> (05/28/20 08:34:46)
Message:

content: Rework API

Use context managers for indent and comment blocks.

Location:
rtemsqual
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • rtemsqual/applconfig.py

    r22ec40f r520ba1d  
    189189
    190190
    191 def _generate_content(group: Item, options: ItemMap) -> SphinxContent:
     191def _generate_file(group: Item, options: ItemMap, target: str) -> None:
    192192    content = SphinxContent()
    193193    group.register_license_and_copyrights(content)
    194194    content.add_header(group["appl-config-group-name"], level="=")
    195     content.add_blank_line()
    196     content.add_lines(group["appl-config-group-description"])
     195    content.add(group["appl-config-group-description"])
    197196    for item in sorted(options.values(), key=lambda x: x.uid):
    198197        name = item["appl-config-option-name"]
    199198        item.register_license_and_copyrights(content)
    200199        content.add_index_entries([name] + item["appl-config-option-index"])
    201         content.add_blank_line()
    202200        content.add_label(name)
    203         content.add_blank_line()
    204201        content.add_header(name, level="-")
    205202        content.add_definition_item("CONSTANT:", f"``{name}``")
     
    211208        _generate_notes(content, item["appl-config-option-notes"])
    212209    content.add_licence_and_copyrights()
    213     return content
     210    content.write(target)
    214211
    215212
     
    231228        options = {}  # type: ItemMap
    232229        _gather_options(group, options)
    233         content = _generate_content(group, options)
    234         content.write(group_config["target"])
     230        _generate_file(group, options, group_config["target"])
  • rtemsqual/content.py

    r22ec40f r520ba1d  
    2525# POSSIBILITY OF SUCH DAMAGE.
    2626
     27from contextlib import contextmanager
     28import itertools
    2729import os
    2830import re
    2931import textwrap
    30 
    31 from typing import List, Union
     32from typing import Callable, ContextManager, Iterator, List, Optional, Union
     33
     34AddContext = Callable[["Content"], ContextManager[None]]
     35GenericContent = Union[str, List[str], "Content"]
    3236
    3337
     
    120124
    121125
    122 def _make_lines(lines: Union[str, List[str]]) -> List[str]:
    123     if not isinstance(lines, list):
    124         return lines.strip("\n").split("\n")
     126def _make_lines(content: GenericContent) -> List[str]:
     127    if isinstance(content, str):
     128        return content.strip("\n").split("\n")
     129    if isinstance(content, list):
     130        return content
     131    return content.lines
     132
     133
     134def _indent(lines: List[str], indent: str,
     135            empty_line_indent: str) -> List[str]:
     136    if indent:
     137        return [
     138            indent + line if line else empty_line_indent + line
     139            for line in lines
     140        ]
    125141    return lines
    126142
    127143
    128 def _make_list(value):
    129     if not isinstance(value, list):
    130         return [value]
    131     return value
     144@contextmanager
     145def _add_context(_content: "Content") -> Iterator[None]:
     146    yield
    132147
    133148
    134149class Content:
    135150    """ This class builds content. """
     151
     152    # pylint: disable=too-many-instance-attributes
    136153    def __init__(self, the_license):
    137         self._content = ""
     154        self._lines = []  # type: List[str]
    138155        self._license = the_license
    139156        self._copyrights = Copyrights()
    140 
    141     def add(self, content: str) -> None:
    142         """ Adds content to the content. """
    143         self._content += content
     157        self._gap = False
     158        self._tab = "  "
     159        self._indents = [""]
     160        self._indent = ""
     161        self._empty_line_indents = [""]
     162        self._empty_line_indent = ""
     163
     164    def __str__(self):
     165        return "\n".join(itertools.chain(self._lines, [""]))
     166
     167    @property
     168    def lines(self) -> List[str]:
     169        """ The lines. """
     170        return self._lines
     171
     172    def append(self, content: GenericContent) -> None:
     173        """ Appends the content. """
     174        self._lines.extend(
     175            _indent(_make_lines(content), self._indent,
     176                    self._empty_line_indent))
     177
     178    def prepend(self, content: GenericContent) -> None:
     179        """ Prepends the content. """
     180        self._lines[0:0] = _indent(_make_lines(content), self._indent,
     181                                   self._empty_line_indent)
     182
     183    def add(self,
     184            content: Optional[GenericContent],
     185            context: AddContext = _add_context) -> None:
     186        """
     187        Skips leading empty lines, adds a gap if needed, then adds the content.
     188        """
     189        if not content:
     190            return
     191        lines = _make_lines(content)
     192        index = 0
     193        for line in lines:
     194            if line:
     195                if self._gap:
     196                    self._lines.extend(
     197                        _indent([""], self._indent, self._empty_line_indent))
     198                self._gap = True
     199                with context(self):
     200                    self._lines.extend(
     201                        _indent(lines[index:], self._indent,
     202                                self._empty_line_indent))
     203                break
     204            index += 1
     205
     206    @property
     207    def gap(self) -> bool:
     208        """
     209        True if the next Content.add() adds a gap before the new content,
     210        otherwise False.
     211        """
     212        return self._gap
     213
     214    @gap.setter
     215    def gap(self, value: bool) -> None:
     216        """ Sets the gap indicator for Content.add(). """
     217        self._gap = value
     218
     219    def _update_indent(self) -> None:
     220        self._indent = "".join(self._indents)
     221        empty_line_indent = "".join(self._empty_line_indents)
     222        if empty_line_indent.isspace():
     223            self._empty_line_indent = ""
     224        else:
     225            self._empty_line_indent = empty_line_indent
     226
     227    def push_indent(self,
     228                    indent: Optional[str] = None,
     229                    empty_line_indent: Optional[str] = None) -> None:
     230        """ Pushes an indent level. """
     231        self._indents.append(indent if indent else self._tab)
     232        self._empty_line_indents.append(
     233            empty_line_indent if empty_line_indent else self._tab)
     234        self._update_indent()
     235
     236    def pop_indent(self) -> None:
     237        """ Pops an indent level. """
     238        self._indents.pop()
     239        self._empty_line_indents.pop()
     240        self._update_indent()
     241
     242    @contextmanager
     243    def indent(self,
     244               indent: Optional[str] = None,
     245               empty_line_indent: Optional[str] = None) -> Iterator[None]:
     246        """ Opens an indent context. """
     247        self.push_indent(indent, empty_line_indent)
     248        yield
     249        self.pop_indent()
     250
     251    def indent_lines(self, level: int) -> None:
     252        """ Indents all lines by the specified indent level. """
     253        prefix = level * self._tab
     254        self._lines = [prefix + line if line else line for line in self._lines]
    144255
    145256    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):
     257        """ Adds a blank line. """
     258        self._lines.append("")
     259
     260    def register_license(self, the_license: str) -> None:
    155261        """ Registers a licence for the content. """
    156262        licenses = re.split(r"\s+OR\s+", the_license)
     
    158264            raise ValueError(the_license)
    159265
    160     def register_copyright(self, statement):
     266    def register_copyright(self, statement: str) -> None:
    161267        """ Registers a copyright statement for the content. """
    162268        self._copyrights.register(statement)
    163269
    164     def write(self, path):
     270    def write(self, path: str) -> None:
    165271        """ Writes the content to the file specified by the path. """
    166272        directory = os.path.dirname(path)
     
    168274            os.makedirs(directory, exist_ok=True)
    169275        with open(path, "w+") as out:
    170             out.write(self._content)
     276            out.write(str(self))
    171277
    172278
     
    175281    def __init__(self):
    176282        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. """
     283        self._tab = "    "
     284
     285    def add_label(self, label: str) -> None:
     286        """ Adds a label. """
     287        self.add(".. _" + label.strip() + ":")
     288
     289    def add_header(self, name, level="=") -> None:
     290        """ Adds a header. """
    184291        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):
     292        self.add([name, level * len(name)])
     293
     294    def add_index_entries(self, entries) -> None:
    200295        """ 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):
     296        self.add([".. index:: " + entry for entry in _make_lines(entries)])
     297
     298    def add_definition_item(self, name, lines) -> None:
    209299        """ 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())
     300        @contextmanager
     301        def _definition_item_context(content: Content) -> Iterator[None]:
     302            content.append(name)
     303            content.push_indent()
     304            yield
     305            content.pop_indent()
     306
     307        self.add(lines, _definition_item_context)
     308
     309    def add_licence_and_copyrights(self) -> None:
     310        """
     311        Adds a licence and copyright block according to the registered licenses
     312        and copyrights.
     313        """
     314        statements = self._copyrights.get_statements()
    225315        if statements:
    226             self._content = f"{spdx}\n.. {statements}\n\n{self._content}"
    227         else:
    228             self._content = f"{spdx}\n{self._content}"
     316            self.prepend("")
     317            self.prepend([f".. {stm}" for stm in statements])
     318        self.prepend([f".. SPDX-License-Identifier: {self._license}", ""])
    229319
    230320
     
    256346
    257347
    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 """
     348_BSD_2_CLAUSE_LICENSE = """Redistribution and use in source and binary \
     349forms, with or without
     350modification, are permitted provided that the following conditions
     351are met:
     3521. Redistributions of source code must retain the above copyright
     353   notice, this list of conditions and the following disclaimer.
     3542. Redistributions in binary form must reproduce the above copyright
     355   notice, this list of conditions and the following disclaimer in the
     356   documentation and/or other materials provided with the distribution.
     357
     358THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     359AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     360IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     361ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     362LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     363CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     364SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     365INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     366CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     367ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     368POSSIBILITY OF SUCH DAMAGE."""
    282369
    283370
    284371class CContent(Content):
    285372    """ This class builds C content. """
     373
     374    # pylint: disable=too-many-public-methods
    286375    def __init__(self):
    287376        super().__init__("BSD-2-Clause")
     
    289378    def add_spdx_license_identifier(self):
    290379        """
    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}")
     380        Adds an SPDX License Identifier according to the registered licenses.
     381        """
     382        self.prepend([f"/* SPDX-License-Identifier: {self._license} */", ""])
    296383
    297384    def add_copyrights_and_licenses(self):
    298385        """
    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)
     386        Adds the copyrights and licenses according to the registered copyrights
     387        and licenses.
     388        """
     389        with self.comment_block():
     390            self.add(self._copyrights.get_statements())
     391            self.add(_BSD_2_CLAUSE_LICENSE)
    305392
    306393    def add_have_config(self):
    307394        """ 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")
     395        self.add(["#ifdef HAVE_CONFIG_H", "#include \"config.h\"", "#endif"])
    327396
    328397    def add_includes(self, includes: List[str], local: bool = False) -> None:
     
    347416        left = "\"" if local else "<"
    348417        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 ")
     418        self.add([
     419            f"#include {left}{inc}{right}"
     420            for inc in sorted(set(includes), key=IncludeKey)
     421        ])
     422
     423    def wrap(self, content: Optional[str], intro: str = "") -> List[str]:
     424        """ Wraps a text. """
     425        if not content:
     426            return [""]
     427        wrapper = textwrap.TextWrapper()
     428        wrapper.drop_whitespace = True
     429        wrapper.initial_indent = intro
     430        wrapper.subsequent_indent = len(intro) * " "
     431        wrapper.width = 79 - len(self._indent)
     432        return wrapper.wrap(content)
     433
     434    def _open_comment_block(self, begin) -> None:
     435        self.add(begin)
     436        self.push_indent(" * ", " *")
     437        self.gap = False
     438
     439    def open_comment_block(self) -> None:
     440        """ Opens a comment block. """
     441        self._open_comment_block("/*")
     442
     443    def open_doxygen_block(self) -> None:
     444        """ Opens a Doxygen comment block. """
     445        self._open_comment_block("/**")
     446
     447    def open_file_block(self) -> None:
     448        """ Opens a Doxygen @file comment block. """
     449        self._open_comment_block(["/**", " * @file"])
     450        self.gap = True
     451
     452    def open_defgroup_block(self, identifier: str, name: str) -> None:
     453        """ Opens a Doxygen @defgroup comment block. """
     454        self._open_comment_block(["/**", f" * @defgroup {identifier} {name}"])
     455        self.gap = True
     456
     457    def open_function_block(self, function: str) -> None:
     458        """ Opens a Doxygen @fn comment block. """
     459        self._open_comment_block(["/**", f" * @fn {function}"])
     460        self.gap = True
     461
     462    def close_comment_block(self) -> None:
     463        """ Closes a comment block. """
     464        self.pop_indent()
     465        self.append(" */")
     466        self.gap = True
     467
     468    @contextmanager
     469    def comment_block(self) -> Iterator[None]:
     470        """ Opens a comment block context. """
     471        self.open_comment_block()
     472        yield
     473        self.close_comment_block()
     474
     475    @contextmanager
     476    def doxygen_block(self) -> Iterator[None]:
     477        """ Opens a Doxygen comment block context. """
     478        self.open_doxygen_block()
     479        yield
     480        self.close_comment_block()
     481
     482    @contextmanager
     483    def file_block(self) -> Iterator[None]:
     484        """ Opens a Doxygen @file comment block context. """
     485        self.open_file_block()
     486        yield
     487        self.close_comment_block()
     488
     489    @contextmanager
     490    def defgroup_block(self, identifier: str, name: str) -> Iterator[None]:
     491        """ Opens a Doxygen @defgroup comment block context. """
     492        self.open_defgroup_block(identifier, name)
     493        yield
     494        self.close_comment_block()
     495
     496    @contextmanager
     497    def function_block(self, function: str) -> Iterator[None]:
     498        """ Opens a Doxygen @fn comment block context. """
     499        self.open_function_block(function)
     500        yield
     501        self.close_comment_block()
     502
     503    def add_brief_description(self, description: Optional[str]) -> None:
     504        """ Adds a brief description. """
     505        return self.add(self.wrap(description, intro="@brief "))
     506
     507    def add_ingroup(self, ingroups: List[str]) -> None:
     508        """ Adds an ingroup comment block. """
     509        self.add(["@ingroup " + ingroup for ingroup in sorted(set(ingroups))])
     510
     511    def add_group(self, identifier: str, name: str, ingroups: List[str],
     512                  brief: Optional[str], description: Optional[str]) -> None:
     513        # pylint: disable=too-many-arguments
     514        """ Adds a group definition. """
     515        with self.defgroup_block(identifier, name):
     516            self.add_ingroup(ingroups)
     517            self.add_brief_description(brief)
     518            self.add(self.wrap(description))
     519
     520    @contextmanager
     521    def header_guard(self, filename: str) -> Iterator[None]:
     522        """ Opens a header guard context. """
     523        filename = os.path.basename(filename)
     524        guard = "_" + filename.replace(".", "_").upper()
     525        self.add([f"#ifndef {guard}", f"#define {guard}"])
     526        yield
     527        self.add(f"#endif /* {guard} */")
     528
     529    @contextmanager
     530    def extern_c(self) -> Iterator[None]:
     531        """ Opens an extern "C" context. """
     532        self.add(["#ifdef __cplusplus", "extern \"C\" {", "#endif"])
     533        yield
     534        self.add(["#ifdef __cplusplus", "}", "#endif"])
  • rtemsqual/glossary.py

    r22ec40f r520ba1d  
    5252    content = SphinxContent()
    5353    content.add_header("Glossary", level="*")
    54     content.add_blank_line()
    55     content.add_line(".. glossary::")
    56     content.add_line(":sorted:", indent=1)
     54    content.add(".. glossary::")
     55    with content.indent():
     56        content.append(":sorted:")
    5757    macro_to_sphinx = MacroToSphinx()
    5858    macro_to_sphinx.set_terms(terms)
     
    6161        text = macro_to_sphinx.substitute(item["text"].strip())
    6262        item.register_license_and_copyrights(content)
    63         content.add_definition_item(item["glossary-term"], text, indent=1)
     63        with content.indent():
     64            content.add_definition_item(item["glossary-term"], text)
    6465    content.add_licence_and_copyrights()
    6566    return content
  • rtemsqual/tests/test_content_c.py

    r22ec40f r520ba1d  
    3131    content = CContent()
    3232    content.add_have_config()
    33     assert content.content == """
     33    assert str(content) == """#ifdef HAVE_CONFIG_H
     34#include "config.h"
     35#endif
     36"""
     37    content.add_have_config()
     38    assert str(content) == """#ifdef HAVE_CONFIG_H
     39#include "config.h"
     40#endif
     41
    3442#ifdef HAVE_CONFIG_H
    3543#include "config.h"
     
    3846
    3947
    40 def test_add_lines():
    41     content = CContent()
    42     content.add_lines("")
    43     assert content.content == ""
    44     content.add_lines("a\nb\n")
    45     assert content.content == """a
    46 b
    47 """
    48     content.add_lines(["c", "d"], indent=1)
    49     assert content.content == """a
    50 b
    51   c
    52   d
    53 """
    54 
    55 
    56 def test_add_line_block():
    57     content = CContent()
    58     content.add_line_block("")
    59     assert content.content == ""
    60     content.add_line_block("a")
    61     assert content.content == """
    62 a
    63 """
    64     content.add_line_block(["b", "c"])
    65     assert content.content == """
    66 a
    67 
    68 b
    69 c
    70 """
    71 
    72 
    7348def test_add_includes():
    7449    content = CContent()
    7550    content.add_includes([])
    76     assert content.content == ""
     51    assert str(content) == ""
    7752    content = CContent()
    7853    content.add_includes(["a", "a"])
    79     assert content.content == """
    80 #include <a>
     54    assert str(content) == """#include <a>
     55"""
     56    content.add_includes(["b"])
     57    assert str(content) == """#include <a>
     58
     59#include <b>
    8160"""
    8261    content = CContent()
    8362    content.add_includes(["c", "b"], local=True)
    84     assert content.content == """
    85 #include "b"
     63    assert str(content) == """#include "b"
    8664#include "c"
    8765"""
    8866    content = CContent()
    8967    content.add_includes(["d/f", "d/e"])
    90     assert content.content == """
    91 #include <d/e>
     68    assert str(content) == """#include <d/e>
    9269#include <d/f>
    9370"""
    9471    content = CContent()
    9572    content.add_includes(["h", "g/h"])
    96     assert content.content == """
    97 #include <h>
     73    assert str(content) == """#include <h>
    9874#include <g/h>
    9975"""
    10076    content = CContent()
    10177    content.add_includes(["i/l/k", "i/j/k"])
    102     assert content.content == """
    103 #include <i/j/k>
     78    assert str(content) == """#include <i/j/k>
    10479#include <i/l/k>
    10580"""
    10681
    10782
    108 def test_add_comment_content():
     83def test_comment_block():
    10984    content = CContent()
    110     content.add_comment_content("")
    111     assert content.content == ""
    112     content.add_comment_content("a")
    113     assert content.content == """ *
     85    with content.comment_block():
     86        assert not content.gap
     87        content.add(content.wrap(""))
     88        assert not content.gap
     89        assert str(content) == """/*
     90"""
     91        content.add(content.wrap("a"))
     92        assert content.gap
     93        assert str(content) == """/*
    11494 * a
     95"""
     96        content.add(content.wrap("b"))
     97        assert content.gap
     98        assert str(content) == """/*
     99 * a
     100 *
     101 * b
     102"""
     103        content.gap = False
     104        content.add(content.wrap("c"))
     105        assert content.gap
     106        assert str(content) == """/*
     107 * a
     108 *
     109 * b
     110 * c
    115111"""
    116112
     
    119115    content = CContent()
    120116    content.add_brief_description("")
    121     assert content.content == ""
     117    assert str(content) == ""
     118    content.gap = True
    122119    content.add_brief_description(
    123120        "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT "
     
    126123        "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE "
    127124        "DISCLAIMED.")
    128     assert content.content == """ *
    129  * @brief THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
    130  *        CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
    131  *        INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    132  *        MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    133  *        DISCLAIMED.
     125    assert str(content) == """
     126@brief THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
     127       IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     128       TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
     129       PARTICULAR PURPOSE ARE DISCLAIMED.
    134130"""
  • rtemsqual/tests/test_content_sphinx.py

    r22ec40f r520ba1d  
    2525# POSSIBILITY OF SUCH DAMAGE.
    2626
    27 import os
    2827import pytest
    2928
     
    4140    sc = SphinxContent()
    4241    sc.add_label("x")
    43     assert ".. _x:\n" == sc.content
     42    assert str(sc) == """.. _x:
     43"""
    4444
    4545
     
    4747    sc = SphinxContent()
    4848    sc.add_header("x")
    49     assert "x\n=\n" == sc.content
     49    assert str(sc) == """x
     50=
     51"""
    5052
    5153
    52 def test_add_blank_line():
     54def test_append():
    5355    sc = SphinxContent()
    54     sc.add_blank_line()
    55     assert "\n" == sc.content
     56    sc.append("x")
     57    assert str(sc) == """x
     58"""
     59    with sc.indent():
     60        sc.append("y")
     61        assert str(sc) == """x
     62    y
     63"""
     64        sc.append("")
     65        assert str(sc) == """x
     66    y
    5667
    57 
    58 def test_add_line():
    59     sc = SphinxContent()
    60     sc.add_line("x")
    61     assert "x\n" == sc.content
    62     sc.add_line("y", 1)
    63     assert "x\n    y\n" == sc.content
    64     sc.add_line("")
    65     assert "x\n    y\n\n" == sc.content
    66 
    67 
    68 def test_add_lines():
    69     sc = SphinxContent()
    70     sc.add_lines("x")
    71     assert sc.content == "x\n"
    72     sc.add_lines("y", 1)
    73     assert sc.content == "x\n    y\n"
    74     sc.add_lines(["a", "b"])
    75     assert sc.content == "x\n    y\na\nb\n"
     68"""
    7669
    7770
     
    7972    sc = SphinxContent()
    8073    sc.add_index_entries(["x", "y"])
    81     assert "\n.. index:: x\n.. index:: y\n" == sc.content
     74    assert str(sc) == """.. index:: x
     75.. index:: y
     76"""
    8277    sc.add_index_entries("z")
    83     assert "\n.. index:: x\n.. index:: y\n\n.. index:: z\n" == sc.content
     78    assert str(sc) == """.. index:: x
     79.. index:: y
     80
     81.. index:: z
     82"""
    8483
    8584
     
    8786    sc = SphinxContent()
    8887    sc.add_definition_item("x", ["y", "z"])
    89     assert sc.content == "\nx\n    y\n    z\n"
     88    assert str(sc) == """x
     89    y
     90    z
     91"""
    9092    sc = SphinxContent()
    9193    sc.add_definition_item("a", "\n b\n")
    92     assert sc.content == "\na\n     b\n"
     94    assert str(sc) == """a
     95     b
     96"""
    9397
    9498
     
    98102        sc.register_license("x")
    99103    sc.register_license("CC-BY-SA-4.0")
    100     assert sc.content == ""
     104    assert str(sc) == ""
    101105    sc.add_licence_and_copyrights()
    102     assert ".. SPDX-License-Identifier: CC-BY-SA-4.0\n\n" == sc.content
     106    assert str(sc) == """.. SPDX-License-Identifier: CC-BY-SA-4.0
     107
     108"""
    103109
    104110
     
    108114        sc.register_license("x")
    109115    sc.register_copyright("Copyright (C) A")
    110     assert sc.content == ""
     116    assert str(sc) == ""
    111117    sc.add_licence_and_copyrights()
    112     assert sc.content == """.. SPDX-License-Identifier: CC-BY-SA-4.0
     118    assert str(sc) == """.. SPDX-License-Identifier: CC-BY-SA-4.0
    113119
    114120.. Copyright (C) A
    115121
    116122"""
    117 
    118 
    119 def test_write(tmpdir):
    120     sc = SphinxContent()
    121     sc.add_line("x")
    122     path = os.path.join(tmpdir, "x", "y")
    123     sc.write(path)
    124     with open(path, "r") as src:
    125         assert "x\n" == src.read()
    126     tmpdir.chdir()
    127     path = "z"
    128     sc.write(path)
    129     with open(path, "r") as src:
    130         assert "x\n" == src.read()
    131123
    132124
  • rtemsqual/validation.py

    r22ec40f r520ba1d  
    6666def _add_ingroup(content: CContent, items: List[Item], prefix: str,
    6767                 key: str) -> None:
    68     for desi in sorted(set(_designator(item[key]) for item in items)):
    69         content.add(f""" * @ingroup {prefix}{desi}
    70 """)
     68    content.add_ingroup(
     69        [f"{prefix}{_designator(item[key])}" for item in items])
    7170
    7271
    7372def _add_test_case_description(content: CContent, item: Item,
    7473                               test_case_to_suites: Dict[str, List[Item]],
    75                                name: str, desi: str) -> None:
    76     content.add(f"""
    77 /**
    78  * @defgroup RTEMSTestCase{desi} {name}
    79  *
    80 """)
    81     _add_ingroup(content, test_case_to_suites[item.uid], "RTEMSTestSuite",
    82                  "test-suite-name")
    83     content.add(f""" *
    84  * @brief Test Case
    85  *
    86  * @{{
    87  */
    88 """)
     74                               identifier: str, name: str) -> None:
     75    with content.defgroup_block(f"RTEMSTestCase{identifier}", name):
     76        _add_ingroup(content, test_case_to_suites[item.uid], "RTEMSTestSuite",
     77                     "test-suite-name")
     78        content.add(["@brief Test Case", "", "@{"])
    8979
    9080
     
    9282    actions = item["test-case-actions"]
    9383    if actions:
    94         content.add_comment_content(
    95             "This test case performs the following actions:")
     84        content.add("This test case performs the following actions:")
    9685        for action in actions:
    97             content.add_comment_content(action["description"], intro="- ")
     86            content.add(content.wrap(action["description"], intro="- "))
    9887            for check in action["checks"]:
    99                 content.add_comment_content(check["description"], intro="  - ")
     88                content.add(content.wrap(check["description"], intro="  - "))
    10089
    10190
    10291def _generate_test_case_actions(item: Item, steps: StepWrapper) -> CContent:
    10392    content = CContent()
    104     first = True
    10593    for action in item["test-case-actions"]:
    106         if not first:
    107             content.add_blank_line()
    108         first = False
    109         content.add_lines(action["action"], indent=1)
     94        content.add(action["action"])
    11095        for check in action["checks"]:
    111             the_check = string.Template(check["check"]).substitute(steps)
    112             content.add_lines(the_check, indent=1)
     96            content.append(string.Template(check["check"]).substitute(steps))
    11397    return content
    11498
     
    118102    name = item["test-case-name"]
    119103    desi = _designator(name)
    120     _add_test_case_description(content, item, test_case_to_suites, name, desi)
    121     content.add_line_block(item["test-case-support"])
    122     content.add(f"""
    123 /**
    124  * @fn void T_case_body_{desi}(void)
    125 """)
    126     content.add_brief_description(item["test-case-brief"])
    127     content.add_comment_content(item["test-case-description"])
    128     _add_test_case_action_description(content, item)
    129     content.add(f""" */
    130 """)
     104    _add_test_case_description(content, item, test_case_to_suites, desi, name)
     105    content.add(item["test-case-support"])
     106    with content.function_block(f"void T_case_body_{desi}(void)"):
     107        content.add_brief_description(item["test-case-brief"])
     108        content.add(content.wrap(item["test-case-description"]))
     109        _add_test_case_action_description(content, item)
    131110    fixture = item["test-case-fixture"]
    132111    if fixture:
    133         content.add_lines(f"T_TEST_CASE_FIXTURE({desi}, &{fixture})")
     112        content.append(f"T_TEST_CASE_FIXTURE({desi}, &{fixture})")
    134113    else:
    135         content.add_lines(f"T_TEST_CASE({desi})")
    136     content.add_lines("{")
    137     prologue = item["test-case-prologue"]
    138     first = True
    139     if prologue:
    140         first = False
    141         content.add_lines(prologue, indent=1)
    142     steps = StepWrapper()
    143     action_content = _generate_test_case_actions(item, steps)
    144     if steps.steps > 0:
    145         if not first:
    146             content.add_blank_line()
    147         first = False
    148         content.add_lines(f"T_plan({steps.steps});", indent=1)
    149     if action_content.content:
    150         if not first:
    151             content.add_blank_line()
    152         first = False
    153         content.add(action_content.content)
    154     epilogue = item["test-case-epilogue"]
    155     if epilogue:
    156         if not first:
    157             content.add_blank_line()
    158         content.add_lines(epilogue, indent=1)
    159     content.add(f"""}}
    160 
    161 /** @}} */
    162 """)
     114        content.append(f"T_TEST_CASE({desi})")
     115    content.append("{")
     116    content.gap = False
     117    with content.indent():
     118        content.add(item["test-case-prologue"])
     119        steps = StepWrapper()
     120        action_content = _generate_test_case_actions(item, steps)
     121        if steps.steps > 0:
     122            content.add(f"T_plan({steps.steps});")
     123        content.add(action_content)
     124        content.add(item["test-case-epilogue"])
     125    content.append(["}", "", "/** @} */"])
    163126
    164127
    165128def _generate_test_suite(content: CContent, item: Item) -> None:
    166129    name = item["test-suite-name"]
    167     content.add(f"""
    168 /**
    169  * @defgroup RTEMSTestSuite{_designator(name)} {name}
    170  *
    171  * @ingroup RTEMSTestSuites
    172  *
    173  * @brief Test Suite
    174 """)
    175     content.add_comment_content(item["test-suite-description"])
    176     content.add(f""" *
    177  * @{{
    178  */
    179 
    180 {item["test-suite-code"]}
    181 /** @}} */
    182 """)
     130    with content.defgroup_block(f"RTEMSTestSuite{_designator(name)}", name):
     131        content.add(["@ingroup RTEMSTestSuites", "", "@brief Test Suite"])
     132        content.add(content.wrap(item["test-suite-description"]))
     133        content.add("@{")
     134    content.add(item["test-suite-code"])
     135    content.add("/** @} */")
    183136
    184137
     
    222175            item.register_license_and_copyrights(content)
    223176        content.add_spdx_license_identifier()
    224         content.add(f"""
    225 /**
    226  * @file
    227  *
    228 """)
    229         _add_ingroup(content, self._test_suites, "RTEMSTestSuite",
    230                      "test-suite-name")
    231         _add_ingroup(content, self._test_cases, "RTEMSTestCase",
    232                      "test-case-name")
    233         content.add(f""" */
    234 """)
     177        with content.file_block():
     178            _add_ingroup(content, self._test_suites, "RTEMSTestSuite",
     179                         "test-suite-name")
     180            _add_ingroup(content, self._test_cases, "RTEMSTestCase",
     181                         "test-case-name")
    235182        content.add_copyrights_and_licenses()
    236183        content.add_have_config()
Note: See TracChangeset for help on using the changeset viewer.