Completed
Push — master ( 90c8f8...6564a8 )
by Aaron
10:10
created

ElementEnum   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 109
Duplicated Lines 0 %

Test Coverage

Coverage 79.41%

Importance

Changes 6
Bugs 0 Features 1
Metric Value
c 6
b 0
f 1
dl 0
loc 109
ccs 27
cts 34
cp 0.7941
rs 10
wmc 14

7 Methods

Rating   Name   Duplication   Size   Complexity  
A update() 0 10 3
A __init__() 0 20 1
A validate() 0 8 1
A unpack() 0 19 2
A make() 0 3 1
B pack() 0 24 5
A valid() 0 13 1
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
    def pack(self, msg):
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
            enum_val = item.value
80
        elif isinstance(item, str):
81 1
            try:
82 1
                enum_val = getattr(self.ref, msg[self.name]).value
83
            except AttributeError:
84 1
                enum_name = re.match(r"<enum '(\S+)'>", str(self.ref)).group(1)
85
                msg = '{} is not a valid {}'.format(msg[self.name], enum_name)
86 1
                raise ValueError(msg)
87
        else:
88 1
            enum_val = self.ref(item).value
89
        data = self._struct.pack(enum_val)
90
91 1
        # If the data does not meet the alignment, add some padding
92
        missing_bytes = len(data) % self._alignment
93 1
        if missing_bytes:
94
            data += b'\x00' * missing_bytes
95
        return data
96 1
97
    def unpack(self, msg, buf):
98 1
        """Unpack data from the supplied buffer using the initialized format."""
99
        ret = self._struct.unpack_from(buf, 0)
100 1
101
        # Remember to remove any alignment-based padding
102
        extra_bytes = self._alignment - 1 - (struct.calcsize(self.format) %
103
                                             self._alignment)
104
        unused = buf[struct.calcsize(self.format) + extra_bytes:]
105
106
        # Convert the returned value to the referenced Enum type
107
        try:
108
            member = self.ref(ret[0])
109
        except ValueError as e:
110
            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
        return (member, unused)
116
117
    def make(self, msg):
118
        """Return the "transformed" value for this element"""
119
        return self.ref(msg[self.name])
120