sltcodec 2.2.0__tar.gz → 2.3.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-2.2.0 → sltcodec-2.3.0}/PKG-INFO +3 -3
- {sltcodec-2.2.0 → sltcodec-2.3.0}/README_pypi.md +2 -2
- {sltcodec-2.2.0 → sltcodec-2.3.0}/pyproject.toml +1 -1
- {sltcodec-2.2.0 → sltcodec-2.3.0}/src/sltcodec/codec.py +13 -4
- {sltcodec-2.2.0 → sltcodec-2.3.0}/src/sltcodec/types.py +11 -2
- {sltcodec-2.2.0 → sltcodec-2.3.0}/tests/test_codec.py +32 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/tests/test_types.py +2 -1
- {sltcodec-2.2.0 → sltcodec-2.3.0}/uv.lock +1 -1
- {sltcodec-2.2.0 → sltcodec-2.3.0}/.github/workflows/publish_to_pypi.yml +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/.github/workflows/publish_to_testpypi.yml +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/.gitignore +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/.python-version +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/.vscode/launch.json +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/.vscode/settings.json +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/LICENSE +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/README.md +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/bump_major.ps1 +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/bump_minor.ps1 +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/bump_patch.ps1 +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/src/sltcodec/__init__.py +0 -0
- {sltcodec-2.2.0 → sltcodec-2.3.0}/tests/test_persistence.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sltcodec
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -77,7 +77,7 @@ The public `PRIMITIVE_TYPES` set contains:
|
|
|
77
77
|
{"bool", "signed int", "int", "unsigned int", "float", "bytearray", "bytes"}
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
`FieldDef.type` may also be a nested `StructDef` or an expression string. Expressions can use values from previously processed fields, allowing dynamic types and
|
|
80
|
+
`FieldDef.type` may also be a nested `StructDef` or an expression string. Expressions can use values from previously processed fields, allowing dynamic types, sizes, and repeat counts. Repeated fields, byte swapping, padding, ranges, and enum metadata are supported.
|
|
81
81
|
|
|
82
82
|
## Enums And Named Structures
|
|
83
83
|
|
|
@@ -180,7 +180,7 @@ internal implementation details and are omitted from the public method tables.
|
|
|
180
180
|
| `size` | `InfoSize \| str` | Static size or expression. |
|
|
181
181
|
| `type` | `str \| StructDef` | Primitive, named, or nested field type. |
|
|
182
182
|
| `scale` | `float` | Numeric scale applied to the field. |
|
|
183
|
-
| `repeat` | `int \| None` | Number of repeated field values. |
|
|
183
|
+
| `repeat` | `int \| str \| None` | Number of repeated field values, or an expression that evaluates to the count. |
|
|
184
184
|
| `description` | `str \| None` | Optional field description. |
|
|
185
185
|
| `range_expression` | `str \| None` | Optional value-range expression. |
|
|
186
186
|
| `enum_def_name` | `str \| None` | Name of the associated enum definition. |
|
|
@@ -60,7 +60,7 @@ The public `PRIMITIVE_TYPES` set contains:
|
|
|
60
60
|
{"bool", "signed int", "int", "unsigned int", "float", "bytearray", "bytes"}
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
`FieldDef.type` may also be a nested `StructDef` or an expression string. Expressions can use values from previously processed fields, allowing dynamic types and
|
|
63
|
+
`FieldDef.type` may also be a nested `StructDef` or an expression string. Expressions can use values from previously processed fields, allowing dynamic types, sizes, and repeat counts. Repeated fields, byte swapping, padding, ranges, and enum metadata are supported.
|
|
64
64
|
|
|
65
65
|
## Enums And Named Structures
|
|
66
66
|
|
|
@@ -163,7 +163,7 @@ internal implementation details and are omitted from the public method tables.
|
|
|
163
163
|
| `size` | `InfoSize \| str` | Static size or expression. |
|
|
164
164
|
| `type` | `str \| StructDef` | Primitive, named, or nested field type. |
|
|
165
165
|
| `scale` | `float` | Numeric scale applied to the field. |
|
|
166
|
-
| `repeat` | `int \| None` | Number of repeated field values. |
|
|
166
|
+
| `repeat` | `int \| str \| None` | Number of repeated field values, or an expression that evaluates to the count. |
|
|
167
167
|
| `description` | `str \| None` | Optional field description. |
|
|
168
168
|
| `range_expression` | `str \| None` | Optional value-range expression. |
|
|
169
169
|
| `enum_def_name` | `str \| None` | Name of the associated enum definition. |
|
|
@@ -149,6 +149,13 @@ def _resolve_byte_swap(field_def: FieldDef, env: dict[str, Any]) -> bool:
|
|
|
149
149
|
return field_def.byte_swap
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
def _resolve_repeat(value: int | str | None, env: dict[str, Any]) -> int | None:
|
|
153
|
+
"""Resolve a static or expression-based repeat count."""
|
|
154
|
+
if isinstance(value, str):
|
|
155
|
+
return int(SltEval(env).eval(value))
|
|
156
|
+
return value
|
|
157
|
+
|
|
158
|
+
|
|
152
159
|
def _is_padding_field_def(field_def: FieldDef) -> bool:
|
|
153
160
|
"""Check whether a field definition represents padding."""
|
|
154
161
|
return (field_def.name.startswith("padding[")
|
|
@@ -328,17 +335,18 @@ def encode(
|
|
|
328
335
|
|
|
329
336
|
for field_value in struct_instance.field_instances:
|
|
330
337
|
field_def = field_value.field_def
|
|
338
|
+
repeat = _resolve_repeat(field_def.repeat, env)
|
|
331
339
|
if _is_padding_field_def(field_def):
|
|
332
340
|
has_padding = True
|
|
333
341
|
value = field_value.value
|
|
334
342
|
if _is_padding_field_def(field_def):
|
|
335
343
|
padding_size = _resolve_info_size(field_def.size, env)
|
|
336
344
|
value = bytearray(padding_size.bytes)
|
|
337
|
-
if
|
|
345
|
+
if repeat is not None and repeat > 1:
|
|
338
346
|
current_offset = _resolve_info_size(field_def.offset, env)
|
|
339
347
|
values = list(value)
|
|
340
348
|
|
|
341
|
-
for i in range(
|
|
349
|
+
for i in range(repeat):
|
|
342
350
|
field_def_repeat = _repeated_field_def(field_def, i,
|
|
343
351
|
current_offset,
|
|
344
352
|
field_def.size)
|
|
@@ -530,8 +538,9 @@ def decode(
|
|
|
530
538
|
current_position = target_offset
|
|
531
539
|
|
|
532
540
|
for field_def in _as_field_defs(struct_def_obj):
|
|
541
|
+
repeat = _resolve_repeat(field_def.repeat, env)
|
|
533
542
|
# Handle non-repeated fields
|
|
534
|
-
if
|
|
543
|
+
if repeat is None or repeat <= 1:
|
|
535
544
|
resolved_offset = _resolve_info_size(field_def.offset, env)
|
|
536
545
|
append_padding_until(resolved_offset)
|
|
537
546
|
field_instance = decode_field(field_def, data, env, type_dict,
|
|
@@ -545,7 +554,7 @@ def decode(
|
|
|
545
554
|
# Handle repeated fields
|
|
546
555
|
current_offset = _resolve_info_size(field_def.offset, env)
|
|
547
556
|
append_padding_until(current_offset)
|
|
548
|
-
for i in range(
|
|
557
|
+
for i in range(repeat):
|
|
549
558
|
field_def_repeat = _repeated_field_def(field_def, i, current_offset,
|
|
550
559
|
field_def.size)
|
|
551
560
|
field_instance = decode_field(field_def_repeat, data, env,
|
|
@@ -98,7 +98,7 @@ class FieldDef:
|
|
|
98
98
|
metadata={"desc": "The type of the field"})
|
|
99
99
|
scale: float = field(default=1.0,
|
|
100
100
|
metadata={"desc": "The scale of the field"})
|
|
101
|
-
repeat: int | None = field(
|
|
101
|
+
repeat: int | str | None = field(
|
|
102
102
|
default=None, metadata={"desc": "The repeat count of the field"})
|
|
103
103
|
description: str | None = field(
|
|
104
104
|
default=None, metadata={"desc": "The description of the field"})
|
|
@@ -231,7 +231,7 @@ class FieldDef:
|
|
|
231
231
|
self.name,
|
|
232
232
|
self._sortable_type(self.type),
|
|
233
233
|
self.scale,
|
|
234
|
-
|
|
234
|
+
self._sortable_repeat(self.repeat),
|
|
235
235
|
"" if self.description is None else self.description,
|
|
236
236
|
"" if self.range_expression is None else self.range_expression,
|
|
237
237
|
"" if self.enum_def_name is None else self.enum_def_name,
|
|
@@ -245,6 +245,15 @@ class FieldDef:
|
|
|
245
245
|
return (0, value.byte, value.bit)
|
|
246
246
|
return (1, value)
|
|
247
247
|
|
|
248
|
+
@staticmethod
|
|
249
|
+
def _sortable_repeat(value: int | str | None) -> tuple[int, int | str]:
|
|
250
|
+
"""Build a comparable key for static or expression repeat counts."""
|
|
251
|
+
if value is None:
|
|
252
|
+
return 0, -1
|
|
253
|
+
if isinstance(value, int):
|
|
254
|
+
return 1, value
|
|
255
|
+
return 2, value
|
|
256
|
+
|
|
248
257
|
@staticmethod
|
|
249
258
|
def _sortable_type(value: str | "StructDef") -> tuple[Any, ...]:
|
|
250
259
|
"""Build a comparable key for primitive or nested field types."""
|
|
@@ -166,6 +166,38 @@ def test_encode_layout_handles_repeat():
|
|
|
166
166
|
]
|
|
167
167
|
|
|
168
168
|
|
|
169
|
+
def test_expression_repeat_uses_previous_field_value():
|
|
170
|
+
"""Test that repeat expressions are evaluated against the field env."""
|
|
171
|
+
seed_field = FieldDef(name="count",
|
|
172
|
+
offset=InfoSize(0, 0),
|
|
173
|
+
size=InfoSize(1, 0),
|
|
174
|
+
type="unsigned int")
|
|
175
|
+
repeated_field = FieldDef(name="value",
|
|
176
|
+
offset=InfoSize(1, 0),
|
|
177
|
+
size=InfoSize(1, 0),
|
|
178
|
+
type="unsigned int",
|
|
179
|
+
repeat="count + 1")
|
|
180
|
+
struct_def = StructDef(fields=[seed_field, repeated_field])
|
|
181
|
+
|
|
182
|
+
encoded = encode(
|
|
183
|
+
layout_for(struct_def),
|
|
184
|
+
StructInstance(
|
|
185
|
+
struct_def=struct_def,
|
|
186
|
+
field_instances=[
|
|
187
|
+
FieldInstance(seed_field, 2),
|
|
188
|
+
FieldInstance(repeated_field, [5, 6, 7]),
|
|
189
|
+
],
|
|
190
|
+
),
|
|
191
|
+
bytearray(),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
assert encoded == bytearray(b"\x02\x05\x06\x07")
|
|
195
|
+
decoded = decode(layout_for(struct_def), encoded)
|
|
196
|
+
|
|
197
|
+
assert [instance.value
|
|
198
|
+
for instance in decoded.field_instances] == [2, 5, 6, 7]
|
|
199
|
+
|
|
200
|
+
|
|
169
201
|
def test_encode_recurses_for_nested_field_types():
|
|
170
202
|
"""Test that encoding a field with a nested field type works correctly."""
|
|
171
203
|
child_field_defs = [
|
|
@@ -39,7 +39,7 @@ def test_field_def_to_json_from_json_round_trip():
|
|
|
39
39
|
size=InfoSize(1, 0),
|
|
40
40
|
type="unsigned int",
|
|
41
41
|
scale=2.0,
|
|
42
|
-
repeat=
|
|
42
|
+
repeat="count + 1",
|
|
43
43
|
description="A repeated value",
|
|
44
44
|
range_expression="0 <= value <= 255",
|
|
45
45
|
enum_def_name=enum_def.name)
|
|
@@ -48,6 +48,7 @@ def test_field_def_to_json_from_json_round_trip():
|
|
|
48
48
|
restored = FieldDef.from_json(payload)
|
|
49
49
|
|
|
50
50
|
assert restored == field_def
|
|
51
|
+
assert restored.repeat == "count + 1"
|
|
51
52
|
assert restored.range_expression == "0 <= value <= 255"
|
|
52
53
|
assert restored.enum_def_name == enum_def.name
|
|
53
54
|
assert restored.to_dict()["enum_def_name"] == "ValueKind"
|
|
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
|
|
File without changes
|
|
File without changes
|