sltcodec 0.4.0__tar.gz → 0.5.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-0.4.0 → sltcodec-0.5.0}/PKG-INFO +1 -1
- {sltcodec-0.4.0 → sltcodec-0.5.0}/pyproject.toml +1 -1
- {sltcodec-0.4.0 → sltcodec-0.5.0}/src/sltcodec/codec.py +18 -7
- {sltcodec-0.4.0 → sltcodec-0.5.0}/src/sltcodec/types.py +71 -18
- {sltcodec-0.4.0 → sltcodec-0.5.0}/tests/test_codec.py +8 -5
- {sltcodec-0.4.0 → sltcodec-0.5.0}/uv.lock +1 -1
- {sltcodec-0.4.0 → sltcodec-0.5.0}/.github/workflows/publish_to_pypi.yml +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/.github/workflows/publish_to_testpypi.yml +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/.gitignore +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/.python-version +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/.vscode/launch.json +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/.vscode/settings.json +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/LICENSE +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/README.md +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/bump_major.ps1 +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/bump_minor.ps1 +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/bump_patch.ps1 +0 -0
- {sltcodec-0.4.0 → sltcodec-0.5.0}/src/sltcodec/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sltcodec
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -319,34 +319,45 @@ def decode_field(
|
|
|
319
319
|
return None
|
|
320
320
|
info = bits_get(data, offset, size, scale=field_def.scale)
|
|
321
321
|
resolved_type = _resolve_field_type(field_def.type, struct_def_dict, env)
|
|
322
|
+
resolved_field_def = FieldDef(
|
|
323
|
+
name=field_def.name,
|
|
324
|
+
offset=offset,
|
|
325
|
+
size=size,
|
|
326
|
+
type=resolved_type,
|
|
327
|
+
scale=field_def.scale,
|
|
328
|
+
repeat=field_def.repeat,
|
|
329
|
+
description=field_def.description,
|
|
330
|
+
range_expression=field_def.range_expression,
|
|
331
|
+
enum_def=field_def.enum_def,
|
|
332
|
+
)
|
|
322
333
|
if isinstance(resolved_type, StructDef):
|
|
323
334
|
return FieldInstance(
|
|
324
|
-
field_def=
|
|
335
|
+
field_def=resolved_field_def,
|
|
325
336
|
value=decode(resolved_type, bytearray(info.to_bytes),
|
|
326
337
|
struct_def_dict, enum_def_dict,
|
|
327
338
|
padding_alignment_bits),
|
|
328
339
|
)
|
|
329
340
|
if resolved_type == "bool":
|
|
330
|
-
return FieldInstance.from_value(
|
|
341
|
+
return FieldInstance.from_value(resolved_field_def,
|
|
331
342
|
info.to_bool,
|
|
332
343
|
enum_def_dict=enum_def_dict)
|
|
333
344
|
if resolved_type == "signed int":
|
|
334
|
-
return FieldInstance.from_value(
|
|
345
|
+
return FieldInstance.from_value(resolved_field_def,
|
|
335
346
|
info.to_signed_int,
|
|
336
347
|
enum_def_dict=enum_def_dict)
|
|
337
348
|
if resolved_type in ["int", "unsigned int"]:
|
|
338
|
-
return FieldInstance.from_value(
|
|
349
|
+
return FieldInstance.from_value(resolved_field_def,
|
|
339
350
|
info.to_unsigned_int,
|
|
340
351
|
enum_def_dict=enum_def_dict)
|
|
341
352
|
if resolved_type == "float":
|
|
342
|
-
return FieldInstance.from_value(
|
|
353
|
+
return FieldInstance.from_value(resolved_field_def,
|
|
343
354
|
info.to_float,
|
|
344
355
|
enum_def_dict=enum_def_dict)
|
|
345
356
|
if resolved_type in ["bytearray", "bytes"]:
|
|
346
|
-
return FieldInstance.from_value(
|
|
357
|
+
return FieldInstance.from_value(resolved_field_def,
|
|
347
358
|
info.to_bytes,
|
|
348
359
|
enum_def_dict=enum_def_dict)
|
|
349
|
-
return FieldInstance.from_value(
|
|
360
|
+
return FieldInstance.from_value(resolved_field_def,
|
|
350
361
|
info.raw_value,
|
|
351
362
|
enum_def_dict=enum_def_dict)
|
|
352
363
|
|
|
@@ -4,12 +4,14 @@ from __future__ import annotations
|
|
|
4
4
|
import json
|
|
5
5
|
import re
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
+
from functools import total_ordering
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
from sltcalc import SltEval
|
|
10
11
|
from sltcore import Info, InfoSize
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
@total_ordering
|
|
13
15
|
@dataclass(frozen=True)
|
|
14
16
|
class EnumDef:
|
|
15
17
|
"""An enumeration definition."""
|
|
@@ -67,7 +69,22 @@ class EnumDef:
|
|
|
67
69
|
"""Create an enum definition from a JSON string."""
|
|
68
70
|
return cls.from_dict(json.loads(data))
|
|
69
71
|
|
|
72
|
+
def __lt__(self, other: object) -> bool:
|
|
73
|
+
"""Compare enum definitions using a stable serialized sort key."""
|
|
74
|
+
if not isinstance(other, EnumDef):
|
|
75
|
+
return NotImplemented
|
|
76
|
+
return self._sort_key() < other._sort_key()
|
|
77
|
+
|
|
78
|
+
def _sort_key(self) -> tuple[Any, ...]:
|
|
79
|
+
"""Build a stable comparison key for ordering enum definitions."""
|
|
80
|
+
return (
|
|
81
|
+
self.name,
|
|
82
|
+
"" if self.description is None else self.description,
|
|
83
|
+
json.dumps(self.values, sort_keys=True),
|
|
84
|
+
)
|
|
70
85
|
|
|
86
|
+
|
|
87
|
+
@total_ordering
|
|
71
88
|
@dataclass(frozen=True)
|
|
72
89
|
class FieldDef:
|
|
73
90
|
"""A field in a structured layout."""
|
|
@@ -283,6 +300,7 @@ class FieldDef:
|
|
|
283
300
|
sort_keys=True)
|
|
284
301
|
|
|
285
302
|
|
|
303
|
+
@total_ordering
|
|
286
304
|
@dataclass(frozen=True)
|
|
287
305
|
class FieldInstance:
|
|
288
306
|
"""A decoded/encodable field value with its field definition."""
|
|
@@ -355,6 +373,7 @@ class FieldInstance:
|
|
|
355
373
|
return None
|
|
356
374
|
|
|
357
375
|
|
|
376
|
+
@total_ordering
|
|
358
377
|
@dataclass(frozen=True)
|
|
359
378
|
class StructDef:
|
|
360
379
|
"""A structured layout definition that groups multiple fields."""
|
|
@@ -369,6 +388,21 @@ class StructDef:
|
|
|
369
388
|
metadata={"desc": "The fields of the structure"},
|
|
370
389
|
)
|
|
371
390
|
|
|
391
|
+
def __lt__(self, other: object) -> bool:
|
|
392
|
+
"""Compare structure definitions using a stable serialized sort key."""
|
|
393
|
+
if not isinstance(other, StructDef):
|
|
394
|
+
return NotImplemented
|
|
395
|
+
return self._sort_key() < other._sort_key()
|
|
396
|
+
|
|
397
|
+
def _sort_key(self) -> tuple[Any, ...]:
|
|
398
|
+
"""Build a stable comparison key for ordering structure definitions."""
|
|
399
|
+
return (
|
|
400
|
+
self.name,
|
|
401
|
+
"" if self.description is None else self.description,
|
|
402
|
+
json.dumps([field.to_dict() for field in self.fields],
|
|
403
|
+
sort_keys=True),
|
|
404
|
+
)
|
|
405
|
+
|
|
372
406
|
def to_dict(self) -> dict[str, Any]:
|
|
373
407
|
"""Convert this structure definition to JSON-serializable data."""
|
|
374
408
|
return {
|
|
@@ -402,6 +436,7 @@ class StructDef:
|
|
|
402
436
|
return cls.from_dict(json.loads(data))
|
|
403
437
|
|
|
404
438
|
|
|
439
|
+
@total_ordering
|
|
405
440
|
@dataclass
|
|
406
441
|
class StructInstance:
|
|
407
442
|
"""A decoded/encodable structure instance."""
|
|
@@ -454,6 +489,23 @@ class StructInstance:
|
|
|
454
489
|
"""Return one field instance by index."""
|
|
455
490
|
return self.field_instances[index]
|
|
456
491
|
|
|
492
|
+
def __lt__(self, other: object) -> bool:
|
|
493
|
+
"""Compare structure instances using a stable serialized sort key."""
|
|
494
|
+
if not isinstance(other, StructInstance):
|
|
495
|
+
return NotImplemented
|
|
496
|
+
return self._sort_key() < other._sort_key()
|
|
497
|
+
|
|
498
|
+
def _sort_key(self) -> tuple[Any, ...]:
|
|
499
|
+
"""Build a stable comparison key for ordering structure instances."""
|
|
500
|
+
return (
|
|
501
|
+
self.struct_def._sort_key(),
|
|
502
|
+
json.dumps([
|
|
503
|
+
field_instance.to_dict()
|
|
504
|
+
for field_instance in self.field_instances
|
|
505
|
+
],
|
|
506
|
+
sort_keys=True),
|
|
507
|
+
)
|
|
508
|
+
|
|
457
509
|
def _sort_field_instances(self) -> None:
|
|
458
510
|
"""Keep field instances sorted by FieldDef order."""
|
|
459
511
|
self.field_instances.sort()
|
|
@@ -466,14 +518,25 @@ class StructInstance:
|
|
|
466
518
|
]
|
|
467
519
|
non_padding_instances.sort()
|
|
468
520
|
|
|
521
|
+
has_unresolved_layout = any(
|
|
522
|
+
not isinstance(field_instance.field_def.offset, InfoSize)
|
|
523
|
+
or not isinstance(field_instance.field_def.size, InfoSize)
|
|
524
|
+
for field_instance in non_padding_instances)
|
|
525
|
+
if has_unresolved_layout:
|
|
526
|
+
# Layout expressions are resolved later in codec paths.
|
|
527
|
+
self.field_instances = non_padding_instances
|
|
528
|
+
self._sort_field_instances()
|
|
529
|
+
self._update_size()
|
|
530
|
+
return
|
|
531
|
+
|
|
469
532
|
rebuilt_instances: list[FieldInstance] = []
|
|
470
533
|
current_offset = InfoSize(0, 0)
|
|
471
534
|
padding_index = 0
|
|
472
535
|
|
|
473
536
|
for field_instance in non_padding_instances:
|
|
474
537
|
field_def = field_instance.field_def
|
|
475
|
-
field_offset =
|
|
476
|
-
field_size =
|
|
538
|
+
field_offset = field_def.offset
|
|
539
|
+
field_size = field_def.size
|
|
477
540
|
if field_offset > current_offset:
|
|
478
541
|
gap_size = field_offset - current_offset
|
|
479
542
|
if gap_size.byte > 0 or gap_size.bit > 0:
|
|
@@ -521,25 +584,15 @@ class StructInstance:
|
|
|
521
584
|
max_end_offset = InfoSize(0, 0)
|
|
522
585
|
for field_instance in self.field_instances:
|
|
523
586
|
field_def = field_instance.field_def
|
|
524
|
-
|
|
525
|
-
|
|
587
|
+
if not isinstance(field_def.offset, InfoSize):
|
|
588
|
+
continue
|
|
589
|
+
if not isinstance(field_def.size, InfoSize):
|
|
590
|
+
continue
|
|
591
|
+
field_offset = field_def.offset
|
|
592
|
+
field_size = field_def.size
|
|
526
593
|
field_end = field_offset + field_size
|
|
527
594
|
if field_end > max_end_offset:
|
|
528
595
|
max_end_offset = field_end
|
|
529
596
|
|
|
530
597
|
if max_end_offset > self.size:
|
|
531
598
|
self.size = max_end_offset
|
|
532
|
-
|
|
533
|
-
@staticmethod
|
|
534
|
-
def _resolve_field_offset(field_def: FieldDef) -> InfoSize:
|
|
535
|
-
"""Resolve field offsets to InfoSize values when possible."""
|
|
536
|
-
if isinstance(field_def.offset, InfoSize):
|
|
537
|
-
return field_def.offset
|
|
538
|
-
return InfoSize(0, 0)
|
|
539
|
-
|
|
540
|
-
@staticmethod
|
|
541
|
-
def _resolve_field_size(field_def: FieldDef) -> InfoSize:
|
|
542
|
-
"""Resolve field sizes to InfoSize values when possible."""
|
|
543
|
-
if isinstance(field_def.size, InfoSize):
|
|
544
|
-
return field_def.size
|
|
545
|
-
return InfoSize(0, 0)
|
|
@@ -475,10 +475,11 @@ def test_type_expression_uses_previous_field_value():
|
|
|
475
475
|
bytearray(),
|
|
476
476
|
)
|
|
477
477
|
decoded_int = decode(struct_def, encoded_int)
|
|
478
|
-
assert decoded_int.field_instances == [
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
]
|
|
478
|
+
assert decoded_int.field_instances[0] == FieldInstance(struct_def[0], 1)
|
|
479
|
+
assert decoded_int.field_instances[1].field_def.offset == InfoSize(1, 0)
|
|
480
|
+
assert decoded_int.field_instances[1].field_def.size == InfoSize(1, 0)
|
|
481
|
+
assert decoded_int.field_instances[1].field_def.type == "int"
|
|
482
|
+
assert decoded_int.field_instances[1].value == 7
|
|
482
483
|
|
|
483
484
|
encoded_float = encode(
|
|
484
485
|
StructInstance(struct_def=StructDef(fields=struct_def),
|
|
@@ -490,7 +491,9 @@ def test_type_expression_uses_previous_field_value():
|
|
|
490
491
|
)
|
|
491
492
|
decoded_float = decode(struct_def, encoded_float)
|
|
492
493
|
assert decoded_float.field_instances[0] == FieldInstance(struct_def[0], 2)
|
|
493
|
-
assert decoded_float.field_instances[1].field_def ==
|
|
494
|
+
assert decoded_float.field_instances[1].field_def.offset == InfoSize(1, 0)
|
|
495
|
+
assert decoded_float.field_instances[1].field_def.size == InfoSize(4, 0)
|
|
496
|
+
assert decoded_float.field_instances[1].field_def.type == "float"
|
|
494
497
|
assert decoded_float.field_instances[1].value == 1.5
|
|
495
498
|
|
|
496
499
|
|
|
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
|