sltcodec 1.1.0__tar.gz → 1.2.0__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.
- {sltcodec-1.1.0 → sltcodec-1.2.0}/PKG-INFO +39 -1
- {sltcodec-1.1.0 → sltcodec-1.2.0}/README.md +38 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/pyproject.toml +1 -1
- {sltcodec-1.1.0 → sltcodec-1.2.0}/src/sltcodec/codec.py +1 -1
- {sltcodec-1.1.0 → sltcodec-1.2.0}/src/sltcodec/types.py +11 -31
- {sltcodec-1.1.0 → sltcodec-1.2.0}/tests/test_codec.py +9 -16
- {sltcodec-1.1.0 → sltcodec-1.2.0}/uv.lock +1 -1
- {sltcodec-1.1.0 → sltcodec-1.2.0}/.github/workflows/publish_to_pypi.yml +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/.github/workflows/publish_to_testpypi.yml +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/.gitignore +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/.python-version +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/.vscode/launch.json +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/.vscode/settings.json +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/LICENSE +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/bump_major.ps1 +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/bump_minor.ps1 +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/bump_patch.ps1 +0 -0
- {sltcodec-1.1.0 → sltcodec-1.2.0}/src/sltcodec/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sltcodec
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Decode and encode bytearrays according to struct layout definitions using sltcore.
|
|
5
5
|
Project-URL: Homepage, https://github.com/fangface-hub/StructLayoutToolkitCodec
|
|
6
6
|
Project-URL: Documentation, https://readthedocs.org
|
|
@@ -236,6 +236,44 @@ bytearray(b"\x01\x07") StructInstance(struct_def=StructDef(...), field_instances
|
|
|
236
236
|
bytearray(b"\x02?\xc0\x00\x00") StructInstance(struct_def=StructDef(...), field_instances=[...])
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
+
## Enum Definitions
|
|
240
|
+
|
|
241
|
+
`FieldDef.enum_def_name` holds only the name of an `EnumDef`, not the definition
|
|
242
|
+
itself. Pass the actual definitions via an `enum_def_dict` (mapping name to
|
|
243
|
+
`EnumDef`) to `decode` / `encode` so the matching `EnumDef` can be resolved and
|
|
244
|
+
attached as `FieldInstance.enum_item`.
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from sltcore import InfoSize
|
|
248
|
+
from sltcodec import EnumDef, FieldDef, decode
|
|
249
|
+
|
|
250
|
+
status_enum = EnumDef(name="Status", values={"OK": 0, "NG": 1})
|
|
251
|
+
|
|
252
|
+
struct_def = [
|
|
253
|
+
FieldDef(name="status",
|
|
254
|
+
offset=InfoSize(0, 0),
|
|
255
|
+
size=InfoSize(1, 0),
|
|
256
|
+
type="unsigned int",
|
|
257
|
+
enum_def_name="Status"),
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
decoded = decode(struct_def, bytearray(b"\x01"),
|
|
261
|
+
enum_def_dict={"Status": status_enum})
|
|
262
|
+
|
|
263
|
+
print(decoded.field_instances[0].value)
|
|
264
|
+
print(decoded.field_instances[0].enum_item)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Output:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
1
|
|
271
|
+
('NG', 1)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
You can also persist enum definitions by name with
|
|
275
|
+
`save_enum_def_dict` / `load_enum_def_dict`, mirroring `StructDef` dictionaries.
|
|
276
|
+
|
|
239
277
|
## Development
|
|
240
278
|
|
|
241
279
|
```bash
|
|
@@ -219,6 +219,44 @@ bytearray(b"\x01\x07") StructInstance(struct_def=StructDef(...), field_instances
|
|
|
219
219
|
bytearray(b"\x02?\xc0\x00\x00") StructInstance(struct_def=StructDef(...), field_instances=[...])
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
+
## Enum Definitions
|
|
223
|
+
|
|
224
|
+
`FieldDef.enum_def_name` holds only the name of an `EnumDef`, not the definition
|
|
225
|
+
itself. Pass the actual definitions via an `enum_def_dict` (mapping name to
|
|
226
|
+
`EnumDef`) to `decode` / `encode` so the matching `EnumDef` can be resolved and
|
|
227
|
+
attached as `FieldInstance.enum_item`.
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from sltcore import InfoSize
|
|
231
|
+
from sltcodec import EnumDef, FieldDef, decode
|
|
232
|
+
|
|
233
|
+
status_enum = EnumDef(name="Status", values={"OK": 0, "NG": 1})
|
|
234
|
+
|
|
235
|
+
struct_def = [
|
|
236
|
+
FieldDef(name="status",
|
|
237
|
+
offset=InfoSize(0, 0),
|
|
238
|
+
size=InfoSize(1, 0),
|
|
239
|
+
type="unsigned int",
|
|
240
|
+
enum_def_name="Status"),
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
decoded = decode(struct_def, bytearray(b"\x01"),
|
|
244
|
+
enum_def_dict={"Status": status_enum})
|
|
245
|
+
|
|
246
|
+
print(decoded.field_instances[0].value)
|
|
247
|
+
print(decoded.field_instances[0].enum_item)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Output:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
1
|
|
254
|
+
('NG', 1)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
You can also persist enum definitions by name with
|
|
258
|
+
`save_enum_def_dict` / `load_enum_def_dict`, mirroring `StructDef` dictionaries.
|
|
259
|
+
|
|
222
260
|
## Development
|
|
223
261
|
|
|
224
262
|
```bash
|
|
@@ -368,7 +368,7 @@ def decode_field(
|
|
|
368
368
|
repeat=field_def.repeat,
|
|
369
369
|
description=field_def.description,
|
|
370
370
|
range_expression=field_def.range_expression,
|
|
371
|
-
|
|
371
|
+
enum_def_name=field_def.enum_def_name,
|
|
372
372
|
byte_swap=byte_swap,
|
|
373
373
|
)
|
|
374
374
|
if isinstance(resolved_type, StructDef):
|
|
@@ -104,8 +104,9 @@ class FieldDef:
|
|
|
104
104
|
default=None, metadata={"desc": "The description of the field"})
|
|
105
105
|
range_expression: str | None = field(
|
|
106
106
|
default=None, metadata={"desc": "The value range expression"})
|
|
107
|
-
|
|
108
|
-
default=None,
|
|
107
|
+
enum_def_name: str | None = field(
|
|
108
|
+
default=None,
|
|
109
|
+
metadata={"desc": "The name of the enum definition for the field"})
|
|
109
110
|
byte_swap: bool | str = field(
|
|
110
111
|
default=False,
|
|
111
112
|
metadata={"desc": "Whether to reverse the field byte order"})
|
|
@@ -137,7 +138,7 @@ class FieldDef:
|
|
|
137
138
|
repeat=None,
|
|
138
139
|
description=self.description,
|
|
139
140
|
range_expression=split_range_expression,
|
|
140
|
-
|
|
141
|
+
enum_def_name=self.enum_def_name,
|
|
141
142
|
byte_swap=split_byte_swap)
|
|
142
143
|
|
|
143
144
|
@staticmethod
|
|
@@ -184,8 +185,8 @@ class FieldDef:
|
|
|
184
185
|
self.description,
|
|
185
186
|
"range_expression":
|
|
186
187
|
self.range_expression,
|
|
187
|
-
"
|
|
188
|
-
|
|
188
|
+
"enum_def_name":
|
|
189
|
+
self.enum_def_name,
|
|
189
190
|
"byte_swap":
|
|
190
191
|
self.byte_swap,
|
|
191
192
|
}
|
|
@@ -205,7 +206,7 @@ class FieldDef:
|
|
|
205
206
|
repeat = data.get("repeat")
|
|
206
207
|
description = data.get("description")
|
|
207
208
|
range_expression = data.get("range_expression")
|
|
208
|
-
|
|
209
|
+
enum_def_name = data.get("enum_def_name")
|
|
209
210
|
byte_swap = data.get("byte_swap", False)
|
|
210
211
|
|
|
211
212
|
def _deserialize_info_like(value: Any) -> Any:
|
|
@@ -242,14 +243,6 @@ class FieldDef:
|
|
|
242
243
|
else:
|
|
243
244
|
type_value = type_data
|
|
244
245
|
|
|
245
|
-
if enum_def_data is None:
|
|
246
|
-
enum_def = None
|
|
247
|
-
elif (isinstance(enum_def_data, dict)
|
|
248
|
-
and enum_def_data.get("__type__") == "EnumDef"):
|
|
249
|
-
enum_def = EnumDef.deserialize(enum_def_data)
|
|
250
|
-
else:
|
|
251
|
-
enum_def = None
|
|
252
|
-
|
|
253
246
|
return cls(
|
|
254
247
|
name=name,
|
|
255
248
|
offset=offset,
|
|
@@ -259,7 +252,7 @@ class FieldDef:
|
|
|
259
252
|
repeat=repeat,
|
|
260
253
|
description=description,
|
|
261
254
|
range_expression=range_expression,
|
|
262
|
-
|
|
255
|
+
enum_def_name=enum_def_name,
|
|
263
256
|
byte_swap=byte_swap,
|
|
264
257
|
)
|
|
265
258
|
|
|
@@ -279,7 +272,7 @@ class FieldDef:
|
|
|
279
272
|
-1 if self.repeat is None else self.repeat,
|
|
280
273
|
"" if self.description is None else self.description,
|
|
281
274
|
"" if self.range_expression is None else self.range_expression,
|
|
282
|
-
self.
|
|
275
|
+
"" if self.enum_def_name is None else self.enum_def_name,
|
|
283
276
|
self._sortable_byte_swap(self.byte_swap),
|
|
284
277
|
)
|
|
285
278
|
|
|
@@ -297,19 +290,6 @@ class FieldDef:
|
|
|
297
290
|
return (0, json.dumps(value.to_dict(), sort_keys=True))
|
|
298
291
|
return (1, value)
|
|
299
292
|
|
|
300
|
-
@staticmethod
|
|
301
|
-
def _sortable_enum_type(value: EnumDef | None) -> str:
|
|
302
|
-
"""Build a comparable key for enum definition metadata."""
|
|
303
|
-
if value is None:
|
|
304
|
-
return ""
|
|
305
|
-
return json.dumps(
|
|
306
|
-
{
|
|
307
|
-
"name": value.name,
|
|
308
|
-
"description": value.description,
|
|
309
|
-
"values": value.values,
|
|
310
|
-
},
|
|
311
|
-
sort_keys=True)
|
|
312
|
-
|
|
313
293
|
@staticmethod
|
|
314
294
|
def _sortable_byte_swap(value: bool | str) -> tuple[int, str]:
|
|
315
295
|
"""Build a comparable key for a byte-swap flag or expression."""
|
|
@@ -369,10 +349,10 @@ class FieldInstance:
|
|
|
369
349
|
field_def: FieldDef,
|
|
370
350
|
enum_def_dict: dict[str, EnumDef] | None) -> EnumDef | None:
|
|
371
351
|
"""Resolve enum definition from field metadata or lookup dictionary."""
|
|
372
|
-
if field_def.enum_def is not None:
|
|
373
|
-
return field_def.enum_def
|
|
374
352
|
if not enum_def_dict:
|
|
375
353
|
return None
|
|
354
|
+
if field_def.enum_def_name is not None:
|
|
355
|
+
return enum_def_dict.get(field_def.enum_def_name)
|
|
376
356
|
if isinstance(field_def.type, str) and field_def.type in enum_def_dict:
|
|
377
357
|
return enum_def_dict[field_def.type]
|
|
378
358
|
return enum_def_dict.get(field_def.name)
|
|
@@ -179,7 +179,7 @@ def test_repeated_field_preserves_range_and_enum_metadata():
|
|
|
179
179
|
type="unsigned int",
|
|
180
180
|
repeat=2,
|
|
181
181
|
range_expression="0 <= value <= 255",
|
|
182
|
-
|
|
182
|
+
enum_def_name=enum_def.name)
|
|
183
183
|
|
|
184
184
|
encoded = encode(
|
|
185
185
|
StructInstance(struct_def=StructDef(fields=[field_def]),
|
|
@@ -192,8 +192,8 @@ def test_repeated_field_preserves_range_and_enum_metadata():
|
|
|
192
192
|
"0 <= value[0] <= 255")
|
|
193
193
|
assert decoded.field_instances[1].field_def.range_expression == (
|
|
194
194
|
"0 <= value[1] <= 255")
|
|
195
|
-
assert decoded.field_instances[0].field_def.
|
|
196
|
-
assert decoded.field_instances[1].field_def.
|
|
195
|
+
assert decoded.field_instances[0].field_def.enum_def_name == enum_def.name
|
|
196
|
+
assert decoded.field_instances[1].field_def.enum_def_name == enum_def.name
|
|
197
197
|
|
|
198
198
|
|
|
199
199
|
def test_split_repeat_replaces_name_in_range_expression():
|
|
@@ -242,23 +242,15 @@ def test_field_def_to_json_from_json_round_trip():
|
|
|
242
242
|
repeat=3,
|
|
243
243
|
description="A repeated value",
|
|
244
244
|
range_expression="0 <= value <= 255",
|
|
245
|
-
|
|
245
|
+
enum_def_name=enum_def.name)
|
|
246
246
|
|
|
247
247
|
payload = field_def.to_json()
|
|
248
248
|
restored = FieldDef.from_json(payload)
|
|
249
249
|
|
|
250
250
|
assert restored == field_def
|
|
251
251
|
assert restored.range_expression == "0 <= value <= 255"
|
|
252
|
-
assert restored.
|
|
253
|
-
assert restored.to_dict()["
|
|
254
|
-
"__type__": "EnumDef",
|
|
255
|
-
"name": "ValueKind",
|
|
256
|
-
"description": None,
|
|
257
|
-
"values": {
|
|
258
|
-
member.name: member.value
|
|
259
|
-
for member in ValueKind
|
|
260
|
-
},
|
|
261
|
-
}
|
|
252
|
+
assert restored.enum_def_name == enum_def.name
|
|
253
|
+
assert restored.to_dict()["enum_def_name"] == "ValueKind"
|
|
262
254
|
|
|
263
255
|
|
|
264
256
|
def test_field_def_byte_swap_json_round_trip():
|
|
@@ -425,9 +417,10 @@ def test_field_instance_from_value_sets_enum_item_from_field_enum_def():
|
|
|
425
417
|
offset=InfoSize(0, 0),
|
|
426
418
|
size=InfoSize(1, 0),
|
|
427
419
|
type="unsigned int",
|
|
428
|
-
|
|
420
|
+
enum_def_name=enum_def.name)
|
|
429
421
|
|
|
430
|
-
field_instance = FieldInstance.from_value(
|
|
422
|
+
field_instance = FieldInstance.from_value(
|
|
423
|
+
field_def, 1, enum_def_dict={enum_def.name: enum_def})
|
|
431
424
|
|
|
432
425
|
assert field_instance.enum_item == ("MANUAL", 1)
|
|
433
426
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|