Path:
strictdoc/backend/sdoc/models/reference.py
Lines:
178
Non-empty lines:
147
Non-empty lines covered with requirements:
147 / 147 (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
parent: Any,
17
g_file_format: Optional[str],
18
g_file_path: str,
19
g_line_range: Optional[str],
20
g_deprecated_file_path: Optional[str] = None,
21
function: Optional[str] = None,
22
clazz: Optional[str] = None,
23
element: Optional[str] = None,
24
id: Optional[str] = None, # noqa: A002
25
hash: Optional[str] = None, # noqa: A002
26
):27
self.parent = parent
28
29
# Default: FileEntryFormat.SOURCECODE # noqa: ERA00130
self.g_file_format: Optional[str] = g_file_format
31
self.g_deprecated_file_path: Optional[str] = None
32
33
if (
34
g_deprecated_file_path is not None
35
and len(g_deprecated_file_path) > 0
36
):37
g_file_path = g_deprecated_file_path
38
self.g_deprecated_file_path = g_deprecated_file_path
39
40
# TODO: Might be worth to prohibit the use of Windows paths altogether.41
self.g_file_path: str = g_file_path
42
file_path_posix = g_file_path.replace("\\", "/")
43
self.file_path_posix = file_path_posix
44
45
#46
# For optional string fields:47
# The textX parser passes an empty string even if there is no field48
# present in the input. We want to convert such empty strings to None49
# for better semantics and easier handling in the rest of the code.50
#51
g_line_range = (
52
g_line_range53
if g_line_range is not None and len(g_line_range) > 0
54
else None
55
)56
self.deprecated_function = (
57
function if function is not None and len(function) > 0 else None
58
)59
self.deprecated_clazz = (
60
clazz if clazz is not None and len(clazz) > 0 else None
61
)62
_id = id if id is not None and len(id) > 0 else None
63
element = element if element is not None and len(element) > 0 else None
64
_hash = hash if hash is not None and len(hash) > 0 else None
65
66
self.g_line_range: Optional[str] = g_line_range
67
self.line_range: Optional[Tuple[int, int]] = None
68
if g_line_range is not None:
69
range_components_str = g_line_range.split(", ")
70
assert len(range_components_str) == 2, range_components_str
71
self.line_range = (
72
int(range_components_str[0]),
73
int(range_components_str[1]),
74
)75
76
self.id: Optional[str] = _id
77
if element is not None:
78
self.element: Optional[str] = element
79
elif self.deprecated_function is not None:
80
self.element = "function"
81
self.id = self.deprecated_function
82
elif self.deprecated_clazz is not None:
83
self.element = "class"
84
self.id = self.deprecated_clazz
85
else:
86
self.element = None
87
88
# Placeholder for drift-detection hash (no runtime functionality yet).89
self.hash: Optional[str] = _hash
90
91
def __setattr__(self, name: str, value: Union[str, Any]) -> None:
92
# Ignore legacy function and clazz from textX post-init. The attributes are already converted to93
# during __init__.94
if name in ("function", "clazz"):
95
return96
object.__setattr__(self, name, value)
97
98
def function(self) -> Optional[str]:
99
if self.deprecated_function is not None:
100
return self.deprecated_function
101
102
if self.element == "function":
103
return self.id
104
105
return None
106
107
def clazz(self) -> Optional[str]:
108
if self.deprecated_clazz is not None:
109
return self.deprecated_clazz
110
111
if self.element == "class":
112
return self.id
113
114
return None
115
116
117
class FileEntryFormat:
118
SOURCECODE = "Sourcecode"
119
PYTHON = "Python"
120
121
122
@auto_described123
class Reference:
124
def __init__(self, ref_type: str, parent: Any):
125
self.parent = parent
126
self.ref_type: str = ref_type
127
self.role: Optional[str] = None
128
129
130
@auto_described131
class FileReference(Reference):
132
"""
133
@relation(SDOC-SRS-145, scope=function)134
"""135
136
def __init__(
137
self, parent: Any, g_file_entry: FileEntry, role: Optional[str] = None
138
):139
super().__init__(ReferenceType.FILE, parent)
140
self.g_file_entry: FileEntry = g_file_entry
141
self.role: Optional[str] = (
142
role if role is not None and len(role) > 0 else None
143
)144
self.mid = MID.create()
145
146
def get_posix_path(self) -> str:
147
return self.g_file_entry.file_path_posix
148
149
def get_file_format(self) -> Optional[str]:
150
return self.g_file_entry.g_file_format
151
152
153
@auto_described154
class ParentReqReference(Reference):
155
def __init__(self, parent: Any, ref_uid: str, role: Optional[str]):
156
super().__init__(ReferenceType.PARENT, parent)
157
self.ref_uid: str = ref_uid
158
# When ROLE: field is not provided for a parent reference, the159
# textX still passes relation_uid as an empty string (instead of None160
# as one could expect).161
self.role: Optional[str] = (
162
role if role is not None and len(role) > 0 else None
163
)164
self.mid = MID.create()
165
166
167
@auto_described168
class ChildReqReference(Reference):
169
def __init__(self, parent: Any, ref_uid: str, role: str):
170
super().__init__(ReferenceType.CHILD, parent)
171
self.ref_uid = ref_uid
172
# When ROLE: field is not provided for a child reference, the173
# textX still passes relation_uid as an empty string (instead of None174
# as one could expect).175
self.role: Optional[str] = (
176
role if role is not None and len(role) > 0 else None
177
)178
self.mid = MID.create()