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
pass25
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()