Issues (43)

starstruct/elementenum.py (2 issues)

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
This code seems to be duplicated in your project.
Loading history...
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
This code seems to be duplicated in your project.
Loading history...
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