StrictDoc Documentation
strictdoc/backend/sdoc/models/reference.py
Source file coverage
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
"""
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_described
13
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: ERA001
30
        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 field
48
        # present in the input. We want to convert such empty strings to None
49
        # for better semantics and easier handling in the rest of the code.
50
        #
51
        g_line_range = (
52
            g_line_range
53
            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 to
93
        # during __init__.
94
        if name in ("function", "clazz"):
95
            return
96
        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_described
123
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_described
131
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_described
154
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, the
159
        # textX still passes relation_uid as an empty string (instead of None
160
        # 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_described
168
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, the
173
        # textX still passes relation_uid as an empty string (instead of None
174
        # 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()