1
|
|
|
"""StarStruct element class.""" |
2
|
|
|
|
3
|
1 |
|
from typing import Optional, Tuple |
|
|
|
|
4
|
|
|
|
5
|
1 |
|
from starstruct.modes import Mode |
6
|
|
|
|
7
|
|
|
|
8
|
1 |
|
def register(cls): |
9
|
|
|
""" A handy decorator to register a class as an element """ |
10
|
1 |
|
Element.register(cls) |
11
|
1 |
|
return cls |
12
|
|
|
|
13
|
|
|
|
14
|
1 |
|
class Element(object): |
15
|
|
|
""" |
16
|
|
|
A class factory that determines the type of the field passed in, and |
17
|
|
|
instantiates the correct class type. |
18
|
|
|
""" |
19
|
1 |
|
elementtypes = [] |
20
|
|
|
|
21
|
1 |
|
@classmethod |
22
|
|
|
def register(cls, element): |
23
|
|
|
"""Function used to register new element subclasses.""" |
24
|
1 |
|
cls.elementtypes.append(element) |
25
|
|
|
|
26
|
1 |
|
@classmethod |
27
|
1 |
|
def factory(cls, field: tuple, mode: Optional[Mode]=Mode.Native, alignment: Optional[int]=1): |
28
|
|
|
""" |
29
|
|
|
Initialize a StarStruct element object based on the type of element |
30
|
|
|
parameters provided. |
31
|
|
|
|
32
|
|
|
|
33
|
|
|
Where the values in the tuple determine the type of element. |
34
|
|
|
|
35
|
|
|
These are the possible element types: |
36
|
|
|
1. Normal (base): a standard python struct format character, and a |
37
|
|
|
field name are provided. The optional element should not provided. |
38
|
|
|
|
39
|
|
|
2. Enum: a standard python struct format character and field name are |
40
|
|
|
provided, but the 3rd optional element is provided which is a |
41
|
|
|
subclass of enum.Enum. |
42
|
|
|
|
43
|
|
|
3. Length: a standard python struct format character that represents |
44
|
|
|
an unsigned numeric value, and the field name are provided, but the |
45
|
|
|
3rd optional element is provided and is a string. In this case the |
46
|
|
|
string is assumed to be another field which is the name of a |
47
|
|
|
Variable element. |
48
|
|
|
|
49
|
|
|
4. Variable: a variable length element that accommodates 0 or more of |
50
|
|
|
another StarStruct.message. The format field should be a valid |
51
|
|
|
StarStruct.message, the optional 3rd element must be provided and |
52
|
|
|
should be the name of a valid Length element or an int. The |
53
|
|
|
validity of the referenced element must be checked after the |
54
|
|
|
creation of the entire message with the Message.validate() function. |
55
|
|
|
|
56
|
|
|
5. Discriminated: a message element that can have multiple formats |
57
|
|
|
such as a C union. The format field should be a dictionary where |
58
|
|
|
the keys represent values of a referenced enumeration field, and |
59
|
|
|
the value for each entry is a valid StarStruct.message, or None. |
60
|
|
|
The optional 3rd element must be provided and should be the name of |
61
|
|
|
a valid Enum element. The validity of the referenced element must |
62
|
|
|
be checked after the creation of the entire message with the |
63
|
|
|
Message.validate() function. |
64
|
|
|
|
65
|
|
|
|
66
|
|
|
:param field: The field must be a tuple of the following form:: |
67
|
|
|
|
68
|
|
|
(name, format, <optional>) |
69
|
|
|
|
70
|
|
|
:param mode: The mode in which to pack the information. |
71
|
|
|
:param alignment: The number of bytes to align objects with. |
72
|
|
|
:returns: An element whose fields match those passed in |
73
|
|
|
""" |
74
|
|
|
|
75
|
1 |
|
if not isinstance(mode, Mode): |
76
|
|
|
raise TypeError('invalid mode: {}'.format(mode)) |
77
|
|
|
|
78
|
|
|
# The field parameter is a single field tuple: |
79
|
|
|
# ('name', 'format', <optional>) |
80
|
1 |
|
if not isinstance(field, tuple): |
81
|
|
|
raise TypeError('invalid element: {}'.format(field)) |
82
|
|
|
|
83
|
|
|
# The name of the element must be a non-null string or bytes |
84
|
|
|
# provided in as the first part of the field tuple |
85
|
1 |
|
if not field[0] or not isinstance(field[0], (str, bytes)): |
86
|
|
|
raise TypeError('invalid name: {}'.format(field[0])) |
87
|
|
|
|
88
|
1 |
|
valid_elems = [] |
89
|
1 |
|
for elem in cls.elementtypes: |
90
|
1 |
|
try: |
91
|
1 |
|
if elem.valid(field): |
92
|
1 |
|
valid_elems.append(elem) |
93
|
1 |
|
except (TypeError, KeyError): |
94
|
1 |
|
continue |
95
|
|
|
|
96
|
1 |
|
if len(valid_elems) > 1: |
97
|
|
|
raise ValueError('More than one elemn was valid.\n\tField: {0}\n\tElems: {1}'.format( |
98
|
|
|
field, valid_elems)) |
99
|
1 |
|
elif len(valid_elems) == 1: |
100
|
1 |
|
return valid_elems[0](field, mode, alignment) |
101
|
|
|
|
102
|
|
|
# If the function made it this far, the field specification is not valid |
103
|
|
|
raise TypeError('invalid field: {}'.format(field)) |
104
|
|
|
|
105
|
1 |
|
@staticmethod |
106
|
1 |
|
def valid(field: tuple) -> bool: |
107
|
|
|
""" |
108
|
|
|
Require element objects to implement this abstract function. |
109
|
|
|
|
110
|
|
|
Validation function to determine if a field tuple represents a valid |
111
|
|
|
element type. |
112
|
|
|
|
113
|
|
|
The basics have already been validated by the Element factory class, |
114
|
|
|
validate that the struct format is a valid numeric value. |
115
|
|
|
|
116
|
|
|
:param field: The format specifier for an element |
117
|
|
|
:returns: Whether this field tuple is valid for this class. |
118
|
|
|
""" |
119
|
|
|
raise NotImplementedError |
120
|
|
|
|
121
|
1 |
|
def validate(self, msg: dict) -> bool: |
122
|
|
|
""" |
123
|
|
|
Require element objects to implement this function. |
124
|
|
|
|
125
|
|
|
:param msg: The current values passed in to the element |
126
|
|
|
:returns: Whether this message represents a valid element. |
127
|
|
|
""" |
128
|
|
|
raise NotImplementedError |
129
|
|
|
|
130
|
1 |
|
def update(self, mode: Mode, alignment: int) -> None: |
131
|
|
|
""" |
132
|
|
|
Require element objects to implement this function. |
133
|
|
|
|
134
|
|
|
:param mode: The new mode for the Element |
135
|
|
|
:param alignment: The new alignment for the element |
136
|
|
|
""" |
137
|
|
|
raise NotImplementedError |
138
|
|
|
|
139
|
1 |
|
def pack(self, msg: dict) -> bytes: |
140
|
|
|
""" |
141
|
|
|
Require element objects to implement this function. |
142
|
|
|
|
143
|
|
|
:param msg: The values to pack into bytes |
144
|
|
|
:returns: The msg packed into bytes as specified by the format |
145
|
|
|
""" |
146
|
|
|
raise NotImplementedError |
147
|
|
|
|
148
|
1 |
|
def unpack(self, msg: dict, buf: bytes) -> Tuple[dict, bytes]: |
149
|
|
|
""" |
150
|
|
|
Require element objects to implement this function. |
151
|
|
|
|
152
|
|
|
:param msg: The values unpacked thus far from the bytes |
153
|
|
|
:param buf: The remaining bytes to unpack |
154
|
|
|
:returns: The updated message and the remaining bytes |
155
|
|
|
""" |
156
|
|
|
raise NotImplementedError |
157
|
|
|
|
158
|
1 |
|
def make(self, msg: dict): |
159
|
|
|
""" |
160
|
|
|
Require element objects to implement this function. |
161
|
|
|
|
162
|
|
|
:param msg: The values to place into the named tuple object |
163
|
|
|
|
164
|
|
|
:todo: How do I specify the correct type for this? |
165
|
|
|
""" |
166
|
|
|
raise NotImplementedError |
167
|
|
|
|
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.py
files in your module folders. Make sure that you place one file in each sub-folder.