Path:
strictdoc/backend/sdoc/models/reference.py
Lines:
179
Non-empty lines:
148
Non-empty lines covered with requirements:
148 / 148 (100.0%)
Functions:
16
Functions covered by requirements:
16 / 16 (100.0%)
- "1.11. Requirement relations" (REQUIREMENT)
- "1.12. Requirement relation roles" (REQUIREMENT)
1
"""2
@relation(SDOC-SRS-31, SDOC-SRS-101, scope=file)3
"""4
5
from typing import Any, Optional, Tuple, Union
6
7
from strictdoc.backend.sdoc.models.grammar_element import ReferenceType
8
from strictdoc.helpers.auto_described import auto_described
9
from strictdoc.helpers.mid import MID
10
11
12
@auto_described13
class FileEntry:
14
def __init__(
15
self,
16
*,
17
parent: Any,
18
g_file_format: Optional[str],
19
g_file_path: str,
20
g_line_range: Optional[str],
21
g_deprecated_file_path: Optional[str] = None,
22
function: Optional[str] = None,
23
clazz: Optional[str] = None,
24
element: Optional[str] = None,
25
id: Optional[str] = None, # noqa: A002
26
hash: Optional[str] = None, # noqa: A002
27
):28
self.parent = parent
29
30
# Default: FileEntryFormat.SOURCECODE # noqa: ERA00131
self.g_file_format: Optional[str] = g_file_format
32
self.g_deprecated_file_path: Optional[str] = None
33
34
if (
35
g_deprecated_file_path is not None
36
and len(g_deprecated_file_path) > 0
37
):38
g_file_path = g_deprecated_file_path
39
self.g_deprecated_file_path = g_deprecated_file_path
40
41
# TODO: Might be worth to prohibit the use of Windows paths altogether.42
self.g_file_path: str = g_file_path
43
file_path_posix = g_file_path.replace("\\", "/")
44
self.file_path_posix = file_path_posix
45
46
#47
# For optional string fields:48
# The textX parser passes an empty string even if there is no field49
# present in the input. We want to convert such empty strings to None50
# for better semantics and easier handling in the rest of the code.51
#52
g_line_range = (
53
g_line_range54
if g_line_range is not None and len(g_line_range) > 0
55
else None
56
)57
self.deprecated_function = (
58
function if function is not None and len(function) > 0 else None
59
)60
self.deprecated_clazz = (
61
clazz if clazz is not None and len(clazz) > 0 else None
62
)63
_id = id if id is not None and len(id) > 0 else None
64
element = element if element is not None and len(element) > 0 else None
65
_hash = hash if hash is not None and len(hash) > 0 else None
66
67
self.g_line_range: Optional[str] = g_line_range
68
self.line_range: Optional[Tuple[int, int]] = None
69
if g_line_range is not None:
70
range_components_str = g_line_range.split(", ")
71
assert len(range_components_str) == 2, range_components_str
72
self.line_range = (
73
int(range_components_str[0]),
74
int(range_components_str[1]),
75
)76
77
self.id: Optional[str] = _id
78
if element is not None:
79
self.element: Optional[str] = element
80
elif self.deprecated_function is not None:
81
self.element = "function"
82
self.id = self.deprecated_function
83
elif self.deprecated_clazz is not None:
84
self.element = "class"
85
self.id = self.deprecated_clazz
86
else:
87
self.element = None
88
89
# Placeholder for drift-detection hash (no runtime functionality yet).90
self.hash: Optional[str] = _hash
91
92
def __setattr__(self, name: str, value: Union[str, Any]) -> None:
93
# Ignore legacy function and clazz from textX post-init. The attributes are already converted to94
# during __init__.95
if name in ("function", "clazz"):
96
return97
object.__setattr__(self, name, value)
98
99
def function(self) -> Optional[str]:
100
if self.deprecated_function is not None:
101
return self.deprecated_function
102
103
if self.element == "function":
104
return self.id
105
106
return None
107
108
def clazz(self) -> Optional[str]:
109
if self.deprecated_clazz is not None:
110
return self.deprecated_clazz
111
112
if self.element == "class":
113
return self.id
114
115
return None
116
117
118
class FileEntryFormat:
119
SOURCECODE = "Sourcecode"
120
PYTHON = "Python"
121
122
123
@auto_described124
class Reference:
125
def __init__(self, ref_type: str, parent: Any):
126
self.parent = parent
127
self.ref_type: str = ref_type
128
self.role: Optional[str] = None
129
130
131
@auto_described132
class FileReference(Reference):
133
"""
134
@relation(SDOC-SRS-145, scope=function)135
"""136
137
def __init__(
138
self, parent: Any, g_file_entry: FileEntry, role: Optional[str] = None
139
):140
super().__init__(ReferenceType.FILE, parent)
141
self.g_file_entry: FileEntry = g_file_entry
142
self.role: Optional[str] = (
143
role if role is not None and len(role) > 0 else None
144
)145
self.mid = MID.create()
146
147
def get_posix_path(self) -> str:
148
return self.g_file_entry.file_path_posix
149
150
def get_file_format(self) -> Optional[str]:
151
return self.g_file_entry.g_file_format
152
153
154
@auto_described155
class ParentReqReference(Reference):
156
def __init__(self, parent: Any, ref_uid: str, role: Optional[str]):
157
super().__init__(ReferenceType.PARENT, parent)
158
self.ref_uid: str = ref_uid
159
# When ROLE: field is not provided for a parent reference, the160
# textX still passes relation_uid as an empty string (instead of None161
# as one could expect).162
self.role: Optional[str] = (
163
role if role is not None and len(role) > 0 else None
164
)165
self.mid = MID.create()
166
167
168
@auto_described169
class ChildReqReference(Reference):
170
def __init__(self, parent: Any, ref_uid: str, role: str):
171
super().__init__(ReferenceType.CHILD, parent)
172
self.ref_uid = ref_uid
173
# When ROLE: field is not provided for a child reference, the174
# textX still passes relation_uid as an empty string (instead of None175
# as one could expect).176
self.role: Optional[str] = (
177
role if role is not None and len(role) > 0 else None
178
)179
self.mid = MID.create()