Path:
strictdoc/core/query_engine/query_object.py
Lines:
416
Non-empty lines:
344
Non-empty lines covered with requirements:
344 / 344 (100.0%)
Functions:
69
Functions covered by requirements:
69 / 69 (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 StringListExpression:
39
def __init__(self, parent: Any, strings: List[StringExpression]):
40
self.parent: Any = parent
41
self.strings: List[StringExpression] = strings
42
43
44
class NodeFieldExpression:
45
def __init__(self, parent: Any, field_name: str):
46
self.parent: Any = parent
47
self.field_name = field_name
48
49
50
class NodeContainsExpression:
51
def __init__(self, parent: Any, string: str):
52
self.parent: Any = parent
53
self.string: str = string
54
55
56
class NodeHasParentRequirementsExpression:
57
def __init__(self, parent: Any, _: Any) -> None:
58
self.parent: Any = parent
59
60
61
class NodeContainsAnyFreeTextExpression:
62
def __init__(self, parent: Any, _: Any):
63
self.parent: Any = parent
64
65
66
class NodeHasChildRequirementsExpression:
67
def __init__(self, parent: Any, _: Any):
68
self.parent: Any = parent
69
70
71
class NodeIsRequirementExpression:
72
def __init__(self, parent: Any, _: Any):
73
self.parent: Any = parent
74
75
76
class NodeIsSourceFileExpression:
77
def __init__(self, parent: Any, _: Any):
78
self.parent: Any = parent
79
80
81
class NodeIsSourceFileWithCompleteCoverageExpression:
82
def __init__(self, parent: Any, _: Any):
83
self.parent: Any = parent
84
85
86
class NodeIsSourceFileWithPartialCoverageExpression:
87
def __init__(self, parent: Any, _: Any):
88
self.parent: Any = parent
89
90
91
class NodeIsSourceFileWithNoCoverageExpression:
92
def __init__(self, parent: Any, _: Any):
93
self.parent: Any = parent
94
95
96
class NodeIsSectionExpression:
97
def __init__(self, parent: Any, _: Any):
98
self.parent: Any = parent
99
100
101
class NodeIsRootExpression:
102
def __init__(self, parent: Any, _: Any):
103
self.parent: Any = parent
104
105
106
class EqualExpression:
107
def __init__(self, parent: Any, lhs_expr: Expression, rhs_expr: Expression):
108
self.parent: Any = parent
109
self.lhs_expr: Expression = lhs_expr
110
self.rhs_expr: Expression = rhs_expr
111
112
113
class AndExpression:
114
def __init__(self, parent: Any, expressions: List[Expression]):
115
self.parent: Any = parent
116
self.expressions: List[Expression] = expressions
117
118
119
class OrExpression:
120
def __init__(self, parent: Any, expressions: List[Expression]):
121
self.parent: Any = parent
122
self.expressions: List[Expression] = expressions
123
124
125
class NotExpression:
126
def __init__(self, parent: Any, expression: Expression):
127
self.parent: Any = parent
128
self.expression: Expression = expression
129
130
131
class NotEqualExpression:
132
def __init__(self, parent: Any, lhs_expr: Expression, rhs_expr: Expression):
133
self.parent: Any = parent
134
self.lhs_expr: Expression = lhs_expr
135
self.rhs_expr: Expression = rhs_expr
136
137
138
class InExpression:
139
def __init__(self, parent: Any, lhs_expr: Expression, rhs_expr: Expression):
140
self.parent: Any = parent
141
self.lhs_expr: Expression = lhs_expr
142
self.rhs_expr: Expression = rhs_expr
143
144
145
class NotInExpression:
146
def __init__(self, parent: Any, lhs_expr: Expression, rhs_expr: Expression):
147
self.parent: Any = parent
148
self.lhs_expr: Expression = lhs_expr
149
self.rhs_expr: Expression = rhs_expr
150
151
152
class AnyInExpression:
153
def __init__(
154
self,
155
parent: Any,
156
lhs_expr: StringListExpression,
157
rhs_expr: Expression,
158
):159
self.parent: Any = parent
160
self.lhs_expr: StringListExpression = lhs_expr
161
self.rhs_expr: Expression = rhs_expr
162
163
164
class AllInExpression:
165
def __init__(
166
self,
167
parent: Any,
168
lhs_expr: StringListExpression,
169
rhs_expr: Expression,
170
):171
self.parent: Any = parent
172
self.lhs_expr: StringListExpression = lhs_expr
173
self.rhs_expr: Expression = rhs_expr
174
175
176
class NoneInExpression:
177
def __init__(
178
self,
179
parent: Any,
180
lhs_expr: StringListExpression,
181
rhs_expr: Expression,
182
):183
self.parent: Any = parent
184
self.lhs_expr: StringListExpression = lhs_expr
185
self.rhs_expr: Expression = rhs_expr
186
187
188
class Query:
189
def __init__(self, root_expression: Any):
190
self.root_expression: Any = root_expression
191
192
193
class QueryNullObject:
194
def evaluate(self, _: Any) -> bool:
195
return True
196
197
198
class QueryObject:
199
def __init__(
200
self, query: Query, traceability_index: TraceabilityIndex
201
) -> None:
202
self.query: Query = query
203
self.traceability_index: TraceabilityIndex = traceability_index
204
205
def evaluate(self, node: SDocExtendedElementIF) -> bool:
206
return self._evaluate(node, self.query.root_expression)
207
208
def _evaluate(self, node: SDocExtendedElementIF, expression: Any) -> bool:
209
if isinstance(expression, EqualExpression):
210
return self._evaluate_equal(node, expression)
211
if isinstance(expression, NotEqualExpression):
212
return self._evaluate_not_equal(node, expression)
213
if isinstance(expression, NodeContainsExpression):
214
return self._evaluate_node_contains(node, expression)
215
if isinstance(expression, NodeContainsAnyFreeTextExpression):
216
return self._evaluate_node_contains_any_text(node)
217
if isinstance(expression, NodeHasParentRequirementsExpression):
218
return self._evaluate_node_has_parent_requirements(node)
219
if isinstance(expression, NodeHasChildRequirementsExpression):
220
return self._evaluate_node_has_child_requirements(node)
221
if isinstance(expression, NodeIsRequirementExpression):
222
return (
223
isinstance(node, SDocNode) and node.node_type == "REQUIREMENT"
224
)225
if isinstance(expression, NodeIsSectionExpression):
226
return isinstance(node, SDocNode) and node.node_type == "SECTION"
227
if isinstance(expression, NodeIsSourceFileExpression):
228
return isinstance(node, SourceFileTraceabilityInfo)
229
if isinstance(
230
expression, NodeIsSourceFileWithCompleteCoverageExpression
231
):232
return (
233
isinstance(node, SourceFileTraceabilityInfo)
234
and node.get_coverage() == 100
235
)236
if isinstance(
237
expression, NodeIsSourceFileWithPartialCoverageExpression
238
):239
return isinstance(node, SourceFileTraceabilityInfo) and (
240
0 < node.get_coverage() < 100
241
)242
if isinstance(expression, NodeIsSourceFileWithNoCoverageExpression):
243
return (
244
isinstance(node, SourceFileTraceabilityInfo)
245
and node.get_coverage() == 0
246
)247
if isinstance(expression, NodeIsRootExpression):
248
if isinstance(node, SDocNode):
249
return node.is_root
250
raise RuntimeError(
251
"The node.is_root expression can be only called on nodes."252
)253
if isinstance(expression, NotExpression):
254
return not self._evaluate(node, expression.expression)
255
if isinstance(expression, AndExpression):
256
for sub_expression_ in expression.expressions:
257
if not self._evaluate(node, sub_expression_):
258
return False
259
return True
260
if isinstance(expression, OrExpression):
261
for sub_expression_ in expression.expressions:
262
if self._evaluate(node, sub_expression_):
263
return True
264
return False
265
if isinstance(expression, InExpression):
266
if (
267
rhs_value := self._evaluate_value(node, expression.rhs_expr)
268
) is not None and (
269
lhs_value := self._evaluate_value(node, expression.lhs_expr)
270
) is not None:
271
return lhs_value in rhs_value
272
return False
273
if isinstance(expression, NotInExpression):
274
if (
275
rhs_value := self._evaluate_value(node, expression.rhs_expr)
276
) is not None and (
277
lhs_value := self._evaluate_value(node, expression.lhs_expr)
278
) is not None:
279
return lhs_value not in rhs_value
280
return False
281
if isinstance(expression, AnyInExpression):
282
return any(
283
self._evaluate_list_in(node, lhs_expr_, expression.rhs_expr)
284
for lhs_expr_ in expression.lhs_expr.strings
285
)286
if isinstance(expression, AllInExpression):
287
return all(
288
self._evaluate_list_in(node, lhs_expr_, expression.rhs_expr)
289
for lhs_expr_ in expression.lhs_expr.strings
290
)291
if isinstance(expression, NoneInExpression):
292
return not any(
293
self._evaluate_list_in(node, lhs_expr_, expression.rhs_expr)
294
for lhs_expr_ in expression.lhs_expr.strings
295
)296
assert 0, expression
297
298
def _evaluate_list_in(
299
self,
300
node: SDocExtendedElementIF,
301
lhs_expr: StringExpression,
302
rhs_expr: Expression,
303
) -> bool:
304
if (rhs_value := self._evaluate_value(node, rhs_expr)) is not None and (
305
lhs_value := self._evaluate_value(node, lhs_expr)
306
) is not None:
307
if (
308
isinstance(rhs_expr, NodeFieldExpression)
309
and rhs_expr.field_name == "TAGS"
310
):311
return lhs_value in self._split_list_field_value(rhs_value)
312
return lhs_value in rhs_value
313
return False
314
315
@staticmethod316
def _split_list_field_value(field_value: str) -> List[str]:
317
return field_value.split(", ")
318
319
def _evaluate_equal(
320
self, node: SDocExtendedElementIF, expression: EqualExpression
321
) -> bool:
322
return self._evaluate_value(
323
node, expression.lhs_expr
324
) == self._evaluate_value(node, expression.rhs_expr)
325
326
def _evaluate_not_equal(
327
self, node: SDocExtendedElementIF, expression: NotEqualExpression
328
) -> bool:
329
return self._evaluate_value(
330
node, expression.lhs_expr
331
) != self._evaluate_value(node, expression.rhs_expr)
332
333
def _evaluate_value(
334
self, node: SDocExtendedElementIF, expression: Any
335
) -> Optional[str]:
336
if isinstance(expression, NodeFieldExpression):
337
return self._evaluate_node_field_expression(node, expression)
338
if isinstance(expression, StringExpression):
339
return expression.string
340
if isinstance(expression, NoneExpression):
341
return None
342
assert 0, expression
343
344
def _evaluate_node_field_expression(
345
self,
346
node: SDocExtendedElementIF,
347
expression: NodeFieldExpression,
348
) -> Optional[str]:
349
field_name = expression.field_name
350
if isinstance(node, SDocNode):
351
requirement: SDocNode = assert_cast(node, SDocNode)
352
requirement_document: SDocDocument = assert_cast(
353
requirement.get_document(), SDocDocument
354
)355
document_grammar: DocumentGrammar = assert_cast(
356
requirement_document.grammar, DocumentGrammar
357
)358
element: GrammarElement = document_grammar.elements_by_type[
359
requirement.node_type
360
]361
grammar_field_titles = list(map(lambda f: f.title, element.fields))
362
if field_name not in grammar_field_titles:
363
return None
364
field_value = requirement._get_cached_field(field_name, False)
365
if field_value is not None:
366
return field_value
367
return None
368
else:
369
raise NotImplementedError
370
371
def _evaluate_node_has_parent_requirements(
372
self, node: SDocExtendedElementIF
373
) -> bool:
374
if not isinstance(node, SDocNode):
375
raise TypeError(
376
f"node.has_parent_requirements can be only called on "
377
f"Requirement objects, got: {node.__class__.__name__}. To fix "
378
f"the error, prepend your query with node.is_requirement."
379
)380
return self.traceability_index.has_parent_requirements(node)
381
382
def _evaluate_node_has_child_requirements(
383
self, node: SDocExtendedElementIF
384
) -> bool:
385
if not isinstance(node, SDocNode):
386
raise TypeError(
387
f"node.has_child_requirements can be only called on "
388
f"Requirement objects, got: {node.__class__.__name__}. To fix "
389
f"the error, prepend your query with node.is_requirement."
390
)391
return self.traceability_index.has_children_requirements(node)
392
393
def _evaluate_node_contains(
394
self,
395
node: SDocExtendedElementIF,
396
expression: NodeContainsExpression,
397
) -> bool:
398
if isinstance(node, SDocNode):
399
requirement = assert_cast(node, SDocNode)
400
requirement_field_: SDocNodeField
401
for requirement_field_ in requirement.enumerate_fields():
402
if expression.string in requirement_field_.get_text_value():
403
return True
404
return False
405
raise NotImplementedError
406
407
def _evaluate_node_contains_any_text(
408
self, node: SDocExtendedElementIF
409
) -> bool:
410
if not (isinstance(node, SDocNode) and node.node_type == "SECTION"):
411
raise TypeError(
412
f"node.contains_any_text can be only called on "
413
f"SECTION objects, got: {node.__class__.__name__}. To fix "
414
f"the error, prepend your query with node.is_section."
415
)416
return node.has_any_text_nodes()