xferx 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. cstruct/__init__.py +226 -0
  2. cstruct/abstract.py +406 -0
  3. cstruct/base.py +75 -0
  4. cstruct/c_expr.py +205 -0
  5. cstruct/c_parser.py +495 -0
  6. cstruct/cenum.py +8 -0
  7. cstruct/cstruct.py +90 -0
  8. cstruct/exceptions.py +51 -0
  9. cstruct/field.py +279 -0
  10. cstruct/mem_cstruct.py +152 -0
  11. cstruct/native_types.py +228 -0
  12. tests/__init__.py +0 -0
  13. tests/test_appledos.py +96 -0
  14. tests/test_caps11.py +71 -0
  15. tests/test_cmd.py +100 -0
  16. tests/test_decsys.py +169 -0
  17. tests/test_dms.py +345 -0
  18. tests/test_dos11.py +154 -0
  19. tests/test_dos11dectape.py +68 -0
  20. tests/test_dos11magtest.py +74 -0
  21. tests/test_files11.py +37 -0
  22. tests/test_misc.py +169 -0
  23. tests/test_native.py +32 -0
  24. tests/test_nova_dgdos.py +131 -0
  25. tests/test_nova_dgdosmagtape.py +89 -0
  26. tests/test_os8.py +194 -0
  27. tests/test_pascal.py +75 -0
  28. tests/test_prodos.py +202 -0
  29. tests/test_rsts.py +133 -0
  30. tests/test_rt11.py +60 -0
  31. tests/test_solo.py +143 -0
  32. tests/test_unixv0.py +34 -0
  33. tests/test_unixv6.py +44 -0
  34. xferx/VERSION +1 -0
  35. xferx/__init__.py +0 -0
  36. xferx/abstract.py +350 -0
  37. xferx/apple2/__init__.py +0 -0
  38. xferx/apple2/appledosfs.py +1189 -0
  39. xferx/apple2/commons.py +170 -0
  40. xferx/apple2/disk.py +177 -0
  41. xferx/apple2/pascalfs.py +754 -0
  42. xferx/apple2/ppm.py +449 -0
  43. xferx/apple2/prodosfs.py +2142 -0
  44. xferx/block.py +169 -0
  45. xferx/cache.py +48 -0
  46. xferx/commons.py +174 -0
  47. xferx/native.py +340 -0
  48. xferx/nova/__init__.py +0 -0
  49. xferx/nova/dgdosdumpfs.py +760 -0
  50. xferx/nova/dgdosfs.py +1731 -0
  51. xferx/nova/dgdosmagtapefs.py +408 -0
  52. xferx/pdp11/__init__.py +0 -0
  53. xferx/pdp11/caps11fs.py +556 -0
  54. xferx/pdp11/dos11fs.py +1120 -0
  55. xferx/pdp11/dos11magtapefs.py +525 -0
  56. xferx/pdp11/files11fs.py +789 -0
  57. xferx/pdp11/rad50.py +73 -0
  58. xferx/pdp11/rstsfs.py +1150 -0
  59. xferx/pdp11/rt11fs.py +795 -0
  60. xferx/pdp11/solofs.py +1119 -0
  61. xferx/pdp7/__init__.py +0 -0
  62. xferx/pdp7/block.py +114 -0
  63. xferx/pdp7/codes.py +362 -0
  64. xferx/pdp7/decsysfs.py +1149 -0
  65. xferx/pdp7/unix0fs.py +356 -0
  66. xferx/pdp8/__init__.py +0 -0
  67. xferx/pdp8/dmsfs.py +1266 -0
  68. xferx/pdp8/os8fs.py +1046 -0
  69. xferx/rx.py +168 -0
  70. xferx/shell.py +1050 -0
  71. xferx/tape.py +138 -0
  72. xferx/uic.py +121 -0
  73. xferx/unixfs.py +1139 -0
  74. xferx/volumes.py +242 -0
  75. xferx-3.0.0.dist-info/METADATA +256 -0
  76. xferx-3.0.0.dist-info/RECORD +80 -0
  77. xferx-3.0.0.dist-info/WHEEL +5 -0
  78. xferx-3.0.0.dist-info/entry_points.txt +2 -0
  79. xferx-3.0.0.dist-info/licenses/LICENSE +21 -0
  80. xferx-3.0.0.dist-info/top_level.txt +3 -0
cstruct/__init__.py ADDED
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python
2
+ # Copyright (c) 2013-2025 Andrea Bonomi <andrea.bonomi@gmail.com>
3
+ #
4
+ # Published under the terms of the MIT license.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to
8
+ # deal in the Software without restriction, including without limitation the
9
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ # sell copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
+ # IN THE SOFTWARE.
23
+ #
24
+
25
+ __author__ = "Andrea Bonomi <andrea.bonomi@gmail.com>"
26
+ __license__ = "MIT"
27
+ __version__ = "6.1"
28
+ __date__ = "15 August 2013"
29
+
30
+ from typing import Any, Dict, Optional, Type, Union
31
+
32
+ from .abstract import AbstractCEnum, AbstractCStruct, CStructMeta
33
+ from .base import (
34
+ BIG_ENDIAN,
35
+ CHAR_ZERO,
36
+ DEFINES,
37
+ ENUMS,
38
+ LITTLE_ENDIAN,
39
+ NATIVE_ORDER,
40
+ STRUCTS,
41
+ TYPEDEFS,
42
+ )
43
+ from .c_parser import parse_struct_def
44
+ from .cenum import CEnum
45
+ from .cstruct import CStruct
46
+ from .mem_cstruct import MemCStruct
47
+ from .native_types import get_native_type
48
+
49
+ __all__ = [
50
+ "LITTLE_ENDIAN",
51
+ "BIG_ENDIAN",
52
+ "NATIVE_ORDER",
53
+ "CHAR_ZERO",
54
+ "CStruct",
55
+ "MemCStruct",
56
+ "CEnum",
57
+ "define",
58
+ "undef",
59
+ "getdef",
60
+ "typedef",
61
+ "get_type",
62
+ "sizeof",
63
+ "parse",
64
+ ]
65
+
66
+
67
+ def define(key: str, value: Any) -> None:
68
+ """
69
+ Define a constant that can be used in the C struct
70
+
71
+ Examples:
72
+ >>> define("INIT_THREAD_SIZE", 16384)
73
+
74
+ Args:
75
+ key: identifier
76
+ value: value of the constant
77
+ """
78
+ DEFINES[key] = value
79
+
80
+
81
+ def undef(key: str) -> None:
82
+ """
83
+ Undefine a symbol that was previously defined with define
84
+
85
+ Examples:
86
+ >>> define("INIT_THREAD_SIZE", 16384)
87
+ >>> undef("INIT_THREAD_SIZE")
88
+
89
+ Args:
90
+ key: identifier
91
+
92
+ Raises:
93
+ KeyError: If key is not defined
94
+ """
95
+ del DEFINES[key]
96
+
97
+
98
+ def getdef(key: str) -> Any:
99
+ """
100
+ Return the value for a constant
101
+
102
+ Examples:
103
+ >>> define("INIT_THREAD_SIZE", 16384)
104
+ >>> getdef("INIT_THREAD_SIZE")
105
+
106
+ Args:
107
+ key: identifier
108
+
109
+ Raises:
110
+ KeyError: If key is not defined
111
+ """
112
+ return DEFINES[key]
113
+
114
+
115
+ def typedef(type_: str, alias: str) -> None:
116
+ """
117
+ Define an alias name for a data type
118
+
119
+ Examples:
120
+ >>> typedef("int", "status")
121
+ >>> sizeof("status")
122
+ 4
123
+
124
+ Args:
125
+ type_: data type
126
+ alias: new alias name
127
+ """
128
+ TYPEDEFS[alias] = type_
129
+
130
+
131
+ def get_type(type_: str) -> Any:
132
+ """
133
+ Get a data type (struct, union, enum) by name
134
+
135
+ Examples:
136
+ >>> get_type("struct Position")
137
+ <class 'abc.Position'>
138
+ >>> get_type("enum htmlfont")
139
+ <enum 'htmlfont'>
140
+
141
+ Args:
142
+ type_: C type, struct or union (e.g. 'short int' or 'struct ZYZ'), enum or native type
143
+
144
+ Returns:
145
+ class: data type class
146
+
147
+ Raises:
148
+ KeyError: If type is not defined
149
+ """
150
+ while type_ in TYPEDEFS:
151
+ type_ = TYPEDEFS[type_]
152
+ if isinstance(type_, CStructMeta):
153
+ return type_
154
+ elif type_.startswith("struct ") or type_.startswith("union "):
155
+ kind, type_ = type_.split(" ", 1)
156
+ try:
157
+ return STRUCTS[type_]
158
+ except KeyError:
159
+ raise KeyError(f"Unknown {kind} `{type_}`")
160
+ elif type_.startswith("enum "):
161
+ kind, type_ = type_.split(" ", 1)
162
+ try:
163
+ return ENUMS[type_]
164
+ except KeyError:
165
+ raise KeyError(f"Unknown {kind} `{type_}`")
166
+ else:
167
+ return get_native_type(type_)
168
+
169
+
170
+ def sizeof(type_: str) -> int:
171
+ """
172
+ Return the size of the type.
173
+
174
+ Examples:
175
+ >>> sizeof("struct Position")
176
+ 16
177
+ >>> sizeof('enum htmlfont')
178
+ 4
179
+ >>> sizeof("int")
180
+ 4
181
+
182
+ Args:
183
+ type_: C type, struct or union (e.g. 'short int' or 'struct ZYZ'), enum or native type
184
+
185
+ Returns:
186
+ size: size in bytes
187
+
188
+ Raises:
189
+ KeyError: If type is not defined
190
+ """
191
+ while type_ in TYPEDEFS:
192
+ type_ = TYPEDEFS[type_]
193
+ data_type = get_type(type_)
194
+ return data_type.sizeof()
195
+
196
+
197
+ def parse(
198
+ __struct__: str, __cls__: Optional[Type[AbstractCStruct]] = None, **kargs: Dict[str, Any]
199
+ ) -> Union[Type[AbstractCStruct], Type[AbstractCEnum], None]:
200
+ """
201
+ Return a new class mapping a C struct/union/enum definition.
202
+ If the string does not contains any definition, return None.
203
+ If the string contains multiple struct/union/enum definitions, returns the last definition.
204
+
205
+ Examples:
206
+ >>> cstruct.parse('struct Pair { unsigned char a; unsigned char b; };')
207
+ <class 'abc.Pair'>
208
+
209
+ Args:
210
+ __struct__ (str): definition of the struct (or union/enum) in C syntax
211
+ __cls__ (type): super class - CStruct(default) or MemCStruct
212
+ __byte_order__ (str): byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER
213
+
214
+ Returns:
215
+ cls: __cls__ subclass
216
+
217
+ Raises:
218
+ cstruct.exceptions.ParserError: Parsing exception
219
+
220
+ """
221
+ if __cls__ is None:
222
+ __cls__ = MemCStruct
223
+ cls_def = parse_struct_def(__struct__, __cls__=__cls__, process_muliple_definition=True, **kargs)
224
+ if cls_def is None:
225
+ return None
226
+ return cls_def["__cls__"].parse(cls_def, **kargs)
cstruct/abstract.py ADDED
@@ -0,0 +1,406 @@
1
+ #!/usr/bin/env python
2
+ # Copyright (c) 2013-2025 Andrea Bonomi <andrea.bonomi@gmail.com>
3
+ #
4
+ # Published under the terms of the MIT license.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to
8
+ # deal in the Software without restriction, including without limitation the
9
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ # sell copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
+ # IN THE SOFTWARE.
23
+ #
24
+
25
+ import hashlib
26
+ import struct
27
+ from abc import ABCMeta
28
+ from collections import OrderedDict
29
+ from enum import EnumMeta, IntEnum, _EnumDict
30
+ from io import StringIO
31
+ from typing import Any, BinaryIO, Dict, List, Optional, Tuple, Type, Union
32
+
33
+ from .base import DEFAULT_ENUM_SIZE, ENUM_SIZE_TO_C_TYPE, ENUMS, STRUCTS
34
+ from .c_parser import Tokens, parse_enum, parse_enum_def, parse_struct, parse_struct_def
35
+ from .exceptions import CStructException, ParserError
36
+ from .field import FieldType, calculate_padding
37
+ from .native_types import get_native_type
38
+
39
+ __all__ = ["CStructMeta", "AbstractCStruct", "CEnumMeta", "AbstractCEnum"]
40
+
41
+
42
+ class CStructMeta(ABCMeta):
43
+ __size__: int = 0
44
+
45
+ def __new__(metacls: Type[type], name: str, bases: Tuple[str], namespace: Dict[str, Any]) -> Type[Any]:
46
+ __struct__ = namespace.get("__struct__", None)
47
+ namespace["__cls__"] = bases[0] if bases else None
48
+ # Parse the struct
49
+ if "__struct__" in namespace:
50
+ if isinstance(namespace["__struct__"], (str, Tokens)):
51
+ namespace.update(parse_struct(**namespace))
52
+ __struct__ = True
53
+ if "__def__" in namespace:
54
+ namespace.update(parse_struct_def(**namespace))
55
+ __struct__ = True
56
+ # Create the new class
57
+ new_class: Type[Any] = super().__new__(metacls, name, bases, namespace)
58
+ # Register the class
59
+ if __struct__ is not None and not namespace.get("__anonymous__"):
60
+ STRUCTS[name] = new_class
61
+ return new_class
62
+
63
+ def __len__(cls) -> int:
64
+ "Structure size (in bytes)"
65
+ return cls.__size__
66
+
67
+ @property
68
+ def size(cls) -> int:
69
+ "Structure size (in bytes)"
70
+ return cls.__size__
71
+
72
+
73
+ class AbstractCStruct(metaclass=CStructMeta):
74
+ """
75
+ Abstract C struct to Python class
76
+ """
77
+
78
+ __size__: int = 0
79
+ " Size in bytes "
80
+ __fields__: List[str] = []
81
+ " Struct/union fields "
82
+ __fields_types__: Dict[str, FieldType]
83
+ " Dictionary mapping field names to types "
84
+ __byte_order__: Optional[str] = None
85
+ " Byte order "
86
+ __alignment__: int = 0
87
+ " Alignment "
88
+ __is_union__: bool = False
89
+ " True if the class is an union, False if it is a struct "
90
+
91
+ def __init__(
92
+ self, buffer: Optional[Union[bytes, BinaryIO]] = None, flexible_array_length: Optional[int] = None, **kargs: Dict[str, Any]
93
+ ) -> None:
94
+ self.set_flexible_array_length(flexible_array_length)
95
+ self.__fields__ = [x for x in self.__fields__] # Create a copy
96
+ self.__fields_types__ = OrderedDict({k: v.copy() for k, v in self.__fields_types__.items()}) # Create a copy
97
+ if buffer is not None:
98
+ self.unpack(buffer)
99
+ else:
100
+ try:
101
+ self.unpack(buffer)
102
+ except Exception:
103
+ pass
104
+ for key, value in kargs.items():
105
+ setattr(self, key, value)
106
+
107
+ @classmethod
108
+ def parse(
109
+ cls,
110
+ __struct__: Union[str, Tokens, Dict[str, Any]],
111
+ __name__: Optional[str] = None,
112
+ __byte_order__: Optional[str] = None,
113
+ __is_union__: Optional[bool] = False,
114
+ **kargs: Dict[str, Any],
115
+ ) -> Type["AbstractCStruct"]:
116
+ """
117
+ Return a new class mapping a C struct/union definition.
118
+
119
+ Args:
120
+ __struct__: definition of the struct (or union) in C syntax
121
+ __name__: name of the new class. If empty, a name based on the __struct__ hash is generated
122
+ __byte_order__: byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER
123
+ __is_union__: True for union, False for struct
124
+
125
+ Returns:
126
+ cls: a new class mapping the definition
127
+ """
128
+ cls_kargs: Dict[str, Any] = dict(kargs)
129
+ if __byte_order__ is not None:
130
+ cls_kargs["__byte_order__"] = __byte_order__
131
+ if __is_union__ is not None:
132
+ cls_kargs["__is_union__"] = __is_union__
133
+ cls_kargs["__struct__"] = __struct__
134
+ if isinstance(__struct__, (str, Tokens)):
135
+ del cls_kargs["__struct__"]
136
+ cls_kargs.update(parse_struct_def(__struct__, __cls__=cls, **cls_kargs))
137
+ cls_kargs["__struct__"] = None
138
+ elif isinstance(__struct__, dict):
139
+ del cls_kargs["__struct__"]
140
+ cls_kargs.update(__struct__)
141
+ cls_kargs["__struct__"] = None
142
+ __name__ = cls_kargs.get("__name__") or __name__
143
+ if __name__ is None: # Anonymous struct
144
+ __name__ = cls.__name__ + "_" + hashlib.sha1(str(__struct__).encode("utf-8")).hexdigest()
145
+ cls_kargs["__anonymous__"] = True
146
+ cls_kargs["__name__"] = __name__
147
+ return type(__name__, (cls,), cls_kargs)
148
+
149
+ def set_flexible_array_length(self, flexible_array_length: Optional[int]) -> None:
150
+ """
151
+ Set flexible array length (i.e. number of elements)
152
+
153
+ Args:
154
+ flexible_array_length: flexible array length
155
+
156
+ Raises:
157
+ CStructException: If flexible array is not present in the structure
158
+ """
159
+ if flexible_array_length is not None:
160
+ # Search for the flexible array
161
+ flexible_array: Optional[FieldType] = [x for x in self.__fields_types__.values() if x.flexible_array][0]
162
+ if flexible_array is None:
163
+ raise CStructException("Flexible array not found in struct")
164
+ flexible_array.vlen_ex = flexible_array_length
165
+
166
+ def unpack(self, buffer: Optional[Union[bytes, BinaryIO]], flexible_array_length: Optional[int] = None) -> bool:
167
+ """
168
+ Unpack bytes containing packed C structure data
169
+
170
+ Args:
171
+ buffer: bytes or binary stream to be unpacked
172
+ flexible_array_length: flexible array length
173
+ """
174
+ self.set_flexible_array_length(flexible_array_length)
175
+ if hasattr(buffer, "read"):
176
+ buffer = buffer.read(self.size) # type: ignore
177
+ if not buffer:
178
+ return False
179
+ return self.unpack_from(buffer)
180
+
181
+ def unpack_from(
182
+ self, buffer: Optional[bytes], offset: int = 0, flexible_array_length: Optional[int] = None
183
+ ) -> bool: # pragma: no cover
184
+ """
185
+ Unpack bytes containing packed C structure data
186
+
187
+ Args:
188
+ buffer: bytes to be unpacked
189
+ offset: optional buffer offset
190
+ flexible_array_length: flexible array length
191
+ """
192
+ raise NotImplementedError
193
+
194
+ def pack(self) -> bytes: # pragma: no cover
195
+ """
196
+ Pack the structure data into bytes
197
+
198
+ Returns:
199
+ bytes: The packed structure
200
+ """
201
+ raise NotImplementedError
202
+
203
+ def pack_into(self, buffer: bytearray, offset: int = 0) -> None:
204
+ """
205
+ Pack the structure data into a buffer
206
+
207
+ Args:
208
+ buffer: target buffer (must be large enough to contain the packed structure)
209
+ offset: optional buffer offset
210
+ """
211
+ tmp = self.pack()
212
+ buffer[offset : offset + len(tmp)] = tmp
213
+
214
+ def clear(self) -> None:
215
+ self.unpack(None)
216
+
217
+ def __len__(self) -> int:
218
+ "Actual structure size (in bytes)"
219
+ return self.size
220
+
221
+ @property
222
+ def size(self) -> int:
223
+ "Actual structure size (in bytes)"
224
+ if not self.__fields_types__: # no fields
225
+ return 0
226
+ elif self.__is_union__: # C union
227
+ # Calculate the sizeof union as size of its largest element
228
+ return max(x.vsize for x in self.__fields_types__.values())
229
+ else: # C struct
230
+ # Calculate the sizeof struct as last item's offset + size + padding
231
+ last_field_type = list(self.__fields_types__.values())[-1]
232
+ size = last_field_type.offset + last_field_type.vsize
233
+ padding = calculate_padding(self.__byte_order__, self.__alignment__, size)
234
+ return size + padding
235
+
236
+ @classmethod
237
+ def sizeof(cls) -> int:
238
+ "Structure size in bytes (flexible array member size is omitted)"
239
+ return cls.__size__
240
+
241
+ def inspect(self, start_addr: Optional[int] = None, end_addr: Optional[int] = None) -> str:
242
+ """
243
+ Return memory content in hexadecimal
244
+
245
+ Args:
246
+ start_addr: start address
247
+ end_addr: end address
248
+ """
249
+ buffer = StringIO()
250
+ if hasattr(self, "__mem__"):
251
+ mem = self.__mem__[self.__base__ :]
252
+ else:
253
+ mem = self.pack()
254
+ if end_addr is None:
255
+ end_addr = self.size
256
+ for i in range(start_addr or 0, end_addr, 16):
257
+ row = mem[i : min(i + 16, end_addr)]
258
+ buffer.write(f"{i:08x} ")
259
+ for j, c in enumerate(row):
260
+ separator = " " if j == 7 else ""
261
+ buffer.write(f" {c:02x}{separator}")
262
+ for j in range(len(row) - 1, 15):
263
+ separator = " " if j == 7 else ""
264
+ buffer.write(f" {separator}")
265
+ buffer.write(" |")
266
+ for c in row:
267
+ buffer.write(chr(c) if c >= 32 and c < 127 else ".")
268
+ for j in range(len(row) - 1, 15):
269
+ buffer.write(" ")
270
+ buffer.write("|")
271
+ buffer.write("\n")
272
+ buffer.seek(0, 0)
273
+ return buffer.read()
274
+
275
+ def __eq__(self, other: Any) -> bool:
276
+ return other is not None and isinstance(other, self.__class__) and self.__dict__ == other.__dict__
277
+
278
+ def __ne__(self, other: Any) -> bool:
279
+ return not self.__eq__(other)
280
+
281
+ def __str__(self) -> str:
282
+ result = []
283
+ for field in self.__fields__:
284
+ result.append(field + "=" + str(getattr(self, field, None)))
285
+ return type(self).__name__ + "(" + ", ".join(result) + ")"
286
+
287
+ def __repr__(self) -> str: # pragma: no cover
288
+ return self.__str__()
289
+
290
+ def __getstate__(self) -> bytes:
291
+ """
292
+ This method is called and the returned object is pickled
293
+ as the contents for the instance, instead of the contents of
294
+ the instance’s dictionary
295
+
296
+ Returns:
297
+ bytes: The packed structure
298
+ """
299
+ return self.pack()
300
+
301
+ def __setstate__(self, state: bytes) -> bool:
302
+ """
303
+ This method it is called with the unpickled state
304
+
305
+ Args:
306
+ state: bytes to be unpacked
307
+ """
308
+ return self.unpack(state)
309
+
310
+
311
+ class CEnumMeta(EnumMeta):
312
+ __size__: int
313
+ __native_format__: str
314
+
315
+ class WrapperDict(_EnumDict):
316
+ def __setitem__(self, key: str, value: Any) -> None:
317
+ env = None
318
+ if key == "__enum__":
319
+ env = parse_enum(value)
320
+ elif key == "__def__":
321
+ env = parse_enum_def(value)
322
+
323
+ if env is not None:
324
+ # register the enum constants in the object namespace,
325
+ # using the Python Enum class Namespace dict that does the
326
+ # heavy lifting
327
+ for k, v in env["__constants__"].items():
328
+ super().__setitem__(k, v)
329
+ else:
330
+ return super().__setitem__(key, value)
331
+
332
+ @classmethod
333
+ def __prepare__(metacls, cls, bases, **kwds):
334
+ namespace = EnumMeta.__prepare__(cls, bases, **kwds)
335
+ namespace.__class__ = metacls.WrapperDict
336
+ return namespace
337
+
338
+ def __new__(metacls: Type["CEnumMeta"], cls: str, bases: Tuple[Type, ...], classdict: _EnumDict, **kwds: Any) -> "CEnumMeta":
339
+ inst = super().__new__(metacls, cls, bases, classdict, **kwds)
340
+ if len(inst) > 0:
341
+ if classdict.get("__native_format__"): # data type specified
342
+ inst.__size__ = struct.calcsize(classdict["__native_format__"])
343
+ elif "__size__" in classdict: # size specified
344
+ try:
345
+ inst.__native_format__ = get_native_type(ENUM_SIZE_TO_C_TYPE[inst.__size__]).native_format
346
+ except KeyError:
347
+ raise ParserError(f"Enum has invalid size. Needs to be in {ENUM_SIZE_TO_C_TYPE.keys()}")
348
+ else: # default
349
+ inst.__size__ = DEFAULT_ENUM_SIZE
350
+ inst.__native_format__ = get_native_type(ENUM_SIZE_TO_C_TYPE[inst.__size__]).native_format
351
+ print(f"Warning: __size__ not specified for enum {cls}. Will default to {DEFAULT_ENUM_SIZE} bytes")
352
+
353
+ if not classdict.get("__anonymous__", False):
354
+ ENUMS[cls] = inst
355
+ return inst
356
+
357
+ @property
358
+ def size(cls) -> int:
359
+ "Enum size (in bytes)"
360
+ return cls.__size__
361
+
362
+
363
+ class AbstractCEnum(IntEnum, metaclass=CEnumMeta):
364
+ """
365
+ Abstract C enum to Python class
366
+ """
367
+
368
+ @classmethod
369
+ def parse(
370
+ cls,
371
+ __enum__: Union[str, Tokens, Dict[str, Any]],
372
+ __name__: Optional[str] = None,
373
+ __size__: Optional[int] = None,
374
+ __native_format__: Optional[str] = None,
375
+ **kargs: Dict[str, Any],
376
+ ) -> Type["AbstractCEnum"]:
377
+ """
378
+ Return a new Python Enum class mapping a C enum definition
379
+
380
+ Args:
381
+ __enum__: Definition of the enum in C syntax
382
+ __name__: Name of the new Enum. If empty, a name based on the __enum__ hash is generated
383
+ __size__: Number of bytes that the enum should be read as
384
+ __native_format__: struct module format
385
+
386
+ Returns:
387
+ cls: A new class mapping the definition
388
+ """
389
+ cls_kargs: Dict[str, Any] = dict(kargs)
390
+ if __size__ is not None:
391
+ cls_kargs["__size__"] = __size__
392
+ if __native_format__ is not None:
393
+ cls_kargs["__native_format__"] = __native_format__
394
+
395
+ if isinstance(__enum__, (str, Tokens)):
396
+ cls_kargs.update(parse_enum_def(__enum__, __cls__=cls, **cls_kargs))
397
+ elif isinstance(__enum__, dict):
398
+ cls_kargs.update(__enum__)
399
+
400
+ __name__ = cls_kargs.get("__name__") or __name__
401
+ if __name__ is None:
402
+ __name__ = cls.__name__ + "_" + hashlib.sha1(str(__enum__).encode("utf-8")).hexdigest()
403
+ cls_kargs["__anonymous__"] = True
404
+
405
+ cls_kargs.update(cls_kargs["__constants__"])
406
+ return cls(__name__, cls_kargs)
cstruct/base.py ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python
2
+ # Copyright (c) 2013-2025 Andrea Bonomi <andrea.bonomi@gmail.com>
3
+ #
4
+ # Published under the terms of the MIT license.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to
8
+ # deal in the Software without restriction, including without limitation the
9
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ # sell copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
+ # IN THE SOFTWARE.
23
+ #
24
+
25
+ from typing import TYPE_CHECKING, Any, Dict, Type
26
+
27
+ if TYPE_CHECKING:
28
+ from .abstract import AbstractCEnum, AbstractCStruct
29
+
30
+ __all__ = [
31
+ "LITTLE_ENDIAN",
32
+ "BIG_ENDIAN",
33
+ "NATIVE_ORDER",
34
+ "CHAR_ZERO",
35
+ "STRUCTS",
36
+ "DEFINES",
37
+ "TYPEDEFS",
38
+ "CHAR_ZERO",
39
+ "DEFAULT_ENUM_SIZE",
40
+ ]
41
+
42
+ LITTLE_ENDIAN = "<"
43
+ "Little-endian, std. size & alignment"
44
+ BIG_ENDIAN = ">"
45
+ "Big-endian, std. size & alignment"
46
+ NATIVE_ORDER = "@"
47
+ "Native order, size & alignment"
48
+
49
+ STRUCTS: Dict[str, Type["AbstractCStruct"]] = {}
50
+
51
+ ENUMS: Dict[str, Type["AbstractCEnum"]] = {}
52
+
53
+ DEFINES: Dict[str, Any] = {}
54
+
55
+ TYPEDEFS: Dict[str, str] = {
56
+ "short int": "short",
57
+ "unsigned short int": "unsigned short",
58
+ "ushort": "unsigned short",
59
+ "long int": "long",
60
+ "unsigned long int": "unsigned long",
61
+ "int8_t": "int8",
62
+ "uint8_t": "uint8",
63
+ "int16_t": "int16",
64
+ "uint16_t": "uint16",
65
+ "int32_t": "int32",
66
+ "uint32_t": "uint32",
67
+ "int64_t": "int64",
68
+ "uint64_t": "uint64",
69
+ }
70
+
71
+ ENUM_SIZE_TO_C_TYPE: Dict[int, str] = {1: "int8", 2: "int16", 4: "int32", 8: "int64"}
72
+
73
+ CHAR_ZERO = bytes("\0", "ascii")
74
+
75
+ DEFAULT_ENUM_SIZE = 4