StrictDoc Documentation
strictdoc/backend/sdoc/models/reference.py
Source file coverage
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
"""
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
        *,
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: ERA001
31
        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 field
49
        # present in the input. We want to convert such empty strings to None
50
        # for better semantics and easier handling in the rest of the code.
51
        #
52
        g_line_range = (
53
            g_line_range
54
            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 to
94
        # during __init__.
95
        if name in ("function", "clazz"):
96
            return
97
        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_described
124
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_described
132
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_described
155
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, the
160
        # textX still passes relation_uid as an empty string (instead of None
161
        # 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_described
169
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, the
174
        # textX still passes relation_uid as an empty string (instead of None
175
        # 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()