StrictDoc Documentation
strictdoc/core/query_engine/query_object.py
Source file coverage
Path:
strictdoc/core/query_engine/query_object.py
Lines:
338
Non-empty lines:
276
Non-empty lines covered with requirements:
276 / 276 (100.0%)
Functions:
59
Functions covered by requirements:
59 / 59 (100.0%)
1
"""
2
@relation(SDOC-SRS-155, scope=file)
3
"""
4
 
5
from typing import Any, List, Optional
6
 
7
from strictdoc.backend.sdoc.models.document import SDocDocument
8
from strictdoc.backend.sdoc.models.document_grammar import (
9
    DocumentGrammar,
10
)
11
from strictdoc.backend.sdoc.models.grammar_element import GrammarElement
12
from strictdoc.backend.sdoc.models.model import (
13
    SDocExtendedElementIF,
14
)
15
from strictdoc.backend.sdoc.models.node import SDocNode, SDocNodeField
16
from strictdoc.backend.sdoc_source_code.models.source_file_info import (
17
    SourceFileTraceabilityInfo,
18
)
19
from strictdoc.core.traceability_index import TraceabilityIndex
20
from strictdoc.helpers.cast import assert_cast
21
 
22
 
23
class Expression:
24
    pass
25
 
26
 
27
class StringExpression:
28
    def __init__(self, parent: Any, string: str):
29
        self.parent: Any = parent
30
        self.string: str = string
31
 
32
 
33
class NoneExpression:
34
    def __init__(self, parent: Any, _: str):
35
        self.parent: Any = parent
36
 
37
 
38
class NodeFieldExpression:
39
    def __init__(self, parent: Any, field_name: str):
40
        self.parent: Any = parent
41
        self.field_name = field_name
42
 
43
 
44
class NodeContainsExpression:
45
    def __init__(self, parent: Any, string: str):
46
        self.parent: Any = parent
47
        self.string: str = string
48
 
49
 
50
class NodeHasParentRequirementsExpression:
51
    def __init__(self, parent: Any, _: Any) -> None:
52
        self.parent: Any = parent
53
 
54
 
55
class NodeContainsAnyFreeTextExpression:
56
    def __init__(self, parent: Any, _: Any):
57
        self.parent: Any = parent
58
 
59
 
60
class NodeHasChildRequirementsExpression:
61
    def __init__(self, parent: Any, _: Any):
62
        self.parent: Any = parent
63
 
64
 
65
class NodeIsRequirementExpression:
66
    def __init__(self, parent: Any, _: Any):
67
        self.parent: Any = parent
68
 
69
 
70
class NodeIsSourceFileExpression:
71
    def __init__(self, parent: Any, _: Any):
72
        self.parent: Any = parent
73
 
74
 
75
class NodeIsSourceFileWithCompleteCoverageExpression:
76
    def __init__(self, parent: Any, _: Any):
77
        self.parent: Any = parent
78
 
79
 
80
class NodeIsSourceFileWithPartialCoverageExpression:
81
    def __init__(self, parent: Any, _: Any):
82
        self.parent: Any = parent
83
 
84
 
85
class NodeIsSourceFileWithNoCoverageExpression:
86
    def __init__(self, parent: Any, _: Any):
87
        self.parent: Any = parent
88
 
89
 
90
class NodeIsSectionExpression:
91
    def __init__(self, parent: Any, _: Any):
92
        self.parent: Any = parent
93
 
94
 
95
class NodeIsRootExpression:
96
    def __init__(self, parent: Any, _: Any):
97
        self.parent: Any = parent
98
 
99
 
100
class EqualExpression:
101
    def __init__(self, parent: Any, lhs_expr: Expression, rhs_expr: Expression):
102
        self.parent: Any = parent
103
        self.lhs_expr: Expression = lhs_expr
104
        self.rhs_expr: Expression = rhs_expr
105
 
106
 
107
class AndExpression:
108
    def __init__(self, parent: Any, expressions: List[Expression]):
109
        self.parent: Any = parent
110
        self.expressions: List[Expression] = expressions
111
 
112
 
113
class OrExpression:
114
    def __init__(self, parent: Any, expressions: List[Expression]):
115
        self.parent: Any = parent
116
        self.expressions: List[Expression] = expressions
117
 
118
 
119
class NotExpression:
120
    def __init__(self, parent: Any, expression: Expression):
121
        self.parent: Any = parent
122
        self.expression: Expression = expression
123
 
124
 
125
class NotEqualExpression:
126
    def __init__(self, parent: Any, lhs_expr: Expression, rhs_expr: Expression):
127
        self.parent: Any = parent
128
        self.lhs_expr: Expression = lhs_expr
129
        self.rhs_expr: Expression = rhs_expr
130
 
131
 
132
class InExpression:
133
    def __init__(self, parent: Any, lhs_expr: Expression, rhs_expr: Expression):
134
        self.parent: Any = parent
135
        self.lhs_expr: Expression = lhs_expr
136
        self.rhs_expr: Expression = rhs_expr
137
 
138
 
139
class NotInExpression:
140
    def __init__(self, parent: Any, lhs_expr: Expression, rhs_expr: Expression):
141
        self.parent: Any = parent
142
        self.lhs_expr: Expression = lhs_expr
143
        self.rhs_expr: Expression = rhs_expr
144
 
145
 
146
class Query:
147
    def __init__(self, root_expression: Any):
148
        self.root_expression: Any = root_expression
149
 
150
 
151
class QueryNullObject:
152
    def evaluate(self, _: Any) -> bool:
153
        return True
154
 
155
 
156
class QueryObject:
157
    def __init__(
158
        self, query: Query, traceability_index: TraceabilityIndex
159
    ) -> None:
160
        self.query: Query = query
161
        self.traceability_index: TraceabilityIndex = traceability_index
162
 
163
    def evaluate(self, node: SDocExtendedElementIF) -> bool:
164
        return self._evaluate(node, self.query.root_expression)
165
 
166
    def _evaluate(self, node: SDocExtendedElementIF, expression: Any) -> bool:
167
        if isinstance(expression, EqualExpression):
168
            return self._evaluate_equal(node, expression)
169
        if isinstance(expression, NotEqualExpression):
170
            return self._evaluate_not_equal(node, expression)
171
        if isinstance(expression, NodeContainsExpression):
172
            return self._evaluate_node_contains(node, expression)
173
        if isinstance(expression, NodeContainsAnyFreeTextExpression):
174
            return self._evaluate_node_contains_any_text(node)
175
        if isinstance(expression, NodeHasParentRequirementsExpression):
176
            return self._evaluate_node_has_parent_requirements(node)
177
        if isinstance(expression, NodeHasChildRequirementsExpression):
178
            return self._evaluate_node_has_child_requirements(node)
179
        if isinstance(expression, NodeIsRequirementExpression):
180
            return (
181
                isinstance(node, SDocNode) and node.node_type == "REQUIREMENT"
182
            )
183
        if isinstance(expression, NodeIsSectionExpression):
184
            return isinstance(node, SDocNode) and node.node_type == "SECTION"
185
        if isinstance(expression, NodeIsSourceFileExpression):
186
            return isinstance(node, SourceFileTraceabilityInfo)
187
        if isinstance(
188
            expression, NodeIsSourceFileWithCompleteCoverageExpression
189
        ):
190
            return (
191
                isinstance(node, SourceFileTraceabilityInfo)
192
                and node.get_coverage() == 100
193
            )
194
        if isinstance(
195
            expression, NodeIsSourceFileWithPartialCoverageExpression
196
        ):
197
            return isinstance(node, SourceFileTraceabilityInfo) and (
198
                0 < node.get_coverage() < 100
199
            )
200
        if isinstance(expression, NodeIsSourceFileWithNoCoverageExpression):
201
            return (
202
                isinstance(node, SourceFileTraceabilityInfo)
203
                and node.get_coverage() == 0
204
            )
205
        if isinstance(expression, NodeIsRootExpression):
206
            if isinstance(node, SDocNode):
207
                return node.is_root
208
            raise RuntimeError(
209
                "The node.is_root expression can be only called on nodes."
210
            )
211
        if isinstance(expression, NotExpression):
212
            return not self._evaluate(node, expression.expression)
213
        if isinstance(expression, AndExpression):
214
            for sub_expression_ in expression.expressions:
215
                if not self._evaluate(node, sub_expression_):
216
                    return False
217
            return True
218
        if isinstance(expression, OrExpression):
219
            for sub_expression_ in expression.expressions:
220
                if self._evaluate(node, sub_expression_):
221
                    return True
222
            return False
223
        if isinstance(expression, InExpression):
224
            if (
225
                rhs_value := self._evaluate_value(node, expression.rhs_expr)
226
            ) is not None and (
227
                lhs_value := self._evaluate_value(node, expression.lhs_expr)
228
            ) is not None:
229
                return lhs_value in rhs_value
230
            return False
231
        if isinstance(expression, NotInExpression):
232
            if (
233
                rhs_value := self._evaluate_value(node, expression.rhs_expr)
234
            ) is not None and (
235
                lhs_value := self._evaluate_value(node, expression.lhs_expr)
236
            ) is not None:
237
                return lhs_value not in rhs_value
238
            return False
239
        assert 0, expression
240
 
241
    def _evaluate_equal(
242
        self, node: SDocExtendedElementIF, expression: EqualExpression
243
    ) -> bool:
244
        return self._evaluate_value(
245
            node, expression.lhs_expr
246
        ) == self._evaluate_value(node, expression.rhs_expr)
247
 
248
    def _evaluate_not_equal(
249
        self, node: SDocExtendedElementIF, expression: NotEqualExpression
250
    ) -> bool:
251
        return self._evaluate_value(
252
            node, expression.lhs_expr
253
        ) != self._evaluate_value(node, expression.rhs_expr)
254
 
255
    def _evaluate_value(
256
        self, node: SDocExtendedElementIF, expression: Any
257
    ) -> Optional[str]:
258
        if isinstance(expression, NodeFieldExpression):
259
            return self._evaluate_node_field_expression(node, expression)
260
        if isinstance(expression, StringExpression):
261
            return expression.string
262
        if isinstance(expression, NoneExpression):
263
            return None
264
        assert 0, expression
265
 
266
    def _evaluate_node_field_expression(
267
        self,
268
        node: SDocExtendedElementIF,
269
        expression: NodeFieldExpression,
270
    ) -> Optional[str]:
271
        field_name = expression.field_name
272
        if isinstance(node, SDocNode):
273
            requirement: SDocNode = assert_cast(node, SDocNode)
274
            requirement_document: SDocDocument = assert_cast(
275
                requirement.get_document(), SDocDocument
276
            )
277
            document_grammar: DocumentGrammar = assert_cast(
278
                requirement_document.grammar, DocumentGrammar
279
            )
280
            element: GrammarElement = document_grammar.elements_by_type[
281
                requirement.node_type
282
            ]
283
            grammar_field_titles = list(map(lambda f: f.title, element.fields))
284
            if field_name not in grammar_field_titles:
285
                return None
286
            field_value = requirement._get_cached_field(field_name, False)
287
            if field_value is not None:
288
                return field_value
289
            return None
290
        else:
291
            raise NotImplementedError
292
 
293
    def _evaluate_node_has_parent_requirements(
294
        self, node: SDocExtendedElementIF
295
    ) -> bool:
296
        if not isinstance(node, SDocNode):
297
            raise TypeError(
298
                f"node.has_parent_requirements can be only called on "
299
                f"Requirement objects, got: {node.__class__.__name__}. To fix "
300
                f"the error, prepend your query with node.is_requirement."
301
            )
302
        return self.traceability_index.has_parent_requirements(node)
303
 
304
    def _evaluate_node_has_child_requirements(
305
        self, node: SDocExtendedElementIF
306
    ) -> bool:
307
        if not isinstance(node, SDocNode):
308
            raise TypeError(
309
                f"node.has_child_requirements can be only called on "
310
                f"Requirement objects, got: {node.__class__.__name__}. To fix "
311
                f"the error, prepend your query with node.is_requirement."
312
            )
313
        return self.traceability_index.has_children_requirements(node)
314
 
315
    def _evaluate_node_contains(
316
        self,
317
        node: SDocExtendedElementIF,
318
        expression: NodeContainsExpression,
319
    ) -> bool:
320
        if isinstance(node, SDocNode):
321
            requirement = assert_cast(node, SDocNode)
322
            requirement_field_: SDocNodeField
323
            for requirement_field_ in requirement.enumerate_fields():
324
                if expression.string in requirement_field_.get_text_value():
325
                    return True
326
            return False
327
        raise NotImplementedError
328
 
329
    def _evaluate_node_contains_any_text(
330
        self, node: SDocExtendedElementIF
331
    ) -> bool:
332
        if not (isinstance(node, SDocNode) and node.node_type == "SECTION"):
333
            raise TypeError(
334
                f"node.contains_any_text can be only called on "
335
                f"SECTION objects, got: {node.__class__.__name__}. To fix "
336
                f"the error, prepend your query with node.is_section."
337
            )
338
        return node.has_any_text_nodes()