StrictDoc Documentation
strictdoc/backend/sdoc/models/document_grammar.py
Source file coverage
Path:
strictdoc/backend/sdoc/models/document_grammar.py
Lines:
421
Non-empty lines:
381
Non-empty lines covered with requirements:
381 / 381 (100.0%)
Functions:
15
Functions covered by requirements:
15 / 15 (100.0%)
1
"""
2
@relation(SDOC-SRS-19, scope=file)
3
"""
4
 
5
from typing import Dict, List, Optional, Set
6
 
7
from strictdoc.backend.sdoc.models.grammar_element import (
8
    GrammarElement,
9
    GrammarElementFieldString,
10
    GrammarElementFieldType,
11
)
12
from strictdoc.backend.sdoc.models.model import (
13
    RequirementFieldName,
14
    SDocDocumentIF,
15
    SDocGrammarIF,
16
)
17
 
18
 
19
class DocumentGrammar(SDocGrammarIF):
20
    def __init__(
21
        self,
22
        parent: Optional[SDocDocumentIF],
23
        elements: List[GrammarElement],
24
        import_from_file: Optional[str] = None,
25
    ) -> None:
26
        self.parent: Optional[SDocDocumentIF] = parent
27
        self.elements: List[GrammarElement] = elements
28
 
29
        self.registered_elements: Set[str] = set()
30
        self.elements_by_type: Dict[str, GrammarElement] = {}
31
 
32
        self.update_with_elements(elements)
33
 
34
        # The textX parser passes an empty string instead of None when
35
        # import_from_file is not provided in input.
36
        if import_from_file is not None and len(import_from_file) == 0:
37
            import_from_file = None
38
        self.import_from_file: Optional[str] = import_from_file
39
 
40
        self.is_default = False
41
 
42
        self.ng_line_start: Optional[int] = None
43
        self.ng_col_start: Optional[int] = None
44
 
45
    @staticmethod
46
    def create_default(
47
        parent: Optional[SDocDocumentIF],
48
        enable_mid: bool = False,
49
        include_child_relation: bool = False,
50
    ) -> "DocumentGrammar":
51
        """
52
        @relation(SDOC-SRS-93, scope=function)
53
        """
54
 
55
        text_element: GrammarElement = (
56
            DocumentGrammar.create_default_text_element(enable_mid=enable_mid)
57
        )
58
 
59
        # @relation(SDOC-SRS-132, scope=range_start)
60
        fields: List[GrammarElementFieldType] = []
61
 
62
        if enable_mid:
63
            fields.append(
64
                GrammarElementFieldString(
65
                    parent=None,
66
                    title=RequirementFieldName.MID,
67
                    human_title=None,
68
                    required="False",
69
                )
70
            )
71
 
72
        fields.extend(
73
            [
74
                GrammarElementFieldString(
75
                    parent=None,
76
                    title=RequirementFieldName.UID,
77
                    human_title=None,
78
                    required="False",
79
                ),
80
                GrammarElementFieldString(
81
                    parent=None,
82
                    title=RequirementFieldName.LEVEL,
83
                    human_title=None,
84
                    required="False",
85
                ),
86
                GrammarElementFieldString(
87
                    parent=None,
88
                    title=RequirementFieldName.STATUS,
89
                    human_title=None,
90
                    required="False",
91
                ),
92
                GrammarElementFieldString(
93
                    parent=None,
94
                    title=RequirementFieldName.TAGS,
95
                    human_title=None,
96
                    required="False",
97
                ),
98
                GrammarElementFieldString(
99
                    parent=None,
100
                    title=RequirementFieldName.TITLE,
101
                    human_title=None,
102
                    required="False",
103
                ),
104
                GrammarElementFieldString(
105
                    parent=None,
106
                    title=RequirementFieldName.STATEMENT,
107
                    human_title=None,
108
                    required="False",
109
                ),
110
                GrammarElementFieldString(
111
                    parent=None,
112
                    title=RequirementFieldName.RATIONALE,
113
                    human_title=None,
114
                    required="False",
115
                ),
116
                GrammarElementFieldString(
117
                    parent=None,
118
                    title=RequirementFieldName.COMMENT,
119
                    human_title=None,
120
                    required="False",
121
                ),
122
            ]
123
        )
124
 
125
        requirement_element = GrammarElement(
126
            parent=None,
127
            tag="REQUIREMENT",
128
            property_is_composite="",
129
            property_prefix="",
130
            property_view_style="",
131
            fields=fields,
132
            relations=[],
133
        )
134
        # @relation(SDOC-SRS-132, scope=range_end)
135
 
136
        requirement_element.relations = GrammarElement.create_default_relations(
137
            requirement_element,
138
            include_child=include_child_relation,
139
        )
140
 
141
        elements: List[GrammarElement] = []
142
 
143
        section_element: GrammarElement = (
144
            DocumentGrammar.create_default_section_element(
145
                enable_mid=enable_mid
146
            )
147
        )
148
        elements.append(section_element)
149
 
150
        elements.append(text_element)
151
        elements.append(requirement_element)
152
 
153
        grammar = DocumentGrammar(
154
            parent=parent, elements=elements, import_from_file=None
155
        )
156
        grammar.is_default = True
157
        text_element.parent = grammar
158
        requirement_element.parent = grammar
159
 
160
        return grammar
161
 
162
    @staticmethod
163
    def create_for_test_report(parent: SDocDocumentIF) -> "DocumentGrammar":
164
        section_element: GrammarElement = (
165
            DocumentGrammar.create_default_section_element(
166
                parent=None, enable_mid=parent.config.enable_mid == True
167
            )
168
        )
169
        text_element: GrammarElement = (
170
            DocumentGrammar.create_default_text_element(
171
                parent=None, enable_mid=parent.config.enable_mid == True
172
            )
173
        )
174
 
175
        fields: List[GrammarElementFieldType] = [
176
            GrammarElementFieldString(
177
                parent=None,
178
                title=RequirementFieldName.UID,
179
                human_title=None,
180
                required="False",
181
            ),
182
            GrammarElementFieldString(
183
                parent=None,
184
                title="TEST_PATH",
185
                human_title=None,
186
                required="False",
187
            ),
188
            GrammarElementFieldString(
189
                parent=None,
190
                title="TEST_FUNCTION",
191
                human_title=None,
192
                required="False",
193
            ),
194
            GrammarElementFieldString(
195
                parent=None,
196
                title="DURATION",
197
                human_title=None,
198
                required="False",
199
            ),
200
            GrammarElementFieldString(
201
                parent=None,
202
                title=RequirementFieldName.STATUS,
203
                human_title=None,
204
                required="True",
205
            ),
206
            GrammarElementFieldString(
207
                parent=None,
208
                title=RequirementFieldName.TITLE,
209
                human_title=None,
210
                required="True",
211
            ),
212
            GrammarElementFieldString(
213
                parent=None,
214
                title=RequirementFieldName.STATEMENT,
215
                human_title=None,
216
                required="False",
217
            ),
218
        ]
219
        requirement_element = GrammarElement(
220
            parent=None,
221
            tag="TEST_RESULT",
222
            property_is_composite="",
223
            property_prefix="",
224
            property_view_style="",
225
            fields=fields,
226
            relations=[],
227
        )
228
 
229
        requirement_element.relations = GrammarElement.create_default_relations(
230
            requirement_element
231
        )
232
 
233
        elements: List[GrammarElement] = [
234
            section_element,
235
            text_element,
236
            requirement_element,
237
        ]
238
        grammar = DocumentGrammar(
239
            parent=parent, elements=elements, import_from_file=None
240
        )
241
        grammar.is_default = True
242
        section_element.parent = grammar
243
        text_element.parent = grammar
244
        requirement_element.parent = grammar
245
 
246
        return grammar
247
 
248
    def get_element_by_mid(self, element_mid: str) -> GrammarElement:
249
        for element_ in self.elements:
250
            if element_.mid == element_mid:
251
                return element_
252
        raise AssertionError(
253
            f"Could not find a grammar element with MID: {element_mid}"
254
        )
255
 
256
    def dump_fields(self, node_type: str) -> str:
257
        return ", ".join(
258
            list(
259
                map(
260
                    lambda g: g.title,
261
                    self.elements_by_type[node_type].fields,
262
                )
263
            )
264
        )
265
 
266
    def has_text_element(self) -> bool:
267
        for element_ in self.elements:
268
            if element_.tag == "TEXT":
269
                return True
270
        return False
271
 
272
    def has_custom_elements(self) -> bool:
273
        """
274
        Return True when the grammar defines element types beyond the
275
        built-in set {SECTION, TEXT, REQUIREMENT}.
276
 
277
        When the grammar has not yet been resolved (elements_by_type is
278
        empty but import_from_file is set), True is returned so that the
279
        Markdown writer can emit TYPE fields for round-trip safety.
280
        """
281
        if self.is_default:
282
            return False
283
        _builtin = {"SECTION", "TEXT", "REQUIREMENT"}
284
        if len(self.elements_by_type) == 0:
285
            return self.import_from_file is not None
286
        return any(t not in _builtin for t in self.elements_by_type)
287
 
288
    def add_element_first(self, element: GrammarElement) -> None:
289
        self.elements.insert(0, element)
290
        self.elements_by_type[element.tag] = element
291
        self.registered_elements.add(element.tag)
292
        self.is_default = False
293
 
294
    def update_element(
295
        self, existing_element: GrammarElement, updated_element: GrammarElement
296
    ) -> None:
297
        element_index = self.elements.index(existing_element)
298
        self.elements[element_index] = updated_element
299
        self.elements_by_type[updated_element.tag] = updated_element
300
        self.is_default = False
301
 
302
    def update_with_elements(self, elements: List[GrammarElement]) -> None:
303
        # When elements are created by code, not by textX, it is convenient
304
        # if their .parent is set here automatically.
305
        for element_ in elements:
306
            element_.parent = self
307
 
308
        registered_elements: Set[str] = set()
309
        elements_by_type: Dict[str, GrammarElement] = {}
310
        for element in elements:
311
            registered_elements.add(element.tag)
312
            elements_by_type[element.tag] = element
313
 
314
        self.elements = elements
315
        self.registered_elements = registered_elements
316
        self.elements_by_type = elements_by_type
317
 
318
    @staticmethod
319
    def create_default_text_element(
320
        parent: Optional["DocumentGrammar"] = None,
321
        enable_mid: bool = False,
322
    ) -> GrammarElement:
323
        fields: List[GrammarElementFieldType] = []
324
        if enable_mid:
325
            fields.append(
326
                GrammarElementFieldString(
327
                    parent=None,
328
                    title=RequirementFieldName.MID,
329
                    human_title=None,
330
                    required="False",
331
                )
332
            )
333
        fields.extend(
334
            [
335
                GrammarElementFieldString(
336
                    parent=None,
337
                    title=RequirementFieldName.UID,
338
                    human_title=None,
339
                    required="False",
340
                ),
341
                GrammarElementFieldString(
342
                    parent=None,
343
                    title=RequirementFieldName.STATEMENT,
344
                    human_title=None,
345
                    required="True",
346
                ),
347
            ]
348
        )
349
        text_element = GrammarElement(
350
            parent=parent,
351
            tag="TEXT",
352
            property_is_composite="",
353
            property_prefix="",
354
            property_view_style="",
355
            fields=fields,
356
            relations=[],
357
        )
358
        return text_element
359
 
360
    @staticmethod
361
    def create_default_section_element(
362
        parent: Optional["DocumentGrammar"] = None, enable_mid: bool = False
363
    ) -> GrammarElement:
364
        fields: List[GrammarElementFieldType] = []
365
        if enable_mid:
366
            fields.append(
367
                GrammarElementFieldString(
368
                    parent=None,
369
                    title=RequirementFieldName.MID,
370
                    human_title=None,
371
                    required="False",
372
                )
373
            )
374
        fields.append(
375
            GrammarElementFieldString(
376
                parent=None,
377
                title=RequirementFieldName.UID,
378
                human_title=None,
379
                required="False",
380
            )
381
        )
382
        fields.append(
383
            GrammarElementFieldString(
384
                parent=None,
385
                title=RequirementFieldName.LEVEL,
386
                human_title=None,
387
                required="False",
388
            )
389
        )
390
        fields.append(
391
            GrammarElementFieldString(
392
                parent=None,
393
                title=RequirementFieldName.PREFIX,
394
                human_title=None,
395
                required="False",
396
            )
397
        )
398
        fields.append(
399
            GrammarElementFieldString(
400
                parent=None,
401
                title=RequirementFieldName.TITLE,
402
                human_title=None,
403
                required="True",
404
            ),
405
        )
406
        section_element = GrammarElement(
407
            parent=parent,
408
            tag="SECTION",
409
            property_is_composite="True",
410
            property_prefix="",
411
            property_view_style="",
412
            fields=fields,
413
            relations=[],
414
        )
415
        return section_element
416
 
417
 
418
class DocumentGrammarWrapper:
419
    def __init__(self, grammar: DocumentGrammar):
420
        assert isinstance(grammar, DocumentGrammar), grammar
421
        self.grammar: DocumentGrammar = grammar