Hydrogenlib-Objective-Struct 0.0.1__tar.gz
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.
- hydrogenlib_objective_struct-0.0.1/.gitignore +17 -0
- hydrogenlib_objective_struct-0.0.1/PKG-INFO +15 -0
- hydrogenlib_objective_struct-0.0.1/README.md +0 -0
- hydrogenlib_objective_struct-0.0.1/pyproject.toml +28 -0
- hydrogenlib_objective_struct-0.0.1/src/_hydrogenlib_objective_struct/__about__.py +1 -0
- hydrogenlib_objective_struct-0.0.1/src/_hydrogenlib_objective_struct/__init__.py +3 -0
- hydrogenlib_objective_struct-0.0.1/src/_hydrogenlib_objective_struct/methods.py +26 -0
- hydrogenlib_objective_struct-0.0.1/src/_hydrogenlib_objective_struct/objective_struct.py +223 -0
- hydrogenlib_objective_struct-0.0.1/src/_hydrogenlib_objective_struct/types/__init__.py +14 -0
- hydrogenlib_objective_struct-0.0.1/src/_hydrogenlib_objective_struct/types/base.py +6 -0
- hydrogenlib_objective_struct-0.0.1/src/_hydrogenlib_objective_struct/types/types.py +101 -0
- hydrogenlib_objective_struct-0.0.1/src/_hydrogenlib_objective_struct/types/types.pyi +69 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Hydrogenlib-Objective-Struct
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Project-URL: Documentation, https://github.com/LittleNightSong/HydrogenLib#readme
|
|
5
|
+
Project-URL: Issues, https://github.com/LittleNightSong/HydrogenLib/issues
|
|
6
|
+
Project-URL: Source, https://github.com/LittleNightSong/HydrogenLib
|
|
7
|
+
Author-email: LittleNightSong <LittleNightSongYO@outlook.com>
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
15
|
+
Requires-Python: >=3.12
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "Hydrogenlib-Objective-Struct"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = ''
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
keywords = []
|
|
12
|
+
classifiers = ["Development Status :: 3 - Alpha", "Programming Language :: Python", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"]
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[[project.authors]]
|
|
16
|
+
name = "LittleNightSong"
|
|
17
|
+
email = "LittleNightSongYO@outlook.com"
|
|
18
|
+
[project.urls]
|
|
19
|
+
Documentation = "https://github.com/LittleNightSong/HydrogenLib#readme"
|
|
20
|
+
Issues = "https://github.com/LittleNightSong/HydrogenLib/issues"
|
|
21
|
+
Source = "https://github.com/LittleNightSong/HydrogenLib"
|
|
22
|
+
|
|
23
|
+
[tool.hatch.version]
|
|
24
|
+
path = "src/_hydrogenlib_objective_struct/__about__.py"
|
|
25
|
+
|
|
26
|
+
[tool.hatch.build.targets.wheel]
|
|
27
|
+
packages = ["src/_hydrogenlib_objective_struct"]
|
|
28
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.0.1"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
import typing
|
|
3
|
+
from collections.abc import Buffer
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _CType(typing.Protocol):
|
|
8
|
+
__ctype__: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def to_format_string(format: tuple[str | _CType, ...]):
|
|
12
|
+
format = ''.join(
|
|
13
|
+
map(
|
|
14
|
+
lambda x: x if isinstance(x, str) else x.__ctype__,
|
|
15
|
+
format
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
return format
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def pack(format: tuple[str | _CType, ...], *data: Any) -> bytes:
|
|
22
|
+
return struct.pack(to_format_string(format), *data)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def unpack(format: tuple[str | _CType], data: Buffer):
|
|
26
|
+
return struct.unpack(to_format_string(format), data)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import inspect
|
|
3
|
+
import struct
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from collections.abc import Buffer
|
|
6
|
+
from typing import Self, Generator
|
|
7
|
+
|
|
8
|
+
from _hydrogenlib_core.typefunc import AutoSlots
|
|
9
|
+
|
|
10
|
+
endian_control_characters = tuple('<@=!>')
|
|
11
|
+
|
|
12
|
+
_AOT_TEMPLATE = """
|
|
13
|
+
def pack(self):
|
|
14
|
+
return self.__st_struct__.pack({selfattrs})
|
|
15
|
+
|
|
16
|
+
def pack_into(self, buffer, offset=0):
|
|
17
|
+
self.__st_struct__.pack_into(buffer, offset, {selfattrs})
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def unpack(cls, buffer):
|
|
21
|
+
self = object.__new__(cls)
|
|
22
|
+
{selfattrs} = self.__st_struct__.unpack(buffer)
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def unpack_from(cls, buffer, offset=0):
|
|
27
|
+
self = object.__new__(cls)
|
|
28
|
+
{selfattrs} = cls.__st_struct__.unpack_from(buffer, offset)
|
|
29
|
+
return self
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def iter_unpack(cls, buffer):
|
|
33
|
+
for values in cls.__st_struct__.iter_unpack(buffer):
|
|
34
|
+
self = object.__new__(cls)
|
|
35
|
+
{selfattrs} = values
|
|
36
|
+
yield self
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def iter_unpack_reused(cls, buffer):
|
|
40
|
+
self = object.__new__(cls)
|
|
41
|
+
for values in cls.__st_struct__.iter_unpack(buffer):
|
|
42
|
+
{selfattrs} = values
|
|
43
|
+
yield self
|
|
44
|
+
|
|
45
|
+
def update_from(self, buffer, offset=0):
|
|
46
|
+
{selfattrs} = self.__st_struct__.unpack_from(buffer, offset)
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def __init__(self, {init_signature}):
|
|
50
|
+
{selfattrs} = {attrs}
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Struct(AutoSlots):
|
|
56
|
+
"""
|
|
57
|
+
Base class for creating structured data with a defined format, suitable for packing and unpacking binary data.
|
|
58
|
+
|
|
59
|
+
This class allows the creation of a structure that can be used to pack and unpack binary data according to a
|
|
60
|
+
specified format. It supports type annotations for defining the fields of the structure.
|
|
61
|
+
The class automatically generates the necessary format string for the `struct` module based on the provided
|
|
62
|
+
field types. It also provides methods for comparing instances, packing and unpacking data,
|
|
63
|
+
and converting the structure to a dictionary representation.
|
|
64
|
+
|
|
65
|
+
Usage:
|
|
66
|
+
class MyStruct(Struct):
|
|
67
|
+
a: int
|
|
68
|
+
b: bool
|
|
69
|
+
c: float
|
|
70
|
+
"""
|
|
71
|
+
__no_slots__ = (
|
|
72
|
+
'__st_fields__', '__st_struct__', '__st_format__', '__st_order__',
|
|
73
|
+
'__ost_fields__'
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
__st_fields__: dict[str, type] = None
|
|
77
|
+
__st_struct__: struct.Struct = None
|
|
78
|
+
__st_format__: str = ''
|
|
79
|
+
__st_order__: str = '@'
|
|
80
|
+
|
|
81
|
+
# optimize attributes
|
|
82
|
+
__ost_fields__: tuple[str, ...] = None
|
|
83
|
+
|
|
84
|
+
# __ost_methods__: dict[str, Callable] = None
|
|
85
|
+
|
|
86
|
+
def __init_subclass__(cls, *, middle=False, endian: str = '@', **kwargs):
|
|
87
|
+
if middle:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
from .types import get_ctype
|
|
91
|
+
|
|
92
|
+
cls.__st_fields__ = copy.copy(cls.__st_fields__) or OrderedDict() # 继承父类型的字段
|
|
93
|
+
|
|
94
|
+
new_fields = OrderedDict()
|
|
95
|
+
for name, anno in inspect.get_annotations(cls).items():
|
|
96
|
+
if name in cls.__st_fields__:
|
|
97
|
+
raise TypeError(f"Field {name!r} is already defined")
|
|
98
|
+
new_fields[name] = get_ctype(anno)
|
|
99
|
+
|
|
100
|
+
cls.__st_fields__.update(new_fields)
|
|
101
|
+
cls.__st_format__ += ''.join(new_fields.values()) # 更新新字段的 format string
|
|
102
|
+
|
|
103
|
+
if cls.__st_format__.startswith(endian_control_characters): # 修改字节序
|
|
104
|
+
if cls.__st_format__[0] != endian:
|
|
105
|
+
cls.__st_format__ = endian + cls.__st_format__[1:]
|
|
106
|
+
|
|
107
|
+
cls.__st_struct__ = struct.Struct(cls.__st_format__) # 构造 Struct 类型
|
|
108
|
+
|
|
109
|
+
# 优化
|
|
110
|
+
# 生成 ost-fields
|
|
111
|
+
# cls.__ost_fields__ = tuple(cls.__st_fields__.keys())
|
|
112
|
+
|
|
113
|
+
cls.__ost_fields__ = field_names = tuple(cls.__st_fields__.keys())
|
|
114
|
+
|
|
115
|
+
selfattrs = ', '.join(
|
|
116
|
+
map(
|
|
117
|
+
lambda x: f'self.{x}',
|
|
118
|
+
field_names
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def _processor(name):
|
|
123
|
+
if name in cls.__migrated__:
|
|
124
|
+
return f'{name}={cls.__migrated__[name]}'
|
|
125
|
+
else:
|
|
126
|
+
return f'{name}'
|
|
127
|
+
|
|
128
|
+
# 生成 init 方法的签名
|
|
129
|
+
init_signature = ', '.join(
|
|
130
|
+
map(
|
|
131
|
+
_processor,
|
|
132
|
+
field_names
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# 普通的字段元组
|
|
137
|
+
simple_attrs = ', '.join(field_names)
|
|
138
|
+
|
|
139
|
+
# 生成 ost-methods
|
|
140
|
+
gl = {}
|
|
141
|
+
exec(
|
|
142
|
+
_AOT_TEMPLATE.format(
|
|
143
|
+
selfattrs=selfattrs,
|
|
144
|
+
init_signature=init_signature,
|
|
145
|
+
attrs=simple_attrs,
|
|
146
|
+
),
|
|
147
|
+
globals=gl
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
cls.__ost_methods__ = gl
|
|
151
|
+
|
|
152
|
+
cls.pack = gl['pack']
|
|
153
|
+
cls.pack_into = gl['pack_into']
|
|
154
|
+
cls.unpack = gl['unpack']
|
|
155
|
+
cls.unpack_from = gl['unpack_from']
|
|
156
|
+
cls.iter_unpack = gl['iter_unpack']
|
|
157
|
+
cls.iter_unpack_reused = gl['iter_unpack_reused']
|
|
158
|
+
cls.update_from = gl['update_from']
|
|
159
|
+
cls.__init__ = gl['__init__']
|
|
160
|
+
|
|
161
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
162
|
+
...
|
|
163
|
+
|
|
164
|
+
def __eq__(self, other):
|
|
165
|
+
if isinstance(other, Struct):
|
|
166
|
+
return other.__st_format__ == other.__st_format__ and self.to_dict() == other.to_dict()
|
|
167
|
+
elif isinstance(other, dict):
|
|
168
|
+
return self.to_dict() == other
|
|
169
|
+
elif isinstance(other, bytes):
|
|
170
|
+
return self.unpack(other) == self
|
|
171
|
+
else:
|
|
172
|
+
raise NotImplementedError(other)
|
|
173
|
+
|
|
174
|
+
def pack(self) -> bytes:
|
|
175
|
+
...
|
|
176
|
+
|
|
177
|
+
def pack_into(self, buffer: Buffer, offset=0) -> None:
|
|
178
|
+
...
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def unpack(cls, buffer) -> Self:
|
|
182
|
+
...
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def unpack_from(cls, buffer, offset=0) -> Self:
|
|
186
|
+
...
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def iter_unpack(cls, buffer) -> Generator[Self, None, None]:
|
|
190
|
+
...
|
|
191
|
+
|
|
192
|
+
@classmethod
|
|
193
|
+
def iter_unpack_reused(cls, buffer) -> Generator[Self, None, None]:
|
|
194
|
+
...
|
|
195
|
+
|
|
196
|
+
def update_from(self, buffer: Buffer, offset=0) -> None:
|
|
197
|
+
...
|
|
198
|
+
|
|
199
|
+
def to_dict(self):
|
|
200
|
+
return {
|
|
201
|
+
k: getattr(self, k)
|
|
202
|
+
for k in self.__ost_fields__
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
def __str__(self):
|
|
206
|
+
dct = self.to_dict()
|
|
207
|
+
kv_string = ', '.join(
|
|
208
|
+
map(
|
|
209
|
+
lambda x: f"{x[0]}={x[1]!r}"
|
|
210
|
+
, dct.items()
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
return f'{self.__class__.__name__}' f"({kv_string})"
|
|
214
|
+
|
|
215
|
+
def __repr__(self):
|
|
216
|
+
dct = self.to_dict()
|
|
217
|
+
kv_string = ', '.join(
|
|
218
|
+
map(
|
|
219
|
+
lambda x: f"{x[0]}={x[1]!r}"
|
|
220
|
+
, dct.items()
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
return f'{self.__class__.__name__}[{self.__st_format__}]' f"({kv_string})"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import sys
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
|
|
5
|
+
from .base import make_struct_type
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@lru_cache(maxsize=32)
|
|
9
|
+
def pad(size: int):
|
|
10
|
+
return make_struct_type(
|
|
11
|
+
f'pad_{size}', builtins.bytes, f'{size}x'
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
char = make_struct_type(
|
|
16
|
+
'char', builtins.bytes, 'c'
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
signed_char = make_struct_type(
|
|
20
|
+
'signed_char', builtins.int, 'b'
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
unsigned_char = make_struct_type(
|
|
24
|
+
'unsigned_char', builtins.int, 'B'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# bool = make_struct_type(
|
|
28
|
+
# 'bool', builtins.int, '?'
|
|
29
|
+
# ) # 布尔类型不能添加子类,也不允许添加新的类属性,只能特殊处理
|
|
30
|
+
|
|
31
|
+
short = make_struct_type(
|
|
32
|
+
'short', builtins.int, 'h'
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
unsigned_short = make_struct_type(
|
|
36
|
+
'unsigned_short', builtins.int, 'H'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
unsigned_int = make_struct_type(
|
|
40
|
+
'unsigned_int', builtins.int, 'I'
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
long = make_struct_type(
|
|
44
|
+
'long', builtins.int, 'l'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
unsigned_long = make_struct_type(
|
|
48
|
+
'unsigned_long', builtins.int, 'L'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
long_long = make_struct_type(
|
|
52
|
+
'long_long', builtins.int, 'q'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
unsigned_long_long = make_struct_type(
|
|
56
|
+
'unsigned_long_long', builtins.int, 'Q'
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
double = make_struct_type(
|
|
60
|
+
'double', builtins.float, 'd'
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
short_float = make_struct_type(
|
|
64
|
+
'short_float', builtins.float, 'e'
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
size_t = make_struct_type(
|
|
68
|
+
'size_t', builtins.int, 'n'
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
ssize_t = make_struct_type(
|
|
72
|
+
'ssize_t', builtins.int, 'N'
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# string (char[]), length must be specified when used
|
|
77
|
+
@lru_cache(maxsize=32)
|
|
78
|
+
def string(length):
|
|
79
|
+
return make_struct_type(f'string_{length}', builtins.bytes, f'{length}s')
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# pascal string (length-prefixed)
|
|
83
|
+
@lru_cache(maxsize=32)
|
|
84
|
+
def pascal_string(length):
|
|
85
|
+
return make_struct_type(f'pascal_string_{length}', builtins.bytes, f'{length}p')
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# pointer-sized integer (platform dependent)
|
|
89
|
+
pointer = make_struct_type(
|
|
90
|
+
'pointer', builtins.int, 'P'
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# >= 3.14
|
|
94
|
+
if sys.version_info.major >= 3 and sys.version_info.minor >= 14:
|
|
95
|
+
float_complex = make_struct_type(
|
|
96
|
+
'complex', builtins.complex, 'F'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
double_complex = make_struct_type(
|
|
100
|
+
'double_complex', builtins.complex, 'D'
|
|
101
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _ctype:
|
|
5
|
+
__ctype__: str
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def pad(size: int) -> type: ...
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class char(bytes, _ctype): ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class signed_char(int, _ctype): ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class unsigned_char(int, _ctype): ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class long(int, _ctype): ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class unsigned_long(int, _ctype): ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class short(int, _ctype): ...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class unsigned_short(int, _ctype): ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class unsigned_int(int, _ctype): ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class long_long(int, _ctype): ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class unsigned_long_long(int, _ctype): ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class double(float, _ctype): ...
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class short_double(float, _ctype): ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class short_float(float, _ctype): ...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class size_t(int, _ctype): ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ssize_t(int, _ctype): ...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def string(length: int) -> type[bytes]: ...
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def pascal_string(length: int) -> type[bytes]: ...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class pointer(int, _ctype): ...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if sys.version_info >= (3, 14, 0):
|
|
66
|
+
class float_complex(complex, _ctype): ...
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class double_complex(complex, _ctype): ...
|