1 | """StarStruct element class.""" |
||
2 | |||
3 | 1 | import struct |
|
4 | 1 | import re |
|
5 | 1 | import enum |
|
6 | |||
7 | 1 | from starstruct.element import register, Element |
|
8 | 1 | from starstruct.modes import Mode |
|
9 | |||
10 | |||
11 | 1 | @register |
|
12 | 1 | class ElementEnum(Element): |
|
13 | """ |
||
14 | The enumeration StarStruct element class. |
||
15 | """ |
||
16 | |||
17 | 1 | def __init__(self, field, mode=Mode.Native, alignment=1): |
|
18 | """Initialize a StarStruct element object.""" |
||
19 | |||
20 | # All of the type checks have already been performed by the class |
||
21 | # factory |
||
22 | 1 | self.name = field[0] |
|
23 | 1 | self.ref = field[2] |
|
24 | |||
25 | 1 | self._mode = mode |
|
26 | 1 | self._alignment = alignment |
|
27 | |||
28 | # Validate that the format specifiers are valid struct formats, this |
||
29 | # doesn't have to be done now because the format will be checked when |
||
30 | # any struct functions are called, but it's better to inform the user of |
||
31 | # any errors earlier. |
||
32 | # The easiest way to perform this check is to create a "Struct" class |
||
33 | # instance, this will also increase the efficiency of all struct related |
||
34 | # functions called. |
||
35 | 1 | self.format = mode.value + field[1] |
|
36 | 1 | self._struct = struct.Struct(self.format) |
|
37 | |||
38 | 1 | @staticmethod |
|
39 | def valid(field): |
||
40 | """ |
||
41 | Validation function to determine if a field tuple represents a valid |
||
42 | enum element type. |
||
43 | |||
44 | The basics have already been validated by the Element factory class, |
||
45 | validate that the struct format is a valid numeric or string value. |
||
46 | """ |
||
47 | 1 | return (len(field) == 3 and |
|
48 | isinstance(field[1], str) and |
||
49 | re.match(r'\d*[cbB?hHiIlLqQnNfdP]|\d*[sp]', field[1]) and |
||
50 | issubclass(field[2], enum.Enum)) |
||
51 | |||
52 | 1 | def validate(self, msg): |
|
53 | """ |
||
54 | Ensure that the supplied message contains the required information for |
||
55 | this element object to operate. |
||
56 | |||
57 | The "enum" element requires no further validation. |
||
58 | """ |
||
59 | 1 | pass |
|
60 | |||
61 | 1 | def update(self, mode=None, alignment=None): |
|
62 | """change the mode of the struct format""" |
||
63 | if alignment: |
||
64 | self._alignment = alignment |
||
65 | |||
66 | if mode: |
||
67 | self._mode = mode |
||
68 | self.format = mode.value + self.format[1:] |
||
69 | # recreate the struct with the new format |
||
70 | self._struct = struct.Struct(self.format) |
||
71 | |||
72 | 1 | View Code Duplication | def pack(self, msg): |
0 ignored issues
–
show
Duplication
introduced
by
![]() |
|||
73 | """Pack the provided values into the supplied buffer.""" |
||
74 | # The value to pack could be a raw value, an enum value, or a string |
||
75 | # that represents the enum value, first ensure that the value provided |
||
76 | # is a valid value for the referenced enum class. |
||
77 | 1 | item = msg[self.name] |
|
78 | 1 | if isinstance(item, self.ref): |
|
79 | 1 | enum_item = item |
|
80 | 1 | elif isinstance(item, str): |
|
81 | 1 | try: |
|
82 | 1 | enum_item = getattr(self.ref, msg[self.name]) |
|
83 | 1 | except AttributeError: |
|
84 | 1 | enum_name = re.match(r"<enum '(\S+)'>", str(self.ref)).group(1) |
|
85 | 1 | msg = '{} is not a valid {}'.format(msg[self.name], enum_name) |
|
86 | 1 | raise ValueError(msg) |
|
87 | else: |
||
88 | 1 | enum_item = self.ref(item) |
|
89 | 1 | data = self._struct.pack(enum_item.value) |
|
90 | |||
91 | # If the data does not meet the alignment, add some padding |
||
92 | 1 | missing_bytes = len(data) % self._alignment |
|
93 | 1 | if missing_bytes: |
|
94 | data += b'\x00' * missing_bytes |
||
95 | 1 | return data |
|
96 | |||
97 | 1 | def unpack(self, msg, buf): |
|
98 | """Unpack data from the supplied buffer using the initialized format.""" |
||
99 | 1 | ret = self._struct.unpack_from(buf, 0) |
|
100 | |||
101 | # Remember to remove any alignment-based padding |
||
102 | 1 | extra_bytes = self._alignment - 1 - (struct.calcsize(self.format) % |
|
103 | self._alignment) |
||
104 | 1 | unused = buf[struct.calcsize(self.format) + extra_bytes:] |
|
105 | |||
106 | # Convert the returned value to the referenced Enum type |
||
107 | 1 | try: |
|
108 | 1 | member = self.ref(ret[0]) |
|
109 | 1 | except ValueError as e: |
|
110 | 1 | raise ValueError( |
|
111 | 'Value: {0} was not valid for {1}\n\twith msg: {2},\n\tbuf: {3}'.format( |
||
112 | ret[0], self.ref, msg, buf |
||
113 | )).with_traceback(e.__traceback__) |
||
114 | |||
115 | 1 | return (member, unused) |
|
116 | |||
117 | 1 | View Code Duplication | def make(self, msg): |
0 ignored issues
–
show
|
|||
118 | """Return the "transformed" value for this element""" |
||
119 | # Handle the same conditions that pack handles |
||
120 | 1 | item = msg[self.name] |
|
121 | 1 | if isinstance(item, self.ref): |
|
122 | 1 | enum_item = item |
|
123 | elif isinstance(item, str): |
||
124 | try: |
||
125 | enum_item = getattr(self.ref, msg[self.name]) |
||
126 | except AttributeError: |
||
127 | enum_name = re.match(r"<enum '(\S+)'>", str(self.ref)).group(1) |
||
128 | msg = '{} is not a valid {}'.format(msg[self.name], enum_name) |
||
129 | raise ValueError(msg) |
||
130 | else: |
||
131 | enum_item = self.ref(item) |
||
132 | return enum_item |
||
133 |